From 62e35cd1c7cfe408a5330c688bf0a88a97293af5 Mon Sep 17 00:00:00 2001 From: nathan Date: Fri, 15 Oct 2021 14:03:29 -0600 Subject: [PATCH] * Calendar: Fix changing the recurrence end date did not add/remove the events in the UI --- calendar/inc/class.calendar_ui.inc.php | 40 +++++-- calendar/inc/class.calendar_uiforms.inc.php | 2 +- calendar/js/app.js | 53 ++++++--- calendar/js/app.ts | 118 ++++++++++++-------- 4 files changed, 142 insertions(+), 71 deletions(-) diff --git a/calendar/inc/class.calendar_ui.inc.php b/calendar/inc/class.calendar_ui.inc.php index 5446573d60..d455e58bfa 100644 --- a/calendar/inc/class.calendar_ui.inc.php +++ b/calendar/inc/class.calendar_ui.inc.php @@ -642,16 +642,20 @@ class calendar_ui * * @param int $event_id * @param Api\DateTime $recurrence_date + * @param array $old_event * * @return boolean True if the event was updated, false if it could not be - * updated or was removed. + * updated or was removed. */ - public function update_client($event_id, Api\DateTime $recurrence_date = null) + public function update_client($event_id, Api\DateTime $recurrence_date = null, array $old_event = array()) { - if(!$event_id) return false; - if(is_string($event_id) && strpos($event_id,':') !== FALSE) + if(!$event_id) { - list($event_id, $date) = explode(':',$event_id); + return false; + } + if(is_string($event_id) && strpos($event_id, ':') !== FALSE) + { + list($event_id, $date) = explode(':', $event_id); $recurrence_date = new Api\DateTime($date); } @@ -698,6 +702,21 @@ class calendar_ui else if($event['recur_type'] ) { $this_month = new Api\DateTime('next month'); + $data = []; + if($old_event && ($old_event['start'] != $event['start'] || $old_event['recur_enddate'] != $event['recur_enddate'])) + { + // Set up to clear old events in case recurrence start/end date changed + $old_rrule = calendar_rrule::event2rrule($old_event, true); + + $old_rrule->rewind(); + do + { + $occurrence = $old_rrule->current(); + $data['calendar::' . $old_event['id'] . ':' . $occurrence->format('ts')] = null; + $old_rrule->next(); + } + while($old_rrule->valid() && $occurrence <= $this_month); + } $rrule = calendar_rrule::event2rrule($event, true); $rrule->rewind(); do @@ -705,10 +724,17 @@ class calendar_ui $occurrence = $rrule->current(); $converted = $this->bo->read($event['id'], $occurrence); $this->to_client($converted); - $response->generic('data', array('uid' => 'calendar::'.$converted['row_id'], 'data' => $converted)); + $data['calendar::' . $converted['row_id']] = $converted; $rrule->next(); } - while ($rrule->valid() && $occurrence <= $this_month ); + while($rrule->valid() && $occurrence <= $this_month); + + // Now we have to go through and send each one individually, since client side data can't handle more than one + foreach($data as $uid => $cal_data) + { + $response->apply('egw.dataStoreUID', [$uid, $cal_data]); + } + $response->apply('app.calendar.update_events', [array_keys($data)]); } return true; } diff --git a/calendar/inc/class.calendar_uiforms.inc.php b/calendar/inc/class.calendar_uiforms.inc.php index a71a56af23..015c5be07a 100644 --- a/calendar/inc/class.calendar_uiforms.inc.php +++ b/calendar/inc/class.calendar_uiforms.inc.php @@ -1007,7 +1007,7 @@ class calendar_uiforms extends calendar_ui $response = Api\Json\Response::get(); if($response && $update_type != 'delete' && !$client_updated) { - $client_updated = $this->update_client($event['id']); + $client_updated = $this->update_client($event['id'], null, $old_event); } $msg = $message . ($msg ? ', ' . $msg : ''); diff --git a/calendar/js/app.js b/calendar/js/app.js index 2a10ec8778..a0aee80875 100644 --- a/calendar/js/app.js +++ b/calendar/js/app.js @@ -2,13 +2,12 @@ /** * EGroupware - Calendar - Javascript UI * - * @link http://www.egroupware.org + * @link https://www.egroupware.org * @package calendar - * @author Hadi Nategh + * @author Hadi Nategh * @author Nathan Gray - * @copyright (c) 2008-16 by Ralf Becker + * @copyright (c) 2008-21 by Ralf Becker * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License - * @version $Id$ */ var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { @@ -24,6 +23,7 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); +exports.CalendarApp = void 0; /*egw:uses /api/js/jsapi/egw_app.js; /etemplate/js/etemplate2.js; @@ -649,7 +649,9 @@ var CalendarApp = /** @class */ (function (_super) { // Filter what's allowed down to those we care about var filtered = Object.keys(this._grants).filter(function (account) { return _this.state.owner.indexOf(account) >= 0; }); // Check if we're interested in displaying by owner / participant - var owner_check = et2_widget_event_1.et2_calendar_event.owner_check(cal_event, jQuery.extend({}, { options: { owner: filtered } }, this.et2)); + var owner_check = et2_widget_event_1.et2_calendar_event.owner_check(cal_event, + // Fake the required widget since we don't actually have it right now + jQuery.extend({}, { options: { owner: filtered } }, this.et2)); if (!owner_check) { // The owner is not in the list of what we're allowed / care about return; @@ -668,14 +670,21 @@ var CalendarApp = /** @class */ (function (_super) { } ; // Ask for the real data, we don't have it - egw.json("calendar.calendar_ui.ajax_get", [[pushData.id]], function (data) { - if (data && data.data && data.data.data) - return; + var process_data = function (data) { // Store it, which will call all registered listeners egw.dataStoreUID(data.uid, data.data); // Any existing events were updated. Run this to catch new events or events moved into view - this._update_events(this.state, [data.uid]); - }.bind(this)).sendRequest(true); + _this._update_events(_this.state, [data.uid]); + }; + egw.request("calendar.calendar_ui.ajax_get", [[pushData.id]]).then(function (data) { + if (typeof data.uid !== "undefined") { + return process_data(data); + } + for (var _i = 0, data_1 = data; _i < data_1.length; _i++) { + var e = data_1[_i]; + process_data(e); + } + }); }; /** * Link hander for jDots template to just reload our iframe, instead of reloading whole admin app @@ -1319,9 +1328,9 @@ var CalendarApp = /** @class */ (function (_super) { */ CalendarApp.prototype.freetime_search = function () { var content = this.et2.getArrayMgr('content').data; - content['start'] = this.et2.getWidgetById('start').get_value(); - content['end'] = this.et2.getWidgetById('end').get_value(); - content['duration'] = this.et2.getWidgetById('duration').get_value(); + content['start'] = this.et2.getValueById('start'); + content['end'] = this.et2.getValueById('end'); + content['duration'] = this.et2.getValueById('duration'); var request = this.egw.json('calendar.calendar_uiforms.ajax_freetimesearch', [content], null, null, null, null); request.sendRequest(); }; @@ -1378,8 +1387,10 @@ var CalendarApp = /** @class */ (function (_super) { if (typeof duration != 'undefined' && typeof end != 'undefined') { end.set_disabled(duration.get_value() !== ''); // Only set end date if not provided, adding seconds fails with DST + // @ts-ignore if (!end.disabled && !content.end) { end.set_value(start.get_value()); + // @ts-ignore if (typeof content.duration != 'undefined') end.set_value("+" + content.duration); } @@ -2107,10 +2118,10 @@ var CalendarApp = /** @class */ (function (_super) { */ CalendarApp.prototype.move_edit_series = function (_DOM, _button) { var content = this.et2.getArrayMgr('content').data; - var start_date = this.et2.getWidgetById('start').get_value(); - var end_date = this.et2.getWidgetById('end').get_value(); + var start_date = this.et2.getValueById('start'); + var end_date = this.et2.getValueById('end'); var whole_day = this.et2.getWidgetById('whole_day'); - var duration = '' + this.et2.getWidgetById('duration').get_value(); + var duration = '' + this.et2.getValueById('duration'); var is_whole_day = whole_day && whole_day.get_value() == whole_day.options.selected_value; var button = _button; var that = this; @@ -3151,6 +3162,13 @@ var CalendarApp = /** @class */ (function (_super) { } }, this, null); }; + /** + * We have a list of calendar UIDs of events that need updating. + * Public wrapper for _update_events so we can call it from server + */ + CalendarApp.prototype.update_events = function (uids) { + return this._update_events(this.state, uids); + }; /** * We have a list of calendar UIDs of events that need updating. * @@ -3645,7 +3663,7 @@ var CalendarApp = /** @class */ (function (_super) { } this.et2.getWidgetById('new_alarm[options]').set_value('300'); this.et2.getWidgetById('new_alarm[owner]').set_value('0'); // all participants - this.et2.getWidgetById('button[add_alarm]').click(); + this.et2.getWidgetById('button[add_alarm]').click(event); } }; CalendarApp.prototype.isVideoConference = function (_action, _selected) { @@ -3719,5 +3737,6 @@ var CalendarApp = /** @class */ (function (_super) { CalendarApp.DAYWISE_CACHE_ID = 'calendar_daywise'; return CalendarApp; }(egw_app_1.EgwApp)); +exports.CalendarApp = CalendarApp; app.classes.calendar = CalendarApp; //# sourceMappingURL=app.js.map \ No newline at end of file diff --git a/calendar/js/app.ts b/calendar/js/app.ts index f34cded500..c1e866e89b 100644 --- a/calendar/js/app.ts +++ b/calendar/js/app.ts @@ -1,13 +1,12 @@ /** * EGroupware - Calendar - Javascript UI * - * @link http://www.egroupware.org + * @link https://www.egroupware.org * @package calendar - * @author Hadi Nategh + * @author Hadi Nategh * @author Nathan Gray - * @copyright (c) 2008-16 by Ralf Becker + * @copyright (c) 2008-21 by Ralf Becker * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License - * @version $Id$ */ /*egw:uses @@ -24,6 +23,7 @@ import {EgwApp, PushData} from "../../api/js/jsapi/egw_app"; import {etemplate2} from "../../api/js/etemplate/etemplate2"; import {et2_container} from "../../api/js/etemplate/et2_core_baseWidget"; import {et2_date} from "../../api/js/etemplate/et2_widget_date"; +import {et2_calendar_owner} from "./et2_widget_owner"; import {day, day4, listview, month, planner, week, weekN} from "./View"; import {et2_calendar_view} from "./et2_widget_view"; import {et2_calendar_timegrid} from "./et2_widget_timegrid"; @@ -60,7 +60,7 @@ import {et2_inputWidget} from "../../api/js/etemplate/et2_core_inputWidget"; * changed, we discard the daywise cache and ask the server for the filtered events. * */ -class CalendarApp extends EgwApp +export class CalendarApp extends EgwApp { /** @@ -614,10 +614,14 @@ class CalendarApp extends EgwApp let filtered = Object.keys(this._grants).filter(account => this.state.owner.indexOf(account) >= 0); // Check if we're interested in displaying by owner / participant - let owner_check = et2_calendar_event.owner_check(cal_event, jQuery.extend({}, - {options: {owner: filtered}}, - this.et2 - )); + let owner_check = et2_calendar_event.owner_check( + cal_event, + // Fake the required widget since we don't actually have it right now + jQuery.extend({}, + {options: {owner: filtered}}, + this.et2 + ) + ); if(!owner_check) { // The owner is not in the list of what we're allowed / care about @@ -632,24 +636,35 @@ class CalendarApp extends EgwApp } // Do we already have "fresh" data? Most user actions give fresh data in response - let existing = egw.dataGetUIDdata('calendar::'+pushData.id); + let existing = egw.dataGetUIDdata('calendar::' + pushData.id); if(existing && Math.abs(existing.timestamp - new Date().valueOf()) < 1000) { // Update directly - this._update_events(this.state, ['calendar::'+pushData.id]); + this._update_events(this.state, ['calendar::' + pushData.id]); return; - }; + } + ; // Ask for the real data, we don't have it - egw.json("calendar.calendar_ui.ajax_get", [[pushData.id]], function(data) { - if(data && data.data && data.data.data) return; - + let process_data = (data) => + { // Store it, which will call all registered listeners egw.dataStoreUID(data.uid, data.data); // Any existing events were updated. Run this to catch new events or events moved into view this._update_events(this.state, [data.uid]); - }.bind(this)).sendRequest(true); + } + egw.request("calendar.calendar_ui.ajax_get", [[pushData.id]]).then((data) => + { + if(typeof data.uid !== "undefined") + { + return process_data(data) + } + for(let e of data) + { + process_data(e); + } + }); } /** @@ -1385,9 +1400,9 @@ class CalendarApp extends EgwApp freetime_search() { var content = this.et2.getArrayMgr('content').data; - content['start'] = this.et2.getWidgetById('start').get_value(); - content['end'] = this.et2.getWidgetById('end').get_value(); - content['duration'] = this.et2.getWidgetById('duration').get_value(); + content['start'] = this.et2.getValueById('start'); + content['end'] = this.et2.getValueById('end'); + content['duration'] = this.et2.getValueById('duration'); var request = this.egw.json('calendar.calendar_uiforms.ajax_freetimesearch', [content],null,null,null,null); request.sendRequest(); @@ -1399,8 +1414,8 @@ class CalendarApp extends EgwApp */ check_recur_type() { - var recurType = this.et2.getWidgetById('recur_type'); - var recurData = this.et2.getWidgetById('recur_data'); + var recurType = this.et2.getWidgetById('recur_type'); + var recurData = this.et2.getWidgetById('recur_data'); if(recurType && recurData) { @@ -1454,9 +1469,9 @@ class CalendarApp extends EgwApp */ set_enddate_visibility() { - var duration = this.et2.getWidgetById('duration'); - var start = this.et2.getWidgetById('start'); - var end = this.et2.getWidgetById('end'); + var duration = this.et2.getWidgetById('duration'); + var start = this.et2.getWidgetById('start'); + var end = this.et2.getWidgetById('end'); var content = this.et2.getArrayMgr('content').data; if (typeof duration != 'undefined' && typeof end != 'undefined') @@ -1464,9 +1479,11 @@ class CalendarApp extends EgwApp end.set_disabled(duration.get_value()!==''); // Only set end date if not provided, adding seconds fails with DST + // @ts-ignore if (!end.disabled && !content.end) { end.set_value(start.get_value()); + // @ts-ignore if (typeof content.duration != 'undefined') end.set_value("+"+content.duration); } } @@ -1620,9 +1637,9 @@ class CalendarApp extends EgwApp */ participantOnChange() { - var add = this.et2.getWidgetById('add'); - var quantity = this.et2.getWidgetById('quantity'); - var participant = this.et2.getWidgetById('participant'); + var add = this.et2.getWidgetById('add'); + var quantity = this.et2.getWidgetById('quantity'); + var participant = this.et2.getWidgetById('participant'); // array of participants var value = participant.get_value(); @@ -1691,7 +1708,7 @@ class CalendarApp extends EgwApp // Make the Id from selected button by checking the index var selectedId = _widget.id.match(/^select\[([0-9])\]$/i)[1]; - var sTime = this.et2.getWidgetById(selectedId+'start'); + var sTime = this.et2.getWidgetById(selectedId+'start'); //check the parent window is still open before to try to access it if (window.opener && sTime) @@ -1699,8 +1716,8 @@ class CalendarApp extends EgwApp var editWindowObj = window.opener.etemplate2.getByApplication('calendar')[0]; if (typeof editWindowObj != "undefined") { - var startTime = editWindowObj.widgetContainer.getWidgetById('start'); - var endTime = editWindowObj.widgetContainer.getWidgetById('end'); + var startTime = editWindowObj.widgetContainer.getWidgetById('start'); + var endTime = editWindowObj.widgetContainer.getWidgetById('end'); if (startTime && endTime) { startTime.set_value(sTime.get_value()); @@ -1726,7 +1743,7 @@ class CalendarApp extends EgwApp const view = ( CalendarApp.views['listview'].etemplates[0]).widgetContainer || null; const nm = view ? view.getWidgetById('nm') : null; const filter = view && nm ? nm.getWidgetById('filter') : null; - const dates = view ? view.getWidgetById('calendar.list.dates') : null; + const dates = view ? view.getWidgetById('calendar.list.dates') : null; // Update state when user changes it if(view && filter) @@ -2386,11 +2403,11 @@ class CalendarApp extends EgwApp */ move_edit_series(_DOM,_button) { - var content = this.et2.getArrayMgr('content').data; - var start_date = this.et2.getWidgetById('start').get_value(); - var end_date = this.et2.getWidgetById('end').get_value(); - var whole_day = this.et2.getWidgetById('whole_day'); - var duration = ''+this.et2.getWidgetById('duration').get_value(); + var content : any = this.et2.getArrayMgr('content').data; + var start_date = this.et2.getValueById('start'); + var end_date = this.et2.getValueById('end'); + var whole_day = this.et2.getWidgetById('whole_day'); + var duration = ''+this.et2.getValueById('duration'); var is_whole_day = whole_day && whole_day.get_value() == whole_day.options.selected_value; var button = _button; var that = this; @@ -2623,7 +2640,7 @@ class CalendarApp extends EgwApp { var listview : et2_nextmatch = typeof CalendarApp.views.listview.etemplates[0] !== 'string' && CalendarApp.views.listview.etemplates[0].widgetContainer && - CalendarApp.views.listview.etemplates[0].widgetContainer.getWidgetById('nm'); + CalendarApp.views.listview.etemplates[0].widgetContainer.getWidgetById('nm'); if(listview && listview.activeFilters && listview.activeFilters.search) { state.keywords = listview.activeFilters.search; @@ -3366,7 +3383,7 @@ class CalendarApp extends EgwApp * * @param {widget object} _widget new_alarm[options] selectbox */ - alarm_custom_date (selectbox : HTMLInputElement, _widget? : et2_selectbox) + alarm_custom_date (selectbox? : HTMLInputElement, _widget? : et2_selectbox) { var alarm_date = this.et2.getInputWidgetById('new_alarm[date]'); var alarm_options = _widget || this.et2.getInputWidgetById('new_alarm[options]'); @@ -3402,10 +3419,10 @@ class CalendarApp extends EgwApp */ set_alarmOptions_WD (_egw,_widget) { - var alarm = this.et2.getWidgetById('alarm'); + var alarm = this.et2.getWidgetById('alarm'); if (!alarm) return; // no default alarm var content = this.et2.getArrayMgr('content').data; - var start = this.et2.getWidgetById('start'); + var start = this.et2.getWidgetById('start'); var self= this; var time = alarm.cells[1][0].widget; var event = alarm.cells[1][1].widget; @@ -3716,13 +3733,22 @@ class CalendarApp extends EgwApp else if(typeof framework !== 'undefined') { framework.applications.calendar.sidemenuEntry.hideAjaxLoader(); - egw.loading_prompt('calendar',false) + egw.loading_prompt('calendar', false) } - }, this,null + }, this, null ); } + /** + * We have a list of calendar UIDs of events that need updating. + * Public wrapper for _update_events so we can call it from server + */ + update_events(uids : string[]) + { + return this._update_events(this.state, uids); + } + /** * We have a list of calendar UIDs of events that need updating. * @@ -4343,7 +4369,7 @@ class CalendarApp extends EgwApp // Listview not loaded if(typeof CalendarApp.views.listview.etemplates[0] == 'string') return; - var nm = CalendarApp.views.listview.etemplates[0].widgetContainer.getWidgetById('nm'); + var nm = CalendarApp.views.listview.etemplates[0].widgetContainer.getWidgetById('nm'); // nextmatch missing if(!nm) return; @@ -4522,9 +4548,9 @@ class CalendarApp extends EgwApp return; } } - this.et2.getWidgetById('new_alarm[options]').set_value('300'); - this.et2.getWidgetById('new_alarm[owner]').set_value('0'); // all participants - this.et2.getWidgetById('button[add_alarm]').click(); + ( this.et2.getWidgetById('new_alarm[options]')).set_value('300'); + ( this.et2.getWidgetById('new_alarm[owner]')).set_value('0'); // all participants + ( this.et2.getWidgetById('button[add_alarm]')).click(event); } }