Răsfoiți Sursa

Ticket #5653 - Notifications: Improve grouping for notifications with the same source.

AntonLV 1 lună în urmă
părinte
comite
e9d51bc852

+ 96 - 34
modules/boonex/notifications/classes/BxNtfsCronNotify.php

@@ -19,6 +19,8 @@ class BxNtfsCronNotify extends BxDolCron
     protected $_sModule;
     protected $_oModule;
 
+    protected $_bOwnActions;
+    protected $_bEventsGroupedDb;
     protected $_bDeliveryTimeout;
 
     public function __construct()
@@ -28,6 +30,10 @@ class BxNtfsCronNotify extends BxDolCron
 
         parent::__construct();
 
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $this->_bOwnActions = getParam($CNF['PARAM_OWN_ACTIONS']) == 'on';
+        $this->_bEventsGroupedDb = $this->_oModule->_oConfig->isEventsGroupedDb();
         $this->_bDeliveryTimeout = $this->_oModule->_oConfig->getDeliveryTimeout() > 0;
     }
 
@@ -42,22 +48,47 @@ class BxNtfsCronNotify extends BxDolCron
         }
 
         $aEvents = $this->_oModule->_oDb->getEventsToProcess((int)getParam($CNF['PARAM_QUEUE_ADD_LIMIT']));
-
         foreach($aEvents as $aEvent) {
             if(!empty($aEvent['content']) && is_string($aEvent['content']))
                 $aEvent['content'] = unserialize($aEvent['content']);
 
-            $this->_sendNotifications($aEvent);
+            $this->_processNotifications($aEvent);
         }
+
+        if($this->_bEventsGroupedDb && ($aProfiles = $this->_oModule->_oDb->aggregatorGetProfiles()) && is_array($aProfiles))
+            foreach($aProfiles as $iProfile) {
+                $aEventsIds = [];
+                foreach([BX_BASE_MOD_NTFS_DTYPE_EMAIL, BX_BASE_MOD_NTFS_DTYPE_PUSH] as $sDeliveryType) {
+                    $sMethodGet = 'getNotification' . bx_gen_method_name($sDeliveryType);
+
+                    $aEvents = $this->_oModule->_oDb->aggregatorGetEvents($iProfile, $sDeliveryType);
+                    foreach($aEvents as $aEvent) {
+                        if(($sGroupedByMac = $aEvent['grouped_by_mac'] ?? false))
+                            $aEventsIds = array_merge($aEventsIds,  explode(',', $sGroupedByMac));
+
+                        $sEvent = $this->_oModule->_oTemplate->getPost($aEvent, ['perform_privacy_check_for' => $iProfile, 'show_real_profile' => false]);
+                        if(empty($sEvent) || empty($aEvent['content_parsed']))
+                            continue;
+
+                        $mixedNotification = false;
+                        if(($mixedNotification = $this->_oModule->_oTemplate->$sMethodGet($iProfile, $aEvent)) === false)
+                            continue;
+
+                        $this->_sendNotification($iProfile, $aEvent['id'], $sDeliveryType, $mixedNotification);
+                    }
+                }
+
+                $this->_oModule->_oDb->aggregatorDelete($iProfile, array_unique($aEventsIds));
+            }
     }
 
-    protected function _sendNotifications(&$aEvent)
+    protected function _processNotifications(&$aEvent)
     {
         $aHandler = $this->_oModule->_oConfig->getHandlers($aEvent['type'] . '_' . $aEvent['action']);
         if(empty($aHandler) || !is_array($aHandler))
             return;
 
-        $aDeliveryTypes = array();
+        $aDeliveryTypes = [];
 
         $iId = (int)$aEvent['id'];
         $iSilentMode = $this->_oModule->getSilentMode($aEvent['content']);
@@ -76,32 +107,28 @@ class BxNtfsCronNotify extends BxDolCron
                 break;
 
             default:
-                $aDeliveryTypes = array(BX_BASE_MOD_NTFS_DTYPE_EMAIL, BX_BASE_MOD_NTFS_DTYPE_PUSH);
+                $aDeliveryTypes = [BX_BASE_MOD_NTFS_DTYPE_EMAIL, BX_BASE_MOD_NTFS_DTYPE_PUSH];
         }
 
-        $aSendUsing = array();
+        $aSendUsing = [];
         foreach($aDeliveryTypes as $sDeliveryType) {
             $aHidden = $this->_oModule->_oConfig->getHandlersHidden($sDeliveryType);
             if(in_array($aHandler['id'], $aHidden))
                 continue;
 
-            $sMethodPostfix = bx_gen_method_name($sDeliveryType);
-            $sMethodGet = 'getNotification' . $sMethodPostfix;
-            $sMethodSend = 'sendNotification' . $sMethodPostfix;
-            if(!$this->_oModule->_oTemplate->isMethodExists($sMethodGet) || !method_exists($this->_oModule, $sMethodSend))
+            $sMethod = 'Notification' . bx_gen_method_name($sDeliveryType);
+            $sMethodGet = 'get' . $sMethod;
+            if(!$this->_oModule->_oTemplate->isMethodExists($sMethodGet) || !method_exists($this->_oModule, 'send' . $sMethod))
                 continue;
 
-            $aSendUsing[$sDeliveryType] = array(
-                'method_get' => $sMethodGet,
-            	'method_send' => $sMethodSend,
-            );
+            $aSendUsing[$sDeliveryType] = $sMethodGet;
         }
 
         if(empty($aSendUsing) || !is_array($aSendUsing))
             return;
 
         $iOwner = (int)$aEvent['owner_id'];
-        $aRecipients = array();
+        $aRecipients = [];
 
         //--- Get recipients: Subscribers.
         $oConnection = BxDolConnection::getObjectInstance($this->_oModule->_oConfig->getObject('conn_subscriptions'));
@@ -133,51 +160,86 @@ class BxNtfsCronNotify extends BxDolCron
             if($iIdRead >= $iId)
                 continue;
 
+            if(!$this->_bOwnActions && $aEvent['author_id'] == $iRecipient)
+                continue;
+
             if($oPrivacyExt !== false && !$oPrivacyExt->check($aEvent['id'], $iRecipient)) 
                 continue;
 
             if($oPrivacyInt !== false && !$oPrivacyInt->check($aEvent['id'], $iRecipient))
                 continue;
 
-            foreach($aSendUsing as $sDeliveryType => $aDeliveryType)
+            /**
+             * Check if the Recipient can view the notification.
+             */
+            $sEvent = $this->_oModule->_oTemplate->getPost($aEvent, ['perform_privacy_check_for' => $iRecipient, 'show_real_profile' => false]);
+            if(empty($sEvent) || empty($aEvent['content_parsed']))
+                continue;
+
+            foreach($aSendUsing as $sDeliveryType => $sDeliveryTypeGet) {
+                $mixedNotification = false;
+                if(!$this->_bEventsGroupedDb && ($mixedNotification = $this->_oModule->_oTemplate->$sDeliveryTypeGet($iRecipient, $aEvent)) === false)
+                    continue;
+
                 foreach($aSettingTypes as $sSettingType) {
-                    $aSetting = $this->_oModule->_oDb->getSetting(array('by' => 'tsu_allowed', 'handler_id' => $aHandler['id'], 'delivery' => $sDeliveryType, 'type' => $sSettingType, 'user_id' => $iRecipient));
+                    $aSetting = $this->_oModule->_oDb->getSetting(['by' => 'tsu_allowed', 'handler_id' => $aHandler['id'], 'delivery' => $sDeliveryType, 'type' => $sSettingType, 'user_id' => $iRecipient]);
                     if(empty($aSetting) || !is_array($aSetting))
                         continue;
 
                     if((int)$aSetting['active_adm'] == 0 || (int)$aSetting['active_pnl'] == 0)
                         continue;
 
-                    $mixedNotification = $this->_oModule->_oTemplate->{$aDeliveryType['method_get']}($iRecipient, $aEvent);
-                    if($mixedNotification === false)
-                        continue;
-            
-                    /**
-                     * 'break' is essential in the next two conditions to avoid 
-                     * duplicate sending to the same recipient.
-                     */
-                    if($this->_bDeliveryTimeout && $this->_oModule->_oDb->queueAdd(array(
-                        'profile_id' => $iRecipient, 
-                        'event_id' => $aEvent['id'], 
-                        'delivery' => $sDeliveryType,
-                        'content' => serialize($mixedNotification),
-                        'date' => time()
-                    )) !== false)
+                    if($this->_bEventsGroupedDb) {
+                        $this->_oModule->_oDb->aggregatorAdd([
+                            'profile_id' => $iRecipient, 
+                            'event_id' => $aEvent['id'], 
+                            'delivery' => $sDeliveryType
+                        ]);
                         break;
+                    }
 
-                    if($this->_oModule->{$aDeliveryType['method_send']}($iRecipient, $mixedNotification) !== false)
+                    /**
+                     * (break) is essential to avoid duplicate sending to the same recipient.
+                     */
+                    if($this->_sendNotification($iRecipient, $aEvent['id'], $sDeliveryType, $mixedNotification) !== false)
                         break;
                 }
+            }
         }
     }
 
     protected function _addRecipient($iUser, $sSettingType, &$aRecipients)
     {
         if(!isset($aRecipients[$iUser]))
-            $aRecipients[$iUser] = array();
+            $aRecipients[$iUser] = [];
 
         $aRecipients[$iUser][] = $sSettingType;
     }
+
+    private function _sendNotification($iRecipient, $iEventId, $sDeliveryType, $aNotification)
+    {
+        /**
+         * 'return true' (break) is essential to avoid duplicate sending to the same recipient.
+         * Don't send directly if message was successfully queued.
+         */
+        if($this->_bDeliveryTimeout && $this->_oModule->_oDb->queueAdd([
+            'profile_id' => $iRecipient, 
+            'event_id' => $iEventId, 
+            'delivery' => $sDeliveryType,
+            'content' => serialize($aNotification),
+            'date' => time()
+        ]) !== false)
+            return true;
+
+        /**
+         * 'return true' (break) is essential to avoid duplicate sending to the same recipient.
+         * If a message was sent as 'personal', don't send it by subscription ('follow_member', 'follow_context').
+         */
+        if($this->_oModule->{'sendNotification' . bx_gen_method_name($sDeliveryType)}($iRecipient, $aNotification) !== false)
+            return true;
+
+        return false;
+    }
 }
 
 /** @} */

+ 41 - 1
modules/boonex/notifications/classes/BxNtfsDb.php

@@ -12,6 +12,7 @@
 class BxNtfsDb extends BxBaseModNotificationsDb
 {
     protected $_sTableEvt2Usr;
+    protected $_sTableAggregator;
     protected $_sTableQueue;
     protected $_sTableRead;
     protected $_sTableEtemplates;
@@ -30,6 +31,7 @@ class BxNtfsDb extends BxBaseModNotificationsDb
         );
 
         $this->_sTableEvt2Usr = $this->_sPrefix . 'events2users';
+        $this->_sTableAggregator = $this->_sPrefix . 'aggregator';
         $this->_sTableQueue = $this->_sPrefix . 'queue';
         $this->_sTableRead = $this->_sPrefix . 'read';
         $this->_sTableEtemplates = $this->_sPrefix . 'etemplates';
@@ -502,7 +504,8 @@ class BxNtfsDb extends BxBaseModNotificationsDb
         if(!empty($aEvents) && is_array($aEvents)) {
             $aEventEnd = end($aEvents);
 
-            $this->_oConfig->setProcessedEvent($aEventEnd['id']);
+            //TODO: uncomment!!!
+            //$this->_oConfig->setProcessedEvent($aEventEnd['id']);
 
             reset($aEvents);
         }
@@ -510,6 +513,43 @@ class BxNtfsDb extends BxBaseModNotificationsDb
         return $aEvents;
     }
 
+    public function aggregatorGetProfiles()
+    {
+        return $this->getColumn("SELECT DISTINCT `profile_id` FROM `" . $this->_sTableAggregator . "`");
+    }
+
+    public function aggregatorGetEvents($iProfileId, $sDeliveryType)
+    {
+        $sQuery = "SELECT 
+                `te`.*, GROUP_CONCAT(`te`.`id` ORDER BY `th`.`priority` DESC SEPARATOR ',') AS `grouped_by_mac` 
+            FROM `" . $this->_sTableAggregator . "` AS `ta` 
+            INNER JOIN `" . $this->_sTable . "` AS `te` ON `ta`.`event_id`=`te`.`id` 
+            INNER JOIN `" . $this->_sTableHandlers . "` AS `th` ON `te`.`type`=`th`.`alert_unit` AND `te`.`action`=`th`.`alert_action`
+            WHERE 
+                `ta`.`profile_id`=:profile_id AND `ta`.`delivery`=:delivery 
+            GROUP BY `te`.`source_mac`";
+
+        return $this->getAll($sQuery, [
+            'profile_id' => $iProfileId,
+            'delivery' => $sDeliveryType
+        ]);
+    }
+
+    public function aggregatorAdd($aSet)
+    {
+        if(empty($aSet))
+            return false;
+
+        return $this->query("INSERT IGNORE INTO `" . $this->_sTableAggregator . "` SET " . $this->arrayToSQL($aSet)) !== false;
+    }
+
+    public function aggregatorDelete($iProfileId, $aEventsIds)
+    {
+        return (int)$this->query("DELETE FROM `" . $this->_sTableAggregator . "` WHERE `profile_id`=:profile_id AND `event_id` IN (" . $this->implode_escape($aEventsIds) . ")", [
+            'profile_id' => $iProfileId,
+        ]) > 0;
+    }
+
     public function queueGet($aParams)
     {
     	$aMethod = array('name' => 'getAll', 'params' => array(0 => 'query'));

+ 6 - 14
modules/boonex/notifications/classes/BxNtfsTemplate.php

@@ -385,13 +385,9 @@ class BxNtfsTemplate extends BxBaseModNotificationsTemplate
 
     public function getNotificationEmail($iRecipient, &$aEvent)
     {
-        $sEvent = $this->getPost($aEvent, ['perform_privacy_check_for' => $iRecipient, 'show_real_profile' => false]);
-        if(empty($sEvent) || empty($aEvent['content_parsed']))
-            return false;
-
         $aContent = &$aEvent['content'];
 
-        $sIconUrl = !empty($aContent['owner_icon']) ? $aContent['owner_icon'] : $this->getIconUrl('std-icon.svg');
+        $sIconUrl = ($_sIconUrl = $aContent['author_icon'] ?? false) || ($_sIconUrl = $aContent['owner_icon'] ?? false) ? $_sIconUrl : $this->getIconUrl('std-icon.svg');
         $sContentUrl = bx_absolute_url($this->_getContentLink($aEvent, getParam('sys_api_url_root_email') != ''));
         $sContent = is_array($aEvent['content_parsed']) && isset($aEvent['content_parsed']['email']) ? $aEvent['content_parsed']['email'] : $aEvent['content_parsed'];
         $sSummary = (($sK = 'subentry_summary') && ($sV = $aContent[$sK] ?? false)) || (($sK = 'entry_summary') && ($sV = $aContent[$sK] ?? false)) ? $sV : '';
@@ -448,10 +444,6 @@ class BxNtfsTemplate extends BxBaseModNotificationsTemplate
 
     public function getNotificationPush($iRecipient, &$aEvent)
     {
-        $sEvent = $this->getPost($aEvent, array('perform_privacy_check_for' => $iRecipient, 'show_real_profile' => false));
-        if(empty($sEvent) || empty($aEvent['content_parsed']))
-            return false;
-
         $sMessage = is_array($aEvent['content_parsed']) && isset($aEvent['content_parsed']['push']) ? $aEvent['content_parsed']['push'] : $aEvent['content_parsed'];
         $sMessage = preg_replace('/<\/?[a-zA-Z0-9=\'":;\(\)\s_-]+>/i', ($sChar = getParam("bx_ntfs_option_tag_to_char_push")) !== false ? $sChar : '"', $sMessage);
         if($sMessage)
@@ -461,14 +453,14 @@ class BxNtfsTemplate extends BxBaseModNotificationsTemplate
             return false;
 
         $aContent = &$aEvent['content'];
-        return array(
-            'content' => array(
+        return [
+            'content' => [
                 'url' => bx_absolute_url($this->_getContentLink($aEvent, getParam('sys_api_url_root_push') != '')),
                 'message' => $sMessage,
                 'icon' => !empty($aContent['owner_icon']) ? $aContent['owner_icon'] : ''
-            ),
-            'settings' => !empty($aContent['settings']['push']) ? $aContent['settings']['push'] : array()
-        );
+            ],
+            'settings' => !empty($aContent['settings']['push']) ? $aContent['settings']['push'] : []
+        ];
     }
 
     public function getEmpty($bVisible = true)

+ 10 - 2
modules/boonex/notifications/install/sql/install.sql

@@ -13,8 +13,8 @@ CREATE TABLE IF NOT EXISTS `bx_notifications_events` (
   `object_privacy_view` varchar(32) NOT NULL default '3',
   `subobject_id` int(11) NOT NULL default '0',
   `content` text NOT NULL,
-  `source` varchar(32) NOT NULL default '',
-  `source_mac` varchar(32) NOT NULL default '',
+  `source` varchar(48) NOT NULL default '',
+  `source_mac` varchar(64) NOT NULL default '',
   `allow_view_event_to` varchar(32) NOT NULL default '3',
   `date` int(11) NOT NULL default '0',
   `processed` tinyint(4) NOT NULL default '0',
@@ -212,6 +212,14 @@ INSERT INTO `bx_notifications_settings`(`group`, `handler_id`, `delivery`, `type
 ('action_required', @iHandlerId, 'email', 'personal', '_bx_ntfs_alert_action_reported_content_personal', 8),
 ('action_required', @iHandlerId, 'push', 'personal', '_bx_ntfs_alert_action_reported_content_personal', 8);
 
+CREATE TABLE IF NOT EXISTS `bx_notifications_aggregator` (
+  `id` int(11) NOT NULL auto_increment,
+  `profile_id` int(11) NOT NULL DEFAULT '0',
+  `event_id` int(11) NOT NULL DEFAULT '0',
+  `delivery` varchar(64) NOT NULL default '',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `item` (`profile_id`, `event_id`, `delivery`(16))
+);
 
 CREATE TABLE IF NOT EXISTS `bx_notifications_queue` (
   `id` int(11) NOT NULL auto_increment,