Explorar o código

Merge branch 'master' of https://github.com/unaio/una

AntonLV %!s(int64=4) %!d(string=hai) anos
pai
achega
3e63a6b7e1
Modificáronse 38 ficheiros con 1084 adicións e 478 borrados
  1. 61 1
      inc/classes/BxDolAccount.php
  2. 49 3
      inc/classes/BxDolAccountQuery.php
  3. 6 0
      inc/classes/BxDolAclQuery.php
  4. 34 3
      inc/classes/BxDolCronAccount.php
  5. 3 0
      inc/classes/BxDolPage.php
  6. 5 4
      inc/classes/BxDolStorageLocal.php
  7. 3 2
      modules/boonex/editor/classes/BxEditorDb.php
  8. 37 21
      modules/boonex/editor/classes/BxEditorEditor.php
  9. 12 30
      modules/boonex/editor/classes/BxEditorModule.php
  10. 5 16
      modules/boonex/editor/install/langs/en.xml
  11. 38 85
      modules/boonex/editor/install/sql/install.sql
  12. 4 0
      modules/boonex/editor/js/_notes/dwsync.xml
  13. 406 145
      modules/boonex/editor/js/editor.js
  14. 61 0
      modules/boonex/editor/js/embed-block.js
  15. 82 0
      modules/boonex/editor/js/embed-inline.js
  16. 0 71
      modules/boonex/editor/js/embed.js
  17. 0 38
      modules/boonex/editor/js/eventHandler.js
  18. 0 33
      modules/boonex/editor/js/libs.js
  19. 92 0
      modules/boonex/editor/js/mention.js
  20. 4 0
      modules/boonex/editor/plugins/editorjs/code.js
  21. 13 0
      modules/boonex/editor/plugins/editorjs/delimiter.js
  22. 7 0
      modules/boonex/editor/plugins/editorjs/editor.js
  23. 0 0
      modules/boonex/editor/plugins/editorjs/edjsHTML.js
  24. 15 0
      modules/boonex/editor/plugins/editorjs/header.js
  25. 4 0
      modules/boonex/editor/plugins/editorjs/image.js
  26. 4 0
      modules/boonex/editor/plugins/editorjs/inline-code.js
  27. 4 0
      modules/boonex/editor/plugins/editorjs/list.js
  28. 4 0
      modules/boonex/editor/plugins/editorjs/marker.js
  29. 13 0
      modules/boonex/editor/plugins/editorjs/paragraph.js
  30. 32 0
      modules/boonex/editor/plugins/tribute/tribute.css
  31. 0 0
      modules/boonex/editor/plugins/tribute/tribute.min.js
  32. 4 0
      modules/boonex/editor/template/css/_notes/dwsync.xml
  33. 0 23
      modules/boonex/editor/template/css/main.css
  34. 18 0
      modules/boonex/english/data/langs/system/en.xml
  35. 1 1
      modules/boonex/stream/classes/BxStrmGridRecordings.php
  36. 32 0
      studio/template/scripts/BxBaseStudioPermissionsLevels.php
  37. 26 0
      template/scripts/BxBaseFormAccount.php
  38. 5 2
      template/scripts/BxBaseServiceAccount.php

+ 61 - 1
inc/classes/BxDolAccount.php

@@ -251,7 +251,11 @@ class BxDolAccount extends BxDolFactory implements iBxDolSingleton
         $sPasswordHash = encryptUserPwd($sPassword, $sSalt);
         $iId = (int)$iAccountId ? (int)$iAccountId : $this->_iAccountID;
         $oAccountSender = BxDolAccount::getInstance();
-        if((int)BxDolAccountQuery::getInstance()->updatePassword($sPasswordHash, $sSalt, $iId) > 0) {
+
+        $this->_oQuery->logPassword($iId);
+        $iPasswordExpired = $this->getPasswordExpiredDateByAccount($iAccountId);
+        
+        if((int)$this->_oQuery->updatePassword($sPasswordHash, $sSalt, $iId, $iPasswordExpired) > 0) {
             bx_alert('account', 'edited', $iId, $oAccountSender ? $oAccountSender->id() : $iId, array('action' => 'reset_password'));
             $this->doAudit($iId, '_sys_audit_action_account_reset_password');
             return true;
@@ -705,6 +709,62 @@ class BxDolAccount extends BxDolFactory implements iBxDolSingleton
         return md5($a['email'] . $a['salt'] . BX_DOL_SECRET);
     }
     
+    public function getPasswordExpiredDate($iPasswordExpiredForMembership, $iAccountId = false)
+    {
+        if ($iPasswordExpiredForMembership == 0)
+            return 0;
+        
+        $iAccountId = (int)$iAccountId ? (int)$iAccountId : $this->_iAccountID;
+        
+        $aAccountInfo = $this->_oQuery->getInfoById($iAccountId);
+                
+        $iLastPassChanged = $this->_oQuery->getLastPasswordChanged($iAccountId);
+        if ($iLastPassChanged == 0)
+            $iLastPassChanged = $aAccountInfo['added'];
+
+        return $iPasswordExpiredForMembership * 86400 + $iLastPassChanged;
+    }
+    
+    public function getPasswordExpiredDateByAccount($iAccountId = false)
+    {
+        $iAccountId = (int)$iAccountId ? (int)$iAccountId : $this->_iAccountID;
+        
+        $oACL = BxDolAcl::getInstance();
+
+        $aProfiles = BxDolAccount::getInstance($iAccountId)->getProfiles();
+        $iPasswordExpiredForMembership = 0;
+        foreach ($aProfiles as $aProfile) {
+            $aMembersipInfo = $oACL->getMemberMembershipInfo($aProfile['id']);
+            $Memberships = [];
+            BxDolAclQuery::getInstance()->getLevels(['type' => 'by_id', 'value' => $aMembersipInfo['id']], $aMembership);
+            if($aMembership['password_expired'] > 0){
+                if ($iPasswordExpiredForMembership > 0 && $aMembership['password_expired'] < $iExpired)
+                    $iPasswordExpiredForMembership = $aMembership['password_expired'];
+                if ($iPasswordExpiredForMembership == 0 )
+                    $iPasswordExpiredForMembership = $aMembership['password_expired'];
+            }
+        }
+
+        return $this->getPasswordExpiredDate($iPasswordExpiredForMembership, $iAccountId);  
+    }
+    
+    public function isNeedChangePassword($iAccountId = false)
+    {
+        $iAccountId = (int)$iAccountId ? (int)$iAccountId : $this->_iAccountID;
+        
+        $aAccountInfo = $this->getInfo();
+        list($sPageLink, $aPageParams) = bx_get_base_url_inline();
+        $bNeedRedirectToChangePassword = true;
+
+        if (isset($aPageParams['i']) && $aPageParams['i'] == 'account-settings-password')
+            $bNeedRedirectToChangePassword = false;
+
+        if ($aAccountInfo['password_expired'] >0 && $aAccountInfo['password_expired'] < time() && $bNeedRedirectToChangePassword){
+            header('Location: ' . BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=account-settings-password'));
+            exit;
+        }
+    }
+    
     public function doAudit($iAccountId, $sAction, $aData = array())
     {
         $iAccountId = (int)$iAccountId ? (int)$iAccountId : $this->_iAccountID;

+ 49 - 3
inc/classes/BxDolAccountQuery.php

@@ -152,18 +152,64 @@ class BxDolAccountQuery extends BxDolDb implements iBxDolSingleton
     }
 
     /**
-     * Update last logged in time
+     * Update password
      * @param $sPasswordHash - password hash
      * @param $sSalt - pasword salt
      * @param $iAccountId - account id to update password for
      * @return number of affected rows
      */
-    public function updatePassword($sPasswordHash, $sSalt, $iAccountId)
+    public function updatePassword($sPasswordHash, $sSalt, $iAccountId, $iPasswordExpired)
     {
-        $sQuery = $this->prepare("UPDATE `sys_accounts` SET `password` = ?, `salt` = ? WHERE `id`= ?", $sPasswordHash, $sSalt, $iAccountId);
+        $sQuery = $this->prepare("UPDATE `sys_accounts` SET `password` = ?, `salt` = ?, `password_expired` = ? WHERE `id`= ?", $sPasswordHash, $sSalt, $iPasswordExpired, $iAccountId);
         return $this->query($sQuery);
     }
 
+    /**
+     * Save password log
+     * @param $sPasswordHash - password hash
+     * @param $sSalt - pasword salt
+     * @param $iAccountId - account id to update password for
+     * @return number of affected rows
+     */
+    public function logPassword($iAccountId)
+    {
+        $iCountPassword = (int)getParam('sys_account_accounts_password_log_count');
+        
+        if ($iCountPassword > 0){
+            $sSql = $this->prepare("SELECT `password`, `salt` FROM `sys_accounts` WHERE `id` = ?", $iAccountId);
+            $aAccount = $this->getRow($sSql);
+
+            $sQuery = "INSERT INTO `sys_accounts_password` (`password`, `password_changed`, `salt`, `account_id`) VALUES(:password, :password_changed, :salt, :account_id)";
+            $aBindings = array(
+               'password' => $aAccount['password'],
+               'password_changed' => time(),
+               'salt' => $aAccount['salt'],
+               'account_id' => $iAccountId,
+            );
+            $this->query($sQuery, $aBindings);
+            
+            $this->query($this->prepare("DELETE FROM `sys_accounts_password` WHERE `id` NOT IN (SELECT `id` FROM (SELECT `id` FROM `sys_accounts_password` WHERE `account_id` = ? ORDER BY `password_changed` DESC LIMIT 0, " . getParam('sys_account_accounts_password_log_count') . ") a)", $iAccountId));
+        }
+    }
+    
+    public function getLastPasswordChanged($iAccountId)
+    {
+        $sSql = $this->prepare("SELECT `password_changed` FROM `sys_accounts_password` WHERE `account_id` = ? ORDER BY password_changed  DESC limit 0, 1", $iAccountId);
+        return (int)$this->getOne($sSql);
+    }
+    
+    public function getLastPasswordLog($iAccountId)
+    {
+        $sSql = $this->prepare("SELECT * FROM `sys_accounts_password` WHERE `account_id` = ? ", $iAccountId);
+        return $this->getAll($sSql);
+    }
+    
+    public function updatePasswordExpired($iAccountId, $iPasswordExpired)
+    {
+        $sQuery = $this->prepare("UPDATE `sys_accounts` SET `password_expired` = ? WHERE `id`= ?", $iPasswordExpired, $iAccountId);
+        return $this->query($sQuery);
+    }
+    
     /**
      * Update last logged in time
      * @param  int    $iID account id

+ 6 - 0
inc/classes/BxDolAclQuery.php

@@ -101,6 +101,10 @@ class BxDolAclQuery extends BxDolDb implements iBxDolSingleton
             case 'all_order_id':
                 $sOrderClause = "ORDER BY `tal`.`ID` ASC";
                 break;
+                
+            case 'password_can_expired':
+                $sWhereClause .= "AND PasswordExpired <> 0";
+                break;
 
             case 'all':
                 break;
@@ -117,6 +121,8 @@ class BxDolAclQuery extends BxDolDb implements iBxDolSingleton
                 `tal`.`QuotaSize` AS `quota_size`,
                 `tal`.`QuotaNumber` AS `quota_number`,
                 `tal`.`QuotaMaxFileSize` AS `quota_max_file_size`,
+                `tal`.`PasswordExpired` AS `password_expired`,
+                `tal`.`PasswordExpiredNotify` AS `password_expired_notify`,
                 `tal`.`Order` AS `order`" . $sSelectClause . "
             FROM `sys_acl_levels` AS `tal` " . $sJoinClause . "
             WHERE 1 " . $sWhereClause . " " . $sOrderClause . " " . $sLimitClause;

+ 34 - 3
inc/classes/BxDolCronAccount.php

@@ -22,11 +22,40 @@ class BxDolCronAccount extends BxDolCron
 
     public function processing()
     {
-        if(getParam('enable_notification_account') != 'on')
-            return;
-
         set_time_limit(0);
         ignore_user_abort();
+        
+        $aEmails = [];
+        
+        /* password expired soon email */
+        $aMemberships = [];
+        $Membership = BxDolAclQuery::getInstance()->getLevels(['type' => 'password_can_expired'], $aMemberships);
+        foreach($aMemberships as $aMembership) {
+            $aProfiles = BxDolAclQuery::getInstance()->getProfilesByMembership([$aMembership['id']]);
+            foreach($aProfiles as $aProfile) {
+                $iPasswordExpired = BxDolAccount::getInstance()->getPasswordExpiredDate($aMembership['password_expired'], $aProfile['account_id']);
+                $aAccountInfo = BxDolAccountQuery::getInstance()->getInfoById($aProfile['account_id']);
+                $iLastPassChanged = BxDolAccountQuery::getInstance()->getLastPasswordChanged($aProfile['account_id']);
+                if (
+                    !in_array($aAccountInfo['email'], $aEmails) 
+                    && ($aMembership['password_expired'] - $aMembership['password_expired_notify']) * 86400 + $iLastPassChanged < time()
+                    && $iPasswordExpired >= time()
+                ){
+                    $aPlus = array();
+                    $aPlus['expired_date'] = date('d.m.Y', $iPasswordExpired);
+                    $aTemplate = BxDolEmailTemplates::getInstance()->parseTemplate('t_AccountPasswordExpired', $aPlus);
+
+                    sendMail($aAccountInfo['email'], $aTemplate['Subject'], $aTemplate['Body'], $aProfile['id']);
+                    $aEmails[] = $aAccountInfo['email'];
+                }
+
+                BxDolAccountQuery::getInstance()->updatePasswordExpired($aProfile['account_id'], $iPasswordExpired);
+            }
+        }
+        
+        /* new accounts email */
+        if(getParam('enable_notification_account') != 'on')
+            return;
 
         $this->processNewlyJoined();
 
@@ -58,6 +87,8 @@ class BxDolCronAccount extends BxDolCron
             if(sendMail($aAccount['email'], $aTemplate['Subject'], $aTemplate['Body'], $aProfile['id']))
             	$aSent[] = $aAccount['email'];
         }
+       
+        
     }
 
     protected function processNewlyJoined()

+ 3 - 0
inc/classes/BxDolPage.php

@@ -554,6 +554,9 @@ class BxDolPage extends BxDolFactory implements iBxDolFactoryObject, iBxDolRepla
      */
     public function displayPage ($oTemplate = null)
     {
+        if(isLogged())
+            BxDolAccount::getInstance()->isNeedChangePassword();
+        
         if (!$oTemplate)
             $oTemplate = BxDolTemplate::getInstance();
 

+ 5 - 4
inc/classes/BxDolStorageLocal.php

@@ -144,16 +144,17 @@ class BxDolStorageLocal extends BxDolStorage
 
     protected function mkdir($sDirName, $sRights = false)
     {
-        if (false == $sRights)
+        if (false == $sRights) 
             $sRights = BX_DOL_STORAGE_DIR_RIGHTS;
 
+        $sDirName = bx_ltrim_str($sDirName, BX_DIRECTORY_PATH_ROOT); 
         $aDirs = explode('/', $sDirName);
-        $sDir='';
+        $sDir = BX_DIRECTORY_PATH_ROOT;
         foreach ($aDirs as $sPart) {
             $sDir .= $sPart.'/';
             if (!is_dir($sDir) && strlen($sDir) > 0 && !file_exists($sDir)) {
-                mkdir($sDir);
-                chmod($sDir, $sRights);
+                mkdir($sDir); 
+                chmod($sDir, $sRights); 
             }
         }
     }

+ 3 - 2
modules/boonex/editor/classes/BxEditorDb.php

@@ -16,10 +16,11 @@ class BxEditorDb extends BxDolModuleDb
         parent::__construct($oConfig);
     }
     
-    public function getSettings ($sMode)
+    public function getSettings ($sMode, $sType)
     {
-        return $this->getOne("SELECT group_concat(`name` ORDER BY `order` SEPARATOR ',') FROM `bx_editor_toolbar_buttons` WHERE `mode` = :mode AND `active` = 1", array(
+        return $this->getOne("SELECT group_concat(`name` ORDER BY `order` SEPARATOR ',') FROM `bx_editor_toolbar_buttons` WHERE `mode` = :mode AND `inline` = :type AND `active` = 1", array(
             'mode' => $sMode,
+            'type' => $sType,
         ));
     }
 }

+ 37 - 21
modules/boonex/editor/classes/BxEditorEditor.php

@@ -25,12 +25,10 @@ class BxEditorEditor extends BxDolEditor
     protected static $CONF_COMMON = "
         var oParams = {   
 			root_url: '{bx_url_root}',
-            bx_url_uploader: '{bx_url_uploader}',
             name: '{bx_var_editor_name}',
             selector: '{bx_var_selector}',
 			toolbar: {toolbar},
-			buttons: {buttons},
-			
+			toolbar_inline: {toolbar_inline},
         }
         {bx_var_editor_name} = bx_ex_editor_init({bx_var_editor_name}, oParams);";
 
@@ -64,31 +62,32 @@ class BxEditorEditor extends BxDolEditor
         switch ($iViewMode) {
                 
             case BX_EDITOR_MINI:
-                $sToolbarItems = $this->_oModule->_oDb->getSettings('mini');
+                $sToolbarItems = $this->_oModule->_oDb->getSettings('mini', 1);
+                $sToolbarItemsInline = $this->_oModule->_oDb->getSettings('mini', 0);
                 break;
                 
             case BX_EDITOR_FULL:
-                $sToolbarItems = $this->_oModule->_oDb->getSettings('full');
+                $sToolbarItems = $this->_oModule->_oDb->getSettings('full', 1);
+                $sToolbarItemsInline = $this->_oModule->_oDb->getSettings('full', 0);
             break;
                 
             case BX_EDITOR_STANDARD:
             default:
-                $sToolbarItems = $this->_oModule->_oDb->getSettings('standard');
+                $sToolbarItems = $this->_oModule->_oDb->getSettings('standard', 1);
+                $sToolbarItemsInline = $this->_oModule->_oDb->getSettings('standard', 0);
         }
         
         if ($this->_sButtonsCustom !== false) {
             $sToolbarItems = $this->_sButtonsCustom;
         }
         
-        $sToolbarItems = str_replace(',', "','", $sToolbarItems);
-        $sToolbarItems = str_replace(",'separator',", '],[', $sToolbarItems);
-        $sToolbarItems = "['" . $sToolbarItems . "']";
+        $sToolbarItems = "'" . str_replace(',', "','", $sToolbarItems) . "'";
+        $sToolbarItemsInline = "'" . str_replace(',', "','", $sToolbarItemsInline) . "'";
         
         $sEditorName = 'editor_' . str_replace(['-', ' '], '_', $aAttrs['form_id'] . '_' . $aAttrs['element_name']);
         
         $this->_oTemplate->addJsTranslation([
-            '_bx_editor_embed_popup_link',
-            '_bx_editor_embed_popup_embed',
+            '_bx_editor_embed_popup_header',
         ]);
         
         // initialize editor
@@ -97,24 +96,21 @@ class BxEditorEditor extends BxDolEditor
             'bx_var_query_params' => isset($aAttrs['query_params']) ? json_encode($aAttrs['query_params']) : "''",
             'bx_var_form_id' => $aAttrs['form_id'],
             'toolbar' => $sToolbarItems ? '[' . $sToolbarItems . ']' : 'false',
-			'buttons' => json_encode($this->_aButtons),
-            'insert_as_plain_text' => getParam('sys_quill_insert_as_plain_text') == 'on' ? 'true' : 'false',
-            'bx_var_css_additional_class' => $sToolbarItems ? '' : 'bx-form-input-html-quill-empty',
+            'toolbar_inline' => $sToolbarItemsInline ? '[' . $sToolbarItemsInline . ']' : 'false',
+            'bx_var_css_additional_class' => $sToolbarItems ? '' : 'bx-form-input-html-editor-empty',
             'bx_var_element_name' => str_replace(['-', ' '], '_', $aAttrs['element_name']),
             'bx_var_editor_name' => $sEditorName,
-            'bx_var_skin' => bx_js_string($this->_aObject['skin'], BX_ESCAPE_STR_APOS),
             'bx_url_root' => bx_js_string(BX_DOL_URL_ROOT, BX_ESCAPE_STR_APOS),
-            'bx_url_uploader' => bx_js_string(BX_DOL_URL_ROOT . "storage.php?o=sys_images_editor&t=sys_images_editor&a=upload", BX_ESCAPE_STR_APOS)
         ));
 
         $sInitCallBack = "
-            bQuillEditorInited = true;
+            bEditorInited = true;
         " . $sInitEditor;
 
         if ($bDynamicMode) {
             list($aJs, $aCss) = $this->_getJsCss(true);
 
-            $sScript = "var " . $sEditorName . "; " . $this->_oTemplate->addJsPreloaded($aJs, $sInitCallBack, "typeof bQuillEditorInited === 'undefined'", $sInitEditor);
+            $sScript = "var " . $sEditorName . "; " . $this->_oTemplate->addJsPreloaded($aJs, $sInitCallBack, "typeof bEditorInited === 'undefined'", $sInitEditor);
             $sScript = $this->_oTemplate->_wrapInTagJsCode($sScript);
             $sScript = $this->_oTemplate->addCss($aCss, true) . $sScript;
 
@@ -141,6 +137,7 @@ class BxEditorEditor extends BxDolEditor
         list($aJs, $aCss) = $this->_getJsCss();
 		
         $this->_oTemplate->addJs($aJs);
+        
         $this->_oTemplate->addCss($aCss);
         
         $this->_bJsCssAdded = true;
@@ -151,17 +148,36 @@ class BxEditorEditor extends BxDolEditor
     protected function _getJsCss($bUseUrlsForJs = false)
     {
 		$sJsPrefix = $bUseUrlsForJs ? BX_DOL_URL_MODULES : BX_DIRECTORY_PATH_MODULES;
+        
+        $sJsPrefixPlugins = $bUseUrlsForJs ? BX_DOL_URL_PLUGINS : BX_DIRECTORY_PATH_PLUGINS_PUBLIC;
+        
 		$sJsSuffix = $bUseUrlsForJs ? '' : '|';
 		
 		$aCss = [
 			 BX_DIRECTORY_PATH_MODULES . 'boonex/editor/template/css/|main.css',  
+             BX_DIRECTORY_PATH_MODULES . 'boonex/editor/plugins/tribute/|tribute.css',  
 		];
+        
 		$aJs = [
+            $sJsPrefix . 'boonex/editor/plugins/editorjs/' . $sJsSuffix . 'editor.js',
+            $sJsPrefix . 'boonex/editor/plugins/editorjs/' . $sJsSuffix . 'header.js',
+            $sJsPrefix . 'boonex/editor/plugins/editorjs/' . $sJsSuffix . 'paragraph.js',
+            $sJsPrefix . 'boonex/editor/plugins/editorjs/' . $sJsSuffix . 'edjsHTML.js',
+            $sJsPrefix . 'boonex/editor/plugins/editorjs/' . $sJsSuffix . 'list.js',
+            $sJsPrefix . 'boonex/editor/plugins/editorjs/' . $sJsSuffix . 'delimiter.js',
+            $sJsPrefix . 'boonex/editor/plugins/editorjs/' . $sJsSuffix . 'code.js',
+            $sJsPrefix . 'boonex/editor/plugins/editorjs/' . $sJsSuffix . 'inline-code.js',
+            $sJsPrefix . 'boonex/editor/plugins/editorjs/' . $sJsSuffix . 'image.js',
+            $sJsPrefix . 'boonex/editor/plugins/editorjs/' . $sJsSuffix . 'marker.js',
+            
+            $sJsPrefix . 'boonex/editor/plugins/tribute/' . $sJsSuffix . 'tribute.min.js',
+          
+            /*$sJsPrefix . 'boonex/editor/js/' . $sJsSuffix . 'embed-inline.js',
+            $sJsPrefix . 'boonex/editor/js/' . $sJsSuffix . 'embed-block.js',
+            $sJsPrefix . 'boonex/editor/js/' . $sJsSuffix . 'mention.js',*/
             $sJsPrefix . 'boonex/editor/js/' . $sJsSuffix . 'editor.js',
-            'https://unpkg.com/@popperjs/core@2',
-            'https://unpkg.com/tippy.js@6'
         ];
-		
+
         return array($aJs, $aCss);
     }
 }

+ 12 - 30
modules/boonex/editor/classes/BxEditorModule.php

@@ -18,36 +18,18 @@ class BxEditorModule extends BxDolModule
         parent::__construct($aModule);
         
         $this->_aButtons = [
-			'bold' => ['icon' => 'bold', 'text' => _t('_bx_editor_toolbar_action_bold'), 'command' => 'toggleBold()', 'checkOnSelect' => true],
-			'italic' => ['icon' => 'italic', 'text' => _t('_bx_editor_toolbar_action_italic'), 'command' => 'toggleItalic()', 'checkOnSelect' => true],
-			'underline' => ['icon' => 'underline', 'text' => _t('_bx_editor_toolbar_action_underline'), 'command' => 'toggleUnderline()', 'checkOnSelect' => true],
-			'strike' => ['icon' => 'strikethrough', 'text' => _t('_bx_editor_toolbar_action_strike'), 'command' => 'toggleStrike()', 'checkOnSelect' => true],
-			'subscript' => ['icon' => 'subscript', 'text' => _t('_bx_editor_toolbar_action_subscript'), 'command' => 'toggleSubscript()', 'checkOnSelect' => true],
-			'superscript' => ['icon' => 'superscript', 'text' => _t('_bx_editor_toolbar_action_superscript'), 'command' => 'toggleSuperscript()', 'checkOnSelect' => true],
-			'code' => ['icon' => 'code', 'text' => _t('_bx_editor_toolbar_action_code'), 'command' => 'toggleCode()', 'checkOnSelect' => true],
-			'highlight' => ['icon' => 'highlighter', 'text' => _t('_bx_editor_toolbar_action_highlight'), 'command' => 'toggleHighlight()', 'checkOnSelect' => true],
-			
-			'indent' => ['icon' => 'indent', 'text' => _t('_bx_editor_toolbar_action_indent'), 'command' => 'setBlockquote()'],
-			'outdent' => ['icon' => 'outdent', 'text' => _t('_bx_editor_toolbar_action_outdent'), 'command' => 'unsetBlockquote()'],
-			
-			'bulletList' => ['icon' => 'list-ul', 'text' => _t('_bx_editor_toolbar_action_bullet_list'), 'command' => 'toggleBulletList()', 'checkOnSelect' => true],
-			'orderedList' => ['icon' => 'list-ol', 'text' => _t('_bx_editor_toolbar_action_ordered_list'), 'command' => 'toggleOrderedList()', 'checkOnSelect' => true],
-			
-			'codeBlock' => ['icon' => 'code', 'text' => _t('_bx_editor_toolbar_action_code_block'), 'command' => 'toggleCodeBlock()', 'checkOnSelect' => true],
-			
-			'alignLeft' => ['icon' => 'align-left', 'text' => _t('_bx_editor_toolbar_action_align_left'), 'command' => 'setTextAlign("left")'],
-			'alignCenter' => ['icon' => 'align-center', 'text' => _t('_bx_editor_toolbar_action_align_center'), 'command' => 'setTextAlign("center")'],
-			'alignRight' => ['icon' => 'align-right', 'text' => _t('_bx_editor_toolbar_action_align_right'), 'command' => 'setTextAlign("right")'],
-			'alignJustify' => ['icon' => 'align-justify', 'text' => _t('_bx_editor_toolbar_action_align_justify'), 'command' => 'setTextAlign("justify")'],
-			
-			'h1' => ['text' => _t('_bx_editor_toolbar_action_h1'), 'command' => 'toggleHeading({ level: 1 })'],
-			'h2' => ['text' => _t('_bx_editor_toolbar_action_h2'), 'command' => 'toggleHeading({ level: 2 })'],
-			'h3' => ['text' => _t('_bx_editor_toolbar_action_h3'), 'command' => 'toggleHeading({ level: 3 })'],
-			'h4' => ['text' => _t('_bx_editor_toolbar_action_h4'), 'command' => 'toggleHeading({ level: 4 })'],
-			
-			'link' => ['icon' => 'link', 'text' => _t('_bx_editor_toolbar_action_link'), 'command' => 'bx_ex_editor_add_link(oEditor, oParams)', 'command_ex' => true],
-			'image' => ['icon' => 'image', 'text' => _t('_bx_editor_toolbar_action_image'), 'command' => 'bx_ex_editor_add_image(oEditor, oParams.bx_url_uploader)', 'command_ex' => true],
-			'embed' => ['icon' => 'share-alt', 'text' => _t('_bx_editor_toolbar_action_embed'), 'command' => 'bx_ex_editor_add_embed(oEditor, oParams)', 'command_ex' => true],
+			'bold' => ['icon' => 'bold', 'text' => _t('_bx_editor_toolbar_action_bold')],
+			'italic' => ['icon' => 'italic', 'text' => _t('_bx_editor_toolbar_action_italic')],
+			'link' => ['icon' => 'link', 'text' => _t('_bx_editor_toolbar_action_link')],
+			'marker' => ['icon' => 'marker', 'text' => _t('_bx_editor_toolbar_action_marker')],
+			'inlineCode' => ['icon' => 'inlineCode', 'text' => _t('_bx_editor_toolbar_action_inlineCode')],
+			'header' => ['icon' => 'header', 'text' => _t('_bx_editor_toolbar_action_header')],
+			'list' => ['icon' => 'list-ul', 'text' => _t('_bx_editor_toolbar_action_list')],
+			'code' => ['icon' => 'code', 'text' => _t('_bx_editor_toolbar_action_code')],
+            'delimiter' => ['icon' => 'delimiter', 'text' => _t('_bx_editor_toolbar_action_delimiter')],
+			'link' => ['icon' => 'link', 'text' => _t('_bx_editor_toolbar_action_link')],
+			'image' => ['icon' => 'image', 'text' => _t('_bx_editor_toolbar_action_image')],
+			'embed' => ['icon' => 'share-alt', 'text' => _t('_bx_editor_toolbar_action_embed')],
 		];
     }
 

+ 5 - 16
modules/boonex/editor/install/langs/en.xml

@@ -13,23 +13,12 @@
 	<string name="_bx_editor_toolbar_action_italic"><![CDATA[Italic]]></string>
 	<string name="_bx_editor_toolbar_action_underline"><![CDATA[Underline]]></string>
 	<string name="_bx_editor_toolbar_action_strike"><![CDATA[Strike]]></string>
-	<string name="_bx_editor_toolbar_action_subscript"><![CDATA[Subscript]]></string>
-	<string name="_bx_editor_toolbar_action_superscript"><![CDATA[Superscript]]></string>
+	<string name="_bx_editor_toolbar_action_marker"><![CDATA[Marker]]></string>
+	<string name="_bx_editor_toolbar_action_inlineCode"><![CDATA[Inline Code]]></string>
+	<string name="_bx_editor_toolbar_action_header"><![CDATA[Header]]></string>
+	<string name="_bx_editor_toolbar_action_list"><![CDATA[List]]></string>
+	<string name="_bx_editor_toolbar_action_delimiter"><![CDATA[Delimiter]]></string>
 	<string name="_bx_editor_toolbar_action_code"><![CDATA[Code]]></string>
-	<string name="_bx_editor_toolbar_action_highlight"><![CDATA[Highlight]]></string>
-	<string name="_bx_editor_toolbar_action_indent"><![CDATA[Indent]]></string>
-	<string name="_bx_editor_toolbar_action_outdent"><![CDATA[Outdent]]></string>
-	<string name="_bx_editor_toolbar_action_bullet_list"><![CDATA[Bullet List]]></string>
-	<string name="_bx_editor_toolbar_action_ordered_list"><![CDATA[Ordered List]]></string>
-	<string name="_bx_editor_toolbar_action_code_block"><![CDATA[Code block]]></string>
-	<string name="_bx_editor_toolbar_action_align_left"><![CDATA[Align left]]></string>
-	<string name="_bx_editor_toolbar_action_align_center"><![CDATA[Align center]]></string>
-	<string name="_bx_editor_toolbar_action_align_right"><![CDATA[Align right]]></string>
-	<string name="_bx_editor_toolbar_action_align_justify"><![CDATA[Align justify]]></string>
-	<string name="_bx_editor_toolbar_action_h1"><![CDATA[H1]]></string>
-	<string name="_bx_editor_toolbar_action_h2"><![CDATA[H2]]></string>
-	<string name="_bx_editor_toolbar_action_h3"><![CDATA[H3]]></string>
-	<string name="_bx_editor_toolbar_action_h4"><![CDATA[H4]]></string>
 	<string name="_bx_editor_toolbar_action_link"><![CDATA[Link]]></string>
 	<string name="_bx_editor_toolbar_action_image"><![CDATA[Image]]></string>
 	<string name="_bx_editor_toolbar_action_embed"><![CDATA[Embed]]></string>

+ 38 - 85
modules/boonex/editor/install/sql/install.sql

@@ -5,96 +5,49 @@ CREATE TABLE IF NOT EXISTS `bx_editor_toolbar_buttons` (
   `order` tinyint(4) NOT NULL default '0',
   `name` varchar(64) NOT NULL DEFAULT '',
   `mode` varchar(10) NOT NULL DEFAULT '',
+  `inline` tinyint(4) NOT NULL default '0',
   PRIMARY KEY (`id`)
 );
 
-INSERT INTO `bx_editor_toolbar_buttons`(`active`, `order`, `name`, `mode`) VALUES
-(1, 0, 'bold', 'full'),
-(1, 1, 'italic', 'full'),
-(1, 2, 'underline', 'full'),
-(1, 3, 'strike', 'full'),
-(1, 4, 'subscript', 'full'),
-(1, 5, 'superscript', 'full'),
-(1, 6, 'code', 'full'),
-(1, 7, 'highlight', 'full'),
-(1, 8, 'separator', 'full'),
-(1, 9, 'indent', 'full'),
-(1, 10, 'outdent', 'full'),
-(1, 11, 'bulletList', 'full'),
-(1, 12, 'codeBlock', 'full'),
-(1, 13, 'orderedList', 'full'),
-(1, 14, 'separator', 'full'),
-(1, 15, 'alignLeft', 'full'),
-(1, 16, 'alignCenter', 'full'),
-(1, 17, 'alignRight', 'full'),
-(1, 18, 'alignJustify', 'full'),
-(1, 19, 'separator', 'full'),
-(1, 20, 'h1', 'full'),
-(1, 21, 'h2', 'full'),
-(1, 22, 'h3', 'full'),
-(1, 23, 'h4', 'full'),
-(1, 24, 'separator', 'full'),
-(1, 25, 'link', 'full'),
-(1, 26, 'image', 'full'),
-(1, 27, 'embed', 'full'),
+INSERT INTO `bx_editor_toolbar_buttons`(`active`, `order`, `name`, `mode`, `inline`) VALUES
+(1, 0, 'bold', 'full', 0),
+(1, 1, 'italic', 'full', 0),
+(1, 2, 'link', 'full', 0),
+(1, 3, 'marker', 'full', 0),
+(1, 4, 'inlineCode', 'full', 0),
 
-(1, 0, 'bold', 'standard'),
-(1, 1, 'italic', 'standard'),
-(1, 2, 'underline', 'standard'),
-(1, 3, 'strike', 'standard'),
-(0, 4, 'subscript', 'standard'),
-(0, 5, 'superscript', 'standard'),
-(0, 6, 'code', 'standard'),
-(0, 7, 'highlight', 'standard'),
-(1, 8, 'separator', 'standard'),
-(0, 9, 'indent', 'standard'),
-(0, 10, 'outdent', 'standard'),
-(1, 11, 'bulletList', 'standard'),
-(0, 12, 'codeBlock', 'standard'),
-(1, 13, 'orderedList', 'standard'),
-(1, 14, 'separator', 'standard'),
-(1, 15, 'alignLeft', 'standard'),
-(1, 16, 'alignCenter', 'standard'),
-(1, 17, 'alignRight', 'standard'),
-(1, 18, 'alignJustify', 'standard'),
-(1, 19, 'separator', 'standard'),
-(1, 20, 'h1', 'standard'),
-(1, 21, 'h2', 'standard'),
-(1, 22, 'h3', 'standard'),
-(1, 23, 'h4', 'standard'),
-(1, 24, 'separator', 'standard'),
-(1, 25, 'link', 'standard'),
-(1, 26, 'image', 'standard'),
-(1, 27, 'embed', 'standard'),
+(1, 5, 'header', 'full', 1),
+(1, 6, 'list', 'full', 1),
+(1, 7, 'image', 'full', 1),
+(1, 8, 'embed', 'full', 1),
+(1, 9, 'code', 'full', 1),
+(1, 10, 'delimiter', 'full', 1),
 
-(1, 0, 'bold', 'mini'),
-(1, 1, 'italic', 'mini'),
-(1, 2, 'underline', 'mini'),
-(0, 3, 'strike', 'mini'),
-(0, 4, 'subscript', 'mini'),
-(0, 5, 'superscript', 'mini'),
-(0, 6, 'code', 'mini'),
-(0, 7, 'highlight', 'mini'),
-(0, 8, 'separator', 'mini'),
-(0, 9, 'indent', 'mini'),
-(0, 10, 'outdent', 'mini'),
-(0, 11, 'bulletList', 'mini'),
-(0, 12, 'codeBlock', 'mini'),
-(0, 13, 'orderedList', 'mini'),
-(1, 14, 'separator', 'mini'),
-(1, 15, 'alignLeft', 'mini'),
-(1, 16, 'alignCenter', 'mini'),
-(1, 17, 'alignRight', 'mini'),
-(1, 18, 'alignJustify', 'mini'),
-(0, 19, 'separator', 'mini'),
-(0, 20, 'h1', 'mini'),
-(0, 21, 'h2', 'mini'),
-(0, 22, 'h3', 'mini'),
-(0, 23, 'h4', 'mini'),
-(1, 24, 'separator', 'mini'),
-(1, 25, 'link', 'mini'),
-(1, 26, 'image', 'mini'),
-(1, 27, 'embed', 'mini');
+(1, 0, 'bold', 'standard', 0),
+(1, 1, 'italic', 'standard', 0),
+(1, 2, 'link', 'standard', 0),
+(0, 3, 'marker', 'standard', 0),
+(0, 4, 'inlineCode', 'standard', 0),
+
+(1, 5, 'header', 'standard', 1),
+(1, 6, 'list', 'standard', 1),
+(1, 7, 'image', 'standard', 1),
+(1, 8, 'embed', 'standard', 1),
+(0, 9, 'code', 'standard', 1),
+(0, 10, 'delimiter', 'standard', 1),
+
+(1, 0, 'bold', 'mini', 0),
+(1, 1, 'italic', 'mini', 0),
+(1, 2, 'link', 'mini', 0),
+(0, 3, 'marker', 'mini', 0),
+(0, 4, 'inlineCode', 'mini', 0),
+
+(0, 5, 'header', 'mini', 1),
+(0, 6, 'list', 'mini', 1),
+(0, 7, 'image', 'mini', 1),
+(0, 8, 'embed', 'mini', 1),
+(0, 9, 'code', 'mini', 1),
+(0, 10, 'delimiter', 'mini', 1);
 
 -- Studio page and widget
 

+ 4 - 0
modules/boonex/editor/js/_notes/dwsync.xml

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<dwsync>
+<file name="libs.js" server="online.me:5190/public_html/" local="133014851988546974" remote="133014852050000000" Dst="0" />
+</dwsync>

+ 406 - 145
modules/boonex/editor/js/editor.js

@@ -1,162 +1,423 @@
 function bx_ex_editor_init(oEditor, oParams)
 {
-	import(sUrlRoot + '/modules/boonex/editor/js/libs.js').then((Module) => {
-		$(oParams.selector).after("<div class=\"mt-px flex items-center p-2 bg-white dark:bg-gray-800 shadow ring-1 ring-gray-300 dark:ring-gray-700 rounded-t-md\"><div id='" + oParams.name + "_toolbar' class='editor_toolbar flex space-x-0.5 w-full justify-start items-center flex-wrap'></div></div><div id='" + oParams.name + "' class='bx-def-font-inputs bx-form-input-textarea bx-form-input-html bx-form-input-html-editor mt-px text-gray-700 dark:text-gray-300  w-full p-4  ring-1 ring-gray-300 dark:ring-gray-700 dark:focus:placeholder-700 bg-gray-50 dark:bg-gray-900/50 placeholder-gray-500 focus:outline-none focus:bg-white dark:focus:bg-gray-900 focus:placeholder-gray-300 dark:focus:placeholder-gray-700  focus:text-gray-900 dark:focus:text-gray-100 text-base border-0 flex-wrap" + oParams.css_class + "'></div>" );
-    	$(oParams.selector).hide();
-		oEditor = new Module.Editor({
-			element: document.querySelector('#' + oParams.name),
-			extensions: [
-				Module.Document, 
-				Module.Paragraph, 
-				Module.Text, 
-				Module.Heading,
-				Module.Bold, 
-				Module.Italic, 
-				Module.Strike,
-				Module.Underline, 
-				Module.Subscript,
-				Module.Superscript, 
-				Module.Blockquote,
-				Module.BulletList,
-				Module.OrderedList,
-				Module.ListItem,
-				Module.Code,
-				Module.Highlight,
-				Module.CodeBlock,
-				Module.Highlight,
-				Module.Heading,
-				Module.Link,
-				Module.Image.configure({
-                  allowBase64: true,
-                }),
-				Module.Mention.configure({
-                    HTMLAttributes: {
-                        class: 'mention',
-                        },
-                    suggestion: {
-                      },
-                }),
-				Module.Span,
-				Module.Iframe,
-				Module.Div,
-				Module.Embed,
-                Module.EventHandler.configure({
-                  uploaderUrl: oParams.bx_url_uploader,
-                }),
-				Module.TextAlign.configure({
-					types: ['heading', 'paragraph'],
-				})
-
-			],
-			editorProps: {
-				attributes: {
-					class: 'prose prose-sm sm:prose lg:prose-lg xl:prose-2xl m-5 focus:outline-none',
-				},
-			},
-			  
-			content: $(oParams.selector).val(),
-			onUpdate({ editor }) {
-				$(oParams.selector).val(editor.getHTML());
-			},
-			onSelectionUpdate({ editor }) {
-				bx_ex_editor_check(editor, oParams);
-			}
-		});
-		
-		oParams.toolbar.forEach(
-			elements => {
-				elements.forEach(
-					element => {
-						var oBtnData = eval("oParams.buttons." + element);
-						if (oBtnData){
-							var oBtn = $('<button title = "' + oBtnData.text + '">' + (oBtnData.icon ? '<i class="sys-icon ' + oBtnData.icon + ' w-5 opacity-70 group-hover:opacity-100"></i>': oBtnData.text) + '</button>').attr({
-								 class: "group justify-center  border border-transparent hover:border-gray-200/50 text-gray-600 dark:text-gray-400 active:bg-gray-300 active:border-gray-300 dark:active:bg-black/50   hover:bg-gray-200/50 dark:hover:text-gray-200 dark:hover:bg-gray-700/50 dark:hover:border-gray-700/50 dark:active:border-gray-700 hover:text-gray-800 flex items-center px-2 py-1.5  text-sm font-medium rounded-md",
-                                 type: 'button',
-								 id: element
-							});
-							$('#' + oParams.name + '_toolbar').append(oBtn);
-							$('#' + oParams.name + '_toolbar button#' + element).bind( "click", function() {
-								if(oBtnData.command_ex && oBtnData.command_ex == true)
-									eval(oBtnData.command);
-								else
-									eval('oEditor.commands.' + oBtnData.command);
-								
-								bx_ex_editor_check(oEditor, oParams);
-							});
-						}
-					}
-				)
-			}
-		);
-	});
-}
-
-function bx_ex_editor_add_link(oEditor, oParams)
-{
-	if(oEditor.isActive('link')){
-		oEditor.commands.unsetLink()
-	}
-	else{
-		bx_prompt(_t('_bx_editor_embed_popup_link'), '', function(oPopup){
-			 oEditor.commands.setLink({ href: $(oPopup).find("input[type = 'text']").val() })
-		});
-	}
-}
+    $(oParams.selector).after("<div id='" + oParams.name + "' class='" + oParams.name + " bx-def-font-inputs bx-form-input-textarea bx-form-input-html bx-form-input-html-editor mt-px text-gray-700 dark:text-gray-300  w-full p-4  ring-1 ring-gray-300 dark:ring-gray-700 dark:focus:placeholder-700 bg-gray-50 dark:bg-gray-900/50 placeholder-gray-500 focus:outline-none focus:bg-white dark:focus:bg-gray-900 focus:placeholder-gray-300 dark:focus:placeholder-gray-700  focus:text-gray-900 dark:focus:text-gray-100 text-base border-0 flex-wrap" + oParams.css_class + "'></div>" );
+    
+    $(oParams.selector).hide();
+    
+    var oImage = {
+        class: ImageTool,
+            config: {
+            field: 'file',    
+            uploader: {
+              uploadByFile(file){
+                    return new Promise((resolve, reject) => {
+                        const formData = new FormData();
+                        formData.append("file", file);
+                        fetch(oParams.root_url + "storage.php?o=sys_images_editor&t=sys_images_editor&a=upload", {
+                                method: "POST",
+                                body: formData
+                            }
+                        )
+                        .then(response => response.json())
+                        .then(result => {
+                            if ('undefined' != typeof(result.link))
+                                resolve({
+                                    success: 1,
+                                    file: {
+                                        url: result.link,
+                                    }
+                                });
+                            else
+                                reject("Upload failed");
+                        })
+                        .catch(error => {
+                            reject("Upload failed");
+                        });
+                    });    
+              },
 
-function bx_ex_editor_add_image(oEditor, uploaderUrl)
-{
-	var input = document.createElement('input');
-	input.type = 'file';
+              uploadByUrl(url){
+                  return new Promise((resolve, reject) => {
+                      resolve({
+                        success: 1,
+                        file: {
+                            url: url,
+                        }
+                      });
+                  });
+              }
+            }    
+        }
+    };
+    
+    var oTools = {
+        paragraph: {
+          class: Paragraph,
+          inlineToolbar: true,
+        },
+        embedin: BxEmbedInline,
+        mention: BxMention,
+        marker: Marker,
+        inlineCode: InlineCode,
+    };
 
-	input.onchange = e => { 
-		bx_ex_editor_upload(oEditor, uploaderUrl, e.target.files[0])
-	}
+    oParams.toolbar.forEach(function(item){
+        switch(item) {
+            case 'header':
+                oTools.header = Header;
+                break;
 
-	input.click();
-}
+            case 'list':
+                oTools.list = List;
+                break;
 
-function bx_ex_editor_upload(oEditor, uploaderUrl, file)
-{
-    const formData = new FormData();
-    formData.append("file", file);
-    fetch(uploaderUrl, {
-            method: "POST",
-            body: formData
+            case 'image':
+                oTools.image = oImage;
+                break;
+                
+            case 'embed':
+                oTools.embed = BxEmbedBlock;
+                break;
+                
+            case 'code':
+                oTools.code = CodeTool;
+                break;
+                
+            case 'delimiter':
+                oTools.delimiter = Delimiter;
+                break;
         }
-    )
-    .then(response => response.json())
-    .then(result => {
-        if ('undefined' != typeof(result.link))
-            oEditor.commands.setImage({ src: result.link });
-            oEditor.commands.setParagraph()
     })
-    .catch(error => {
+    
+    oEditor = new EditorJS({
+        holderId : oParams.name,
+        inlineToolbar: oParams.toolbar_inline.concat(['embedin', 'mention']),
+        tools: oTools,
+        onReady: () => {
+            oEditor.blocks.renderFromHTML($(oParams.selector).val());
+        },
+        onChange:() =>{
+            
+            oEditor.save().then((savedData) =>{
+                const edjsParser = edjsHTML({embed2: bx_ex_editor_custom_parser});
+                oData = edjsParser.parse(savedData);
+                var s ='';
+                oData.forEach(function(item){
+                    s += item;
+                })
+                
+                $(oParams.selector).val(s)
+                
+            }).catch((error) =>{
+                console.log("error", error)
+            })  
+        }
+    }); 
+    
+    /*$('#' + oParams.name).atwho({
+        at: "@",
+        searchKey: 'label',
+        insertTpl: '<a class="bx-menthion-link" dchar="@" data-profile-id="${value}" href="${url}">@${label}</a>',
+        displayTpl: '<li class="bx-mention-row" data-value="${value}"><span>${label}</span></li>',
+        callbacks: {
+            remoteFilter: function(query, callback) {
+                $.getJSON(oParams.root_url + "/searchExtended.php?action=get_mention&symbol=%40&", {term: query}, function(data) {
+                    callback(data);
+                });
+            }
+        },
     });
+    
+    $('#' + oParams.name).atwho({
+        at: "#",
+        searchKey: 'label',
+        insertTpl: '<a class="bx-menthion-link" dchar="#" data-profile-id="${value}" href="${url}">#${label}</a>',
+        displayTpl: '<li class="bx-mention-row" data-value="${value}"><span>${label}</span> <img class="bx-def-round-corners" src="${thumb}" /></li>',
+        callbacks: {
+            remoteFilter: function(query, callback) {
+                $.getJSON(oParams.root_url + "/searchExtended.php?action=get_mention&symbol=%23&", {term: query}, function(data) {
+                    callback(data);
+                });
+            }
+        },
+    });*/
+    tribute = new Tribute({
+        collection: [
+            {
+                selectTemplate: function(item) {
+                    if (this.range.isContentEditable(this.current.element)) {
+                        return ('<a class="bx-menthion-link" dchar="@" data-profile-id="' + item.original.value + '" href="' + item.original.url + '">@' + item.original.label + '</a>');
+                    }
+                    return "@" + item.original.value;
+                },
+                values: function (text, cb) {
+                    $.getJSON( "/searchExtended.php?action=get_mention&symbol=%40&", {term: text}, function(data) {
+                        cb(data);
+                    });
+                },
+                lookup: "label",
+                fillAttr: "label"
+            },
+            {
+                trigger: "#",
+                selectTemplate: function(item) {
+                    if (this.range.isContentEditable(this.current.element)) {
+                        return ('<a class="bx-menthion-link" dchar="#" data-profile-id="' + item.original.value + '" href="' + item.original.url + '">#' + item.original.label + '</a>');
+                    }
+                    return "#" + item.original.value;
+                },
+                values: function (text, cb) {
+                    $.getJSON( "/searchExtended.php?action=get_mention&symbol=%23&", {term: text}, function(data) {
+                        cb(data);
+                    });
+                },
+                lookup: "label",
+                fillAttr: "label"
+            }
+        ]
+    })
+    tribute.attach(document.getElementById(oParams.name));
 }
 
-function bx_ex_editor_add_embed(oEditor, oParams)
+function bx_ex_editor_custom_parser(block)
 {
-	bx_prompt(_t('_bx_editor_embed_popup_embed'), '', function(oPopup){
-        oEditor.commands.setEmbed({ class: 'bx-embed-link', source: $(oPopup).find("input[type = 'text']").val() })
-    });
+    return '<p><span class="bx-embed-link" source="' + block.data.source + '">' + block.data.source + '</span></p>';
 }
 
-function bx_ex_editor_check(oEditor, oParams)
-{
-	oParams.toolbar.forEach(
-	elements => {
-		elements.forEach(
-			element => {
-				var oBtnData = eval("oParams.buttons." + element);
-				if (oBtnData && oBtnData.checkOnSelect && oBtnData.checkOnSelect == true){
-					if(oEditor.isActive(element))
-						$('#' + oParams.name + '_toolbar button#' + element).addClass('selected');
-					else
-						$('#' + oParams.name + '_toolbar button#' + element).removeClass('selected');
-				}
-			}
-		)
-	})
+class BxEmbedBlock {
+    
+    constructor({ data, block }){
+        this.blockAPI = block
+   
+    this.nodes = {
+      linkContent: null,
+    };
+
+    this._data = {
+      link: '',
+      meta: {},
+    };
+
+    this.data = data;
+  }
+    
+    static get toolbox() {
+        return {
+          title: 'Embed',
+          icon: '<svg width="19" height="13" viewBox="0 0 19 13"><path d="M18.004 5.794c.24.422.18.968-.18 1.328l-4.943 4.943a1.105 1.105 0 1 1-1.562-1.562l4.162-4.162-4.103-4.103A1.125 1.125 0 1 1 12.97.648l4.796 4.796c.104.104.184.223.239.35zm-15.142.547l4.162 4.162a1.105 1.105 0 1 1-1.562 1.562L.519 7.122c-.36-.36-.42-.906-.18-1.328a1.13 1.13 0 0 1 .239-.35L5.374.647a1.125 1.125 0 0 1 1.591 1.591L2.862 6.341z"></path></svg>'
+        };
+    }
+
+     render() {
+        var sLink = '';
+       
+        var oObj = document.createElement('span');
+        oObj.setAttribute('source', sLink)
+        oObj.className = 'bx-embed-link';
+        oObj.innerHTML = sLink;
+        this.nodes.linkContent = oObj;
+        var $this = this;
+        bx_prompt(_t('_bx_editor_embed_popup_header'), '', 
+            function(oPopup){
+                sLink = $(oPopup).find("input[type = 'text']").val();
+                if (sLink != ''){
+                    $this.nodes.linkContent.setAttribute('source', sLink);
+                    bx_embed_link($this.nodes.linkContent);
+                    $this.blockAPI.dispatchChange();
+                }
+                else{
+                    $this.nodes.linkContent.remove();
+                    $this.blockAPI.dispatchChange();
+                }
+            }
+            ,function(oPopup){
+                $this.nodes.linkContent.remove();
+                $this.blockAPI.dispatchChange();
+            }
+        );
+        
+        return this.nodes.linkContent;
+    }
+        
+    save(blockContent){
+        return {
+            source: this.nodes.linkContent.getAttribute('source')
+        }
+    }
+}
+
+class BxEmbedInline {
+    static get CSS() {
+        return 'bx-embed-link';
+    };
+
+    constructor({api}) {
+        this.api = api;
+        this.button = null;
+        this.tag = 'span';
+
+        this.iconClasses = {
+          base: this.api.styles.inlineToolButton,
+          active: this.api.styles.inlineToolButtonActive
+        };
+    }
+
+    static get isInline() {
+        return true;
+    }
+
+    render() {
+        return;
+    }
+
+    surround(range) {
+        if (!range) {
+            return;
+        }
+
+        let termWrapper = this.api.selection.findParentTag(this.tag, BxEmbedInline.CSS);
+        if (termWrapper) {
+          this.unwrap(termWrapper);
+        } else {
+          this.wrap(range);
+        }
+    }
+
+    wrap(range) {
+        let marker = document.createElement(this.tag);
+
+        marker.classList.add(BxEmbedInline.CSS);
+
+        marker.appendChild(range.extractContents());
+        range.insertNode(marker);
+
+        this.api.selection.expandToTag(marker);
+    }
+    
+    unwrap(termWrapper) {
+        this.api.selection.expandToTag(termWrapper);
+
+        let sel = window.getSelection();
+        let range = sel.getRangeAt(0);
+
+        let unwrappedContent = range.extractContents();
+
+        termWrapper.parentNode.removeChild(termWrapper);
+
+        range.insertNode(unwrappedContent);
+
+        sel.removeAllRanges();
+        sel.addRange(range);
+    }
+
+    checkState() {
+        const termTag = this.api.selection.findParentTag(this.tag, BxEmbedInline.CSS);
+
+        this.button.classList.toggle(this.iconClasses.active, !!termTag);
+    }
+
+    static get sanitize() {
+        return {
+          span: function(el) {
+              const source = el.getAttribute('source')
+              return {
+                  class: BxEmbedInline.CSS,
+                  source: source
+              }
+          }
+        };
+    }
 }
 
+class BxMention {
+
+    static get CSS() {
+        return 'bx-menthion-link';
+    };
+
+    constructor({api}) {
+        this.api = api;
+
+        this.button = null;
+
+        this.tag = 'a';
+
+        this.iconClasses = {
+          base: this.api.styles.inlineToolButton,
+          active: this.api.styles.inlineToolButtonActive
+        };
+    }
+
+    static get isInline() {
+        return true;
+    }
+
+    render() {
+        return;
+    }
+    
+    surround(range) {
+        if (!range) {
+            return;
+        }
+
+        let termWrapper = this.api.selection.findParentTag(this.tag, BxMention.CSS);
+
+        if (termWrapper) {
+            this.unwrap(termWrapper);
+        } else {
+            this.wrap(range);
+        }
+    }
+
+    wrap(range) {
+        let marker = document.createElement(this.tag);
+
+        marker.classList.add(BxMention.CSS);
+
+        marker.appendChild(range.extractContents());
+        range.insertNode(marker);
+
+        this.api.selection.expandToTag(marker);
+    }
 
+    unwrap(termWrapper) {
+        this.api.selection.expandToTag(termWrapper);
+
+        let sel = window.getSelection();
+        let range = sel.getRangeAt(0);
+
+        let unwrappedContent = range.extractContents();
+
+        termWrapper.parentNode.removeChild(termWrapper);
+
+        range.insertNode(unwrappedContent);
+
+        sel.removeAllRanges();
+        sel.addRange(range);
+    }
+    
+    checkState() {
+        const termTag = this.api.selection.findParentTag(this.tag, BxMention.CSS);
+
+        this.button.classList.toggle(this.iconClasses.active, !!termTag);
+    }
+    
+    get toolboxIcon() {
+        return require('./../assets/icon.svg').default;
+    }
+    
+    static get sanitize() {
+        return {
+            a: function(el) {
+                return {
+                  class: BxMention.CSS,
+                  href: el.getAttribute('href'),
+                  'data-profile-id': el.getAttribute('data-profile-id'),
+                  title: el.getAttribute('title'),
+                  dchar: el.getAttribute('dchar')
+                }
+            }
+        };
+    }
+}

+ 61 - 0
modules/boonex/editor/js/embed-block.js

@@ -0,0 +1,61 @@
+class BxEmbedBlock {
+    
+    constructor({ data, block }){
+        this.blockAPI = block
+   
+    this.nodes = {
+      linkContent: null,
+    };
+
+    this._data = {
+      link: '',
+      meta: {},
+    };
+
+    this.data = data;
+  }
+    
+    static get toolbox() {
+        return {
+          title: 'Embed',
+          icon: '<svg width="19" height="13" viewBox="0 0 19 13"><path d="M18.004 5.794c.24.422.18.968-.18 1.328l-4.943 4.943a1.105 1.105 0 1 1-1.562-1.562l4.162-4.162-4.103-4.103A1.125 1.125 0 1 1 12.97.648l4.796 4.796c.104.104.184.223.239.35zm-15.142.547l4.162 4.162a1.105 1.105 0 1 1-1.562 1.562L.519 7.122c-.36-.36-.42-.906-.18-1.328a1.13 1.13 0 0 1 .239-.35L5.374.647a1.125 1.125 0 0 1 1.591 1.591L2.862 6.341z"></path></svg>'
+        };
+    }
+
+     render() {
+        var sLink = '';
+       
+        var oObj = document.createElement('span');
+        oObj.setAttribute('source', sLink)
+        oObj.className = 'bx-embed-link';
+        oObj.innerHTML = sLink;
+        this.nodes.linkContent = oObj;
+        var $this = this;
+        bx_prompt(_t('_bx_editor_embed_popup_header'), '', 
+            function(oPopup){
+                sLink = $(oPopup).find("input[type = 'text']").val();
+                if (sLink != ''){
+                    $this.nodes.linkContent.setAttribute('source', sLink);
+                    bx_embed_link($this.nodes.linkContent);
+                    $this.blockAPI.dispatchChange();
+                }
+                else{
+                    $this.nodes.linkContent.remove();
+                    $this.blockAPI.dispatchChange();
+                }
+            }
+            ,function(oPopup){
+                $this.nodes.linkContent.remove();
+                $this.blockAPI.dispatchChange();
+            }
+        );
+        
+        return this.nodes.linkContent;
+    }
+        
+    save(blockContent){
+        return {
+            source: this.nodes.linkContent.getAttribute('source')
+        }
+    }
+}

+ 82 - 0
modules/boonex/editor/js/embed-inline.js

@@ -0,0 +1,82 @@
+class BxEmbedInline {
+    static get CSS() {
+        return 'bx-embed-link';
+    };
+
+    constructor({api}) {
+        this.api = api;
+        this.button = null;
+        this.tag = 'span';
+
+        this.iconClasses = {
+          base: this.api.styles.inlineToolButton,
+          active: this.api.styles.inlineToolButtonActive
+        };
+    }
+
+    static get isInline() {
+        return true;
+    }
+
+    render() {
+        return;
+    }
+
+    surround(range) {
+        if (!range) {
+            return;
+        }
+
+        let termWrapper = this.api.selection.findParentTag(this.tag, BxEmbedInline.CSS);
+        if (termWrapper) {
+          this.unwrap(termWrapper);
+        } else {
+          this.wrap(range);
+        }
+    }
+
+    wrap(range) {
+        let marker = document.createElement(this.tag);
+
+        marker.classList.add(BxEmbedInline.CSS);
+
+        marker.appendChild(range.extractContents());
+        range.insertNode(marker);
+
+        this.api.selection.expandToTag(marker);
+    }
+    
+    unwrap(termWrapper) {
+        this.api.selection.expandToTag(termWrapper);
+
+        let sel = window.getSelection();
+        let range = sel.getRangeAt(0);
+
+        let unwrappedContent = range.extractContents();
+
+        termWrapper.parentNode.removeChild(termWrapper);
+
+        range.insertNode(unwrappedContent);
+
+        sel.removeAllRanges();
+        sel.addRange(range);
+    }
+
+    checkState() {
+        const termTag = this.api.selection.findParentTag(this.tag, BxEmbedInline.CSS);
+
+        this.button.classList.toggle(this.iconClasses.active, !!termTag);
+    }
+
+    static get sanitize() {
+        return {
+          span: function(el) {
+              const source = el.getAttribute('source')
+              return {
+                  class: BxEmbedInline.CSS,
+                  source: source
+              }
+          }
+        };
+    }
+}

+ 0 - 71
modules/boonex/editor/js/embed.js

@@ -1,71 +0,0 @@
-import{Node, mergeAttributes, textblockTypeInputRule, nodeInputRule} from 'https://esm.sh/@tiptap/core';
-var inputRegex = /(!\[(.+|:?)]\((\S+)(?:(?:\s+)["'](\S+)["'])?\))$/;
-import{Plugin,PluginKey,TextSelection} from 'https://esm.sh/v85/prosemirror-state@1.4.0/es2022/prosemirror-state.js';
-var a = Node.create({
-    name: 'embed',
-    addOptions: function () {
-        return {
-            inline: false,
-            allowBase64: false,
-            HTMLAttributes: {}
-        };
-    },
-    inline: function () {
-        return this.options.inline;
-    },
-    group: function () {
-        return this.options.inline ? 'inline' : 'block';
-    },
-    draggable: true,
-    addAttributes: function () {
-        return {
-            class: {
-                default: null
-            },
-            source: {
-                default: null
-            },
-            title: {
-                default: null
-            }
-        };
-    },
-    parseHTML: function () {
-        return [
-            {
-                tag: this.options.allowBase64
-                    ? 'div[class]'
-                    : 'div[class]:not([src^="data:"])'
-            },
-        ];
-    },
-    renderHTML: function (_a) {
-        var HTMLAttributes = _a.HTMLAttributes;
-        return ['div', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)];
-    },
-    addCommands: function () {
-        var _this = this;
-        return {
-            setEmbed: function (options) { return function (_a) {
-                var commands = _a.commands;
-                return commands.insertContent({
-                    type: _this.name,
-                    attrs: options
-                });
-            }; }
-        };
-    },
-    addInputRules: function () {
-        return [
-            nodeInputRule({
-                find: inputRegex,
-                type: this.type,
-                getAttributes: function (match) {
-                    var className = match[2], source = match[3], title = match[4];
-                    return { class: className, source: source, title: title };
-                }
-            }),
-        ];
-    }
-});
-export{a as Embed,a as default};

+ 0 - 38
modules/boonex/editor/js/eventHandler.js

@@ -1,38 +0,0 @@
-import{Extension} from 'https://esm.sh/@tiptap/core';
-import{Plugin, PluginKey} from 'https://esm.sh/v85/prosemirror-state@1.4.0/es2022/prosemirror-state.js';
-
-var a = Extension.create({
-    name: 'eventHandler',
-    addOptions: function () {
-        return {
-            uploaderUrl: ""
-        };
-    },
-    addProseMirrorPlugins: function () {
-        var _this = this;
-        console.log(_this.options.uploaderUrl)
-        return [
-            new Plugin({
-                key: new PluginKey('eventHandler'),
-                uploaderUrl: _this.options.uploaderUrl,
-                props: {
-                    handlePaste: function (view, event, slice) { 
-                        const dT = event.clipboardData || window.clipboardData;
-                        const images = dT.files;
-                        if (images.length === 0)
-                            return
-                        event.preventDefault();
-                        
-                        for (var i = 0; i < images.length; i++) {
-      
-                            bx_ex_editor_upload(_this.editor, this.spec.uploaderUrl, images[i])
-                           
-                        }
-                    }
-                }
-            }),
-        ];
-    }
-});
-
-export{a as EventHandler ,a as default};

+ 0 - 33
modules/boonex/editor/js/libs.js

@@ -1,33 +0,0 @@
-import {Editor, Mark, mergeAttributes,Node } from 'https://esm.sh/@tiptap/core'
-import Document from 'https://esm.sh/@tiptap/extension-document'
-import Paragraph from 'https://esm.sh/@tiptap/extension-paragraph'
-import Text from 'https://esm.sh/@tiptap/extension-text'
-import Bold from 'https://esm.sh/@tiptap/extension-bold'
-import Italic from 'https://esm.sh/@tiptap/extension-italic'
-import Strike from 'https://esm.sh/@tiptap/extension-strike'
-import Underline from 'https://esm.sh/@tiptap/extension-underline'
-import Subscript from 'https://esm.sh/@tiptap/extension-subscript'
-import Superscript from 'https://esm.sh/@tiptap/extension-superscript'
-import Blockquote from 'https://esm.sh/@tiptap/extension-blockquote'
-import Code from 'https://esm.sh/@tiptap/extension-code'
-import Highlight from 'https://esm.sh/@tiptap/extension-highlight'
-
-import ListItem from 'https://esm.sh/@tiptap/extension-list-item'
-import OrderedList from 'https://esm.sh/@tiptap/extension-ordered-list'
-import BulletList from 'https://esm.sh/@tiptap/extension-bullet-list'
-import CodeBlock from 'https://esm.sh/@tiptap/extension-code-block'
-
-import TextAlign from 'https://esm.sh/@tiptap/extension-text-align'
-import Heading from 'https://esm.sh/@tiptap/extension-heading'
-
-import Link from 'https://esm.sh/@tiptap/extension-link'
-import Image from 'https://esm.sh/@tiptap/extension-image'
-
-import Mention from 'https://esm.sh/@tiptap/extension-mention'
-
-import Embed from '/modules/boonex/editor/js/embed.js'
-import EventHandler  from '/modules/boonex/editor/js/eventHandler.js'
-//import Image from '/modules/boonex/editor/js/image.js'
-
-
-export { Editor, Document, Paragraph, Text, Bold, Italic, Strike, Underline, Subscript, Superscript, Blockquote, BulletList, ListItem, Code, Highlight, CodeBlock, OrderedList, TextAlign, Heading, Link, Image, Mention, Mark, mergeAttributes, Embed, EventHandler };

+ 92 - 0
modules/boonex/editor/js/mention.js

@@ -0,0 +1,92 @@
+class BxMention {
+
+    static get CSS() {
+        return 'bx-menthion-link';
+    };
+
+    constructor({api}) {
+        this.api = api;
+
+        this.button = null;
+
+        this.tag = 'a';
+
+        this.iconClasses = {
+          base: this.api.styles.inlineToolButton,
+          active: this.api.styles.inlineToolButtonActive
+        };
+    }
+
+    static get isInline() {
+        return true;
+    }
+
+    render() {
+        return;
+    }
+    
+    surround(range) {
+        if (!range) {
+            return;
+        }
+
+        let termWrapper = this.api.selection.findParentTag(this.tag, BxMention.CSS);
+
+        if (termWrapper) {
+            this.unwrap(termWrapper);
+        } else {
+            this.wrap(range);
+        }
+    }
+
+    wrap(range) {
+        let marker = document.createElement(this.tag);
+
+        marker.classList.add(BxMention.CSS);
+
+        marker.appendChild(range.extractContents());
+        range.insertNode(marker);
+
+        this.api.selection.expandToTag(marker);
+    }
+
+    unwrap(termWrapper) {
+        this.api.selection.expandToTag(termWrapper);
+
+        let sel = window.getSelection();
+        let range = sel.getRangeAt(0);
+
+        let unwrappedContent = range.extractContents();
+
+        termWrapper.parentNode.removeChild(termWrapper);
+
+        range.insertNode(unwrappedContent);
+
+        sel.removeAllRanges();
+        sel.addRange(range);
+    }
+    
+    checkState() {
+        const termTag = this.api.selection.findParentTag(this.tag, BxMention.CSS);
+
+        this.button.classList.toggle(this.iconClasses.active, !!termTag);
+    }
+    
+    get toolboxIcon() {
+        return require('./../assets/icon.svg').default;
+    }
+    
+    static get sanitize() {
+        return {
+            a: function(el) {
+                return {
+                  class: BxMention.CSS,
+                  href: el.getAttribute('href'),
+                  'data-profile-id': el.getAttribute('data-profile-id'),
+                  title: el.getAttribute('title'),
+                  dchar: el.getAttribute('dchar')
+                }
+            }
+        };
+    }
+}

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 4 - 0
modules/boonex/editor/plugins/editorjs/code.js


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 13 - 0
modules/boonex/editor/plugins/editorjs/delimiter.js


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 7 - 0
modules/boonex/editor/plugins/editorjs/editor.js


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
modules/boonex/editor/plugins/editorjs/edjsHTML.js


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 15 - 0
modules/boonex/editor/plugins/editorjs/header.js


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 4 - 0
modules/boonex/editor/plugins/editorjs/image.js


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 4 - 0
modules/boonex/editor/plugins/editorjs/inline-code.js


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 4 - 0
modules/boonex/editor/plugins/editorjs/list.js


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 4 - 0
modules/boonex/editor/plugins/editorjs/marker.js


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 13 - 0
modules/boonex/editor/plugins/editorjs/paragraph.js


+ 32 - 0
modules/boonex/editor/plugins/tribute/tribute.css

@@ -0,0 +1,32 @@
+.tribute-container {
+  position: absolute;
+  top: 0;
+  left: 0;
+  height: auto;
+  overflow: auto;
+  display: block;
+  z-index: 999999;
+}
+.tribute-container ul {
+  margin: 0;
+  margin-top: 2px;
+  padding: 0;
+  list-style: none;
+  background: #efefef;
+}
+.tribute-container li {
+  padding: 5px 5px;
+  cursor: pointer;
+}
+.tribute-container li.highlight {
+  background: #ddd;
+}
+.tribute-container li span {
+  font-weight: bold;
+}
+.tribute-container li.no-match {
+  cursor: default;
+}
+.tribute-container .menu-highlighted {
+  font-weight: bold;
+}

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
modules/boonex/editor/plugins/tribute/tribute.min.js


+ 4 - 0
modules/boonex/editor/template/css/_notes/dwsync.xml

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<dwsync>
+<file name="main.css" server="online.me:5190/public_html/" local="133021785768803574" remote="133021785780000000" Dst="0" />
+</dwsync>

+ 0 - 23
modules/boonex/editor/template/css/main.css

@@ -1,23 +0,0 @@
-.bx-form-input-html-editor{
-	border: none !important;
-    border-radius: 0 0 0.375rem 0.375rem !important;
-}
-
-.bx-form-input-html-editor .bx-embed-link{
-    width:100%;
-    background: #E1ED10;
-    height: 100px;
-    content: attr(source);
-}
-.bx-form-input-html-editor .bx-embed-link::before{
-    content: attr(source);
-    width: 100%;
-    display: block;
-    height: 100%;
-    line-height: 100px;
-    position: relative;
-    text-align: center;
-    vertical-align: baseline;
-    font-size: 16px;
-    border:1px solid #000;
-}

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

@@ -1355,6 +1355,9 @@
 	<string name="_adm_prm_dsc_level_name"><![CDATA[Short name for the membership level (i.e Premium, Gold, Newcomer, etc.)]]></string>
 	<string name="_adm_prm_dsc_level_quota_max_file_size"><![CDATA[Limit max file size for user with this level across all modules (0 - unlimited, other system limits may apply).]]></string>
 	<string name="_adm_prm_dsc_level_quota_number"><![CDATA[Limit  amount of files a user with this level can upload (0 - unlimited).]]></string>
+    <string name="_adm_prm_dsc_level_password_expired"><![CDATA[Period after which the password must be changed, in days (0 - unlimited).]]></string>
+    <string name="_adm_prm_dsc_level_password_expired_notify"><![CDATA[The period for which the notification of an expired password will be sent, in days (0 - unlimited).]]></string>
+    
 	<string name="_adm_prm_dsc_level_quota_size"><![CDATA[Limit how much space can be used by user with this level across all modules (0 - unlimited).]]></string>
 	<string name="_adm_prm_dsc_price_add_days"><![CDATA[Use 0 for life-time]]></string>
 	<string name="_adm_prm_err_level_create"><![CDATA[Cannot create membership level]]></string>
@@ -1367,6 +1370,8 @@
 	<string name="_adm_prm_err_level_name"><![CDATA[You need to enter name(3, 100) for all languages]]></string>
 	<string name="_adm_prm_err_level_quota_max_file_size"><![CDATA[You need to enter a numeric value, leave 0 for unlimited]]></string>
 	<string name="_adm_prm_err_level_quota_number"><![CDATA[You need to enter a numeric value, leave 0 for unlimited]]></string>
+    <string name="_adm_prm_err_level_password_expired"><![CDATA[You need to enter a numeric value, leave 0 for unlimited]]></string>
+    <string name="_adm_prm_err_level_password_expired_notify"><![CDATA[You need to enter a numeric value, leave 0 for unlimited]]></string>
 	<string name="_adm_prm_err_level_quota_size"><![CDATA[You need to enter a numeric value, leave 0 for unlimited]]></string>
 	<string name="_adm_prm_err_price_create"><![CDATA[Cannot create pricing option]]></string>
 	<string name="_adm_prm_err_price_duplicate"><![CDATA[The similar pricing option already exists for this level.]]></string>
@@ -1397,6 +1402,8 @@
 	<string name="_adm_prm_txt_level_premium"><![CDATA[Premium]]></string>
 	<string name="_adm_prm_txt_level_quota_max_file_size"><![CDATA[Max File Size Limit(Mb)]]></string>
 	<string name="_adm_prm_txt_level_quota_number"><![CDATA[Max Files Limit]]></string>
+    <string name="_adm_prm_txt_level_password_expired"><![CDATA[Password Expired]]></string>
+    <string name="_adm_prm_txt_level_password_expired_notify"><![CDATA[Password Expired Notify]]></string>
 	<string name="_adm_prm_txt_level_quota_size"><![CDATA[Storage Limit(Mb)]]></string>
 	<string name="_adm_prm_txt_level_standard"><![CDATA[Standard]]></string>
 	<string name="_adm_prm_txt_level_suspended"><![CDATA[Suspended]]></string>
@@ -1531,6 +1538,7 @@
     <string name="_adm_stg_cpt_option_sys_allow_plus_in_email"><![CDATA[Allow to use plus sign ("+") in email address]]></string>
     <string name="_adm_stg_cpt_option_sys_accounts_pruning"><![CDATA[Accounts pruning]]></string>
     <string name="_adm_stg_cpt_option_sys_accounts_pruning_interval"><![CDATA[Accounts pruning period (in days)]]></string>
+    <string name="_adm_stg_cpt_option_sys_accounts_password_log_count"><![CDATA[Number of stored old passwords  (0 = do not store)]]></string>
     <string name="_adm_stg_cpt_option_sys_lock_from_unauthenticated"><![CDATA[Lock site from unauthenticated users]]></string>
     <string name="_adm_stg_cpt_option_sys_lock_from_unauthenticated_exceptions"><![CDATA[Comma separated list of pages URIs, which are avalilable for unauthenticated users]]></string>
     <string name="_adm_stg_cpt_option_value_sys_account_pruning_no"><![CDATA[No pruning]]></string>
@@ -2249,6 +2257,12 @@ If it is not enabled then please consider implement this optimization, since it
     <string name="_sys_et_txt_body_account"><![CDATA[{email_header}
 <p>{account_output}</p>
 {email_footer}]]></string>
+    
+     <string name="_sys_et_txt_body_account_password_expired"><![CDATA[{email_header}
+<p>Your password will expired in {expired_date}</p>
+<p>Please, sign-in and change password</p>
+{email_footer}]]></string>
+    
     <string name="_sys_et_txt_body_pruning"><![CDATA[{email_header}
 <p>{pruning_output}</p>
 {email_footer}]]></string>
@@ -2307,6 +2321,7 @@ If it is not enabled then please consider implement this optimization, since it
         <string name="_sys_et_txt_name_account_change_status_suspended"><![CDATA[Account status changed to suspended]]></string>
         <string name="_sys_et_txt_name_manage_approve"><![CDATA[Approve: Contact content's author]]></string>
 	<string name="_sys_et_txt_name_system_account"><![CDATA[New Accounts]]></string>
+    <string name="_sys_et_txt_name_system_account_password_expired"><![CDATA[Expired Password]]></string>
 	<string name="_sys_et_txt_name_system_pruning"><![CDATA[Pruning result]]></string>
 	<string name="_sys_et_txt_name_upgrade_failed"><![CDATA[Upgrade failed]]></string>
 	<string name="_sys_et_txt_name_upgrade_modules_failed"><![CDATA[Modules upgrade failed]]></string>
@@ -2329,6 +2344,8 @@ If it is not enabled then please consider implement this optimization, since it
         <string name="_sys_et_txt_subject_account_change_status_suspended"><![CDATA[Account suspended]]></string>
         <string name="_sys_et_txt_subject_manage_approve"><![CDATA[Approvement. A question about '{content_title}'.]]></string>
 	<string name="_sys_et_txt_subject_account"><![CDATA[{site_name} {account_count} accounts created yesterday]]></string>
+    <string name="_sys_et_txt_subject_account_password_expired"><![CDATA[{site_name} - your password will expired soon]]></string>
+    
 	<string name="_sys_et_txt_subject_pruning"><![CDATA[{site_name} pruning result]]></string>
 	<string name="_sys_et_txt_subject_upgrade_failed"><![CDATA[{site_name} upgrade failed]]></string>
 	<string name="_sys_et_txt_subject_upgrade_modules_failed"><![CDATA[Modules upgrade failed on {site_name}]]></string>
@@ -2371,6 +2388,7 @@ If it is not enabled then please consider implement this optimization, since it
 	<string name="_sys_form_account_input_password_current"><![CDATA[Current Password]]></string>
 	<string name="_sys_form_account_input_password_current_error"><![CDATA[Current password isn't correct, please enter password you are using to login to this site.]]></string>
 	<string name="_sys_form_account_input_password_error"><![CDATA[The password must at least 8 characters long and contain at least 1 uppercase letter, 1 lowercase letter and 1 digit.]]></string>
+    <string name="_sys_form_account_input_password_error_old_used"><![CDATA[This password already used before. Please use newone.]]></string>
 	<string name="_sys_form_account_input_receive_news"><![CDATA[Receive Newsletters]]></string>
 	<string name="_sys_form_account_input_receive_updates"><![CDATA[Receive Site Updates]]></string>
 	<string name="_sys_form_account_input_submit"><![CDATA[Submit]]></string>

+ 1 - 1
modules/boonex/stream/classes/BxStrmGridRecordings.php

@@ -86,7 +86,7 @@ class BxStrmGridRecordings extends BxTemplGrid
             $sUrl = $this->_publish(array_shift($aIds), $sErrorMsg);
         
         if ($sUrl) {
-            $a = ['open_url' => $sUrl];
+            $a = ['redirect' => $sUrl];
         }
         elseif ($sErrorMsg) {
             $s = BxTemplFunctions::getInstance()->popupBox(

+ 32 - 0
studio/template/scripts/BxBaseStudioPermissionsLevels.php

@@ -478,6 +478,38 @@ class BxBaseStudioPermissionsLevels extends BxDolStudioPermissionsLevels
                         'error' => _t('_adm_prm_err_level_quota_number'),
                     ),
                 ),
+                'PasswordExpired' => array(
+                    'type' => 'text',
+                    'name' => 'PasswordExpired',
+                    'caption' => _t('_adm_prm_txt_level_password_expired'),
+                    'info' => _t('_adm_prm_dsc_level_password_expired'),
+                    'value' => isset($aLevel['password_expired']) ? (int)$aLevel['password_expired'] : '0',
+                    'required' => '1',
+                    'db' => array (
+                        'pass' => 'Int',
+                    ),
+                    'checker' => array (
+                        'func' => 'preg',
+                        'params' => array('/^[0-9]+$/'),
+                        'error' => _t('_adm_prm_err_level_password_expired'),
+                    ),
+                ),
+                'PasswordExpiredNotify' => array(
+                    'type' => 'text',
+                    'name' => 'PasswordExpiredNotify',
+                    'caption' => _t('_adm_prm_txt_level_password_expired_notify'),
+                    'info' => _t('_adm_prm_dsc_level_password_expired_notify'),
+                    'value' => isset($aLevel['password_expired_notify']) ? (int)$aLevel['password_expired_notify'] : '0',
+                    'required' => '1',
+                    'db' => array (
+                        'pass' => 'Int',
+                    ),
+                    'checker' => array (
+                        'func' => 'preg',
+                        'params' => array('/^[0-9]+$/'),
+                        'error' => _t('_adm_prm_err_level_password_expired_notify'),
+                    ),
+                ),
                 'Icon' => array(
                     'type' => 'textarea',
                     'name' => 'Icon',

+ 26 - 0
template/scripts/BxBaseFormAccount.php

@@ -16,6 +16,26 @@ class BxFormAccountCheckerHelper extends BxDolFormCheckerHelper
     {
         return $s == bx_process_input(bx_get(BxTemplFormAccount::$FIELD_PASSWORD));
     }
+    
+    function checkPassword ($s, $r)
+    {
+        $bValid = parent::checkPreg($s, $r);
+        
+        if (!$bValid)
+            return $bValid;
+        
+        $aPasswords = BxDolAccountQuery::getInstance()->getLastPasswordLog( BxDolAccount::getInstance()->id());
+        $bUsed = false;
+        foreach ($aPasswords as $aPassword){
+            if($aPassword['password'] == encryptUserPwd($s, $aPassword['salt']))
+                $bUsed = true;
+        }
+        
+        if ($bUsed)
+            return _t('_sys_form_account_input_password_error_old_used');
+        
+        return true;
+    }
 
     /**
      * Password confirmation check.
@@ -152,6 +172,12 @@ class BxBaseFormAccount extends BxTemplFormView
         if ($sPwd) {
             $sSalt = genRndSalt();
             $sPasswordHash = encryptUserPwd($sPwd, $sSalt);
+            
+            $oAccount = BxDolAccount::getInstance($val, true);
+            $iPasswordExpired = $oAccount->getPasswordExpiredDateByAccount($val);
+            
+            BxDolAccountQuery::getInstance()->logPassword($val);
+            BxDolAccountQuery::getInstance()->updatePasswordExpired($val, $iPasswordExpired);
         }
 
         $aValsToAdd = array_merge(

+ 5 - 2
template/scripts/BxBaseServiceAccount.php

@@ -593,8 +593,11 @@ class BxBaseServiceAccount extends BxDol
         $sPwd = genRndPwd(8, false);
         $sSalt = genRndSalt();
         $sPasswordHash = encryptUserPwd($sPwd, $sSalt);
-
-        $this->_oAccountQuery->updatePassword($sPasswordHash, $sSalt, $iAccountId);
+        
+        $oAccount = BxDolAccount::getInstance($iAccountId);
+        $iPasswordExpired = $oAccount->getPasswordExpiredDateByAccount($iAccountId);
+        
+        $this->_oAccountQuery->updatePassword($sPasswordHash, $sSalt, $iAccountId, $iPasswordExpired);
 
         bx_alert('account', 'edited', $iAccountId, $iAccountId, array('action' => 'forgot_password'));
 

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio