diff --git a/api/src/Db.php b/api/src/Db.php index 1f15ef54ab..af9c1bad2a 100644 --- a/api/src/Db.php +++ b/api/src/Db.php @@ -2287,7 +2287,7 @@ class Db if ($this->Debug) echo "
sql='$sql'
"; - if ($line === false && $file === false) // call by union, to return the sql rather then run the query + if ($line === false && $file === false) // call by union, to return the sql rather than run the query { return $sql; } diff --git a/api/src/Storage/Tracking.php b/api/src/Storage/Tracking.php index 3a88b50d2d..47d3bf435d 100644 --- a/api/src/Storage/Tracking.php +++ b/api/src/Storage/Tracking.php @@ -596,7 +596,7 @@ abstract class Tracking if (!$this->notify_current_user && $this->user) // do we have a current user and should we notify the current user about his own changes { - //error_log("do_notificaton() adding user=$this->user to email_sent, to not notify him"); + //error_log("do_notification() adding user=$this->user to email_sent, to not notify him"); $email_sent[] = $GLOBALS['egw']->accounts->id2name($this->user,'account_email'); } $skip_notify = $this->get_config('skip_notify',$data,$old); @@ -722,8 +722,8 @@ abstract class Tracking * * Called by track() or externally for sending async notifications * - * Method changes $GLOBALS['egw_info']['user'], so everything called by it, eg. get_(subject|body|links|attachements), - * must NOT store something from user enviroment! By the end of the method, everything get changed back. + * Method changes $GLOBALS['egw_info']['user'], so everything called by it, e.g. get_(subject|body|links|attachments), + * must NOT store something from user environment! By the end of the method, everything get changed back. * * @param array $data current entry * @param array $old = null old/last state of the entry or null for a new entry @@ -807,7 +807,7 @@ abstract class Tracking $attachments = $this->get_attachments($data,$old,$receiver); } - // restore user enviroment BEFORE calling notification class or returning + // restore user environment BEFORE calling notification class or returning $GLOBALS['egw_info']['user'] = $save_user; // need to call preferences constructor and read_repository, to set user timezone again $GLOBALS['egw']->preferences->__construct($GLOBALS['egw_info']['user']['account_id']); @@ -843,12 +843,12 @@ abstract class Tracking $notification->set_reply_to($reply_to); $notification->set_subject($subject); $notification->set_links(array($link)); - $notification->set_popupdata($link?$link['app']:null, $link); + $notification->set_popupdata($link['app']??null, $link, $link['id']??null); if ($attachments && is_array($attachments)) { $notification->set_attachments($attachments); } - // run immediatly during async service, as sending mail with Horde fails, if PHP is already in shutdown + // run immediately during async service, as sending mail with Horde fails, if PHP is already in shutdown // (json requests take care of that by calling Egw::__desctruct() explicit before it's regular triggered) $run = isset($GLOBALS['egw_info']['flags']['async-service']) ? 'call_user_func_array' : Api\Egw::class.'::on_shutdown'; $run(static function($notification, $sender, $receiver, $subject) @@ -1088,7 +1088,7 @@ abstract class Tracking * @param array $data * @param array $old * @param boolean $integrate_link to have links embedded inside the body - * @param int|string $receiver nummeric account_id or email address + * @param int|string $receiver numeric account_id or email address * @return string */ public function get_body($html_email,$data,$old,$integrate_link = true,$receiver=null) diff --git a/calendar/inc/class.calendar_boupdate.inc.php b/calendar/inc/class.calendar_boupdate.inc.php index f0b8b69369..2640dd4cdf 100644 --- a/calendar/inc/class.calendar_boupdate.inc.php +++ b/calendar/inc/class.calendar_boupdate.inc.php @@ -1236,9 +1236,9 @@ class calendar_boupdate extends calendar_bo // popup notifiactions: set subject, different message (without separator) and (always) links $notification->set_popupsubject($subject); - if ($method =='REQUEST') + if ($method == 'REQUEST') { - // Add ACCEPT|REHECT|TENTATIVE actions + // Add ACCEPT|REJECT|TENTATIVE actions $notification->set_popupdata('calendar', array( 'event_id' => $event['id'], 'user_id' => $userid, diff --git a/notifications/inc/class.notifications.inc.php b/notifications/inc/class.notifications.inc.php index bd031dad63..6f035650d9 100644 --- a/notifications/inc/class.notifications.inc.php +++ b/notifications/inc/class.notifications.inc.php @@ -593,7 +593,7 @@ class notifications { } break; // stop running through chain } - // backend sucseeded + // backend succeeded $user_notified = true; if($action == 'stop' || $action == 'fail') { break; } // stop running through chain } @@ -827,13 +827,15 @@ class notifications { * Set popup data * * @param string $_appname - * @param array $_data + * @param ?array $_data + * * @return boolean */ - public function set_popupdata($_appname, $_data) + public function set_popupdata(string $_appname, ?array $_data, ?int $_id=null) { $this->popup_data = array( 'appname' => $_appname, + 'id' => $_id ?: $_data['id'] ?? null, 'data' => $_data ); diff --git a/notifications/inc/class.notifications_ajax.inc.php b/notifications/inc/class.notifications_ajax.inc.php index cd3da43325..1b1bf4574d 100644 --- a/notifications/inc/class.notifications_ajax.inc.php +++ b/notifications/inc/class.notifications_ajax.inc.php @@ -129,13 +129,14 @@ class notifications_ajax } /** - * Remove given notification id(s) from the table + * Remove given notification id(s) and app_ids from the table * * @param int[]|array[] $notifymessages one or multiple notify_id(s) or objects incl. id attribute + * @param array[] $app_ids app-name int[] pairs */ - public function delete_message(array $notifymessages) + public function delete_message(array $notifymessages, array $app_ids=[]) { - $this->update($notifymessages, null); // null = delete + $this->update($notifymessages, null, $app_ids ?? []); // null = delete // if we delete all messages (we either delete one or all!), we return the next chunk of messages directly if (count($notifymessages) > 1) @@ -165,11 +166,12 @@ class notifications_ajax * * @param array $notifymessages * @param string|null $status use null to delete + * @param array[] $app_ids app-name int[] pairs * @return array */ - protected function update(array $notifymessages, $status='SEEN') + protected function update(array $notifymessages, $status='SEEN', array $app_ids=[]) { - $notify_ids = $app_ids = []; + $notify_ids = []; foreach ($notifymessages as $data) { if (is_array($data) && !empty($data['id'])) @@ -187,14 +189,13 @@ class notifications_ajax } $cut_off = $this->db->quote(Api\DateTime::to(self::CUT_OFF_DATE, Api\DateTime::DATABASE)); try { - // MariaDB code using JSON_EXTRACT() foreach($app_ids as $app => $ids) { $where = [ 'account_id' => $this->recipient->account_id, 'notify_type' => self::_type, - "JSON_EXTRACT(notify_data, '$.appname') = ".$this->db->quote($app), - "JSON_EXTRACT(notify_data, '$.data.id') IN (".implode(',', array_map([$this->db, 'quote'], array_unique($ids))).')', + 'notify_app' => $app, + 'notify_app_id' => array_unique($ids), 'notify_created > '.$cut_off, ]; if (isset($status)) @@ -207,21 +208,8 @@ class notifications_ajax } } } - // other DBs catch (Api\Db\Exception $e) { - foreach($this->db->select(self::_notification_table, 'notify_id,notify_data', [ - 'account_id' => $this->recipient->account_id, - 'notify_type' => self::_type, - 'notify_created > '.$cut_off, - "notify_data <> '[]'", // does not return NULL or '[]' rows - ], __LINE__, __FILE__, false,'', self::_appname) as $row) - { - if (($data = json_decode($row['notify_data'], true)) && - isset($data['data']['id']) && in_array($data['data']['id'], $app_ids[$data['appname']] ?? [])) - { - $notify_ids[] = $row['notify_id']; - } - } + // ignore, if DB is not yet updated with notify_app(_id) columns } $where = [ 'notify_id' => array_unique($notify_ids), diff --git a/notifications/inc/class.notifications_popup.inc.php b/notifications/inc/class.notifications_popup.inc.php index 45189ca7c0..24b28fa51d 100644 --- a/notifications/inc/class.notifications_popup.inc.php +++ b/notifications/inc/class.notifications_popup.inc.php @@ -18,7 +18,7 @@ use EGroupware\Api; * @abstract egwpopup is a two stage notification. In the first stage * notification is written into self::_notification_table. * In the second stage a request from the client reads - * out the table to look if there is a notificaton for this + * out the table to look if there is a notification for this * client. The second stage is done in class.notifications_ajax.inc.php */ class notifications_popup implements notifications_iface @@ -128,6 +128,8 @@ class notifications_popup implements notifications_iface 'notify_type' => self::_type, 'notify_data' => $_data && is_array($_data) ? json_encode($_data) : NULL, 'notify_created' => new Api\DateTime(), + 'notify_app' => $_data['appname'], + 'notify_app_id' => $_data['id'], ), false,__LINE__,__FILE__,self::_appname); if ($result === false) throw new Exception("Can't save notification into SQL table"); $push = new Api\Json\Push($this->recipient->account_id); @@ -154,10 +156,20 @@ class notifications_popup implements notifications_iface $result = []; if (($total = $db->select(self::_notification_table, 'COUNT(*)', [ - 'account_id' => $_account_id, - 'notify_type' => self::_type, - 'notify_created > '.($cut_off=$db->quote(Api\DateTime::to(notifications_ajax::CUT_OFF_DATE, Api\DateTime::DATABASE))), - ], __LINE__, __FILE__, false, '', self::_appname)->fetchColumn())) + 'account_id' => $_account_id, + 'notify_type' => self::_type, + 'notify_created > '.($cut_off=$db->quote(Api\DateTime::to(notifications_ajax::CUT_OFF_DATE, Api\DateTime::DATABASE))), + 'notify_app_id IS NULL', + ], __LINE__, __FILE__, false, '', self::_appname)->fetchColumn()+ + $db->select( + '('.$db->select(self::_notification_table, 'notify_app,notify_app_id', [ + 'account_id' => $_account_id, + 'notify_type' => self::_type, + 'notify_created > '.($cut_off=$db->quote(Api\DateTime::to(notifications_ajax::CUT_OFF_DATE, Api\DateTime::DATABASE))), + 'notify_app_id IS NOT NULL', + ], false, false, false, 'GROUP BY notify_app,notify_app_id', self::_appname).') AS app_ids', + 'COUNT(*)', false, __LINE__, __FILE__, false, '', self::_appname)->fetchColumn() + )) { $n = 0; $chunk_size = 150; @@ -188,26 +200,22 @@ class notifications_popup implements notifications_iface 'current' => new Api\DateTime('now'), 'actions' => is_array($actions) ? $actions : NULL, 'extra_data' => $data['data'] ?? [], + 'app' => $notification['notify_app'] ?? $data['data']['app'] ?? null, + 'app_id' => $notification['notify_app_id'] ?? $data['data']['id'] ?? null, ]; // aggregate by app:id reporting only the newest entry - if (!empty($data['extra_data']['id'])) + if (!empty($data['app_id'])) { - if (!isset($result[$id = $data['extra_data']['app'] . ':' . $data['extra_data']['id']])) + if (!isset($result[$id = $data['app'] . ':' . $data['app_id']])) { $result[$id] = $data; } - else - { - $total--; - /* in case we want to show all - $result['id']['others'][] = $data; - */ - } } else { $result[] = $data; } + if (count($result) >= min($num_rows, $total)) break; } $n += $chunk_size; } diff --git a/notifications/js/notificationajaxpopup.js b/notifications/js/notificationajaxpopup.js index 4344810388..2227c7f840 100644 --- a/notifications/js/notificationajaxpopup.js +++ b/notifications/js/notificationajaxpopup.js @@ -6,7 +6,6 @@ * @subpackage ajaxpoup * @link http://www.egroupware.org * @author Cornelius Weiss