diff --git a/calendar/inc/class.calendar_boupdate.inc.php b/calendar/inc/class.calendar_boupdate.inc.php index 4eaca20a5f..2a3ce0c3b6 100644 --- a/calendar/inc/class.calendar_boupdate.inc.php +++ b/calendar/inc/class.calendar_boupdate.inc.php @@ -851,9 +851,10 @@ class calendar_boupdate extends calendar_bo * @param array $new_event =null Event after the change * @param int|string $user =0 User/participant who started the notify, default current user * @param array $alarm =null values for "offset", "start", etc. + * @parqm boolean $ignore_prefs Ignore the user's preferences about when they want to be notified and send it * @return bool true/false */ - function _send_update($msg_type, $to_notify, $old_event, $new_event=null, $user=0, array $alarm=null) + function _send_update($msg_type, $to_notify, $old_event, $new_event=null, $user=0, array $alarm=null, $ignore_prefs = false) { //error_log(__METHOD__."($msg_type,".array2string($to_notify).",...) ".array2string($new_event)); if (!is_array($to_notify)) @@ -1035,7 +1036,7 @@ class calendar_boupdate extends calendar_bo $fullname = $res_info && !empty($res_info['name']) ? $res_info['name'] : $userid; } $m_type = $msg_type; - if (!self::update_requested($userid, $part_prefs, $m_type, $old_event, $new_event, $role, + if (!$ignore_prefs && !self::update_requested($userid, $part_prefs, $m_type, $old_event, $new_event, $role, $event['participants'][$GLOBALS['egw_info']['user']['account_id']])) { continue; diff --git a/calendar/inc/class.calendar_uiforms.inc.php b/calendar/inc/class.calendar_uiforms.inc.php index 5736dbacb8..e3016395da 100644 --- a/calendar/inc/class.calendar_uiforms.inc.php +++ b/calendar/inc/class.calendar_uiforms.inc.php @@ -42,6 +42,7 @@ class calendar_uiforms extends calendar_ui 'cat_acl' => true, 'meeting' => true, 'mail_import' => true, + 'notify' => true ); /** @@ -1773,90 +1774,14 @@ class calendar_uiforms extends calendar_ui $content['duration'] = $content['end'] - $content['start']; if (isset($this->durations[$content['duration']])) $content['end'] = ''; - $row = 3; $readonlys = $content['participants'] = $preserv['participants'] = array(); // preserve some ui elements, if set eg. under error-conditions foreach(array('quantity','resource','role') as $n) { if (isset($event['participants'][$n])) $content['participants'][$n] = $event['participants'][$n]; } - foreach($event['participant_types'] as $type => $participants) - { - $name = 'accounts'; - if (isset($this->bo->resources[$type])) - { - $name = $this->bo->resources[$type]['app']; - } - // sort participants (in there group/app) by title - uksort($participants, array($this, 'uid_title_cmp')); - foreach($participants as $id => $status) - { - $uid = $type == 'u' ? $id : $type.$id; - $quantity = $role = null; - calendar_so::split_status($status,$quantity,$role); - $preserv['participants'][$row] = $content['participants'][$row] = array( - 'app' => $name == 'accounts' ? ($GLOBALS['egw']->accounts->get_type($id) == 'g' ? 'Group' : 'User') : $name, - 'uid' => $uid, - 'status' => $status, - 'old_status' => $status, - 'quantity' => $quantity > 1 || $uid[0] == 'r' ? $quantity : '', // only display quantity for resources or if > 1 - 'role' => $role, - ); - // replace iCal roles with a nicer label and remove regular REQ-PARTICIPANT - if (isset($this->bo->roles[$role])) - { - $content['participants'][$row]['role_label'] = lang($this->bo->roles[$role]); - } - // allow third party apps to use categories for roles - elseif(substr($role,0,6) == 'X-CAT-') - { - $content['participants'][$row]['role_label'] = $GLOBALS['egw']->categories->id2name(substr($role,6)); - } - else - { - $content['participants'][$row]['role_label'] = lang(str_replace('X-','',$role)); - } - $content['participants'][$row]['delete_id'] = strpbrk($uid,'"\'<>') !== false ? md5($uid) : $uid; - //echo "

$uid ($quantity): $role --> {$content['participants'][$row]['role']}

\n"; + $this->setup_participants($event,$content,$readonlys,$preserv,$view); - if (($no_status = !$this->bo->check_status_perms($uid,$event)) || $view) - $readonlys['participants'][$row]['status'] = $no_status; - if ($preserv['hide_delete'] || !$this->bo->check_perms(Acl::EDIT,$event)) - $readonlys['participants']['delete'][$uid] = true; - // todo: make the participants available as links with email as title - $content['participants'][$row++]['title'] = $this->get_title($uid); - // enumerate group-invitations, so people can accept/reject them - if ($name == 'accounts' && $GLOBALS['egw']->accounts->get_type($id) == 'g' && - ($members = $GLOBALS['egw']->accounts->members($id,true))) - { - $sel_options['status']['G'] = lang('Select one'); - // sort members by title - usort($members, array($this, 'uid_title_cmp')); - foreach($members as $member) - { - if (!isset($participants[$member]) && $this->bo->check_perms(Acl::READ,0,$member)) - { - $preserv['participants'][$row] = $content['participants'][$row] = array( - 'app' => 'Group invitation', - 'uid' => $member, - 'status' => 'G', - ); - $readonlys['participants'][$row]['quantity'] = $readonlys['participants']['delete'][$member] = true; - // read access is enough to invite participants, but you need edit rights to change status - $readonlys['participants'][$row]['status'] = !$this->bo->check_perms(Acl::EDIT,0,$member); - $content['participants'][$row++]['title'] = Api\Accounts::username($member); - } - } - } - } - // resouces / apps we shedule, atm. resources and addressbook - $content['participants']['cal_resources'] = ''; - foreach($this->bo->resources as $data) - { - if ($data['app'] == 'email') continue; // make no sense, as we cant search for email - $content['participants']['cal_resources'] .= ','.$data['app']; - } - } $content['participants']['status_date'] = $preserv['actual_date']; // set notify_externals in participants from cfs if (!empty($event['##notify_externals'])) @@ -2045,6 +1970,98 @@ class calendar_uiforms extends calendar_ui } } + /** + * Set up the participants for display in edit dialog + * + * @param $event + * @param $content + * @param $readonlys + * @param $preserv + * @param $view + */ + protected function setup_participants($event, &$content, &$readonlys, &$preserv, $view) + { + $row = 3; + foreach($event['participant_types'] as $type => $participants) + { + $name = 'accounts'; + if (isset($this->bo->resources[$type])) + { + $name = $this->bo->resources[$type]['app']; + } + // sort participants (in there group/app) by title + uksort($participants, array($this, 'uid_title_cmp')); + foreach($participants as $id => $status) + { + $uid = $type == 'u' ? $id : $type.$id; + $quantity = $role = null; + calendar_so::split_status($status,$quantity,$role); + $preserv['participants'][$row] = $content['participants'][$row] = array( + 'app' => $name == 'accounts' ? ($GLOBALS['egw']->accounts->get_type($id) == 'g' ? 'Group' : 'User') : $name, + 'uid' => $uid, + 'status' => $status, + 'old_status' => $status, + 'quantity' => $quantity > 1 || $uid[0] == 'r' ? $quantity : '', // only display quantity for resources or if > 1 + 'role' => $role, + ); + // replace iCal roles with a nicer label and remove regular REQ-PARTICIPANT + if (isset($this->bo->roles[$role])) + { + $content['participants'][$row]['role_label'] = lang($this->bo->roles[$role]); + } + // allow third party apps to use categories for roles + elseif(substr($role,0,6) == 'X-CAT-') + { + $content['participants'][$row]['role_label'] = $GLOBALS['egw']->categories->id2name(substr($role,6)); + } + else + { + $content['participants'][$row]['role_label'] = lang(str_replace('X-','',$role)); + } + $content['participants'][$row]['delete_id'] = strpbrk($uid,'"\'<>') !== false ? md5($uid) : $uid; + //echo "

$uid ($quantity): $role --> {$content['participants'][$row]['role']}

\n"; + + if (($no_status = !$this->bo->check_status_perms($uid,$event)) || $view) + $readonlys['participants'][$row]['status'] = $no_status; + if ($preserv['hide_delete'] || !$this->bo->check_perms(Acl::EDIT,$event)) + $readonlys['participants']['delete'][$uid] = true; + // todo: make the participants available as links with email as title + $content['participants'][$row++]['title'] = $this->get_title($uid); + // enumerate group-invitations, so people can accept/reject them + if ($name == 'accounts' && $GLOBALS['egw']->accounts->get_type($id) == 'g' && + ($members = $GLOBALS['egw']->accounts->members($id,true))) + { + $sel_options['status']['G'] = lang('Select one'); + // sort members by title + usort($members, array($this, 'uid_title_cmp')); + foreach($members as $member) + { + if (!isset($participants[$member]) && $this->bo->check_perms(Acl::READ,0,$member)) + { + $preserv['participants'][$row] = $content['participants'][$row] = array( + 'app' => 'Group invitation', + 'uid' => $member, + 'status' => 'G', + ); + $readonlys['participants'][$row]['quantity'] = $readonlys['participants']['delete'][$member] = true; + // read access is enough to invite participants, but you need edit rights to change status + $readonlys['participants'][$row]['status'] = !$this->bo->check_perms(Acl::EDIT,0,$member); + $content['participants'][$row++]['title'] = Api\Accounts::username($member); + } + } + } + } + // resouces / apps we shedule, atm. resources and addressbook + $content['participants']['cal_resources'] = ''; + foreach($this->bo->resources as $data) + { + if ($data['app'] == 'email') continue; // make no sense, as we cant search for email + $content['participants']['cal_resources'] .= ','.$data['app']; + } + } + } + + /** * Remove (shared) lock via ajax, when edit popup get's closed * @@ -3451,4 +3468,35 @@ class calendar_uiforms extends calendar_ui return $this->process_edit($event); } + + public function notify($content=array()) + { + if(is_array($content) && $content['button']) + { + $participants = array_filter($content['participants']['notify']); + $this->bo->_send_update(MSG_ALARM,$participants,$content,null,0,null,true); + Framework::window_close(); + } + + list($id, $date) = explode(':',$_GET['id']); + $content = array(); + $event = $this->bo->read($id, $date); + $this->setup_participants($event, $content, $readonlys,$preserve,true); + $content = array_merge($event, $content); + + $sel_options = array( + 'recur_type' => &$this->bo->recur_types, + 'status' => $this->bo->verbose_status, + 'duration' => $this->durations, + 'role' => $this->bo->roles + ); + $readonlys = []; + + + + $etpl = new Etemplate('calendar.notify_dialog'); + $preserve = $content; + + $etpl->exec('calendar.calendar_uiforms.notify', $content, $sel_options, $readonlys, $preserve,2); + } } diff --git a/calendar/inc/class.calendar_uilist.inc.php b/calendar/inc/class.calendar_uilist.inc.php index e398d0c810..b4b576aec0 100644 --- a/calendar/inc/class.calendar_uilist.inc.php +++ b/calendar/inc/class.calendar_uilist.inc.php @@ -963,6 +963,15 @@ class calendar_uilist extends calendar_ui 'hint' => 'Do not notify of these changes', 'group' => $group, ), + 'notifications' => array( + 'caption' => 'Send notifications', + 'hint' => 'Send notifications to users right now', + 'url' => 'calendar.calendar_uiforms.notify&id=$app_id', + 'popup' => Link::get_registry('calendar', 'view_popup'), + 'allowOnMultiple' => false, + 'group' => $group, + 'disableClass' => 'rowNoView', + ) ); $status = array_map('lang',$this->bo->verbose_status); unset($status['G']); diff --git a/calendar/inc/class.calendar_uiviews.inc.php b/calendar/inc/class.calendar_uiviews.inc.php index 84f5e174cc..3505f7c1ca 100644 --- a/calendar/inc/class.calendar_uiviews.inc.php +++ b/calendar/inc/class.calendar_uiviews.inc.php @@ -911,6 +911,7 @@ class calendar_uiviews extends calendar_ui $actions['copy']['onExecute'] = 'javaScript:app.calendar.action_open'; $actions['print']['open'] = '{"app": "calendar", "type": "edit", "extra": "cal_id=$id&print=1"}'; $actions['print']['onExecute'] = 'javaScript:app.calendar.action_open'; + $actions['notifications']['onExecute'] = 'javaScript:app.calendar.action_open'; foreach($actions['status']['children'] as $id => &$status) { diff --git a/calendar/js/View.js b/calendar/js/View.js index 12dd8cb8ec..2ce6b3f3cc 100644 --- a/calendar/js/View.js +++ b/calendar/js/View.js @@ -13,6 +13,7 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); +exports.listview = exports.planner = exports.month = exports.weekN = exports.week = exports.day4 = exports.day = exports.View = void 0; var View = /** @class */ (function () { function View() { } diff --git a/calendar/js/app.js b/calendar/js/app.js index e4e56b5780..66f3006e7d 100644 --- a/calendar/js/app.js +++ b/calendar/js/app.js @@ -1650,9 +1650,10 @@ var CalendarApp = /** @class */ (function (_super) { } else if (_action.data.url) { var url = _action.data.url; - url = url.replace(/(\$|%24)app/, app).replace(/(\$|%24)app_id/, app_id) + url = url.replace(/(\$|%24)app_id/, app_id) + .replace(/(\$|%24)app/, app) .replace(/(\$|%24)id/, id); - this.egw.open_link(url); + this.egw.open_link(url, _action.data.target, _action.data.popup); } }; /** @@ -3572,8 +3573,7 @@ var CalendarApp = /** @class */ (function (_super) { // Dates are user time, but we told javascript it was UTC. // Send just the timestamp (as a string) with no timezone (typeof _data.start != "string" ? _data.start.toJSON() : _data.start).slice(0, -1), - (typeof _data.end != "string" ? _data.end.toJSON() : _data.end).slice(0, -1), - { + (typeof _data.end != "string" ? _data.end.toJSON() : _data.end).slice(0, -1), { participants: Object.keys(_data.participants).filter(function (v) { return v.match(/^[0-9]/); }) }], function (_value) { if (_value) { diff --git a/calendar/js/app.ts b/calendar/js/app.ts index 40c64d3b91..00129808e7 100644 --- a/calendar/js/app.ts +++ b/calendar/js/app.ts @@ -1795,9 +1795,10 @@ class CalendarApp extends EgwApp else if (_action.data.url) { var url = _action.data.url; - url = url.replace(/(\$|%24)app/,app).replace(/(\$|%24)app_id/,app_id) - .replace(/(\$|%24)id/,id); - this.egw.open_link(url); + url = url.replace(/(\$|%24)app_id/,app_id) + .replace(/(\$|%24)app/,app) + .replace(/(\$|%24)id/,id); + this.egw.open_link(url, _action.data.target, _action.data.popup); } } diff --git a/calendar/js/et2_widget_event.js b/calendar/js/et2_widget_event.js index 9e5295ddb8..9d2d5fcc2b 100644 --- a/calendar/js/et2_widget_event.js +++ b/calendar/js/et2_widget_event.js @@ -22,6 +22,7 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); +exports.et2_calendar_event = void 0; /*egw:uses /etemplate/js/et2_core_valueWidget; */ @@ -722,7 +723,6 @@ var et2_calendar_event = /** @class */ (function (_super) { * @private */ et2_calendar_event.prototype._status_check = function (event, filter, owner) { - var _a; if (!owner || !event) { return false; } @@ -756,7 +756,7 @@ var et2_calendar_event = /** @class */ (function (_super) { return element.id == owner; }) || {}; var matching_participant = typeof resource.resources == "undefined" ? - resource : (_a = resource) === null || _a === void 0 ? void 0 : _a.resources.filter(function (id) { return typeof event.participants[id] != "undefined"; }); + resource : resource === null || resource === void 0 ? void 0 : resource.resources.filter(function (id) { return typeof event.participants[id] != "undefined"; }); if (matching_participant.length > 0) { return this._status_check(event, filter, matching_participant); } diff --git a/calendar/js/et2_widget_owner.js b/calendar/js/et2_widget_owner.js index 3d2b0f0a69..607e633bb1 100644 --- a/calendar/js/et2_widget_owner.js +++ b/calendar/js/et2_widget_owner.js @@ -22,6 +22,7 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); +exports.et2_calendar_owner = void 0; /*egw:uses et2_widget_taglist; */ diff --git a/calendar/js/et2_widget_planner.js b/calendar/js/et2_widget_planner.js index e33f3ae7ce..0aec1e9af4 100644 --- a/calendar/js/et2_widget_planner.js +++ b/calendar/js/et2_widget_planner.js @@ -22,6 +22,7 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); +exports.et2_calendar_planner = void 0; /*egw:uses /calendar/js/et2_widget_view.js; /calendar/js/et2_widget_planner_row.js; diff --git a/calendar/js/et2_widget_planner_row.js b/calendar/js/et2_widget_planner_row.js index fa978f58ec..912597dd50 100644 --- a/calendar/js/et2_widget_planner_row.js +++ b/calendar/js/et2_widget_planner_row.js @@ -22,6 +22,7 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); +exports.et2_calendar_planner_row = void 0; /*egw:uses /calendar/js/et2_widget_view.js; /calendar/js/et2_widget_daycol.js; @@ -372,7 +373,7 @@ var et2_calendar_planner_row = /** @class */ (function (_super) { get: function () { return this._date_helper; }, - enumerable: true, + enumerable: false, configurable: true }); /** diff --git a/calendar/js/et2_widget_timegrid.js b/calendar/js/et2_widget_timegrid.js index efbba06d40..9daab81187 100644 --- a/calendar/js/et2_widget_timegrid.js +++ b/calendar/js/et2_widget_timegrid.js @@ -22,6 +22,7 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); +exports.et2_calendar_timegrid = void 0; /*egw:uses /calendar/js/et2_widget_view.js; */ diff --git a/calendar/js/et2_widget_view.js b/calendar/js/et2_widget_view.js index e9245bc72f..d7cdc6e8c8 100644 --- a/calendar/js/et2_widget_view.js +++ b/calendar/js/et2_widget_view.js @@ -22,6 +22,7 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); +exports.et2_calendar_view = void 0; var et2_core_valueWidget_1 = require("../../api/js/etemplate/et2_core_valueWidget"); var et2_core_inheritance_1 = require("../../api/js/etemplate/et2_core_inheritance"); /** @@ -278,7 +279,7 @@ var et2_calendar_view = /** @class */ (function (_super) { get: function () { return this._date_helper; }, - enumerable: true, + enumerable: false, configurable: true }); et2_calendar_view.prototype._createNamespace = function () { diff --git a/calendar/templates/default/notify_dialog.xet b/calendar/templates/default/notify_dialog.xet new file mode 100644 index 0000000000..cb1ff08a90 --- /dev/null +++ b/calendar/templates/default/notify_dialog.xet @@ -0,0 +1,65 @@ + + + + + \ No newline at end of file