Procházet zdrojové kódy

Ticket #3732 - Pre-moderation for group based modules: Notifications.

AntonLV před 4 roky
rodič
revize
d0aad220fa

+ 6 - 0
inc/classes/BxDolAcl.php

@@ -47,6 +47,12 @@ define('MEMBERSHIP_ID_SUSPENDED', 6);
 define('MEMBERSHIP_ID_MODERATOR', 7);
 define('MEMBERSHIP_ID_ADMINISTRATOR', 8);
 
+/**
+ * Standard membership actions
+ */
+define('MEMBERSHIP_ACTION_EDIT_ANY_ENTRY', 'edit any entry');
+define('MEMBERSHIP_ACTION_DELETE_ANY_ENTRY', 'delete any entry');
+
 /**
  * Indices for checkAction() result array
  */

+ 37 - 3
inc/classes/BxDolAclQuery.php

@@ -396,7 +396,7 @@ class BxDolAclQuery extends BxDolDb implements iBxDolSingleton
 	    return $this->query($sQuery);
     }
 
-	function getContentByLevelAsSQLPart($sContentTable, $sContentField, $mixedLevelId)
+    function getContentByLevelAsSQLPart($sContentTable, $sContentField, $mixedLevelId)
     {
     	$sJoin = $sWhere = ""; 
         $iLevelId = !is_array($mixedLevelId) ? $mixedLevelId : 0;
@@ -442,13 +442,47 @@ class BxDolAclQuery extends BxDolDb implements iBxDolSingleton
             );
         }
     }
+    
+    function getContentByActionAsSQLPart($sContentTable, $sContentField, $mixedActionName, $aParams = [])
+    {
+        $sWhere = " AND (`tlm`.`DateStarts` IS NULL OR `tlm`.`DateStarts` <= NOW()) AND (`tlm`.`DateExpires` IS NULL OR `tlm`.`DateExpires` > NOW()) ";
+
+        $sJoinWhere = "";
+        if(is_array($mixedActionName))
+            $sJoinWhere .= " AND `ta`.`Name` IN (" . $this->implode_escape($mixedActionName) . ")";
+        else
+            $sJoinWhere .= $this->prepareAsString(" AND `ta`.`Name` = ?", $mixedActionName);
+        
+        if(!empty($aParams['module']))
+            $sJoinWhere .= $this->prepareAsString(" AND `ta`.`Module` = ?", $aParams['module']);
+
+        $sJoin = " INNER JOIN `sys_acl_levels_members` AS `tlm` ON `" . $sContentTable . "`.`" . $sContentField . "`=`tlm`.`IDMember` INNER JOIN `sys_acl_matrix` AS `tm` ON `tlm`.`IDLevel`=`tm`.`IDLevel` INNER JOIN `sys_acl_actions` AS `ta` ON (`tm`.`IDAction`=`ta`.`ID` " . $sJoinWhere . ") ";
+
+        return array(
+            'where' => $sWhere,
+            'join' => $sJoin
+        );
+    }
 
     function getProfilesByMembership($mixedLevelId)
     {
     	$aSqlParts = $this->getContentByLevelAsSQLPart('sys_profiles', 'id', $mixedLevelId);
 
-    	$sSql = $this->prepare("SELECT `sys_profiles`.* FROM `sys_profiles`" . $aSqlParts['join'] . " WHERE 1" . $aSqlParts['where']);
-    	return $this->getAll($sSql); 
+    	return $this->getAll("SELECT `sys_profiles`.* FROM `sys_profiles`" . $aSqlParts['join'] . " WHERE 1" . $aSqlParts['where']);
+    }
+
+    function getProfilesByAction($mixedActionName, $aParams = [])
+    {
+        $sMethod = "getAll";
+        $sSqlSelect = "`sys_profiles`.*";
+        if(isset($aParams['ids_only']) && $aParams['ids_only'] === true) {
+            $sMethod = "getColumn";
+            $sSqlSelect = "`sys_profiles`.`id`";
+        }
+
+        $aSqlParts = $this->getContentByActionAsSQLPart('sys_profiles', 'id', $mixedActionName, $aParams);
+
+    	return $this->$sMethod("SELECT DISTINCT " . $sSqlSelect . " FROM `sys_profiles`" . $aSqlParts['join'] . " WHERE 1" . $aSqlParts['where']);
     }
 }
 

+ 1 - 1
install/sql/system.sql

@@ -345,7 +345,7 @@ INSERT INTO `sys_options`(`category_id`, `name`, `caption`, `value`, `type`, `ex
 
 (@iCategoryId, 'sys_csp_frame_ancestors', '_adm_stg_cpt_option_sys_csp_frame_ancestors', '*', 'digit', '', '', '', '', 150),
 
-(@iCategoryId, 'sys_allow_approve_by_role', '_adm_stg_cpt_option_sys_allow_approve_by_role', '', 'checkbox', '', '', '', '', 160);
+(@iCategoryId, 'sys_notify_to_approve_by_role', '_adm_stg_cpt_option_sys_notify_to_approve_by_role', '', 'checkbox', '', '', '', '', 160);
 
 --
 -- CATEGORY (HIDDEN): System

+ 137 - 68
modules/base/general/classes/BxBaseModGeneralModule.php

@@ -1729,74 +1729,88 @@ class BxBaseModGeneralModule extends BxDolModule
         if(BxDolPrivacy::getObjectInstance($sEventPrivacy) === false)
             $sEventPrivacy = '';
 
-        $aResult = array(
-            'handlers' => array(
-                array('group' => $sModule . '_object', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'added', 'module_name' => $sModule, 'module_method' => 'get_notifications_post', 'module_class' => 'Module', 'module_event_privacy' => $sEventPrivacy),
-                array('group' => $sModule . '_object', 'type' => 'update', 'alert_unit' => $sModule, 'alert_action' => 'edited'),
-                array('group' => $sModule . '_object', 'type' => 'delete', 'alert_unit' => $sModule, 'alert_action' => 'deleted'),
+        $aResult = [
+            'handlers' => [
+                ['group' => $sModule . '_object', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'added', 'module_name' => $sModule, 'module_method' => 'get_notifications_post', 'module_class' => 'Module', 'module_event_privacy' => $sEventPrivacy],
+                ['group' => $sModule . '_object', 'type' => 'update', 'alert_unit' => $sModule, 'alert_action' => 'edited'],
+                ['group' => $sModule . '_object', 'type' => 'delete', 'alert_unit' => $sModule, 'alert_action' => 'deleted'],
 
-                array('group' => $sModule . '_comment', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'commentPost', 'module_name' => $sModule, 'module_method' => 'get_notifications_comment', 'module_class' => 'Module', 'module_event_privacy' => $sEventPrivacy),
-                array('group' => $sModule . '_comment', 'type' => 'delete', 'alert_unit' => $sModule, 'alert_action' => 'commentRemoved'),
+                ['group' => $sModule . '_object_approved', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'approved', 'module_name' => $sModule, 'module_method' => 'get_notifications_post_approved', 'module_class' => 'Module', 'module_event_privacy' => $sEventPrivacy],
 
-                array('group' => $sModule . '_reply', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'replyPost', 'module_name' => $sModule, 'module_method' => 'get_notifications_reply', 'module_class' => 'Module', 'module_event_privacy' => $sEventPrivacy),
-                array('group' => $sModule . '_reply', 'type' => 'delete', 'alert_unit' => $sModule, 'alert_action' => 'replyRemoved'),
+                ['group' => $sModule . '_comment', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'commentPost', 'module_name' => $sModule, 'module_method' => 'get_notifications_comment', 'module_class' => 'Module', 'module_event_privacy' => $sEventPrivacy],
+                ['group' => $sModule . '_comment', 'type' => 'delete', 'alert_unit' => $sModule, 'alert_action' => 'commentRemoved'],
 
-                array('group' => $sModule . '_vote', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'doVote', 'module_name' => $sModule, 'module_method' => 'get_notifications_vote', 'module_class' => 'Module', 'module_event_privacy' => $sEventPrivacy),
-                array('group' => $sModule . '_vote', 'type' => 'delete', 'alert_unit' => $sModule, 'alert_action' => 'undoVote'),
+                ['group' => $sModule . '_reply', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'replyPost', 'module_name' => $sModule, 'module_method' => 'get_notifications_reply', 'module_class' => 'Module', 'module_event_privacy' => $sEventPrivacy],
+                ['group' => $sModule . '_reply', 'type' => 'delete', 'alert_unit' => $sModule, 'alert_action' => 'replyRemoved'],
 
-                array('group' => $sModule . '_reaction', 'type' => 'insert', 'alert_unit' => $sModule . '_reactions', 'alert_action' => 'doVote', 'module_name' => $sModule, 'module_method' => 'get_notifications_reaction', 'module_class' => 'Module', 'module_event_privacy' => $sEventPrivacy),
-                array('group' => $sModule . '_reaction', 'type' => 'delete', 'alert_unit' => $sModule . '_reactions', 'alert_action' => 'undoVote'),
+                ['group' => $sModule . '_vote', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'doVote', 'module_name' => $sModule, 'module_method' => 'get_notifications_vote', 'module_class' => 'Module', 'module_event_privacy' => $sEventPrivacy],
+                ['group' => $sModule . '_vote', 'type' => 'delete', 'alert_unit' => $sModule, 'alert_action' => 'undoVote'],
 
-                array('group' => $sModule . '_score_up', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'doVoteUp', 'module_name' => $sModule, 'module_method' => 'get_notifications_score_up', 'module_class' => 'Module', 'module_event_privacy' => $sEventPrivacy),
+                ['group' => $sModule . '_reaction', 'type' => 'insert', 'alert_unit' => $sModule . '_reactions', 'alert_action' => 'doVote', 'module_name' => $sModule, 'module_method' => 'get_notifications_reaction', 'module_class' => 'Module', 'module_event_privacy' => $sEventPrivacy],
+                ['group' => $sModule . '_reaction', 'type' => 'delete', 'alert_unit' => $sModule . '_reactions', 'alert_action' => 'undoVote'],
 
-                array('group' => $sModule . '_score_down', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'doVoteDown', 'module_name' => $sModule, 'module_method' => 'get_notifications_score_down', 'module_class' => 'Module', 'module_event_privacy' => $sEventPrivacy),
-            ),
-            'settings' => array(
-                array('group' => 'content', 'unit' => $sModule, 'action' => 'added', 'types' => array('follow_member', 'follow_context')),
-                array('group' => 'comment', 'unit' => $sModule, 'action' => 'commentPost', 'types' => array('personal', 'follow_member', 'follow_context')),
-                array('group' => 'reply', 'unit' => $sModule, 'action' => 'replyPost', 'types' => array('personal')),
-                array('group' => 'vote', 'unit' => $sModule, 'action' => 'doVote', 'types' => array('personal', 'follow_member', 'follow_context')),
-                array('group' => 'vote', 'unit' => $sModule . '_reactions', 'action' => 'doVote', 'types' => array('personal', 'follow_member', 'follow_context')),
-                array('group' => 'score_up', 'unit' => $sModule, 'action' => 'doVoteUp', 'types' => array('personal', 'follow_member', 'follow_context')),
-                array('group' => 'score_down', 'unit' => $sModule, 'action' => 'doVoteDown', 'types' => array('personal', 'follow_member', 'follow_context'))
-            ),
-            'alerts' => array(
-                array('unit' => $sModule, 'action' => 'added'),
-                array('unit' => $sModule, 'action' => 'edited'),
-                array('unit' => $sModule, 'action' => 'deleted'),
+                ['group' => $sModule . '_score_up', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'doVoteUp', 'module_name' => $sModule, 'module_method' => 'get_notifications_score_up', 'module_class' => 'Module', 'module_event_privacy' => $sEventPrivacy],
 
-                array('unit' => $sModule, 'action' => 'commentPost'),
-                array('unit' => $sModule, 'action' => 'commentRemoved'),
+                ['group' => $sModule . '_score_down', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'doVoteDown', 'module_name' => $sModule, 'module_method' => 'get_notifications_score_down', 'module_class' => 'Module', 'module_event_privacy' => $sEventPrivacy],
 
-                array('unit' => $sModule, 'action' => 'replyPost'),
-                array('unit' => $sModule, 'action' => 'replyRemoved'),
+                //--- Moderation related: For 'admins'.
+                ['group' => $sModule . '_object_pending_approval', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'pending_approval', 'module_name' => $sModule, 'module_method' => 'get_notifications_post_pending_approval', 'module_class' => 'Module', 'module_event_privacy' => $sEventPrivacy],
+            ],
+            'settings' => [
+                ['group' => 'content', 'unit' => $sModule, 'action' => 'added', 'types' => ['follow_member', 'follow_context']],
+                ['group' => 'content_updated', 'unit' => $sModule, 'action' => 'approved', 'types' => ['personal']],
+                ['group' => 'comment', 'unit' => $sModule, 'action' => 'commentPost', 'types' => ['personal', 'follow_member', 'follow_context']],
+                ['group' => 'reply', 'unit' => $sModule, 'action' => 'replyPost', 'types' => ['personal']],
+                ['group' => 'vote', 'unit' => $sModule, 'action' => 'doVote', 'types' => ['personal', 'follow_member', 'follow_context']],
+                ['group' => 'vote', 'unit' => $sModule . '_reactions', 'action' => 'doVote', 'types' => ['personal', 'follow_member', 'follow_context']],
+                ['group' => 'score_up', 'unit' => $sModule, 'action' => 'doVoteUp', 'types' => ['personal', 'follow_member', 'follow_context']],
+                ['group' => 'score_down', 'unit' => $sModule, 'action' => 'doVoteDown', 'types' => ['personal', 'follow_member', 'follow_context']],
 
-                array('unit' => $sModule, 'action' => 'doVote'),
-                array('unit' => $sModule, 'action' => 'undoVote'),
+                //--- Moderation related: For 'admins'.
+                ['group' => 'action_required', 'unit' => $sModule, 'action' => 'pending_approval', 'types' => ['personal']],
+            ],
+            'alerts' => [
+                ['unit' => $sModule, 'action' => 'added'],
+                ['unit' => $sModule, 'action' => 'edited'],
+                ['unit' => $sModule, 'action' => 'deleted'],
 
-                array('unit' => $sModule . '_reactions', 'action' => 'doVote'),
-                array('unit' => $sModule . '_reactions', 'action' => 'undoVote'),
+                ['unit' => $sModule, 'action' => 'approved'],
 
-                array('unit' => $sModule, 'action' => 'doVoteUp'),
-                array('unit' => $sModule, 'action' => 'doVoteDown'),
-            )
-        );
+                ['unit' => $sModule, 'action' => 'commentPost'],
+                ['unit' => $sModule, 'action' => 'commentRemoved'],
+
+                ['unit' => $sModule, 'action' => 'replyPost'],
+                ['unit' => $sModule, 'action' => 'replyRemoved'],
+
+                ['unit' => $sModule, 'action' => 'doVote'],
+                ['unit' => $sModule, 'action' => 'undoVote'],
+
+                ['unit' => $sModule . '_reactions', 'action' => 'doVote'],
+                ['unit' => $sModule . '_reactions', 'action' => 'undoVote'],
+
+                ['unit' => $sModule, 'action' => 'doVoteUp'],
+                ['unit' => $sModule, 'action' => 'doVoteDown'],
+
+                //--- Moderation related: For 'admins'.
+                ['unit' => $sModule, 'action' => 'pending_approval'],
+            ]
+        ];
 
         if(!empty($this->_oConfig->CNF['FIELDS_DELAYED_PROCESSING'])) {
-            $aResult['handlers'] = array_merge($aResult['handlers'], array(
-                array('group' => $sModule . '_object_publish_failed', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'publish_failed', 'module_name' => $sModule, 'module_method' => 'get_notifications_post_publish_failed', 'module_class' => 'Module', 'module_event_privacy' => ''),
-                array('group' => $sModule . '_object_publish_succeeded', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'publish_succeeded', 'module_name' => $sModule, 'module_method' => 'get_notifications_post_publish_succeeded', 'module_class' => 'Module', 'module_event_privacy' => ''),
-            ));
-
-            $aResult['settings'] = array_merge($aResult['settings'], array(
-                array('group' => 'content_publish_failed', 'unit' => $sModule, 'action' => 'publish_failed', 'types' => array('personal')),
-                array('group' => 'content_publish_succeeded', 'unit' => $sModule, 'action' => 'publish_succeeded', 'types' => array('personal')),
-            ));
-
-            $aResult['alerts'] = array_merge($aResult['alerts'], array(
-                array('unit' => $sModule, 'action' => 'publish_failed'),
-                array('unit' => $sModule, 'action' => 'publish_succeeded'),
-            ));
+            $aResult['handlers'] = array_merge($aResult['handlers'], [
+                ['group' => $sModule . '_object_publish_failed', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'publish_failed', 'module_name' => $sModule, 'module_method' => 'get_notifications_post_publish_failed', 'module_class' => 'Module', 'module_event_privacy' => ''],
+                ['group' => $sModule . '_object_publish_succeeded', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'publish_succeeded', 'module_name' => $sModule, 'module_method' => 'get_notifications_post_publish_succeeded', 'module_class' => 'Module', 'module_event_privacy' => ''],
+            ]);
+
+            $aResult['settings'] = array_merge($aResult['settings'], [
+                ['group' => 'content_updated', 'unit' => $sModule, 'action' => 'publish_failed', 'types' => ['personal']],
+                ['group' => 'content_updated', 'unit' => $sModule, 'action' => 'publish_succeeded', 'types' => ['personal']],
+            ]);
+
+            $aResult['alerts'] = array_merge($aResult['alerts'], [
+                ['unit' => $sModule, 'action' => 'publish_failed'],
+                ['unit' => $sModule, 'action' => 'publish_succeeded'],
+            ]);
         }
 
         return $aResult;
@@ -1841,6 +1855,24 @@ class BxBaseModGeneralModule extends BxDolModule
         );
     }
 
+    public function serviceGetNotificationsPostPendingApproval($aEvent)
+    {
+        $aResult = $this->serviceGetNotificationsPost($aEvent);
+
+        /**
+         * Unset Entry Author because the notification is addressed to 'admins'.
+         */
+        if(isset($aResult['entry_author']))
+            unset($aResult['entry_author']);
+
+        return $aResult;
+    }
+
+    public function serviceGetNotificationsPostApproved($aEvent)
+    {
+        return $this->serviceGetNotificationsPost($aEvent);
+    }
+
     public function serviceGetNotificationsPostPublishFailed($aEvent)
     {
         return $this->serviceGetNotificationsPost($aEvent);
@@ -1851,6 +1883,7 @@ class BxBaseModGeneralModule extends BxDolModule
         return $this->serviceGetNotificationsPost($aEvent);
     }
 
+
     /**
      * Entry post comment for Notifications module
      */
@@ -1863,22 +1896,22 @@ class BxBaseModGeneralModule extends BxDolModule
         if(empty($aContentInfo) || !is_array($aContentInfo))
             return array();
 
-		$oComment = BxDolCmts::getObjectInstance($CNF['OBJECT_COMMENTS'], $iContentId);
+        $oComment = BxDolCmts::getObjectInstance($CNF['OBJECT_COMMENTS'], $iContentId);
         if(!$oComment || !$oComment->isEnabled())
             return array();
 
         $sEntryUrl = BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $aContentInfo[$CNF['FIELD_ID']]);
         $sEntryCaption = isset($aContentInfo[$CNF['FIELD_TITLE']]) ? $aContentInfo[$CNF['FIELD_TITLE']] : strmaxtextlen($aContentInfo[$CNF['FIELD_TEXT']], 20, '...');
 
-		return array(
-			'entry_sample' => $CNF['T']['txt_sample_single'],
-			'entry_url' => $sEntryUrl,
-			'entry_caption' => $sEntryCaption,
-			'entry_author' => $aContentInfo[$CNF['FIELD_AUTHOR']],
-			'subentry_sample' => $CNF['T']['txt_sample_comment_single'],
-			'subentry_url' => $oComment->getViewUrl((int)$aEvent['subobject_id']),
-			'lang_key' => '', //may be empty or not specified. In this case the default one from Notification module will be used.
-		);
+        return array(
+            'entry_sample' => $CNF['T']['txt_sample_single'],
+            'entry_url' => $sEntryUrl,
+            'entry_caption' => $sEntryCaption,
+            'entry_author' => $aContentInfo[$CNF['FIELD_AUTHOR']],
+            'subentry_sample' => $CNF['T']['txt_sample_comment_single'],
+            'subentry_url' => $oComment->getViewUrl((int)$aEvent['subobject_id']),
+            'lang_key' => '', //may be empty or not specified. In this case the default one from Notification module will be used.
+        );
     }
 
 	/**
@@ -2577,7 +2610,7 @@ class BxBaseModGeneralModule extends BxDolModule
         if(!$iProfileId)
             $iProfileId = $this->_iProfileId;
 
-    	$aCheck = checkActionModule($iProfileId, 'edit any entry', $this->getName(), $isPerformAction);
+    	$aCheck = checkActionModule($iProfileId, MEMBERSHIP_ACTION_EDIT_ANY_ENTRY, $this->getName(), $isPerformAction);
     	if($aCheck[CHECK_ACTION_RESULT] === CHECK_ACTION_RESULT_ALLOWED)
     		return CHECK_ACTION_RESULT_ALLOWED;
 
@@ -2592,7 +2625,7 @@ class BxBaseModGeneralModule extends BxDolModule
         if(!$iProfileId)
             $iProfileId = $this->_iProfileId;
 
-    	$aCheck = checkActionModule($iProfileId, 'delete any entry', $this->getName(), $isPerformAction);
+    	$aCheck = checkActionModule($iProfileId, MEMBERSHIP_ACTION_DELETE_ANY_ENTRY, $this->getName(), $isPerformAction);
     	if($aCheck[CHECK_ACTION_RESULT] === CHECK_ACTION_RESULT_ALLOWED)
     		return CHECK_ACTION_RESULT_ALLOWED;
 
@@ -2765,8 +2798,9 @@ class BxBaseModGeneralModule extends BxDolModule
 
         $iId = (int)$aContentInfo[$CNF['FIELD_ID']];
 
+        $sModule = $this->getName();
         $aParams = $this->_alertParams($aContentInfo);
-        bx_alert($this->getName(), 'approved', $iId, false, $aParams);
+        bx_alert($sModule, 'approved', $iId, false, $aParams);
     }
 
     /**
@@ -3125,6 +3159,41 @@ class BxBaseModGeneralModule extends BxDolModule
         return sendMailTemplate($sETemplate, 0, $aContentInfo[$CNF['FIELD_AUTHOR']], $aEParams);
     }
 
+    protected function _processModerationNotifications($aContentInfo)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aRecipients = [];
+        if(getParam('sys_notify_to_approve_by_role') == 'on' && !empty($CNF['FIELD_ALLOW_VIEW_TO']) && (int)$aContentInfo[$CNF['FIELD_ALLOW_VIEW_TO']] < 0) {
+            $iContextProfileId = abs((int)$aContentInfo[$CNF['FIELD_ALLOW_VIEW_TO']]);
+            $oContextProfile = BxDolProfile::getInstance($iContextProfileId);
+
+            $aRecipients = bx_srv($oContextProfile->getModule(), 'get_admins_by_action', [$iContextProfileId, [
+                BX_BASE_MOD_GROUPS_ACTION_EDIT_CONTENT, 
+                BX_BASE_MOD_GROUPS_ACTION_DELETE_CONTENT
+            ]]);
+        }
+
+        $sModule = $this->getName();
+
+        if(empty($aRecipients))
+            $aRecipients = BxDolAclQuery::getInstance()->getProfilesByAction([
+                MEMBERSHIP_ACTION_EDIT_ANY_ENTRY, 
+                MEMBERSHIP_ACTION_DELETE_ANY_ENTRY
+            ], [
+                'module' => $sModule, 
+                'ids_only' => true
+            ]);
+
+        if(empty($aRecipients))
+            return;
+
+        foreach($aRecipients as $iRecipientId)
+            bx_alert($sModule, 'pending_approval', $aContentInfo[$CNF['FIELD_ID']], $aContentInfo[$CNF['FIELD_AUTHOR']], [
+                'object_author_id' => (int)$iRecipientId
+            ]);
+    }
+
     protected function _rss ($aArgs, $sClass = 'SearchResult')
     {
         $sMode = array_shift($aArgs);

+ 44 - 7
modules/base/groups/classes/BxBaseModGroupsModule.php

@@ -220,6 +220,32 @@ class BxBaseModGroupsModule extends BxBaseModProfileModule
         return $this->_oDb->getRole($iGroupProfileId, $iProfileId);
     }
 
+    /*
+     * Get context (group) advanced members who can perform the specified action.
+     * If 'Roles' are disabled for the context then all context admins are returned.
+     */
+    public function serviceGetAdminsByAction($iGroupProfileId, $mixedAction)
+    {
+        $aGroupContentInfo = $this->_oDb->getContentInfoByProfileId($iGroupProfileId);
+        if(empty($aGroupContentInfo) || !is_array($aGroupContentInfo))
+            return [];
+
+        $aAdmins = $this->_oDb->getAdmins($iGroupProfileId);
+        if(!$this->_oConfig->isRoles())
+            return $aAdmins;
+
+        if(!is_array($mixedAction))
+            $mixedAction = [$mixedAction];
+
+        $aResult = [];
+        foreach($mixedAction as $sAction)
+            foreach($aAdmins as $iAdminProfileId)
+                if(!in_array($iAdminProfileId, $aResult) && $this->isAllowedActionByRole($sAction, $aGroupContentInfo, $iGroupProfileId, $iAdminProfileId))
+                    $aResult[] = $iAdminProfileId;
+
+        return $aResult;
+    }
+
     /**
      * Delete profile from fans and admins tables
      * @param $iProfileId profile id 
@@ -1222,25 +1248,32 @@ class BxBaseModGroupsModule extends BxBaseModProfileModule
             $sActionModule = $this->getName();
         }
 
-        $bResult = $iProfileId ? $this->isAllowedModuleActionByRole($sActionModule, $sAction, $iProfileRole) : false;
+        $bResult = false;
+        if($iProfileId)
+            $bResult = $this->isAllowedModuleActionByRole($sActionModule, $sAction, $iProfileRole);
+
         // in case neither of the profile's roles are having permissions set explicitly then fallback to an old way
         if ($bResult === NULL) {
             $bResult = false;
-            if(!empty($iProfileRole)) {
+            if($this->isFanByGroupProfileId($iGroupProfileId)) {
                 switch ($sAction) {
                     case BX_BASE_MOD_GROUPS_ACTION_DELETE:
                     case BX_BASE_MOD_GROUPS_ACTION_EDIT:
                     case BX_BASE_MOD_GROUPS_ACTION_CHANGE_COVER:
                     case BX_BASE_MOD_GROUPS_ACTION_MANAGE_ROLES:
-                        if ($this->isRole($iProfileRole, BX_BASE_MOD_GROUPS_ROLE_ADMINISTRATOR)) $bResult = true;
+                        if($this->isRole($iProfileRole, BX_BASE_MOD_GROUPS_ROLE_ADMINISTRATOR)) 
+                            $bResult = true;
                         break;
+
                     case BX_BASE_MOD_GROUPS_ACTION_MANAGE_FANS:
                     case BX_BASE_MOD_GROUPS_ACTION_INVITE:
                     case BX_BASE_MOD_GROUPS_ACTION_EDIT_CONTENT:
                     case BX_BASE_MOD_GROUPS_ACTION_DELETE_CONTENT:
                     case BX_BASE_MOD_GROUPS_ACTION_TIMELINE_POST_PIN:
-                        if ($this->isRole($iProfileRole, BX_BASE_MOD_GROUPS_ROLE_ADMINISTRATOR) || $this->isRole($iProfileRole, BX_BASE_MOD_GROUPS_ROLE_MODERATOR)) $bResult = true;
+                        if($this->isRole($iProfileRole, BX_BASE_MOD_GROUPS_ROLE_ADMINISTRATOR) || $this->isRole($iProfileRole, BX_BASE_MOD_GROUPS_ROLE_MODERATOR)) 
+                            $bResult = true;
                         break;
+
                     default:
                         $bResult = true;
                 }
@@ -1248,7 +1281,7 @@ class BxBaseModGroupsModule extends BxBaseModProfileModule
         }
 
         // call alert to allow custom checks
-        bx_alert('system', 'check_allowed_action_by_role', 0, 0, array(
+        bx_alert('system', 'check_allowed_action_by_role', 0, 0, [
             'module' => $this->getName(), 
             'multi_roles' => $this->_oConfig->isMultiRoles(),
             'action' => $sAction,
@@ -1258,13 +1291,15 @@ class BxBaseModGroupsModule extends BxBaseModProfileModule
             'profile_id' => $iProfileId, 
             'profile_role' => $iProfileRole,
             'override_result' => &$bResult
-        ));
+        ]);
 
         return $bResult;
     }
 
-    public function isAllowedModuleActionByRole($sModule, $sAction, $iProfileRole) {
+    public function isAllowedModuleActionByRole($sModule, $sAction, $iProfileRole)
+    {
         static $aRoles;
+
         if (!$aRoles && isset($this->_oConfig->CNF['OBJECT_PRE_LIST_ROLES']) && !empty($this->_oConfig->CNF['OBJECT_PRE_LIST_ROLES']))
             $aRoles = BxBaseFormView::getDataItems($this->_oConfig->CNF['OBJECT_PRE_LIST_ROLES'], true, BX_DATA_VALUES_ALL);
 
@@ -1503,6 +1538,8 @@ class BxBaseModGroupsModule extends BxBaseModProfileModule
             'extras' => &$aParams
         ]);
         bx_alert($sModule, $sAction, $iId, false, $aParams);
+
+        $this->_processModerationNotifications($aContentInfo);
     }
 
     public function addFollower ($iProfileId1, $iProfileId2)

+ 8 - 0
modules/base/profile/classes/BxBaseModProfileDb.php

@@ -27,6 +27,14 @@ class BxBaseModProfileDb extends BxBaseModGeneralDb
         return $aInfo;
     }
 
+    public function getContentInfoByProfileId ($iProfileId)
+    {
+        $sQuery = $this->prepare ("SELECT `c`.*, `p`.`account_id`, `p`.`id` AS `profile_id`, `a`.`email` AS `profile_email`, `a`.`ip` AS `profile_ip`, `p`.`status` AS `profile_status` FROM `" . $this->_oConfig->CNF['TABLE_ENTRIES'] . "` AS `c` INNER JOIN `sys_profiles` AS `p` ON (`p`.`content_id` = `c`.`id` AND `p`.`type` = ?) INNER JOIN `sys_accounts` AS `a` ON (`p`.`account_id` = `a`.`id`) WHERE `p`.`id` = ?", $this->_oConfig->getName(), $iProfileId);
+        $aInfo = $this->getRow($sQuery);
+        bx_alert('profile', 'content_info_by_profile_id', $iProfileId, 0, array('module' => $this->_oConfig->getName(), 'info' => &$aInfo));
+        return $aInfo;
+    }
+
     public function resetContentPictureByFileId($iFileId, $sFieldPicture)
     {
         return $this->query("UPDATE `" . $this->_oConfig->CNF['TABLE_ENTRIES'] . "` SET `" . $sFieldPicture . "` = 0 WHERE `" . $sFieldPicture . "` = :file", [

+ 2 - 0
modules/base/text/classes/BxBaseModTextModule.php

@@ -758,6 +758,8 @@ class BxBaseModTextModule extends BxBaseModGeneralModule implements iBxDolConten
         $aParams = $this->_alertParamsAdd($aContentInfo);
         bx_alert('system', 'prepare_alert_params', 0, 0, array('unit'=> $this->getName(), 'action' => &$sAction, 'object_id' => &$iId, 'sender_id' => &$iAuthorId, 'extras' => &$aParams));
         bx_alert($this->getName(), $sAction, $iId, $iAuthorId, $aParams);
+
+        $this->_processModerationNotifications($aContentInfo);
     }
 
     public function alertAfterEdit($aContentInfo)

+ 0 - 8
modules/boonex/ads/classes/BxAdsModule.php

@@ -556,7 +556,6 @@ class BxAdsModule extends BxBaseModTextModule
         $aResult = parent::serviceGetNotificationsData();
         $aResult['handlers'] = array_merge($aResult['handlers'], array(
             array('group' => $sModule . '_interest', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'doInterest', 'module_name' => $sModule, 'module_method' => 'get_notifications_interest', 'module_class' => 'Module', 'module_event_privacy' => $sEventPrivacy),
-            array('group' => $sModule . '_approved', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'approved', 'module_name' => $sModule, 'module_method' => 'get_notifications_approved', 'module_class' => 'Module', 'module_event_privacy' => $sEventPrivacy),
             array('group' => $sModule . '_paid', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'license_register', 'module_name' => $sModule, 'module_method' => 'get_notifications_license_register', 'module_class' => 'Module', 'module_event_privacy' => $sEventPrivacy),
             //---> To Buyer
             array('group' => $sModule . '_shipped', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'shipped', 'module_name' => $sModule, 'module_method' => 'get_notifications_shipped', 'module_class' => 'Module', 'module_event_privacy' => $sEventPrivacy),
@@ -574,7 +573,6 @@ class BxAdsModule extends BxBaseModTextModule
         $aResult['settings'] = array_merge($aResult['settings'], array(
             array('group' => 'interest', 'unit' => $sModule, 'action' => 'doInterest', 'types' => array('personal')),
 
-            array('group' => 'usage', 'unit' => $sModule, 'action' => 'approved', 'types' => array('personal')),
             array('group' => 'usage', 'unit' => $sModule, 'action' => 'license_register', 'types' => array('personal')),
             array('group' => 'usage', 'unit' => $sModule, 'action' => 'shipped', 'types' => array('personal')),
             array('group' => 'usage', 'unit' => $sModule, 'action' => 'received', 'types' => array('personal')),
@@ -586,7 +584,6 @@ class BxAdsModule extends BxBaseModTextModule
 
         $aResult['alerts'] = array_merge($aResult['alerts'], array(
             array('unit' => $sModule, 'action' => 'doInterest'),
-            array('unit' => $sModule, 'action' => 'approved'),
             array('unit' => $sModule, 'action' => 'license_register'),
             array('unit' => $sModule, 'action' => 'shipped'),
             array('unit' => $sModule, 'action' => 'received'),
@@ -686,11 +683,6 @@ class BxAdsModule extends BxBaseModTextModule
         );
     }
 
-    public function serviceGetNotificationsApproved($aEvent)
-    {
-        return $this->_serviceGetNotificationsByEntryAndAction($aEvent, 'approved');
-    }
-
     public function serviceGetNotificationsLicenseRegister($aEvent)
     {
         return $this->_serviceGetNotificationsByEntryAndAction($aEvent, 'license_register');

+ 0 - 3
modules/boonex/ads/install/langs/en.xml

@@ -562,9 +562,6 @@
     <string name="_bx_ads_alert_action_doInterest_personal"><![CDATA[Someone interested in your ad]]></string>
     <string name="_bx_ads_txt_ntfs_subobject_interested"><![CDATA[<b class="bx-ntfs-content-profile">{owner_name}</b> {subentry_sample} <b class="bx-ntfs-content-entry">{entry_caption}</b>]]></string>
 
-    <string name="_bx_ads_alert_action_approved_personal"><![CDATA[Your ad was approved]]></string>
-    <string name="_bx_ads_txt_ntfs_object_approved"><![CDATA[<b class="bx-ntfs-content-profile">{owner_name}</b> approved your ad <b class="bx-ntfs-content-entry">{entry_caption}</b>]]></string>
-
     <string name="_bx_ads_alert_action_license_register_personal"><![CDATA[Your ad was purchased]]></string>
     <string name="_bx_ads_txt_ntfs_object_license_register"><![CDATA[<b class="bx-ntfs-content-profile">{owner_name}</b> purchased your ad <b class="bx-ntfs-content-entry">{entry_caption}</b>]]></string>
 

+ 1 - 1
modules/boonex/english/data/langs/system/en.xml

@@ -1645,7 +1645,7 @@
 	<string name="_adm_stg_cpt_option_sys_default_curl_timeout"><![CDATA[cURL - default timeout]]></string>
     <string name="_adm_stg_cpt_option_sys_ssl_allow_untrusted"><![CDATA[cURL - trust unsigned certificates or not verified hosts]]></string>
     <string name="_adm_stg_cpt_option_sys_csp_frame_ancestors"><![CDATA['Content-Security-Policy: frame-ancestors' header value]]></string>
-    <string name="_adm_stg_cpt_option_sys_allow_approve_by_role"><![CDATA[Allow context member with necessary role to approve content in the context]]></string>
+    <string name="_adm_stg_cpt_option_sys_notify_to_approve_by_role"><![CDATA[Notify context member with necessary role to approve content in the context]]></string>
     <string name="_adm_stg_cpt_option_template"><![CDATA[Default Template]]></string>
     <string name="_adm_stg_err_option_template"><![CDATA[Template cannot be empty and must have a valid name.]]></string>
     <string name="_adm_stg_cpt_option_use_like_operator"><![CDATA[Use operator LIKE for search (recommended for small content)]]></string>

+ 11 - 5
modules/boonex/notifications/classes/BxNtfsGridSettingsAdministration.php

@@ -178,11 +178,17 @@ class BxNtfsGridSettingsAdministration extends BxTemplGrid
                 $this->_updateSettingTitle($mixedValueKey, $aRow);
         }
 
-        if($this->_bGrouped && $aRow['type'] == BX_NTFS_STYPE_OTHER)
-            $mixedValue = sprintf($this->_sTitleMask, _t($this->_oModule->_oConfig->getHandlersUnitTitle($aRow['unit'])), $mixedValue);
-
-        if($this->_bGrouped && !$this->_isSettingsGroupValid($aRow))
-            $mixedValue = $this->_oTemplate->parseIcon('exclamation-triangle', array('class' => 'bx-def-margin-sec-right', 'title' => _t('_bx_ntfs_grid_column_title_title_warning'))) . $mixedValue;
+        if($this->_bGrouped) {
+            $sValueKey = '_bx_ntfs_alert_action_group_' . $aRow['group'];
+            $sValue = _t($sValueKey);
+            if(strcmp($sValueKey, $sValue) !== 0)
+                $mixedValue = $sValue;
+            else if($aRow['type'] == BX_NTFS_STYPE_OTHER)
+                $mixedValue = sprintf($this->_sTitleMask, _t($this->_oModule->_oConfig->getHandlersUnitTitle($aRow['unit'])), $mixedValue);
+
+            if(!$this->_isSettingsGroupValid($aRow))
+                $mixedValue = $this->_oTemplate->parseIcon('exclamation-triangle', array('class' => 'bx-def-margin-sec-right', 'title' => _t('_bx_ntfs_grid_column_title_title_warning'))) . $mixedValue;
+        }            
 
         return parent::_getCellDefault($mixedValue, $sKey, $aField, $aRow);
     }

+ 8 - 0
modules/boonex/notifications/classes/BxNtfsTemplate.php

@@ -371,6 +371,14 @@ class BxNtfsTemplate extends BxBaseModNotificationsTemplate
 
         $sKey = '';
         switch($aEvent['action']) {
+            case 'pending_approval':
+                $sKey = '_bx_ntfs_txt_object_pending_approval';
+                break;
+
+            case 'approved':
+                $sKey = '_bx_ntfs_txt_object_approved';
+                break;
+
             case 'publish_failed':
                 $sKey = '_bx_ntfs_txt_object_publish_failed';
                 break;

+ 9 - 3
modules/boonex/notifications/install/langs/en.xml

@@ -47,14 +47,16 @@
     <string name="_bx_ntfs_option_processed_event"><![CDATA[The last processed event upon notifications sending]]></string>
     <string name="_bx_ntfs_option_clear_interval"><![CDATA[Notification lifetime, in days (0 - no limit)]]></string>
 
-  <string name="_bx_ntfs_privacy_view"><![CDATA[View event]]></string>
+    <string name="_bx_ntfs_privacy_view"><![CDATA[View event]]></string>
+
+    <string name="_bx_ntfs_alert_action_group_content_updated"><![CDATA[Content was updated]]></string>
+    <string name="_bx_ntfs_alert_action_group_action_required"><![CDATA[Your action required]]></string>
 
     <string name="_bx_ntfs_alert_action_added"><![CDATA[add]]></string>
     <string name="_bx_ntfs_alert_action_commentPost"><![CDATA[add comment]]></string>
     <string name="_bx_ntfs_alert_action_replyPost"><![CDATA[reply to own comment]]></string>
     <string name="_bx_ntfs_alert_action_doVote"><![CDATA[add vote]]></string>
-    <string name="_bx_ntfs_alert_action_timeline_post_common"><![CDATA[post to timeline]]></string> 
-
+    <string name="_bx_ntfs_alert_action_timeline_post_common"><![CDATA[post to timeline]]></string>
     <string name="_bx_ntfs_alert_action_mention_added_personal"><![CDATA[Someone mentions you]]></string>
     <string name="_bx_ntfs_alert_action_friendship_added_personal"><![CDATA[You get a new friend]]></string>
     <string name="_bx_ntfs_alert_action_subscription_added_personal"><![CDATA[You get a new follower]]></string>
@@ -85,6 +87,8 @@
     <string name="_bx_ntfs_alert_action_timeline_post_common_follow_context"><![CDATA[New posts on Timeline]]></string>
     <string name="_bx_ntfs_alert_action_publish_failed_personal"><![CDATA[Delayed publishing failed]]></string>
     <string name="_bx_ntfs_alert_action_publish_succeeded_personal"><![CDATA[Delayed publishing succeeded]]></string>
+    <string name="_bx_ntfs_alert_action_pending_approval_personal"><![CDATA[New pending approval content]]></string>
+    <string name="_bx_ntfs_alert_action_approved_personal"><![CDATA[Your content was approved]]></string>
 
     <string name="_bx_ntfs_setting_title_mask"><![CDATA[{0}: {1}]]></string>
     <string name="_bx_ntfs_setting_type_personal"><![CDATA[Activity related to you]]></string>
@@ -106,6 +110,8 @@
     <string name="_bx_ntfs_txt_object_added_in_context"><![CDATA[<b class="bx-ntfs-content-profile">{object_owner_name}</b> added a {entry_sample} <b class="bx-ntfs-content-entry">{entry_caption}</b> in <b class="bx-ntfs-content-profile">{owner_name}</b>]]></string>
     <string name="_bx_ntfs_txt_object_publish_failed"><![CDATA[Failed to publish your {entry_sample} <b class="bx-ntfs-content-entry">{entry_caption}</b>]]></string>
     <string name="_bx_ntfs_txt_object_publish_succeeded"><![CDATA[Your {entry_sample} <b class="bx-ntfs-content-entry">{entry_caption}</b> was published successfully]]></string>
+    <string name="_bx_ntfs_txt_object_pending_approval"><![CDATA[<b class="bx-ntfs-content-profile">{owner_name}</b> added a pending approval {entry_sample} <b class="bx-ntfs-content-entry">{entry_caption}</b>]]></string>
+    <string name="_bx_ntfs_txt_object_approved"><![CDATA[<b class="bx-ntfs-content-profile">{owner_name}</b> approved your {entry_sample} <b class="bx-ntfs-content-entry">{entry_caption}</b>]]></string>
     <string name="_bx_ntfs_txt_subobject_added"><![CDATA[<b class="bx-ntfs-content-profile">{owner_name}</b> added a {subentry_sample} <b class="bx-ntfs-content-entry">{entry_caption}</b>]]></string>
     <string name="_bx_ntfs_txt_subobject_added_in_context"><![CDATA[<b class="bx-ntfs-content-profile">{owner_name}</b> has got a {subentry_sample} <b class="bx-ntfs-content-entry">{entry_caption}</b>]]></string>
 

+ 0 - 3
modules/boonex/russian/data/langs/bx_ads/ru.xml

@@ -554,9 +554,6 @@
     <string name="_bx_ads_alert_action_doInterest_personal"><![CDATA[Кто-то заинтересовался вашим объявлением]]></string>
     <string name="_bx_ads_txt_ntfs_subobject_interested"><![CDATA[<b class="bx-ntfs-content-profile">{owner_name}</b> {subentry_sample} <b class="bx-ntfs-content-entry">{entry_caption}</b>]]></string>
 
-    <string name="_bx_ads_alert_action_approved_personal"><![CDATA[Ваше объявление было активировано]]></string>
-    <string name="_bx_ads_txt_ntfs_object_approved"><![CDATA[<b class="bx-ntfs-content-profile">{owner_name}</b> активировал ваше объявление <b class="bx-ntfs-content-entry">{entry_caption}</b>]]></string>
-
     <string name="_bx_ads_alert_action_license_register_personal"><![CDATA[Ваш товар был куплен]]></string>
     <string name="_bx_ads_txt_ntfs_object_license_register"><![CDATA[<b class="bx-ntfs-content-profile">{owner_name}</b> купил ваш товар <b class="bx-ntfs-content-entry">{entry_caption}</b>]]></string>
 

+ 7 - 1
modules/boonex/russian/data/langs/bx_notifications/ru.xml

@@ -49,12 +49,14 @@
 
     <string name="_bx_ntfs_privacy_view"><![CDATA[Просматривать события]]></string>
 
+    <string name="_bx_ntfs_alert_action_group_content_updated"><![CDATA[Контент был обновлен]]></string>
+    <string name="_bx_ntfs_alert_action_group_action_required"><![CDATA[Требуются ваши действия]]></string>
+
     <string name="_bx_ntfs_alert_action_added"><![CDATA[добавить]]></string>
     <string name="_bx_ntfs_alert_action_commentPost"><![CDATA[добавить коментарий]]></string>
     <string name="_bx_ntfs_alert_action_replyPost"><![CDATA[ответ на собственный комментарий]]></string>
     <string name="_bx_ntfs_alert_action_doVote"><![CDATA[добавить голос]]></string>
     <string name="_bx_ntfs_alert_action_timeline_post_common"><![CDATA[сообщение на линейке]]></string>
-
     <string name="_bx_ntfs_alert_action_mention_added_personal"><![CDATA[Кто-то упомянул о Вас]]></string>
     <string name="_bx_ntfs_alert_action_friendship_added_personal"><![CDATA[У Вас новый друг]]></string>
     <string name="_bx_ntfs_alert_action_subscription_added_personal"><![CDATA[У Вас новый последователь]]></string>
@@ -85,6 +87,8 @@
     <string name="_bx_ntfs_alert_action_timeline_post_common_follow_context"><![CDATA[Новые сообщения на Ленте]]></string>
     <string name="_bx_ntfs_alert_action_publish_failed_personal"><![CDATA[Отложенная публикация не удалась]]></string>
     <string name="_bx_ntfs_alert_action_publish_succeeded_personal"><![CDATA[Отложенная публикация успешно выполнена]]></string>
+    <string name="_bx_ntfs_alert_action_pending_approval_personal"><![CDATA[Новый контент, ожидающий одобрения]]></string>
+    <string name="_bx_ntfs_alert_action_approved_personal"><![CDATA[Ваш контент был активирован]]></string>
 
     <string name="_bx_ntfs_setting_title_mask"><![CDATA[{0}: {1}]]></string>
     <string name="_bx_ntfs_setting_type_personal"><![CDATA[Активность, относящаяся к вам]]></string>
@@ -106,6 +110,8 @@
     <string name="_bx_ntfs_txt_object_added_in_context"><![CDATA[<b class="bx-ntfs-content-profile">{object_owner_name}</b> - {entry_sample} <b class="bx-ntfs-content-entry">{entry_caption}</b> в <b class="bx-ntfs-content-profile">{owner_name}</b>]]></string>
     <string name="_bx_ntfs_txt_object_publish_failed"><![CDATA[Ошибка при публикации: {entry_sample} <b class="bx-ntfs-content-entry">{entry_caption}</b>]]></string>
     <string name="_bx_ntfs_txt_object_publish_succeeded"><![CDATA[Успешная публикация: {entry_sample} <b class="bx-ntfs-content-entry">{entry_caption}</b>]]></string>
+    <string name="_bx_ntfs_txt_object_pending_approval"><![CDATA[<b class="bx-ntfs-content-profile">{owner_name}</b> - {entry_sample} <b class="bx-ntfs-content-entry">{entry_caption}</b> ожидает одобрения]]></string>
+    <string name="_bx_ntfs_txt_object_approved"><![CDATA[<b class="bx-ntfs-content-profile">{owner_name}</b> активировал {entry_sample} <b class="bx-ntfs-content-entry">{entry_caption}</b>]]></string>
     <string name="_bx_ntfs_txt_subobject_added"><![CDATA[<b class="bx-ntfs-content-profile">{owner_name}</b> - {subentry_sample} <b class="bx-ntfs-content-entry">{entry_caption}</b>]]></string>
     <string name="_bx_ntfs_txt_subobject_added_in_context"><![CDATA[<b class="bx-ntfs-content-profile">{owner_name}</b> - {subentry_sample} <b class="bx-ntfs-content-entry">{entry_caption}</b>]]></string>
 

+ 1 - 1
modules/boonex/russian/data/langs/system/ru.xml

@@ -1631,7 +1631,7 @@
 	<string name="_adm_stg_cpt_option_sys_default_curl_timeout"><![CDATA[cURL - Таймаут по умолчанию]]></string>
     <string name="_adm_stg_cpt_option_sys_ssl_allow_untrusted"><![CDATA[cURL - доверять неподписанным сертификатам или неподтвержденным хостам]]></string>
     <string name="_adm_stg_cpt_option_sys_csp_frame_ancestors"><![CDATA[Значение заголовка 'Content-Security-Policy: frame-ancestors']]></string>
-    <string name="_adm_stg_cpt_option_sys_allow_approve_by_role"><![CDATA[Разрешить участнику контекста с необходимой ролью утверждать контент в контексте]]></string>
+    <string name="_adm_stg_cpt_option_sys_notify_to_approve_by_role"><![CDATA[Оповещать участников контекста с определенной ролью о необходимости одобрять контент в контексте]]></string>
     <string name="_adm_stg_cpt_option_template"><![CDATA[Основная тема]]></string>
     <string name="_adm_stg_err_option_template"><![CDATA[Тема не может быть пустой и должна иметь приемлемое название.]]></string>
     <string name="_adm_stg_cpt_option_use_like_operator"><![CDATA[Использовать для поиска оператор LIKE (рекомендуется для небольшого содержимого)]]></string>