Просмотр исходного кода

Upgrade script from 11.0.0 to 11.0.1

Alex Trofimov 6 лет назад
Родитель
Сommit
f0af61e2c6
62 измененных файлов с 27183 добавлено и 0 удалено
  1. 9 0
      upgrade/files/11.0.0-11.0.1/check.php
  2. 709 0
      upgrade/files/11.0.0-11.0.1/files/inc/classes/BxDolAccount.php
  3. 783 0
      upgrade/files/11.0.0-11.0.1/files/inc/classes/BxDolAcl.php
  4. 448 0
      upgrade/files/11.0.0-11.0.1/files/inc/classes/BxDolAclQuery.php
  5. 177 0
      upgrade/files/11.0.0-11.0.1/files/inc/classes/BxDolBadgesQuery.php
  6. 309 0
      upgrade/files/11.0.0-11.0.1/files/inc/classes/BxDolContentInfo.php
  7. 1807 0
      upgrade/files/11.0.0-11.0.1/files/inc/classes/BxDolForm.php
  8. 183 0
      upgrade/files/11.0.0-11.0.1/files/inc/classes/BxDolGridConnections.php
  9. 376 0
      upgrade/files/11.0.0-11.0.1/files/inc/classes/BxDolPayments.php
  10. 608 0
      upgrade/files/11.0.0-11.0.1/files/inc/classes/BxDolProfile.php
  11. 395 0
      upgrade/files/11.0.0-11.0.1/files/inc/classes/BxDolProfileQuery.php
  12. 392 0
      upgrade/files/11.0.0-11.0.1/files/inc/classes/BxDolScore.php
  13. 155 0
      upgrade/files/11.0.0-11.0.1/files/inc/classes/BxDolScoreQuery.php
  14. 1067 0
      upgrade/files/11.0.0-11.0.1/files/inc/classes/BxDolSearch.php
  15. 348 0
      upgrade/files/11.0.0-11.0.1/files/inc/classes/BxDolStorageQuery.php
  16. 900 0
      upgrade/files/11.0.0-11.0.1/files/inc/classes/BxDolTranscoder.php
  17. 152 0
      upgrade/files/11.0.0-11.0.1/files/inc/classes/BxDolVoteQuery.php
  18. 32 0
      upgrade/files/11.0.0-11.0.1/files/inc/classes/BxDolVoteStars.php
  19. 164 0
      upgrade/files/11.0.0-11.0.1/files/inc/js/classes/BxDolMenuMoreAuto.js
  20. 327 0
      upgrade/files/11.0.0-11.0.1/files/inc/js/jquery.webForms.js
  21. 1953 0
      upgrade/files/11.0.0-11.0.1/files/inc/utils.inc.php
  22. 12 0
      upgrade/files/11.0.0-11.0.1/files/inc/version.inc.php
  23. 533 0
      upgrade/files/11.0.0-11.0.1/files/modules/base/general/classes/BxBaseModGeneralFormEntry.php
  24. 621 0
      upgrade/files/11.0.0-11.0.1/files/modules/base/general/classes/BxBaseModGeneralFormsEntryHelper.php
  25. 2551 0
      upgrade/files/11.0.0-11.0.1/files/modules/base/general/classes/BxBaseModGeneralModule.php
  26. 151 0
      upgrade/files/11.0.0-11.0.1/files/modules/base/general/classes/BxBaseModGeneralPageEntry.php
  27. 134 0
      upgrade/files/11.0.0-11.0.1/files/modules/base/general/classes/BxBaseModGeneralSearchResult.php
  28. 29 0
      upgrade/files/11.0.0-11.0.1/files/modules/base/groups/classes/BxBaseModGroupsConfig.php
  29. 832 0
      upgrade/files/11.0.0-11.0.1/files/modules/base/groups/classes/BxBaseModGroupsModule.php
  30. 79 0
      upgrade/files/11.0.0-11.0.1/files/modules/base/groups/classes/BxBaseModGroupsPageEntry.php
  31. 95 0
      upgrade/files/11.0.0-11.0.1/files/modules/base/groups/classes/BxBaseModGroupsTemplate.php
  32. 278 0
      upgrade/files/11.0.0-11.0.1/files/modules/base/payment/classes/BxBaseModPaymentCart.php
  33. 36 0
      upgrade/files/11.0.0-11.0.1/files/modules/base/profile/classes/BxBaseModProfileConfig.php
  34. 113 0
      upgrade/files/11.0.0-11.0.1/files/modules/base/profile/classes/BxBaseModProfileDb.php
  35. 239 0
      upgrade/files/11.0.0-11.0.1/files/modules/base/profile/classes/BxBaseModProfileFormEntry.php
  36. 1836 0
      upgrade/files/11.0.0-11.0.1/files/modules/base/profile/classes/BxBaseModProfileModule.php
  37. 501 0
      upgrade/files/11.0.0-11.0.1/files/modules/base/profile/classes/BxBaseModProfileTemplate.php
  38. 528 0
      upgrade/files/11.0.0-11.0.1/files/modules/base/profile/template/css/main.css
  39. 65 0
      upgrade/files/11.0.0-11.0.1/files/modules/base/text/classes/BxBaseModTextMenuAttachments.php
  40. 0 0
      upgrade/files/11.0.0-11.0.1/files/plugins_public/croppie/croppie.min.js
  41. 42 0
      upgrade/files/11.0.0-11.0.1/files/searchKeyword.php
  42. 522 0
      upgrade/files/11.0.0-11.0.1/files/studio/classes/BxDolStudioBuilderPageQuery.php
  43. 295 0
      upgrade/files/11.0.0-11.0.1/files/studio/classes/BxDolStudioForm.php
  44. 749 0
      upgrade/files/11.0.0-11.0.1/files/studio/classes/BxDolStudioInstallerUtils.php
  45. 650 0
      upgrade/files/11.0.0-11.0.1/files/studio/classes/BxDolStudioToolsAudit.php
  46. 118 0
      upgrade/files/11.0.0-11.0.1/files/studio/template/css/forms.css
  47. 1823 0
      upgrade/files/11.0.0-11.0.1/files/studio/template/scripts/BxBaseStudioBuilderPage.php
  48. 638 0
      upgrade/files/11.0.0-11.0.1/files/studio/template/scripts/BxBaseStudioDesigner.php
  49. 24 0
      upgrade/files/11.0.0-11.0.1/files/template/comment.html
  50. 10 0
      upgrade/files/11.0.0-11.0.1/files/template/comments_uploader_nfw.html
  51. 117 0
      upgrade/files/11.0.0-11.0.1/files/template/css/acl.css
  52. 535 0
      upgrade/files/11.0.0-11.0.1/files/template/css/cmts.css
  53. 637 0
      upgrade/files/11.0.0-11.0.1/files/template/css/forms.css
  54. 1 0
      upgrade/files/11.0.0-11.0.1/files/template/menu_more_auto.html
  55. 14 0
      upgrade/files/11.0.0-11.0.1/files/template/profile_membership_stats.html
  56. 112 0
      upgrade/files/11.0.0-11.0.1/files/template/scripts/BxBaseCmtsMenuUnitMeta.php
  57. 143 0
      upgrade/files/11.0.0-11.0.1/files/template/scripts/BxBaseMenuMoreAuto.php
  58. 399 0
      upgrade/files/11.0.0-11.0.1/files/template/scripts/BxBaseSearchResult.php
  59. 401 0
      upgrade/files/11.0.0-11.0.1/files/template/scripts/BxBaseServiceProfiles.php
  60. 7 0
      upgrade/files/11.0.0-11.0.1/files_delete.php
  61. 3 0
      upgrade/files/11.0.0-11.0.1/script.php
  62. 16 0
      upgrade/files/11.0.0-11.0.1/sql.sql

+ 9 - 0
upgrade/files/11.0.0-11.0.1/check.php

@@ -0,0 +1,9 @@
+<?php
+
+$mixCheckResult = 'Update can not be applied';
+
+$sVer = $this->oDb->getOne("SELECT `version` FROM `sys_modules` WHERE `name` = 'system'");
+if ('11.0.0' == $sVer)
+    $mixCheckResult = true;
+
+return $mixCheckResult;

+ 709 - 0
upgrade/files/11.0.0-11.0.1/files/inc/classes/BxDolAccount.php

@@ -0,0 +1,709 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaCore UNA Core
+ * @{
+ */
+
+bx_import('BxDolAcl');
+
+class BxDolAccount extends BxDolFactory implements iBxDolSingleton
+{
+    protected $_iAccountID;
+    protected $_aInfo;
+    protected $_oQuery;
+
+    /**
+     * Constructor
+     */
+    protected function __construct ($iAccountId)
+    {
+        $iAccountId = (int)$iAccountId;
+        $sClass = get_class($this) . '_' . $iAccountId;
+        if (isset($GLOBALS['bxDolClasses'][$sClass]))
+            trigger_error ('Multiple instances are not allowed for the class: ' . get_class($this), E_USER_ERROR);
+
+        parent::__construct();
+
+        $this->_iAccountID = $iAccountId; // since constructor is protected $iAccountId is always valid
+        $this->_oQuery = BxDolAccountQuery::getInstance();
+    }
+
+    /**
+     * Prevent cloning the instance
+     */
+    public function __clone()
+    {
+        $sClass = get_class($this) . '_' . $this->_iProfileID;
+        if (isset($GLOBALS['bxDolClasses'][$sClass]))
+            trigger_error('Clone is not allowed for the class: ' . get_class($this), E_USER_ERROR);
+    }
+
+    /**
+     * Get singleton instance of the class
+     */
+    public static function getInstance($mixedAccountId = false,  $bClearCache = false)
+    {
+        if (!$mixedAccountId)
+            $mixedAccountId = getLoggedId();
+
+        $iAccountId = self::getID($mixedAccountId);
+        if (!$iAccountId)
+            return false;
+
+        $sClass = __CLASS__ . '_' . $iAccountId;
+        
+        if ($bClearCache && isset($GLOBALS['bxDolClasses'][$sClass]))
+            unset($GLOBALS['bxDolClasses'][$sClass]);
+        
+        if(!isset($GLOBALS['bxDolClasses'][$sClass]))
+            $GLOBALS['bxDolClasses'][$sClass] = new BxDolAccount($iAccountId);
+
+        return $GLOBALS['bxDolClasses'][$sClass];
+    }
+
+    /**
+     * Get studio operator account singleton instance on the class
+     */
+    public static function getInstanceStudioOperator()
+    {
+        $oQuery = BxDolAccountQuery::getInstance();
+        if (!($iId = $oQuery->getStudioOperatorId()))
+            return false;
+
+        return self::getInstance($iId);
+    }
+
+    /**
+     * Get account id
+     */
+    public function id()
+    {
+        $a = $this->getInfo($this->_iAccountID);
+        return isset($a['id']) ? $a['id'] : false;
+    }
+
+    /**
+     * Check if account is confirmed, it is checked by email confirmation
+     */
+    public function isConfirmed($iAccountId = false)
+    {
+        $iId = (int)$iAccountId ? (int)$iAccountId : $this->_iAccountID;
+
+        $a = $this->getInfo($iId);
+
+        $bResult = false;
+        $sConfirmationType = getParam('sys_account_confirmation_type');
+        switch($sConfirmationType) {
+            case BX_ACCOUNT_CONFIRMATION_NONE:
+                $bResult = true;
+                break;
+
+            case BX_ACCOUNT_CONFIRMATION_EMAIL:
+                if($a['email_confirmed'])
+                    $bResult = true;
+                break;
+
+            case BX_ACCOUNT_CONFIRMATION_PHONE:
+                if($a['phone_confirmed'])
+                    $bResult = true;
+                break;
+
+            case BX_ACCOUNT_CONFIRMATION_EMAIL_PHONE:
+                if($a['email_confirmed'] && $a['phone_confirmed'])
+                    $bResult = true;
+                break;
+        }
+
+        bx_alert('account', 'is_confirmed', $iId, false, array('type' => $sConfirmationType, 'override_result' => &$bResult));
+
+        return $bResult;
+    }
+    
+    public function getCurrentConfirmationStatusValue($iAccountId = false)
+    {
+        $a = $this->getInfo((int)$iAccountId);
+        $sTmp = $a['email_confirmed'] . $a['phone_confirmed'];
+        switch ($sTmp) {
+            case '01':
+                return BX_ACCOUNT_CONFIRMATION_PHONE;
+            case '10':
+                return BX_ACCOUNT_CONFIRMATION_EMAIL;
+            case '11':
+                return BX_ACCOUNT_CONFIRMATION_EMAIL_PHONE;
+        }
+        return BX_ACCOUNT_CONFIRMATION_NONE;
+    }
+
+    public function isConfirmedEmail($iAccountId = false)
+    {
+        $iId = (int)$iAccountId ? (int)$iAccountId : $this->_iAccountID;
+
+        $bResult = false;
+        if(self::isNeedConfirmEmail()) {
+            $a = $this->getInfo($iId);
+
+            $bResult = $a['email_confirmed'] ? true : false;
+        }
+        else 
+            $bResult = true;
+
+        bx_alert('account', 'is_confirmed_email', $iId, false, array('override_result' => &$bResult));
+
+        return $bResult;
+    }
+
+    public function isConfirmedPhone($iAccountId = false)
+    {
+        $iId = (int)$iAccountId ? (int)$iAccountId : $this->_iAccountID;
+
+        $bResult = false;        
+        if(self::isNeedConfirmPhone()) {
+            $a = $this->getInfo((int)$iAccountId);
+
+            $bResult = $a['phone_confirmed'] ? true : false;
+        }
+        else
+            $bResult = true;
+
+        bx_alert('account', 'is_confirmed_phone', $iId, false, array('override_result' => &$bResult));
+
+        return $bResult;
+    }
+
+    static public function isNeedConfirmEmail()
+    {
+        if (getParam('sys_account_confirmation_type') == BX_ACCOUNT_CONFIRMATION_EMAIL || getParam('sys_account_confirmation_type') == BX_ACCOUNT_CONFIRMATION_EMAIL_PHONE) 
+            return true;
+        return false;
+    }
+    
+    static public function isNeedConfirmPhone()
+    {
+        if (getParam('sys_account_confirmation_type') == BX_ACCOUNT_CONFIRMATION_PHONE || getParam('sys_account_confirmation_type') == BX_ACCOUNT_CONFIRMATION_EMAIL_PHONE) 
+            return true;
+        return false;
+    }
+    
+    public function isLocked($iAccountId = false)
+    {
+        $a = $this->getInfo((int)$iAccountId);
+        return $a['locked'] ? true : false;
+    }
+    
+    //
+
+    /**
+     * Set account email to confirmed or unconfirmed
+     * @param  int  $isConfirmed - false: mark email as unconfirmed, true: as confirmed
+     * @param  int  $iAccountId  - optional account id
+     * @return true on success or false on error
+     */
+    public function updateEmailConfirmed($isConfirmed, $isAutoSendConfrmationEmail = true, $iAccountId = false)
+    {
+        $iId = (int)$iAccountId ? (int)$iAccountId : $this->_iAccountID;
+
+        if (!$isConfirmed && $isAutoSendConfrmationEmail && self::isNeedConfirmEmail()) // if email_confirmation procedure is enabled - send email confirmation letter
+            $this->sendConfirmationEmail($iId);
+
+        if ($this->_oQuery->updateEmailConfirmed($isConfirmed, $iId)) {
+            $this->_aInfo = false;
+            bx_alert('account', $this->isConfirmed() ? 'confirm' : 'unconfirm', $iId);
+            return true;
+        }
+        return false;
+    }
+    
+    /**
+     * Set account phone to confirmed or unconfirmed
+     * @param  int  $isConfirmed - false: mark phone as unconfirmed, true: as confirmed
+     * @param  int  $iAccountId  - optional account id
+     * @return true on success or false on error
+     */
+    public function updatePhoneConfirmed($isConfirmed, $iAccountId = false)
+    {
+        $iId = (int)$iAccountId ? (int)$iAccountId : $this->_iAccountID;
+        
+        if ($this->_oQuery->updatePhoneConfirmed($isConfirmed, $iId)) {
+            $this->_aInfo = false;
+            bx_alert('account', $this->isConfirmed() ? 'confirm' : 'unconfirm', $iId);
+            return true;
+        }
+        return false;
+    }
+    
+    /**
+     * Set account phone
+     * @param  string  $sPhone - phone number
+     * @param  int  $iAccountId  - optional account id
+     * @return true on success or false on error
+     */
+    public function updatePhone($sPhone, $iAccountId = false)
+    {
+        $iId = (int)$iAccountId ? (int)$iAccountId : $this->_iAccountID;
+        if ($this->_oQuery->updatePhone($sPhone, $iId)) {
+            bx_alert('account', 'set_phone', $iId);
+            return true;
+        }
+        return false;
+    }
+    /**
+     * Switch context automatically to the first available profile
+     * @param $iProfileIdFilterOut profile ID to exclude from the list of possible profiles
+     * @param $iAccountId account ID to use istead of current account
+     * @return true on success or false on error
+     */ 
+    public function updateProfileContextAuto($iProfileIdFilterOut = false, $iAccountId = false)
+    {
+        $oAccount = (!$iAccountId || $iAccountId == $this->_iAccountID ? $this : BxDolAccount::getInstance ($iAccountId));
+        if (!$oAccount)
+            return false;
+        $aAccountInfo = $oAccount->getInfo();
+        $aProfiles = $oAccount->getProfiles();
+        $oProfileAccount = BxDolProfile::getInstanceAccountProfile($oAccount->id());
+
+        // unset deleted profile and account profile
+        if ($iProfileIdFilterOut)
+            unset($aProfiles[$iProfileIdFilterOut]);
+        unset($aProfiles[$oProfileAccount->id()]);
+
+        if ($aProfiles) {
+            // try to use another profile
+            reset($aProfiles);
+            $iProfileId = key($aProfiles);
+        } 
+        else {
+            // if no profiles exist, use account profile
+            $iProfileId = $oProfileAccount->id();
+        }
+
+        return $oAccount->updateProfileContext($iProfileId);
+    }
+    
+    public function updateProfileContext($iSwitchToProfileId, $iAccountId = false)
+    {
+        $iId = (int)$iAccountId ? (int)$iAccountId : $this->_iAccountID;
+        $aInfo = $this->getInfo((int)$iId);
+        if (!$aInfo)
+            return false;
+
+        $ret = null;
+        bx_alert('account', 'before_switch_context', $iId, $iSwitchToProfileId, array('profile_id_current' => $aInfo['profile_id'], 'override_result' => &$ret));
+        if ($ret !== null)
+            return $ret;
+
+        if (!$this->_oQuery->updateCurrentProfile($iId, $iSwitchToProfileId))
+            return false;
+
+        $this->_aInfo = false;
+            
+        bx_alert('account', 'switch_context', $iId, $iSwitchToProfileId, array('profile_id_old' => $aInfo['profile_id']));
+
+        return true;
+    }
+
+    /**
+     * Send "confirmation" email
+     */
+    public function sendConfirmationEmail($iAccountId = false)
+    {
+    	$sName = $this->getDisplayName($iAccountId); 
+        $sEmail = $this->getEmail($iAccountId);
+
+        $oKey = BxDolKey::getInstance();
+        $sConfirmationCode = $oKey->getNewKey(array('account_id' => $iAccountId));
+        $sConfirmationLink = bx_append_url_params(BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=confirm-email'), array('code' => urlencode($sConfirmationCode)));
+
+        $aPlus = array(
+        	'name' => $sName,
+        	'email' => $sEmail,
+        	'conf_code' => $sConfirmationCode,
+        	'conf_link' => $sConfirmationLink,
+        	'conf_form_link' => BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=confirm-email')
+        );
+
+        $aTemplate = BxDolEmailTemplates::getInstance()->parseTemplate('t_Confirmation', $aPlus);
+        $bRv = $aTemplate && sendMail($sEmail, $aTemplate['Subject'], $aTemplate['Body'], 0, array(), BX_EMAIL_SYSTEM);
+        
+        if($bRv){
+            $this->doAudit($iAccountId, '_sys_audit_action_account_resend_confirmation_email');
+        }
+        return $bRv;
+    }
+
+    /**
+     * Get account info
+     */
+    public function getInfo($iAccountId = false)
+    {        
+        if ($iAccountId && $iAccountId != $this->_iAccountID)
+            return $this->_oQuery->getInfoById((int)$iAccountId ? (int)$iAccountId : $this->_iAccountID);
+
+        if ($this->_aInfo)
+            return $this->_aInfo;
+
+        $this->_aInfo = $this->_oQuery->getInfoById($this->_iAccountID);
+        return $this->_aInfo;
+    }
+
+    /**
+     * Get account display name
+     */
+    public function getDisplayName($iAccountId = false)
+    {
+        $aInfo = $this->getInfo($iAccountId);
+        return bx_process_output(!empty($aInfo['name']) ? $aInfo['name'] : _t('_sys_txt_user_n', $aInfo['id']));
+    }
+
+    /**
+     * Get account url
+     */
+    public function getUrl($iAccountId = false)
+    {
+        return 'javascript:void(0);';
+    }
+
+    /**
+     * Get account url
+     */
+    public function getUnit($iAccountId = false, $aParams = array())
+    {
+        
+        $sTemplate = 'unit';
+        $sTemplateSize = false;
+        $aTemplateVars = array();
+        if(!empty($aParams['template'])) {
+            if(is_string($aParams['template']))
+                $sTemplate = $aParams['template'];
+            else if(is_array($aParams['template'])) {
+                if(!empty($aParams['template']['name']))
+                    $sTemplate = $aParams['template']['name'];
+
+                if(!empty($aParams['template']['size']))
+                    $sTemplateSize = $aParams['template']['size'];
+
+                if(!empty($aParams['template']['vars']))
+                    $aTemplateVars = $aParams['template']['vars'];
+            }
+        }
+        $sTemplate = 'account_' . $sTemplate . '.html';
+        if(empty($sTemplateSize))
+            $sTemplateSize = 'thumb';
+
+        $sTitle = $this->getDisplayName($iAccountId);
+
+        $aTmplVars = array(
+            'size' => $sTemplateSize,
+            'color' => implode(', ', BxDolTemplate::getColorCode(($iAccountId ? $iAccountId : $this->_iAccountID), 1.0)),
+            'letter' => mb_strtoupper(mb_substr($sTitle, 0, 1)),
+            'content_url' => $this->getUrl($iAccountId),
+            'title' => $sTitle,
+            'title_attr' => bx_html_attribute($sTitle),
+            'bx_if:show_online' => array(
+                'condition' => $this->isOnline($iAccountId),
+                'content' => array()
+            )
+        );
+
+        if(!empty($aTemplateVars) && is_array($aTemplateVars))
+            $aTmplVars = array_merge ($aTmplVars, $aTemplateVars);
+
+        return BxDolTemplate::getInstance()->parseHtmlByName($sTemplate, $aTmplVars);
+    }
+
+    /**
+     * Get picture url
+     */
+    public function getPicture($iAccountId = false)
+    {
+        return BxDolTemplate::getInstance()->getImageUrl('account-picture.png');
+    }
+
+    /**
+     * Get big (2x) avatar url
+     */
+    public function getAvatarBig($iAccountId = false)
+    {
+        return BxDolTemplate::getInstance()->getImageUrl('account-avatar-big.png');
+    }
+
+    /**
+     * Get avatar url
+     */
+    public function getAvatar($iAccountId = false)
+    {
+        return BxDolTemplate::getInstance()->getImageUrl('account-avatar.png');
+    }
+
+    /**
+     * Get thumb picture url
+     */
+    public function getThumb($iAccountId = false)
+    {
+        return BxDolTemplate::getInstance()->getImageUrl('account-thumb.png');
+    }
+
+    /**
+     * Get icon picture url
+     */
+    public function getIcon($iAccountId = false)
+    {
+        return BxDolTemplate::getInstance()->getImageUrl('account-icon.png');
+    }
+
+    /**
+     * Get account email
+     */
+    public function getEmail($iAccountId = false)
+    {
+        $iAccountId = (int)$iAccountId ? (int)$iAccountId : $this->_iAccountID;
+        $aAccountInfo = $this->getInfo($iAccountId);
+        return $aAccountInfo['email'];
+    }
+
+    /**
+     * Get language ID
+     */
+    public function getLanguageId($iAccountId = false)
+    {
+        $iAccountId = (int)$iAccountId ? (int)$iAccountId : $this->_iAccountID;
+        $aAccountInfo = $this->getInfo($iAccountId);
+        return $aAccountInfo['lang_id'];
+    }
+
+    /**
+     * Is account online
+     */
+    public function isOnline($iAccountId = false)
+    {
+        $iAccountId = (int)$iAccountId ? (int)$iAccountId : $this->_iAccountID;
+        return $this->_oQuery->isOnline($iAccountId);
+    }
+
+    /**
+     * Validate account.
+     * @param $s - account identifier (id or email)
+     * @return account id or false if account was not found
+     */
+    static public function getID($s)
+    {
+        $oQuery = BxDolAccountQuery::getInstance();
+
+        bx_import('BxDolForm');
+        if (BxDolFormCheckerHelper::checkEmail($s)) {
+            $iId = (int)$oQuery->getIdByEmail($s);
+            return $iId ? $iId : false;
+        }
+        if (preg_match("/^\+[0-9\s]+$/", $s)) {
+            $iId = (int)$oQuery->getIdByPhone($s);
+            return $iId ? $iId : false;
+        }
+        
+        $iId = $oQuery->getIdById((int)$s);
+        return $iId ? $iId : false;
+    }
+
+    /**
+     * Check if profiles limit reached
+     */
+    public function isProfilesLimitReached ()
+    {
+        $iProfilesLimit = (int)getParam('sys_account_limit_profiles_number');
+        bx_alert('account', 'get_limit_profiles_number', 0, 0, array('account_id' => $this->_iAccountID, 'number' => &$iProfilesLimit));
+        if ($iProfilesLimit && ($iProfilesNum = $this->getProfilesNumber()) && $iProfilesNum >= $iProfilesLimit)
+            return true;
+
+        return false;
+    }
+
+    /**
+     * Get number of profiles associated with the account
+     */
+    public function getProfilesNumber ($isFilterNonSwitchableProfiles = true)
+    {
+        $a = $this->getProfilesIds($isFilterNonSwitchableProfiles);
+        return count($a);
+    }
+    
+    /**
+     * Get all profile ids associated with the account
+     */
+    public function getProfilesIds ($isFilterNonSwitchableProfiles = true, $isFilterSystemProfiles = true)
+    {
+        $a = $this->getProfiles ($isFilterNonSwitchableProfiles, $isFilterSystemProfiles);
+        return $a ? array_keys($a) : array();
+    }
+
+    /**
+     * Get all profiles associated with the account
+     */
+    public function getProfiles ($isFilterNonSwitchableProfiles = true, $isFilterSystemProfiles = true)
+    {
+        $oProfileQuery = BxDolProfileQuery::getInstance();
+        $aProfiles = $oProfileQuery->getProfilesByAccount($this->_iAccountID);
+
+        if ($isFilterNonSwitchableProfiles) {
+            foreach ($aProfiles as $iProfileId => $aProfile) {
+                if ('system' == $aProfile['type'])
+                    continue;
+                if (!BxDolService::call($aProfile['type'], 'act_as_profile'))
+                    unset($aProfiles[$iProfileId]);
+            }
+        }
+
+        if ($isFilterSystemProfiles) {
+            foreach ($aProfiles as $iProfileId => $aProfile) {
+                if ('system' == $aProfile['type'])
+                    unset($aProfiles[$iProfileId]);
+            }
+        }
+
+        return $aProfiles;
+    }
+
+    /**
+     * Delete profile.
+     * @param $bDeleteWithContent - delete associated profiles with all its contents
+     */
+    function delete($bDeleteWithContent = false)
+    {
+        $aAccountInfo = $this->_oQuery->getInfoById($this->_iAccountID);
+        if (!$aAccountInfo)
+            return false;
+
+        // create system event before deletion
+        $isStopDeletion = false;
+        bx_alert('account', 'before_delete', $this->_iAccountID, 0, array('delete_with_content' => $bDeleteWithContent, 'stop_deletion' => &$isStopDeletion));
+        if ($isStopDeletion)
+            return false;
+
+        $oAccountQuery = BxDolAccountQuery::getInstance();
+
+        $oProfileQuery = BxDolProfileQuery::getInstance();
+        $aProfiles = $oProfileQuery->getProfilesByAccount($this->_iAccountID);
+        foreach ($aProfiles as $iProfileId => $aRow) {
+            $oProfile = BxDolProfile::getInstance($iProfileId);
+            if (!$oProfile)
+                continue;
+            $oProfile->delete(false, $bDeleteWithContent, true);
+        }
+
+        // delete profile
+        if (!$oAccountQuery->delete($this->_iAccountID))
+            return false;
+
+        // unset class instance to prevent creating the instance again
+        $sClass = __CLASS__ . '_' . $this->_iAccountID;
+        unset($GLOBALS['bxDolClasses'][$sClass]);
+
+       // create system event
+        bx_alert('account', 'delete', $this->_iAccountID, 0, array ('delete_with_content' => $bDeleteWithContent));
+        
+        $this->doAudit($this->_iAccountID, $bDeleteWithContent ? '_sys_audit_action_account_deleted_with_content' : '_sys_audit_action_account_deleted');
+        
+        return true;
+    }
+
+    /**
+     * Add permament messages.
+     */
+    public function addInformerPermanentMessages ($oInformer)
+    {
+        if (!$this->isConfirmedEmail()) {
+            $sUrl = BxDolPermalinks::getInstance()->permalink('page.php?i=confirm-email') . '&resend=1';
+            $aAccountInfo = $this->getInfo();
+            $oInformer->add('sys-account-unconfirmed-email', _t('_sys_txt_account_unconfirmed_email', $sUrl, $aAccountInfo['email']), BX_INFORMER_ALERT);
+        }
+		if (!$this->isConfirmedPhone()) {
+            $sUrl = BxDolPermalinks::getInstance()->permalink('page.php?i=confirm-phone') . '';
+            $aAccountInfo = $this->getInfo();
+            $oInformer->add('sys-account-unconfirmed-phone', _t('_sys_txt_account_unconfirmed_phone', $sUrl), BX_INFORMER_ALERT);
+        }
+    }
+
+    /**
+     * Get unsubscribe link for the specified mesage type
+     */
+    public function getUnsubscribeLink($iEmailType, $iAccountId = false)
+    {
+        $iAccountId = (int)$iAccountId ? (int)$iAccountId : $this->_iAccountID;
+        $sUrl = '';
+        switch ($iEmailType) {
+            case BX_EMAIL_NOTIFY:
+                $sUrl = 'page.php?i=unsubscribe-notifications';
+                break;
+            case BX_EMAIL_MASS:
+                $sUrl = 'page.php?i=unsubscribe-news';
+                break;
+            default:
+                return '';
+        }
+        return BxDolPermalinks::getInstance()->permalink($sUrl) . '&id=' . $iAccountId . '&code=' . $this->getEmailHash();
+    }
+
+    public function getEmailHash($iAccountId = false)
+    {
+        $iAccountId = (int)$iAccountId ? (int)$iAccountId : $this->_iAccountID;
+        $a = $this->getInfo();
+        return md5($a['email'] . $a['salt'] . BX_DOL_SECRET);
+    }
+    
+    public function doAudit($iAccountId, $sAction, $aData = array())
+    {
+        $iAccountId = (int)$iAccountId ? (int)$iAccountId : $this->_iAccountID;
+        bx_audit(
+            $iAccountId, 
+            'bx_accounts', 
+            $sAction,  
+            array('content_title' => $this->getEmail(), 'data' => $aData)
+        );
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden.
+     */
+    static public function isAllowedCreate ($iProfileId, $isPerformAction = false)
+    {
+        $aCheck = checkActionModule($iProfileId, 'create account', 'system', $isPerformAction);
+        if ($aCheck[CHECK_ACTION_RESULT] !== CHECK_ACTION_RESULT_ALLOWED)
+            return MsgBox($aCheck[CHECK_ACTION_MESSAGE]);
+        return CHECK_ACTION_RESULT_ALLOWED;
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden.
+     */
+    static public function isAllowedEdit ($iProfileId, $aContentInfo, $isPerformAction = false)
+    {
+        $oProfile = BxDolProfile::getInstance($iProfileId);
+        if (!$oProfile)
+            return _t('_sys_txt_access_denied');
+
+        $aProfileInfo = $oProfile->getInfo();
+        if (!$aProfileInfo || getLoggedId() != $aProfileInfo['account_id'])
+            return _t('_sys_txt_access_denied');
+
+        return CHECK_ACTION_RESULT_ALLOWED;
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden.
+     */
+    static public function isAllowedDelete ($iProfileId, $aContentInfo, $isPerformAction = false)
+    {
+        $iAccountId = (int)BxDolProfile::getInstance($iProfileId)->getAccountId();
+        if(isAdmin($iAccountId) && $iAccountId == (int)$aContentInfo['id'])
+            return _t('_sys_txt_access_denied');
+
+        $aCheck = checkActionModule($iProfileId, 'delete account', 'system', $isPerformAction);
+        if ($aCheck[CHECK_ACTION_RESULT] !== CHECK_ACTION_RESULT_ALLOWED)
+            return $aCheck[CHECK_ACTION_MESSAGE];
+
+        return CHECK_ACTION_RESULT_ALLOWED;
+    }
+
+}
+
+/** @} */

+ 783 - 0
upgrade/files/11.0.0-11.0.1/files/inc/classes/BxDolAcl.php

@@ -0,0 +1,783 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaCore UNA Core
+ * @{
+ */
+
+/**
+ * Message constants passed to _t_ext() function by checkAction()
+ *
+ * NOTE: checkAction() returns language dependent messages
+ */
+define('CHECK_ACTION_MESSAGE_NOT_ALLOWED',			"_sys_acl_action_not_allowed");
+define('CHECK_ACTION_MESSAGE_LIMIT_REACHED',		"_sys_acl_action_limit_reached");
+define('CHECK_ACTION_MESSAGE_MESSAGE_EVERY_PERIOD',	"_sys_acl_action_every_period");
+define('CHECK_ACTION_MESSAGE_NOT_ALLOWED_BEFORE',	"_sys_acl_action_not_allowed_before");
+define('CHECK_ACTION_MESSAGE_NOT_ALLOWED_AFTER',	"_sys_acl_action_not_allowed_after");
+
+define('CHECK_ACTION_MESSAGE_UNAUTHENTICATED',	    "_sys_acl_action_unauthenticated");
+define('CHECK_ACTION_MESSAGE_UNCONFIRMED',			"_sys_acl_action_unconfirmed");
+define('CHECK_ACTION_MESSAGE_PENDING',			    "_sys_acl_action_pending");
+define('CHECK_ACTION_MESSAGE_SUSPENDED',			"_sys_acl_action_suspended");
+
+/**
+ * Nodes of $args array that are passed to _t_ext() function by checkAction()
+ */
+define('CHECK_ACTION_LANG_FILE_ACTION', 1);
+define('CHECK_ACTION_LANG_FILE_MEMBERSHIP', 2);
+define('CHECK_ACTION_LANG_FILE_LIMIT', 3);
+define('CHECK_ACTION_LANG_FILE_PERIOD', 4);
+define('CHECK_ACTION_LANG_FILE_AFTER', 5);
+define('CHECK_ACTION_LANG_FILE_BEFORE', 6);
+define('CHECK_ACTION_LANG_FILE_SITE_EMAIL',	7);
+define('CHECK_ACTION_LANG_FILE_PERIOD_RESTART_AT', 8);
+
+/**
+ * Standard membership ID's
+ */
+define('MEMBERSHIP_ID_NON_MEMBER', 1);
+define('MEMBERSHIP_ID_ACCOUNT', 2);
+define('MEMBERSHIP_ID_STANDARD', 3);
+define('MEMBERSHIP_ID_UNCONFIRMED', 4);
+define('MEMBERSHIP_ID_PENDING', 5);
+define('MEMBERSHIP_ID_SUSPENDED', 6);
+define('MEMBERSHIP_ID_MODERATOR', 7);
+define('MEMBERSHIP_ID_ADMINISTRATOR', 8);
+
+/**
+ * Indices for checkAction() result array
+ */
+define('CHECK_ACTION_RESULT', 0);
+define('CHECK_ACTION_MESSAGE', 1);
+define('CHECK_ACTION_PARAMETER', 3);
+
+/**
+ * CHECK_ACTION_RESULT node values
+ */
+define('CHECK_ACTION_RESULT_ALLOWED', 0);
+define('CHECK_ACTION_RESULT_NOT_ALLOWED', 1);
+define('CHECK_ACTION_RESULT_NOT_ACTIVE', 2);
+define('CHECK_ACTION_RESULT_LIMIT_REACHED', 3);
+define('CHECK_ACTION_RESULT_NOT_ALLOWED_BEFORE', 4);
+define('CHECK_ACTION_RESULT_NOT_ALLOWED_AFTER', 5);
+
+/**
+ * Standard period units
+ */
+define('MEMBERSHIP_PERIOD_UNIT_DAY', 'day');
+define('MEMBERSHIP_PERIOD_UNIT_WEEK', 'week');
+define('MEMBERSHIP_PERIOD_UNIT_MONTH', 'month');
+define('MEMBERSHIP_PERIOD_UNIT_YEAR', 'year');
+
+
+class BxDolAcl extends BxDolFactory implements iBxDolSingleton
+{
+    protected $oDb;
+
+    protected $_aStandardMemberships = array(
+        MEMBERSHIP_ID_NON_MEMBER => 1,
+        MEMBERSHIP_ID_ACCOUNT => 1,
+        MEMBERSHIP_ID_UNCONFIRMED => 1,
+        MEMBERSHIP_ID_PENDING => 1,
+        MEMBERSHIP_ID_SUSPENDED => 1,
+        MEMBERSHIP_ID_STANDARD => 1,
+    );
+
+    protected $_aProfileStatus2LevelMap = array (
+        BX_PROFILE_STATUS_SUSPENDED => MEMBERSHIP_ID_SUSPENDED,
+        BX_PROFILE_STATUS_PENDING => MEMBERSHIP_ID_PENDING,
+    );
+
+    protected $_aLevel2MessageMap = array (
+        MEMBERSHIP_ID_NON_MEMBER => '_sys_acl_action_unauthenticated',
+        MEMBERSHIP_ID_ACCOUNT => '_sys_acl_action_account',
+        MEMBERSHIP_ID_UNCONFIRMED => '_sys_acl_action_unconfirmed',
+        MEMBERSHIP_ID_PENDING => '_sys_acl_action_pending',
+        MEMBERSHIP_ID_SUSPENDED => '_sys_acl_action_suspended',
+    );
+
+    protected function __construct()
+    {
+        if (isset($GLOBALS['bxDolClasses'][get_class($this)]))
+            trigger_error ('Multiple instances are not allowed for the class: ' . get_class($this), E_USER_ERROR);
+
+        parent::__construct();
+
+        $this->oDb = BxDolAclQuery::getInstance();
+    }
+
+    /**
+     * Prevent cloning the instance
+     */
+    public function __clone()
+    {
+        if (isset($GLOBALS['bxDolClasses'][get_class($this)]))
+            trigger_error('Clone is not allowed for the class: ' . get_class($this), E_USER_ERROR);
+    }
+
+    /**
+     * Get singleton instance of the class
+     */
+    public static function getInstance()
+    {
+        if(!isset($GLOBALS['bxDolClasses'][__CLASS__]))
+            $GLOBALS['bxDolClasses'][__CLASS__] = new BxTemplAcl();
+
+        return $GLOBALS['bxDolClasses'][__CLASS__];
+    }
+
+	/**
+     * Get necessary condition array to use membership level in search classes
+     * @param $sContentField content table field name
+     * @param $mixedLevelId level ID or array of level IDs
+     * @return array of conditions is returned
+     */
+    public function getContentByLevelAsCondition($sContentField, $mixedLevelId)
+    {
+        $iLevelId = !is_array($mixedLevelId) ? $mixedLevelId : 0;
+        if (!$iLevelId && is_array($mixedLevelId) && 1 == count($mixedLevelId)) {
+            $a = array_values($mixedLevelId);
+            $iLevelId = array_shift($a);
+        }
+
+        // unconfirmed
+        if (MEMBERSHIP_ID_UNCONFIRMED == $iLevelId) {
+            return array(
+                'restriction_sql' => ' AND `sys_accounts`.`email_confirmed` = 0 ',
+                'restriction' => array (),
+                'join' => array (),
+            );
+        }
+        // standard
+        elseif (MEMBERSHIP_ID_STANDARD == $iLevelId) {
+            return array(
+                'restriction_sql' => ' AND (`tlm`.DateStarts IS NULL OR `tlm`.DateStarts <= NOW()) AND (`tlm`.DateExpires IS NULL OR `tlm`.DateExpires > NOW()) AND `tlm`.`IDMember` IS NULL AND `sys_accounts`.`email_confirmed` != 0 ',
+                'restriction' => array (
+                ),
+                'join' => array (
+                    'acl_members' => array(
+                        'type' => 'LEFT',
+                        'table' => 'sys_acl_levels_members',
+                        'table_alias' => 'tlm',
+                        'mainField' => $sContentField,
+                        'onField' => 'IDMember',
+                        'joinFields' => array(),
+                    ),
+                ),
+            );
+        }
+        // other levels
+        else {
+            return array(
+                'restriction_sql' => ' AND (`tlm`.DateStarts IS NULL OR `tlm`.DateStarts <= NOW()) AND (`tlm`.DateExpires IS NULL OR `tlm`.DateExpires > NOW()) AND `sys_accounts`.`email_confirmed` != 0 ',
+                'restriction' => array (
+                    'acl_members' => array(
+                        'value' => $mixedLevelId,
+                        'field' => 'IDLevel',
+                        'operator' => is_array($mixedLevelId) ? 'in' : '=',
+                        'table' => 'tlm',
+                    ),
+                ),
+                'join' => array (
+                    'acl_members' => array(
+                        'type' => 'INNER',
+                        'table' => 'sys_acl_levels_members',
+                        'table_alias' => 'tlm',
+                        'mainField' => $sContentField,
+                        'onField' => 'IDMember',
+                        'joinFields' => array(),
+                    ),
+                ),
+            );
+        }
+    }
+
+	/**
+     * Get necessary parts of SQL query to use membership levels in other queries
+     * @param $sContentTable content table name
+     * @param $sContentField content table field name
+     * @param $mixedLevelId level ID or array of level IDs
+     * @return array of SQL string parts, for now 'where' part only is returned
+     */
+    public function getContentByLevelAsSQLPart($sContentTable, $sContentField, $mixedLevelId)
+    {
+        return $this->oDb->getContentByLevelAsSQLPart($sContentTable, $sContentField, $mixedLevelId);
+    }
+
+    /**
+     * Check if member has one of the provided membership levels
+     * @param $mixedPermissions - integer value (every bit is matched with some membership ID) or an array of membership IDs to check permissions for
+     * @param $iProfileId - profile to check, if it isn't provided or is false then currently logged in profile is used.
+     * @return true if member has privided membership levels, or false if member hasn't.
+     */
+    public function isMemberLevelInSet($mixedPermissions, $iProfileId = false)
+    {
+        $iPermissions = 0;
+        if(is_array($mixedPermissions))
+            foreach($mixedPermissions as $iPermissionId)
+                $iPermissions += pow(2, $iPermissionId - 1);
+        else if(is_numeric($mixedPermissions))
+            $iPermissions = (int)$mixedPermissions;
+
+        if(!$iPermissions)
+            return false;
+
+        return ($iPermissions & $this->getMemberLevelBit($iProfileId));
+    }
+
+    /**
+     * Get user's membership level bit for bitwise operarions
+     */
+    public function getMemberLevelBit($iProfileId = 0)
+    {
+        if (!$iProfileId)
+            $iProfileId = bx_get_logged_profile_id();
+
+        $aACL = $this->getMemberMembershipInfo($iProfileId);
+        return pow(2, $aACL['id'] - 1);
+    }
+
+    /**
+     * Checks if a given action is allowed for a given profile and updates action information if the
+     * action is performed.
+     *
+     * @param  int     $iProfileId     ID of a profile that is going to perform an action
+     * @param  int     $iActionId      ID of the action itself
+     * @param  boolean $bPerformAction if true, then action information is updated, i.e. action is 'performed'
+     * @return array(
+     *                                CHECK_ACTION_RESULT => CHECK_ACTION_RESULT_ constant,
+     *                                CHECK_ACTION_MESSAGE => CHECK_ACTION_MESSAGE_ constant,
+     *                                CHECK_ACTION_PARAMETER => additional action parameter (string)
+     *                                )
+     *
+     * NOTES:
+     *
+     * $aResult[CHECK_ACTION_MESSAGE] contains a message with detailed information about the result,
+     * already processed by the language file
+     *
+     * if $aResult[CHECK_ACTION_RESULT] === CHECK_ACTION_RESULT_ALLOWED then this node contains
+     * an empty string
+     *
+     * The error messages themselves are stored in the language file. Additional variables are
+     * passed to the languages.inc.php function _t_ext() as an array and can be used there in the form of
+     * {0}, {1}, {2} ...
+     *
+     * Additional variables passed to the lang. file on errors (can be used in error messages):
+     *
+     * 	For all errors:
+     *
+     * 		$arg0[CHECK_ACTION_LANG_FILE_ACTION]	= name of the action
+     * 		$arg0[CHECK_ACTION_LANG_FILE_MEMBERSHIP]= name of the current membership
+     *
+     * 	CHECK_ACTION_RESULT_LIMIT_REACHED:
+     *
+     * 		$arg0[CHECK_ACTION_LANG_FILE_LIMIT]		= limit on number of actions allowed for the profile
+     * 		$arg0[CHECK_ACTION_LANG_FILE_PERIOD]	= period that the limit is set for (in hours, 0 if unlimited)
+     * 		$arg0[CHECK_ACTION_LANG_FILE_PERIOD_RESTART_AT]	= time when new period begins, so counter will be reset
+     *
+     * 	CHECK_ACTION_RESULT_NOT_ALLOWED_BEFORE:
+     *
+     * 		$arg0[CHECK_ACTION_LANG_FILE_BEFORE]	= date/time since when the action is allowed
+     *
+     * 	CHECK_ACTION_RESULT_NOT_ALLOWED_AFTER:
+     *
+     * 		$arg0[CHECK_ACTION_LANG_FILE_AFTER]		= date/time since when the action is not allowed
+     *
+     * $aResult[CHECK_ACTION_PARAMETER] contains an additional parameter that can be considered
+     * when performing the action (like the number of profiles to show in search result)
+    */
+    function checkAction($iProfileId, $iActionId, $bPerformAction = false)
+    {
+        $aResult = array();
+        $aLangFileParams = array();
+
+        $iProfileId = (int)$iProfileId;
+        $iActionId = (int)$iActionId;
+        $bPerformAction = $bPerformAction ? true : false;
+
+        $aMembership = $this->getMemberMembershipInfo($iProfileId); // get current profile's membership information
+
+        $aLangFileParams[CHECK_ACTION_LANG_FILE_MEMBERSHIP] = _t($aMembership['name']);
+        $aLangFileParams[CHECK_ACTION_LANG_FILE_SITE_EMAIL] = getParam('site_email');
+
+        $aAction = $this->oDb->getAction($aMembership['id'], $iActionId);
+        if (!$aAction)
+            bx_trigger_error('Unknown action ID: ' . $iActionId, 2);
+
+        $aResult[CHECK_ACTION_PARAMETER] = $aAction['additional_param_value'];
+        $aLangFileParams[CHECK_ACTION_LANG_FILE_ACTION] = !empty($aAction['title']) ? _t($aAction['title']) : $aAction['name'];
+
+        /**
+         * Action is not allowed for the current membership
+         */
+        if (is_null($aAction['id'])) {
+
+            $sLangKey = CHECK_ACTION_MESSAGE_NOT_ALLOWED;
+            if (isset($this->_aLevel2MessageMap[$aMembership['id']]))
+                $sLangKey = $this->_aLevel2MessageMap[$aMembership['id']];
+
+            $aResult[CHECK_ACTION_RESULT] = CHECK_ACTION_RESULT_NOT_ALLOWED;
+            $aResult[CHECK_ACTION_MESSAGE] = _t_ext($sLangKey, $aLangFileParams);
+            return $aResult;
+        }
+
+        /**
+         * Check fixed period limitations if present (also for non-members)
+         */
+        if($aAction['allowed_period_start'] && time() < $aAction['allowed_period_start']) {
+            $aLangFileParams[CHECK_ACTION_LANG_FILE_BEFORE] = bx_time_js($aAction['allowed_period_start'], BX_FORMAT_DATE_TIME);
+
+            $aResult[CHECK_ACTION_RESULT] = CHECK_ACTION_RESULT_NOT_ALLOWED_BEFORE;
+            $aResult[CHECK_ACTION_MESSAGE] = _t_ext(CHECK_ACTION_MESSAGE_NOT_ALLOWED_BEFORE, $aLangFileParams);
+
+            return $aResult;
+        }
+
+        if($aAction['allowed_period_end'] && time() > $aAction['allowed_period_end']) {
+            $aLangFileParams[CHECK_ACTION_LANG_FILE_AFTER] = bx_time_js($aAction['allowed_period_end'], BX_FORMAT_DATE_TIME);
+
+            $aResult[CHECK_ACTION_RESULT] = CHECK_ACTION_RESULT_NOT_ALLOWED_AFTER;
+            $aResult[CHECK_ACTION_MESSAGE] = _t_ext(CHECK_ACTION_MESSAGE_NOT_ALLOWED_AFTER, $aLangFileParams);
+
+            return $aResult;
+        }
+
+        /**
+         * if non-member, allow action without performing further checks
+         */
+        if ($aMembership['id'] == MEMBERSHIP_ID_NON_MEMBER) {
+            $aResult[CHECK_ACTION_RESULT] = CHECK_ACTION_RESULT_ALLOWED;
+            return $aResult;
+        }
+
+        /**
+         * Check other limitations (for members only)
+         */
+        $iAllowedCnt = (int)$aAction['allowed_count']; ///< Number of allowed actions. Unlimited if not specified or 0
+        $iPeriodLen = (int)$aAction['allowed_period_len']; ///< Period for AllowedCount in hours. If not specified, AllowedCount is treated as total number of actions permitted.
+
+        if($iAllowedCnt > 0) {
+            $aActionTrack = $this->oDb->getActionTrack($iActionId, $iProfileId);
+
+            $iActionsLeft = $bPerformAction ? $iAllowedCnt - 1 : $iAllowedCnt;
+            $iValidSince = time();
+
+            /**
+             * Member is requesting/performing this action for the first time,
+             * and there is no corresponding record in sys_acl_actions_track table.
+             */
+            if(!$aActionTrack) {
+                $this->oDb->insertActionTarck($iActionId, $iProfileId, $iActionsLeft, $iValidSince);
+
+                $aResult[CHECK_ACTION_RESULT] = CHECK_ACTION_RESULT_ALLOWED;
+                return $aResult;
+            }
+
+            /**
+             * Action has been requested/performed at least once at this point and there is a corresponding record in sys_acl_actions_track table
+             *
+             * Action record in sys_acl_actions_track table is out of date.
+             */
+            $iPeriodEnd = (int)$aActionTrack['valid_since'] + $iPeriodLen * 3600; //ValidSince is in seconds, PeriodLen is in hours
+            if($iPeriodLen > 0 && $iPeriodEnd < time()) {
+                $this->oDb->updateActionTrack($iActionId, $iProfileId, $iActionsLeft, $iValidSince);
+
+                $aResult[CHECK_ACTION_RESULT] = CHECK_ACTION_RESULT_ALLOWED;
+                return $aResult;
+            }
+
+            $iActionsLeft = (int)$aActionTrack['actions_left']; ///< Action record is up to date
+
+            /**
+             * Action limit reached for now
+             */
+            if($iActionsLeft <= 0){
+                $aLangFileParams[CHECK_ACTION_LANG_FILE_LIMIT] = $iAllowedCnt;
+                $aLangFileParams[CHECK_ACTION_LANG_FILE_PERIOD] = $iPeriodLen;
+                $aLangFileParams[CHECK_ACTION_LANG_FILE_PERIOD_RESTART_AT] = bx_time_js($iPeriodEnd);
+
+                $aResult[CHECK_ACTION_RESULT] = CHECK_ACTION_RESULT_LIMIT_REACHED;
+                $aResult[CHECK_ACTION_MESSAGE] = '<div class="bx-acl-err-msg">' . _t_ext(CHECK_ACTION_MESSAGE_LIMIT_REACHED, $aLangFileParams) . ($iPeriodLen > 0 ? _t_ext(CHECK_ACTION_MESSAGE_MESSAGE_EVERY_PERIOD, $aLangFileParams) : '') . '.</div>';
+
+                return $aResult;
+            }
+
+            if($bPerformAction) {
+                $iActionsLeft--;
+                $this->oDb->updateActionTrack($iActionId, $iProfileId, $iActionsLeft);
+            }
+        }
+
+        $aResult[CHECK_ACTION_RESULT] = CHECK_ACTION_RESULT_ALLOWED;
+        return $aResult;
+    }
+
+    /**
+     * Get the list of existing memberships
+     *
+     * @param  bool   $bPurchasableOnly if true, fetches only purchasable memberships; 'purchasable' here means that:
+     *                                  - MemLevels.Purchasable = 'yes'
+     *                                  - MemLevels.Active = 'yes'
+     *                                  - there is at least one pricing option for the membership
+     * @return array( membershipID_1 => membershipName_1,  membershipID_2 => membershipName_2, ...) if no such memberships, then just array()
+     */
+    function getMemberships($bPurchasableOnly = false, $bActiveOnly = false, $isTranslate = true, $bFilterOutSystemAutomaticLevels = false)
+    {
+        $sType = 'all_pair';
+        if($bPurchasableOnly)
+            $sType = 'all_active_purchasble_pair';
+        else if($bActiveOnly)
+            $sType = 'all_active_pair';
+
+        $aLevels = array();
+        $this->oDb->getLevels(array('type' => $sType), $aLevels, false);
+        if ($isTranslate)
+            foreach ($aLevels as $k => $s)
+                $aLevels[$k] = _t($s);
+
+        if ($bFilterOutSystemAutomaticLevels) {
+            unset($aLevels[MEMBERSHIP_ID_NON_MEMBER]);
+            unset($aLevels[MEMBERSHIP_ID_ACCOUNT]);
+            unset($aLevels[MEMBERSHIP_ID_UNCONFIRMED]);
+            unset($aLevels[MEMBERSHIP_ID_PENDING]);
+            unset($aLevels[MEMBERSHIP_ID_SUSPENDED]);
+        }
+
+        return $aLevels;
+    }
+
+    function getMembershipsBy($aParams)
+    {
+        $aLevels = array();
+        $this->oDb->getLevels($aParams, $aLevels, false);
+
+        return $aLevels;
+    }
+
+    /**
+     * Get info about a given membership
+     *
+     * @param  int    $iLevelId membership to get info about
+     * @return array(
+     *                         'id'					=>	ID,
+     *                         'name'					=>	name,
+     *                         'icon'					=>	icon,
+     *                         'description'			=>	description,
+     *                         'active'				=>	active,
+     *                         'purchasable'			=>	purchasable,
+     *                         'removable'				=>	removable
+     *                         'quota_size'			=>	quota size,
+     *                         'quota_number'			=>	quota number,
+     *                         'quota_max_file_size'	=>	quota max file size
+     *                         )
+     */
+    function getMembershipInfo($iLevelId)
+    {
+        $aLevel = array();
+        $this->oDb->getLevels(array('type' => 'by_id', 'value' => $iLevelId), $aLevel, false);
+        return $aLevel;
+    }
+
+    /**
+     * Retrieves information about membership for a given profile at a given moment.
+     *
+     * If there are no memberships purchased/assigned to the
+     * given profile or all of them have expired at the given point,
+     * the profile is assumed to be a standard profile, and the function
+     * returns	information about the Standard membership. This will
+     * also happen if a profile wasnt actually registered in the database
+     * at that point - the function will still return info about Standard
+     * membership, not the Non-member one.
+     *
+     * If there is no profile with the given $iProfileId,
+     * the function returns information about the Non-member or Authenticated
+     * predefined membership.
+     *
+     * The Standard, Authenticated and Non-member memberships have their
+     * DateStarts and DateExpires attributes set to NULL.
+     *
+     * @param  int    $iProfileId ID of a profile to get info about
+     * @param  int    $time       specifies the time to use when determining membership; if not specified, the function takes the current time
+     * @return array(
+     *                           'id'			=> membership id,
+     *                           'name'			=> membership name,
+     *                           'date_starts'	=> (UNIX timestamp) date/time purchased,
+     *                           'date_expires'	=> (UNIX timestamp) date/time expires
+     *                           )
+     */
+    function getMemberMembershipInfo($iProfileId, $iTime = 0)
+    {
+        $aMembershipCurrent = $this->getMemberMembershipInfoCurrent($iProfileId, $iTime);
+        if (isset($this->_aStandardMemberships[$aMembershipCurrent['id']]))
+            return $aMembershipCurrent;
+
+        $aMembership = $aMembershipCurrent;
+        do {
+            $iDateStarts = $aMembership['date_starts'];
+            $aMembership = $this->getMemberMembershipInfoCurrent($iProfileId, ((int)$iDateStarts < 1 ? 0 : $iDateStarts - 1));
+        }
+        while($aMembership['id'] == $aMembershipCurrent['id'] && (int)$aMembership['date_starts']);
+
+        $aMembership = $aMembershipCurrent;
+        do {
+            $iDateExpires = $aMembership['date_expires'];
+            $aMembership = $this->getMemberMembershipInfoCurrent($iProfileId, $iDateExpires);
+        } while($aMembership['id'] == $aMembershipCurrent['id'] && (int)$aMembership['date_expires']);
+
+        $aMembershipCurrent['date_starts'] = $iDateStarts;
+        $aMembershipCurrent['date_expires'] = $iDateExpires;
+
+        return $aMembershipCurrent;
+    }
+
+    /**
+     * Set a membership for a profile
+     *
+     * @param  int     $iProfileId profile that is going to get the membership
+     * @param  int     $iLevelId   membership that is going to be assigned to the profile
+     *                             if $iLevelId == MEMBERSHIP_ID_STANDARD then $days and $bStartsNow parameters are not used,
+     *                             so Standard membership is always set immediately and `forever`
+     * @param  mixed   $mixedPeriod  number of Days to set membership for or an array with 'period'-'period unit' pair. If number or 'period' in pair equal 0, then the membership is set forever
+     * @param  boolean $bStartsNow if true, the membership will start immediately if false, the membership will start after the current membership expires
+     * @return boolean true in case of success, false in case of failure
+     */
+    function setMembership($iProfileId, $iLevelId, $mixedPeriod = 0, $bStartsNow = false, $sTransactionId = '')
+    {
+        $iProfileId = (int)$iProfileId;
+        $iLevelId = (int)$iLevelId;
+        $bStartsNow = $bStartsNow ? true : false;
+
+        if (!$iProfileId)
+            $iProfileId = -1;
+
+        if (empty($sTransactionId))
+            $sTransactionId = 'NULL';
+
+        // check if profile exists
+        if(($sProfileEmail = BxDolProfileQuery::getInstance()->getEmailById($iProfileId)) === false)
+            return false;
+
+        // check if membership exists
+        $aLevel = array();
+        $this->oDb->getLevels(array('type' => 'by_id', 'value' => $iLevelId), $aLevel, false);
+        if(empty($aLevel) || !is_array($aLevel))
+            return false;
+
+        if($iLevelId == MEMBERSHIP_ID_NON_MEMBER)
+            return false;
+
+        $aMembershipCurrent = $this->getMemberMembershipInfo($iProfileId);
+        $aMembershipLatest = $this->getMemberMembershipInfoLatest($iProfileId);
+
+        // setting Standard membership level
+        if ($iLevelId == MEMBERSHIP_ID_STANDARD) {
+            if ($aMembershipCurrent['id'] == MEMBERSHIP_ID_STANDARD)
+                return true;
+
+            // delete present and future memberships
+            $bResult = $this->oDb->deleteLevelByProfileId($iProfileId);
+            if($bResult)
+                $this->oDb->cleanMemory('BxDolAclQuery::getLevelCurrent' . $iProfileId . time());
+
+            return $bResult;
+        }
+
+        if ((is_numeric($mixedPeriod) && (int)$mixedPeriod < 0) || (is_array($mixedPeriod) && (!isset($mixedPeriod['period']) || $mixedPeriod['period'] < 0)))
+            return false;
+
+        $iDateStarts = time();
+        if (!$bStartsNow) {
+            // Make the membership starts after the latest membership expires or return false if latest membership isn't Standard and is lifetime membership.
+            if(!empty($aMembershipLatest['date_expires']))
+                $iDateStarts = $aMembershipLatest['date_expires'];
+            else if(empty($aMembershipLatest['date_expires']) && $aMembershipLatest['id'] != MEMBERSHIP_ID_STANDARD)
+                return false;
+        }
+        else {
+            // Delete any profile's membership level and actions traces
+            $this->oDb->deleteLevelByProfileId($iProfileId, true); 
+            $this->oDb->clearActionsTracksForMember($iProfileId);
+            $this->oDb->cleanMemory('BxDolAclQuery::getLevelCurrent' . $iProfileId . time());
+        }
+
+        // set lifetime membership if 0 days is used.
+        if(is_numeric($mixedPeriod))
+        	$mixedPeriod = array('period' => (int)$mixedPeriod, 'period_unit' => MEMBERSHIP_PERIOD_UNIT_DAY);
+
+        if(!$this->oDb->insertLevelByProfileId($iProfileId, $iLevelId, $iDateStarts, $mixedPeriod, $sTransactionId))
+           return false;
+
+        // raise membership alert
+        $oZ = new BxDolAlerts('profile', 'set_membership', '', $iProfileId, array('mlevel'=> $iLevelId, 'period' => $mixedPeriod['period'], 'period_unit' => $mixedPeriod['period_unit'], 'starts_now' => $bStartsNow, 'txn_id' => $sTransactionId));
+        $oZ->alert();
+
+        // audit
+        $aDataForAudit = array();
+        if (!empty($aMembershipCurrent))
+            $aDataForAudit = array('new_membership_level' => _t($aLevel['name']), 'old_membership_level' => _t($aMembershipCurrent['name']));
+        BxDolProfile::getInstance($iProfileId)->doAudit('_sys_audit_action_set_membership', $aDataForAudit);
+        
+        // Send notification
+        $aTemplate = BxDolEmailTemplates::getInstance()->parseTemplate('t_MemChanged', array('membership_level' => _t($aLevel['name'])), 0, $iProfileId);
+        if ($aTemplate)
+            sendMail($sProfileEmail, $aTemplate['Subject'], $aTemplate['Body']);
+
+        return true;
+    }
+
+    function unsetMembership($iProfileId, $iLevelId, $sTransactionId)
+    {
+    	return $this->oDb->deleteLevelBy(array(
+    		'IDMember' => $iProfileId,
+    		'IDLevel' => $iLevelId,
+    		'TransactionID' => $sTransactionId
+    	));
+    }
+
+    /**
+     * get action id by module and name
+     * @param $sAction action name
+     * @param $sModule module name
+     * @param $aActions array of actions from sys_acl_actions table, with default array keys (starting from 0) and text values
+     */
+    function getMembershipActionId($sAction, $sModule)
+    {
+        $this->oDb->getActions(array('type' => 'by_names_and_module', 'value' => $sAction, 'module' => $sModule), $aActions, false);
+        if (count($aActions) > 1)
+            trigger_error('Duplicate action - name:' . $sAction . ', module:' . $sModule, E_USER_ERROR);
+        $aAction = array_pop($aActions);
+        return $aAction['id'];
+    }
+
+    function getExpirationLetter($iProfileId, $sLevelName, $iLevelExpireDays )
+    {
+        $iProfileId = (int)$iProfileId;
+
+        if(!$iProfileId)
+            return false;
+
+        $oProfileQuery = BxDolProfileQuery::getInstance();
+        $sProfileEmail = $oProfileQuery->getEmailById($iProfileId);
+
+        $aPlus = array(
+            'membership_name' => $sLevelName,
+            'expire_days' => $iLevelExpireDays,
+            'page_url' => BxDolRequest::serviceExists('bx_acl', 'get_view_url') ? BxDolService::call('bx_acl', 'get_view_url') : '#'
+        );
+
+        $aTemplate = BxDolEmailTemplates::getInstance()->parseTemplate('t_MemExpiration', $aPlus, 0, $iProfileId);
+
+        $iResult = $aTemplate && sendMail($sProfileEmail, $aTemplate['Subject'], $aTemplate['Body'], $iProfileId, $aPlus);
+        return !empty($iResult);
+    }
+
+    /**
+     * clear expired membership levels
+     */
+    public function maintenance()
+    {
+        return $this->oDb->maintenance();
+    }
+
+    protected function getMemberMembershipInfoCurrent($iProfileId, $iTime = 0)
+    {
+        $aMemLevel = false;
+
+        // get profile status
+        $oProfile = BxDolProfile::getInstance($iProfileId);
+        $aProfileInfo = $oProfile ? $oProfile->getInfo() : false;
+        $sProfileStatus = $aProfileInfo ? $aProfileInfo['status'] : false;
+        $sProfileType = $aProfileInfo ? $aProfileInfo['type'] : false;
+
+        // account profile
+	    if($sProfileType == 'system') {
+        	$aMemLevel = $this->oDb->getLevelByIdCached(MEMBERSHIP_ID_ACCOUNT);
+        	if (!$aMemLevel)
+                trigger_error ('Standard member level is missing: MEMBERSHIP_ID_ACCOUNT', E_USER_ERROR);
+
+        	return $aMemLevel;
+        }
+
+        // check if account is unconfirmed, every account's profile is unconfirmed if account is unconfirmed
+        $oAccount = $aProfileInfo ? BxDolAccount::getInstance($aProfileInfo['account_id']) : false;
+        if ($oAccount && !$oAccount->isConfirmed()) {
+            $aMemLevel = $this->oDb->getLevelByIdCached(MEMBERSHIP_ID_UNCONFIRMED);
+            if (!$aMemLevel)
+                trigger_error ('Standard member level is missing: MEMBERSHIP_ID_UNCONFIRMED', E_USER_ERROR);
+
+            return $aMemLevel;
+        }
+        
+        // profile is not active, so return standard memberships according to profile status        
+        if (false === $sProfileStatus || BX_PROFILE_STATUS_ACTIVE != $sProfileStatus) {                
+            if (!isset($this->_aProfileStatus2LevelMap[$sProfileStatus]))
+                $iLevelId = MEMBERSHIP_ID_NON_MEMBER; // if there is no profile status - then it isn't member
+            else
+                $iLevelId = $this->_aProfileStatus2LevelMap[$sProfileStatus]; // get member level id which associated with every non-active status
+
+            $aMemLevel = $this->oDb->getLevelByIdCached($iLevelId);
+
+            if (!$aMemLevel)
+                trigger_error ('Standard member level is missing: ' . $iLevelId, E_USER_ERROR);
+
+            return $aMemLevel;
+        }
+
+        // profile is active get memebr level from profile
+        $aMemLevel = $this->oDb->getLevelCurrent((int)$iProfileId, $iTime);
+
+        // There are no purchased/assigned memberships for the profile or all of them have expired.
+        // In this case the profile is assumed to have Standard membership.
+        if (!$aMemLevel || is_null($aMemLevel['id'])) {
+            $aMemLevel = $this->oDb->getLevelByIdCached(MEMBERSHIP_ID_STANDARD);
+            if (!$aMemLevel)
+                trigger_error ('Standard member level is missing: ' . MEMBERSHIP_ID_NON_MEMBER, E_USER_ERROR);
+        }
+
+        return $aMemLevel;
+    }
+
+    protected function getMemberMembershipInfoLatest($iProfileId, $iTime = 0)
+    {
+        $aMembershipCurrent = $this->getMemberMembershipInfoCurrent($iProfileId, $iTime);
+        if (isset($this->_aStandardMemberships[$aMembershipCurrent['id']]))
+            return $aMembershipCurrent;
+
+        $aMembership = $aMembershipCurrent;
+        while ($aMembership['id'] != MEMBERSHIP_ID_STANDARD) {
+            $aMembershipLast = $aMembership;
+            if(!isset($aMembership['date_expires']) || (int)$aMembership['date_expires'] == 0)
+                break;
+
+            $aMembership = $this->getMemberMembershipInfoCurrent($iProfileId, $aMembership['date_expires']);
+        }
+
+        return $aMembershipLast;
+    }
+
+    public function onProfileDelete ($iProfileId)
+    {
+        return $this->oDb->deleteLevelByProfileId($iProfileId, true);
+    }
+}
+
+function checkAction($iProfileId, $iActionId, $bPerformAction = false)
+{
+    return BxDolAcl::getInstance()->checkAction($iProfileId, $iActionId, $bPerformAction);
+}
+
+function checkActionModule($iProfileId, $sActionName, $sModuleName, $bPerformAction = false)
+{
+    $oACL = BxDolAcl::getInstance();
+
+    $iActionId = $oACL->getMembershipActionId($sActionName, $sModuleName);
+    if (!$iActionId)
+        bx_trigger_error("Unknown action: '$sActionName' in module '$sModuleName'", 1);
+
+    return $oACL->checkAction($iProfileId, $iActionId, $bPerformAction);
+}
+
+/** @} */

+ 448 - 0
upgrade/files/11.0.0-11.0.1/files/inc/classes/BxDolAclQuery.php

@@ -0,0 +1,448 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaCore UNA Core
+ * @{
+ */
+
+/**
+ * Database queries for ACL
+ * @see BxDolAcl
+ */
+class BxDolAclQuery extends BxDolDb implements iBxDolSingleton
+{
+    protected function __construct()
+    {
+        if (isset($GLOBALS['bxDolClasses'][get_class($this)]))
+            trigger_error ('Multiple instances are not allowed for the class: ' . get_class($this), E_USER_ERROR);
+
+        parent::__construct();
+    }
+
+    /**
+     * Prevent cloning the instance
+     */
+    public function __clone()
+    {
+        if (isset($GLOBALS['bxDolClasses'][get_class($this)]))
+            trigger_error('Clone is not allowed for the class: ' . get_class($this), E_USER_ERROR);
+    }
+
+    /**
+     * Get singleton instance of the class
+     */
+    static function getInstance()
+    {
+        if(!isset($GLOBALS['bxDolClasses'][__CLASS__]))
+            $GLOBALS['bxDolClasses'][__CLASS__] = new BxDolAclQuery();
+
+        return $GLOBALS['bxDolClasses'][__CLASS__];
+    }
+
+    function getLevels($aParams, &$aItems, $bReturnCount = true)
+    {
+        $aMethod = array('name' => 'getAll', 'params' => array(0 => 'query'));
+        $sSelectClause = $sJoinClause = $sWhereClause = $sOrderClause = $sLimitClause = "";
+
+        if(!isset($aParams['order']) || empty($aParams['order']))
+           $sOrderClause = "ORDER BY `tal`.`Order` ASC";
+
+        switch($aParams['type']) {
+            case 'by_id':
+                $aMethod['name'] = 'getRow';
+                $aMethod['params'][1] = array(
+                	'id' => $aParams['value']
+                );
+
+                $sWhereClause .= "AND `tal`.`ID`=:id";
+                $sLimitClause .= "LIMIT 1";
+                break;
+
+            case 'all_active':
+                $sWhereClause .= "AND `tal`.`Active`='yes'";
+                break;
+
+            case 'all_active_purchasble_pair':
+                $aMethod['name'] = "getPairs";
+                $aMethod['params'][1] = 'id';
+                $aMethod['params'][2] = 'name';
+                $sWhereClause .= "AND `tal`.`Active`='yes' AND `tal`.`Purchasable`='yes'";
+                break;
+
+            case 'all_active_pair':
+                $aMethod['name'] = "getPairs";
+                $aMethod['params'][1] = 'id';
+                $aMethod['params'][2] = 'name';
+                $sWhereClause .= "AND `tal`.`Active`='yes'";
+                break;
+
+            case 'all_active_not_automatic_pair':
+                $aMethod['name'] = "getPairs";
+                $aMethod['params'][1] = 'id';
+                $aMethod['params'][2] = 'name';
+                $sWhereClause .= "AND `tal`.`Active`='yes' AND `tal`.`ID` NOT IN (" . $this->implode_escape(array(
+                    MEMBERSHIP_ID_NON_MEMBER,
+                    MEMBERSHIP_ID_ACCOUNT,
+                    MEMBERSHIP_ID_UNCONFIRMED,
+                    MEMBERSHIP_ID_PENDING,
+                    MEMBERSHIP_ID_SUSPENDED
+                    
+                )) . ")";
+                break;
+
+            case 'all_pair':
+                $aMethod['name'] = "getPairs";
+                $aMethod['params'][1] = 'id';
+                $aMethod['params'][2] = 'name';
+                break;
+
+            case 'all_order_id':
+                $sOrderClause = "ORDER BY `tal`.`ID` ASC";
+                break;
+
+            case 'all':
+                break;
+        }
+
+        $aMethod['params'][0] = "SELECT " . ($bReturnCount ? "SQL_CALC_FOUND_ROWS" : "") . "
+                `tal`.`ID` AS `id`,
+                `tal`.`Name` AS `name`,
+                `tal`.`Icon` AS `icon`,
+                `tal`.`Description` AS `description`,
+                `tal`.`Active` AS `active`,
+                `tal`.`Purchasable` AS `purchasable`,
+                `tal`.`Removable` AS `removable`,
+                `tal`.`QuotaSize` AS `quota_size`,
+                `tal`.`QuotaNumber` AS `quota_number`,
+                `tal`.`QuotaMaxFileSize` AS `quota_max_file_size`,
+                `tal`.`Order` AS `order`" . $sSelectClause . "
+            FROM `sys_acl_levels` AS `tal` " . $sJoinClause . "
+            WHERE 1 " . $sWhereClause . " " . $sOrderClause . " " . $sLimitClause;
+        $aItems = call_user_func_array(array($this, $aMethod['name']), $aMethod['params']);
+
+        if(!$bReturnCount)
+            return !empty($aItems);
+
+        return (int)$this->getOne("SELECT FOUND_ROWS()");
+    }
+
+    function getActions($aParams, &$aItems, $bReturnCount = true)
+    {
+        $sMemoryKey = '';
+        $aMethod = array('name' => 'getAll', 'params' => array(0 => 'query'));
+        $sSelectClause = $sJoinClause = $sWhereClause = $sGroupClause = $sOrderClause = $sLimitClause = "";
+
+        if(!isset($aParams['order']) || empty($aParams['order']))
+           $sOrderClause = "ORDER BY `taa`.`Title` ASC";
+
+           
+        switch($aParams['type']) {
+            case 'by_names_and_module':
+            	$aMethod['params'][1] = array(
+                	'module' => $aParams['module']
+                );
+
+                $sWhereClause .= " AND `taa`.`Name` IN(" . $this->implode_escape($aParams['value']) . ") AND `taa`.`Module` = :module ";
+                $sMemoryKey = 'BxDolAclQuery::getActions' . $aParams['type'] . $aParams['module'] . $sWhereClause;
+                break;
+
+            case 'by_names':
+                $sWhereClause .= " AND `taa`.`Name` IN(" . $this->implode_escape($aParams['value']) . ")";
+                break;
+
+            case 'by_level_id':
+            	$aMethod['params'][1] = array(
+                	'level_id' => $aParams['value'],
+                	'level_code' => pow(2, ($aParams['value'] - 1))
+                );
+
+                $sSelectClause .= ", `tam`.`AllowedCount` AS `allowed_count`, `tam`.`AllowedPeriodLen` AS `allowed_period_len`, `tam`.`AllowedPeriodStart` AS `allowed_period_start`, `tam`.`AllowedPeriodEnd` AS `allowed_period_end`, `tam`.`AdditionalParamValue` AS `additional_param_value` ";
+                $sJoinClause .= "LEFT JOIN `sys_acl_matrix` AS `tam` ON `taa`.`ID`=`tam`.`IDAction` ";
+                $sWhereClause .= "AND `tam`.`IDLevel`=:level_id AND (`taa`.`DisabledForLevels`='0' OR `taa`.`DisabledForLevels`&:level_code=0)";
+                break;
+
+            case 'by_level_id_key_id':
+                $aMethod['name'] = 'getAllWithKey';
+                $aMethod['params'][1] = 'id';
+                $aMethod['params'][2] = array(
+                	'level_id' => $aParams['value']
+                );
+
+                $sSelectClause .= ", `tam`.`AllowedCount` AS `allowed_count`, `tam`.`AllowedPeriodLen` AS `allowed_period_len` ";
+                $sJoinClause .= "LEFT JOIN `sys_acl_matrix` AS `tam` ON `taa`.`ID`=`tam`.`IDAction` ";
+                $sWhereClause .= "AND `tam`.`IDLevel`=:level_id";
+                break;
+
+            case 'counter_by_modules':
+                $aMethod['name'] = 'getPairs';
+                $aMethod['params'][1] = 'module';
+                $aMethod['params'][2] = 'counter';
+                $sSelectClause = ", COUNT(*) AS `counter`";
+                $sGroupClause = "GROUP BY `taa`.`Module`";
+                break;
+
+            case 'counter_by_levels':
+                $aMethod['name'] = 'getPairs';
+                $aMethod['params'][1] = 'level_id';
+                $aMethod['params'][2] = 'counter';
+                $sSelectClause = ", `tam`.`IDLevel` AS `level_id`, COUNT(`tam`.`IDAction`) AS `counter`";
+                $sJoinClause = "LEFT JOIN `sys_acl_matrix` AS `tam` ON `taa`.`ID`=`tam`.`IDAction` AND (`taa`.`DisabledForLevels`='0' OR `taa`.`DisabledForLevels`&POW(2, `tam`.`IDLevel`-1)=0) ";
+                $sGroupClause = "GROUP BY `tam`.`IDLevel`";
+                break;
+        }
+
+        $aMethod['params'][0] = "SELECT " . ($bReturnCount ? "SQL_CALC_FOUND_ROWS" : "") . "
+                `taa`.`ID` AS `id`,
+                `taa`.`Module` AS `module`,
+                `taa`.`Name` AS `name`,
+                `taa`.`Title` AS `title`,
+                `taa`.`Countable` AS `countable`,
+                `taa`.`DisabledForLevels` AS `disabled_for_levels`" . $sSelectClause . "
+            FROM `sys_acl_actions` AS `taa` " . $sJoinClause . "
+            WHERE 1 " . $sWhereClause . " " . $sGroupClause . " " . $sOrderClause . " " . $sLimitClause;
+
+        if ($sMemoryKey) {
+            array_unshift($aMethod['params'], $sMemoryKey, $aMethod['name']);
+            $aItems = call_user_func_array(array($this, 'fromMemory'), $aMethod['params']);
+            return $bReturnCount ? count($aItems) : !empty($aItems);
+        }
+
+        $aItems = call_user_func_array(array($this, $aMethod['name']), $aMethod['params']);
+
+        if(!$bReturnCount)
+            return !empty($aItems);
+
+        return (int)$this->getOne("SELECT FOUND_ROWS()");
+    }
+
+    /**
+     * Fetch the last purchased/assigned membership that is still active for the given profile.
+     *
+     * NOTE. Don't use cache here, because it's causing an error, if a number of memberrship levels are purchased at the same time.
+     * fromMemory returns the same DateExpires because setMembership (old buyMembership) function is called in cycle in the same session.
+     */
+    function getLevelCurrent($iProfileId, $iTime = 0)
+    {
+        $iTime = $iTime == 0 ? time() : (int)$iTime;
+
+        $sSql = $this->prepare("
+            SELECT  `sys_acl_levels_members`.`IDLevel` as `id`,
+                    `sys_acl_levels`.`Name` AS `name`,
+                    `sys_acl_levels`.`QuotaSize` AS `quota_size`,
+                    `sys_acl_levels`.`QuotaNumber` AS `quota_number`,
+                    `sys_acl_levels`.`QuotaMaxFileSize` AS `quota_max_file_size`,
+                    UNIX_TIMESTAMP(`sys_acl_levels_members`.`DateStarts`) as `date_starts`,
+                    UNIX_TIMESTAMP(`sys_acl_levels_members`.`DateExpires`) as `date_expires`,
+                    `sys_acl_levels_members`.`TransactionID` AS `transaction_id`,
+                    `sys_profiles`.`status`
+            FROM `sys_acl_levels_members`
+            RIGHT JOIN `sys_profiles` ON `sys_acl_levels_members`.IDMember = `sys_profiles`.`id`
+                AND (`sys_acl_levels_members`.DateStarts IS NULL OR `sys_acl_levels_members`.DateStarts <= FROM_UNIXTIME(?))
+                AND (`sys_acl_levels_members`.DateExpires IS NULL OR `sys_acl_levels_members`.DateExpires > FROM_UNIXTIME(?))
+            LEFT JOIN `sys_acl_levels` ON `sys_acl_levels_members`.IDLevel = `sys_acl_levels`.ID
+            WHERE `sys_profiles`.`id` = ?
+            ORDER BY `sys_acl_levels_members`.DateStarts DESC
+            LIMIT 1", $iTime, $iTime, $iProfileId);
+
+        return $this->fromMemory('BxDolAclQuery::getLevelCurrent' . $iProfileId . $iTime, 'getRow', $sSql);
+    }
+
+    function getLevelByIdCached($iLevel)
+    {
+        $sQuery = $this->prepare("SELECT
+                `tal`.`ID` AS `id`,
+                `tal`.`Name` AS `name`,
+                `tal`.`QuotaSize` AS `quota_size`,
+                `tal`.`QuotaNumber` AS `quota_number`,
+                `tal`.`QuotaMaxFileSize` AS `quota_max_file_size`
+            FROM `sys_acl_levels` AS `tal`
+            WHERE `tal`.`ID`=?
+            LIMIT 1", $iLevel);
+        return $this->fromCache('sys_acl_levels' . $iLevel, 'getRow', $sQuery);
+    }
+
+    function getAction($iMembershipId, $iActionId)
+    {
+        $sQuery = $this->prepare("SELECT
+                `tam`.`IDAction` AS `id`,
+                `taa`.`Name` AS `name`,
+                `taa`.`Title` AS `title`,
+                `tam`.`AllowedCount` AS `allowed_count`,
+                `tam`.`AllowedPeriodLen` AS `allowed_period_len`,
+                UNIX_TIMESTAMP(`tam`.`AllowedPeriodStart`) as `allowed_period_start`,
+                UNIX_TIMESTAMP(`tam`.`AllowedPeriodEnd`) as `allowed_period_end`,
+                `tam`.`AdditionalParamValue` AS `additional_param_value`
+            FROM `sys_acl_actions` AS `taa`
+            LEFT JOIN `sys_acl_matrix` AS `tam` ON `tam`.`IDAction` = `taa`.`ID` AND `tam`.`IDLevel` = ?
+            WHERE `taa`.`ID` = ?", $iMembershipId, $iActionId);
+        return $this->fromMemory('BxDolAclQuery::getAction' . $iMembershipId . $iActionId, 'getRow', $sQuery);
+    }
+
+    function getActionTrack($iActionId, $iProfileId)
+    {
+        $sQuery = $this->prepare("SELECT
+                `taat`.`ActionsLeft` AS `actions_left`,
+                UNIX_TIMESTAMP(`taat`.`ValidSince`) as `valid_since`
+            FROM `sys_acl_actions_track` AS `taat`
+            WHERE `taat`.`IDAction`=? AND `taat`.`IDMember`=?", $iActionId, $iProfileId);
+        return $this->getRow($sQuery);
+    }
+
+    function insertActionTarck($iActionId, $iProfileId, $iActionsLeft, $iValidSince)
+    {
+        $sQuery = $this->prepare("INSERT INTO `sys_acl_actions_track`(`IDAction`, `IDMember`, `ActionsLeft`, `ValidSince`) VALUES (?, ?, ?, FROM_UNIXTIME(?))", $iActionId, $iProfileId, $iActionsLeft, $iValidSince);
+        return (int)$this->query($sQuery) > 0;
+    }
+
+    function updateActionTrack($iActionId, $iProfileId, $iActionsLeft, $iValidSince = 0)
+    {
+    	$aBindings = array(
+    		'actions_left' => $iActionsLeft,
+    		'action_id' => $iActionId,
+    		'member_id' => $iProfileId
+    	);
+
+        $sUpdateAddon = "";
+        if($iValidSince != 0) {
+        	$aBindings['valid_since'] = $iValidSince;
+
+            $sUpdateAddon = ", ValidSince=FROM_UNIXTIME(:valid_since)";
+        }
+
+        $sQuery = "UPDATE `sys_acl_actions_track` SET `ActionsLeft`=:actions_left" . $sUpdateAddon . " WHERE `IDAction`=:action_id AND `IDMember`=:member_id";
+        return (int)$this->query($sQuery, $aBindings) > 0;
+    }
+
+    function insertLevelByProfileId($iProfileId, $iMembershipId, $iDateStarts, $aPeriod, $sTransactionId)
+    {
+    	$aBindings = array(
+            'member_id' => $iProfileId,
+            'level_id' => $iMembershipId,
+            'transaction_id' => $sTransactionId,
+            'date_starts' => $iDateStarts
+        );
+
+    	$sSetClause = '';
+    	if((int)$aPeriod['period'] != 0) {
+            $aBindings['period'] = (int)$aPeriod['period'];
+
+            switch($aPeriod['period_unit']) {
+                case MEMBERSHIP_PERIOD_UNIT_DAY:
+                case MEMBERSHIP_PERIOD_UNIT_WEEK:
+                    if($aPeriod['period_unit'] == MEMBERSHIP_PERIOD_UNIT_WEEK)
+                        $aBindings['period'] *= 7;
+
+                    $sSetClause = "DATE_ADD(FROM_UNIXTIME(:date_starts), INTERVAL :period DAY)";
+                    break;
+
+                case MEMBERSHIP_PERIOD_UNIT_MONTH:
+                    $sSetClause = "DATE_ADD(FROM_UNIXTIME(:date_starts), INTERVAL :period MONTH)";
+                    break;
+
+                case MEMBERSHIP_PERIOD_UNIT_YEAR:
+                    $sSetClause = "DATE_ADD(FROM_UNIXTIME(:date_starts), INTERVAL :period YEAR)";
+                    break;
+            }
+
+            if(!empty($sSetClause) && !empty($aPeriod['period_reserve'])) {
+                $aBindings['reserve'] = (int)$aPeriod['period_reserve'];
+
+                $sSetClause = "DATE_ADD(" . $sSetClause . ", INTERVAL :reserve DAY)";
+            }
+
+            if(!empty($sSetClause))
+                $sSetClause = ", `DateExpires`=" . $sSetClause;
+    	}
+
+        $sQuery = $this->prepare("INSERT `sys_acl_levels_members` SET `IDMember`=:member_id, `IDLevel`=:level_id, `DateStarts`=FROM_UNIXTIME(:date_starts), `TransactionID`=:transaction_id" . $sSetClause);
+        return (int)$this->query($sQuery, $aBindings) > 0;
+    }
+
+    function deleteLevelByProfileId($iProfileId, $bAll = false)
+    {
+        $sQuery = $this->prepare("DELETE FROM `sys_acl_levels_members` WHERE `IDMember` = ? " . ($bAll ? "" : " AND (`DateExpires` IS NULL OR `DateExpires` > NOW())"), $iProfileId);
+        return (int)$this->query($sQuery) > 0;
+    }
+
+	function deleteLevelBy($aWhere)
+    {
+    	if(empty($aWhere))
+    		return false;
+
+        $sQuery = "DELETE FROM `sys_acl_levels_members` WHERE " . $this->arrayToSQL($aWhere, ' AND ');
+        return (int)$this->query($sQuery) > 0;
+    }
+
+    function maintenance($iDaysToCleanMemLevels = 0)
+    {
+        $sQuery = $this->prepare("DELETE FROM `sys_acl_levels_members` WHERE `DateExpires` < NOW() - INTERVAL ? DAY", $iDaysToCleanMemLevels);
+        if ($iDeleteMemLevels = $this->query($sQuery))
+            $this->query("OPTIMIZE TABLE `sys_acl_levels_members`");
+        return $iDeleteMemLevels;
+    }
+
+    function clearActionsTracksForMember($iMemberId)
+    {
+        $sQuery = $this->prepare("DELETE FROM `sys_acl_actions_track` WHERE `IDMember` = ?", (int)$iMemberId);
+	    return $this->query($sQuery);
+    }
+
+	function getContentByLevelAsSQLPart($sContentTable, $sContentField, $mixedLevelId)
+    {
+    	$sJoin = $sWhere = ""; 
+        $iLevelId = !is_array($mixedLevelId) ? $mixedLevelId : 0;
+        if (!$iLevelId && is_array($mixedLevelId) && 1 == count($mixedLevelId)) {
+            $a = array_values($mixedLevelId);
+            $iLevelId = array_shift($a);
+        }
+
+        // unconfirmed
+        if (MEMBERSHIP_ID_UNCONFIRMED == $iLevelId) {
+            return array(
+                'where' => " AND `sys_accounts`.`email_confirmed` = 0 ",
+                'join' => '',
+            );
+        }
+        // standard
+        elseif (MEMBERSHIP_ID_STANDARD == $iLevelId) {
+
+            $sWhere .= " AND (`tlm`.`DateStarts` IS NULL OR `tlm`.`DateStarts` <= NOW()) AND (`tlm`.`DateExpires` IS NULL OR `tlm`.`DateExpires` > NOW()) AND `tlm`.`IDMember` IS NULL AND `sys_accounts`.`email_confirmed` != 0 ";
+
+            $sJoin .= " LEFT JOIN `sys_acl_levels_members` AS `tlm` ON `" . $sContentTable . "`.`" . $sContentField . "`=`tlm`.`IDMember`";
+
+            return array(
+                'where' => $sWhere,
+                'join' => $sJoin
+            );
+        }
+        // other levels
+        else {
+
+            if(is_array($mixedLevelId))
+                $sWhere .= " AND `tlm`.`IDLevel` IN (" . $this->implode_escape($mixedLevelId) . ")";
+            else
+                $sWhere .= $this->prepareAsString(" AND `tlm`.`IDLevel` = ?", (int)$mixedLevelId);
+
+            $sWhere .= " AND (`tlm`.`DateStarts` IS NULL OR `tlm`.`DateStarts` <= NOW()) AND (`tlm`.`DateExpires` IS NULL OR `tlm`.`DateExpires` > NOW()) AND `sys_accounts`.`email_confirmed` != 0 ";
+
+            $sJoin .= " INNER JOIN `sys_acl_levels_members` AS `tlm` ON `" . $sContentTable . "`.`" . $sContentField . "`=`tlm`.`IDMember`";
+
+            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); 
+    }
+}
+
+/** @} */

+ 177 - 0
upgrade/files/11.0.0-11.0.1/files/inc/classes/BxDolBadgesQuery.php

@@ -0,0 +1,177 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaCore UNA Core
+ * @{
+ */
+
+/**
+ * @see BxDolCategories
+ */
+class BxDolBadgesQuery extends BxDolDb
+{
+    protected $_sTableCategories;
+
+    public function __construct()
+    {
+    	parent::__construct();
+
+        $this->_sTableBadges = 'sys_badges';
+    }
+
+    public function getData($aParams = array(), &$aItems = false)
+    {
+        $aMethod = array('name' => 'getAll', 'params' => array(0 => 'query'));
+
+        $sSelectClause = "`sc`.*";
+        $sJoinClause = $sWhereClause = $sGroupClause = $sLimitClause = "";
+        $sOrderClause = "`sc`.`added` ASC";
+
+        if(isset($aParams['count_only']) && $aParams['count_only'] === true) {
+            $aMethod['name'] = 'getOne';
+            $sSelectClause = "COUNT(`sc`.`id`)";
+        }
+
+        switch($aParams['type']) {
+            case 'counter_by_modules':
+                $aMethod['name'] = 'getPairs';
+                $aMethod['params'][1] = 'module';
+                $aMethod['params'][2] = 'counter';
+                $sSelectClause = "`sc`.`module` AS `module`, COUNT(`sc`.`id`) AS `counter`";
+                $sJoinClause = "LEFT JOIN `sys_badges2objects` `soc` ON `sc`.`id` =  `soc`.`badge_id`";
+                $sGroupClause = "`sc`.`module`";
+                break;
+              
+            case 'by_module&object':
+                $aMethod['name'] = 'getAll';
+                $sSelectClause = "`sc`.`id`, `sc`.`id`, `sc`.`text`, `sc`.`color`, `sc`.`is_icon_only`, `sc`.`icon`, `soc`.`id` AS `badge_id`";
+                $sJoinClause = "LEFT JOIN `sys_badges2objects` `soc` ON `sc`.`id` =  `soc`.`badge_id` AND `soc`.`object_id` = :object_id";
+                $sWhereClause = " AND `sc`.`module` = :module";
+                $aMethod['params'][1] = array(
+                    'object_id' => $aParams['object_id'],
+                    'module' => $aParams['module']
+                );
+                break;
+                
+            case 'by_module&object2':
+                $aMethod['name'] = 'getAll';
+                $sSelectClause = "`sc`.`id`, `sc`.`id`, `sc`.`text`, `sc`.`color`, `sc`.`is_icon_only`, `sc`.`icon`, `soc`.`id` AS `badge_id`";
+                $sJoinClause = "INNER JOIN `sys_badges2objects` `soc` ON `sc`.`id` =  `soc`.`badge_id` AND `soc`.`object_id` = :object_id";
+                $sWhereClause = " AND `sc`.`module` = :module";
+                $aMethod['params'][1] = array(
+                    'object_id' => $aParams['object_id'],
+                    'module' => $aParams['module']
+                );
+                break;
+                
+            case 'by_module&object2_single':
+                $aMethod['name'] = 'getAll';
+                $sSelectClause = "`sc`.`id`, `sc`.`id`, `sc`.`text`, `sc`.`color`, `sc`.`is_icon_only`, `sc`.`icon`, `soc`.`id` AS `badge_id`";
+                $sJoinClause = "INNER JOIN `sys_badges2objects` `soc` ON `sc`.`id` =  `soc`.`badge_id` AND `soc`.`object_id` = :object_id";
+                $sWhereClause = " AND `sc`.`module` = :module";
+                $sLimitClause = "LIMIT 0, 1";
+                $aMethod['params'][1] = array(
+                    'object_id' => $aParams['object_id'],
+                    'module' => $aParams['module']
+                );
+                break;
+                
+            case 'by_module&object&badge':
+                $aMethod['name'] = 'getAll';
+                $sSelectClause = "`sc`.`id`, `sc`.`id`, `sc`.`text`, `sc`.`icon`, `soc`.`id` AS `badge_id`";
+                $sJoinClause = "INNER JOIN `sys_badges2objects` `soc` ON `sc`.`id` =  `soc`.`badge_id` AND `soc`.`object_id` = :object_id";
+                $sWhereClause = " AND `sc`.`module` = :module  AND `sc`.`id` = :badge_id";
+                $aMethod['params'][1] = array(
+                    'object_id' => $aParams['object_id'],
+                    'badge_id' => $aParams['badge_id'],
+                    'module' => $aParams['module']
+                );
+                break;
+                
+            case 'id':
+            	$aMethod['name'] = 'getRow';
+            	$aMethod['params'][1] = array(
+                    'id' => $aParams['id']
+                );
+
+                $sWhereClause = " AND `sc`.`id` = :id";
+                break;
+            
+            case 'by_module':
+                $aMethod['name'] = 'getAll';
+                $sWhereClause = " AND `sc`.`module` = :module";
+                $aMethod['params'][1] = array(
+                    'module' => $aParams['module']
+                );
+                break;
+        }
+
+        if(!empty($sGroupClause))
+            $sGroupClause = "GROUP BY " . $sGroupClause;
+
+        if(!empty($sOrderClause))
+            $sOrderClause = "ORDER BY " . $sOrderClause;
+
+        $aMethod['params'][0] = "SELECT " . $sSelectClause . " 
+            FROM `" . $this->_sTableBadges . "` AS `sc`" . $sJoinClause . " 
+            WHERE 1" . $sWhereClause . " " . $sGroupClause . " " . $sOrderClause. " " . $sLimitClause;
+        $oRv = call_user_func_array(array($this, $aMethod['name']), $aMethod['params']);
+        if ($aItems === false)
+            return $oRv;
+        $aItems = $oRv;   
+    }
+    
+    function update($iId, $aFields)
+    {
+        $sSql = "UPDATE `sys_badges` SET `" . implode("`=?, `", array_keys($aFields)) . "`=?  WHERE `id`=?";
+        $sSql = call_user_func_array(array($this, 'prepare'), array_merge(array($sSql), array_values($aFields), array($iId)));
+        return $this->query($sSql);
+    }
+    
+    function add($iBadgeId, $iObjectId, $sModule)
+    {
+        $sSql = "INSERT `sys_badges2objects` (`badge_id`, `object_id`, `added`, `module`) VALUES (:badge_id, :object_id, :added, :module)";
+        $aBindings = array(
+            'badge_id' => $iBadgeId,
+            'object_id' => $iObjectId,
+            'added' => time(),
+            'module' => $sModule,
+        );
+        return $this->query($sSql, $aBindings);
+    }
+    
+    public function delete($aParams = array())
+    {
+        switch($aParams['type']) {
+            case 'id':
+                $sQuery = " WHERE `badge_id` = :badge_id";
+                $aBindings = array(
+                    'badge_id' => $aParams['id'],
+                );
+                break;
+                
+            case 'by_module&object':
+                $sQuery = " WHERE `module` = :module AND object_id = :object_id";
+                $aBindings = array(
+                    'module' => $aParams['module'],
+                    'object_id' => $aParams['object_id'],
+                );
+                break;
+            case 'by_module&object&badge':   
+                $sQuery = " WHERE `module` = :module AND object_id = :object_id AND badge_id = :badge_id";
+                $aBindings = array(
+                    'module' => $aParams['module'],
+                    'object_id' => $aParams['object_id'],
+                    'badge_id' => $aParams['badge_id'],
+                );
+                break;
+        }
+        
+        $sQuery = "DELETE FROM `sys_badges2objects`" . $sQuery;
+        $this->query($sQuery, $aBindings);
+    }
+}
+
+/** @} */

+ 309 - 0
upgrade/files/11.0.0-11.0.1/files/inc/classes/BxDolContentInfo.php

@@ -0,0 +1,309 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaCore UNA Core
+ * @{
+ */
+
+/**
+ * Get content info from Content modules
+ *
+ * Add record to sys_objects_content_info table to provide unified access to module's content,
+ * to get content info just create an instance of this class and request content info by id,
+ * for example:
+ * @code
+ *  BxDolContentInfo::getObjectInstance('my_system')->getInfo(25); // 25 - is object id
+ * @endcode
+ *
+ * Description of sys_objects_content_info table fields:
+ * @code
+ *  `name` - system name, it is better to use unique module prefix here, lowercase and all spaces are underscored
+ *  `alert_unit` - unit name of the alert which will be fired 
+ *  `alert_action_add` - action name of the alert which is fired after a content was added
+ *  `alert_action_update` - action name of the alert which is fired after a content was updated
+ *  `alert_action_delete` - action name of the alert which is fired after a content was deleted
+ *  `class_name` - your custom class name, if you overrride default class
+ *  `class_file` - your custom class path
+ * @endcode
+ *
+ */
+class BxDolContentInfo extends BxDolFactory implements iBxDolFactoryObject
+{
+    protected $_oQuery;
+
+    protected $_sSystem;
+    protected $_aSystem;
+    protected $_aGrid;
+    protected $_bIgnoreCacheInServiceCalls = false;
+
+    protected function __construct($sSystem)
+    {
+        parent::__construct();
+
+        if(empty($sSystem))
+            return;
+
+        $aSystems = $this->getSystems();
+        if(!isset($aSystems[$sSystem]))
+            return;
+
+        $this->_sSystem = $sSystem;
+        $this->_aSystem = $aSystems[$sSystem];
+        $this->_aGrid = array();
+    }
+
+   /**
+     * get content info object instance
+     * @param $sSystem view object name
+     * @return null on error, or ready to use class instance
+     */
+    public static function getObjectInstance($sSystem)
+    {
+        $sClassKey = 'BxDolContentInfo!' . $sSystem;
+
+        if(isset($GLOBALS['bxDolClasses'][$sClassKey]))
+            return $GLOBALS['bxDolClasses'][$sClassKey];
+
+        $aSystems = self::getSystems();
+        if(!isset($aSystems[$sSystem]))
+            return null;
+
+        $sClassName = 'BxDolContentInfo';
+        if(!empty($aSystems[$sSystem]['class_name'])) {
+            $sClassName = $aSystems[$sSystem]['class_name'];
+            if(!empty($aSystems[$sSystem]['class_file']))
+                require_once(BX_DIRECTORY_PATH_ROOT . $aSystems[$sSystem]['class_file']);
+        }
+
+        $GLOBALS['bxDolClasses'][$sClassKey] = new $sClassName($sSystem);
+        return $GLOBALS['bxDolClasses'][$sClassKey];
+    }
+
+    /**
+     * get content info object instance (for internal usage). 
+     * @see self::getObjectInstanceByAlertAdd, self::getObjectInstanceByAlertUpdate and self::getObjectInstanceByAlertDelete
+     * @param $sAlertType alert type (add, update, delete)
+     * @param $sUnit alert unit
+     * @param $sAction alert action
+     * @return null on error, or ready to use class instance
+     */
+    public static function getObjectInstanceByAlertCommon($sAlertType, $sUnit, $sAction)
+    {
+        $sAlert = $sUnit . '_' . $sAction;
+        $sClassKey = 'BxDolContentInfo!' . bx_gen_method_name($sAlertType . '_' . $sAlert);
+
+        if(isset($GLOBALS['bxDolClasses'][$sClassKey]))
+            return $GLOBALS['bxDolClasses'][$sClassKey];
+
+        $aSystems = self::getSystemsByAlertType($sAlertType);
+        if(!isset($aSystems[$sAlert]))
+            return null;
+
+        $sClassName = 'BxDolContentInfo';
+        if(!empty($aSystems[$sAlert]['class_name'])) {
+            $sClassName = $aSystems[$sAlert]['class_name'];
+            if(!empty($aSystems[$sAlert]['class_file']))
+                require_once(BX_DIRECTORY_PATH_ROOT . $aSystems[$sAlert]['class_file']);
+        }
+
+        $GLOBALS['bxDolClasses'][$sClassKey] = new $sClassName($aSystems[$sAlert]['name']);
+        return $GLOBALS['bxDolClasses'][$sClassKey];
+    }
+
+    public static function getObjectInstanceByAlertAdd($sUnit, $sAction)
+    {
+        return self::getObjectInstanceByAlertCommon('add', $sUnit, $sAction);
+    }
+
+    public static function getObjectInstanceByAlertUpdate($sUnit, $sAction)
+    {
+        return self::getObjectInstanceByAlertCommon('update', $sUnit, $sAction);
+    }
+
+    public static function getObjectInstanceByAlertDelete($sUnit, $sAction)
+    {
+        return self::getObjectInstanceByAlertCommon('delete', $sUnit, $sAction);
+    }
+
+	/**
+     * get content info object instance (for internal usage). 
+     * @see self::getObjectInstanceByAlertAdd, self::getObjectInstanceByAlertUpdate and self::getObjectInstanceByAlertDelete
+     * @param $sAlertType alert type (add, update, delete)
+     * @param $sUnit alert unit
+     * @param $sAction alert action
+     * @return null on error, or ready to use class instance
+     */
+    public static function getObjectInstanceByGrid($sGrid)
+    {
+        $aGrids = self::getGrids();
+        if(!isset($aGrids[$sGrid]))
+            return null;
+
+        $oObject = self::getObjectInstance($aGrids[$sGrid]['object']);
+        $oObject->setGrid($aGrids[$sGrid]);
+
+        return $oObject;
+    }
+
+    public static function &getSystems()
+    {
+        $sKey = 'bx_dol_cache_memory_content_info_systems';
+
+        if(!isset($GLOBALS[$sKey]))
+            $GLOBALS[$sKey] = BxDolDb::getInstance()->fromCache('sys_objects_content_info', 'getAllWithKey', '
+                SELECT
+                    `id` as `id`,
+                    `name` AS `name`,
+                    `title` AS `title`,
+                    `alert_unit` AS `alert_unit`,
+                    `alert_action_add` AS `alert_action_add`,
+                    `alert_action_update` AS `alert_action_update`,
+                    `alert_action_delete` AS `alert_action_delete`,
+                    `class_name` AS `class_name`,
+                    `class_file` AS `class_file`
+                FROM `sys_objects_content_info`', 'name');
+
+        return $GLOBALS[$sKey];
+    }
+
+    public static function &getGrids()
+    {
+        $sKey = 'bx_dol_cache_memory_content_info_grids';
+
+        if(!isset($GLOBALS[$sKey]))
+            $GLOBALS[$sKey] = BxDolDb::getInstance()->fromCache('sys_objects_content_info_grids', 'getAllWithKey', '
+                SELECT
+                    `id` as `id`,
+                    `object` AS `object`,
+                    `grid_object` AS `grid_object`,
+                    `grid_field_id` AS `grid_field_id`,
+                    `condition` AS `condition`,
+                    `selection` AS `selection`
+                FROM `sys_content_info_grids`', 'grid_object');
+
+        return $GLOBALS[$sKey];
+    }
+
+    public static function &getSystemsByAlertType($sAlertType)
+    {
+        $sKey = 'bx_dol_cache_memory_content_info_systems_' . $sAlertType;
+
+        if(!isset($GLOBALS[$sKey])) {
+            $aSystems = BxDolContentInfo::getSystems();
+            foreach($aSystems as $aSystem)
+                $GLOBALS[$sKey][$aSystem['alert_unit'] . '_' . $aSystem['alert_action_' . $sAlertType]] = $aSystem;
+        }
+
+        return $GLOBALS[$sKey];
+    }
+
+    public static function formatFields($aContentInfo)
+    {
+        foreach ($aContentInfo as $sKey => $mixedValue)
+            if(is_numeric($mixedValue)) {
+                if(strpos($mixedValue, '.') !== false)
+                    $aContentInfo[$sKey] = (float)$mixedValue;
+                else 
+                    $aContentInfo[$sKey] = (int)$mixedValue;
+            }
+
+        return $aContentInfo;
+    }
+
+    public function setGrid($aGrid)
+    {
+        $this->_aGrid = $aGrid;
+    }
+
+    public function setIgnoreCacheInServiceCalls($b)
+    {
+        $this->_bIgnoreCacheInServiceCalls = $b;
+    }
+
+    public function getName()
+    {
+        return $this->_sSystem;
+    }
+
+    public function getTitle()
+    {
+        return $this->_aSystem['title'];
+    }
+
+    public function getGrid()
+    {
+        return $this->_aGrid;
+    }
+
+    public function getContentAuthor ($iContentId)
+    {
+        return $this->_call('get_author', $iContentId);
+    }
+
+    public function getContentDateAdded ($iContentId)
+    {
+        return $this->_call('get_date_added', $iContentId);
+    }
+
+    public function getContentDateChanged ($iContentId)
+    {
+        return $this->_call('get_date_changed', $iContentId);
+    }
+
+    public function getContentTitle ($iContentId)
+    {
+        return $this->_call('get_title', $iContentId);
+    }
+
+    public function getContentThumb ($iContentId)
+    {
+        return $this->_call('get_thumb', $iContentId);
+    }
+
+    public function getContentLink ($iContentId)
+    {
+        return $this->_call('get_link', $iContentId);
+    }
+
+    public function getContentText ($iContentId)
+    {
+        return $this->_call('get_text', $iContentId);
+    }
+
+    public function getContentInfo ($iContentId, $bSearchableFieldsOnly = true)
+    {
+        return $this->_call('get_info', $iContentId, $bSearchableFieldsOnly);
+    }
+
+    public function getContentSearchResultUnit ($iContentId, $sUnitTemplate = '')
+    {
+        return $this->_call('get_search_result_unit', $iContentId, $sUnitTemplate);
+    }
+
+    public function getAll ($aParams = array())
+    {
+        return $this->_call('get_all', $aParams);
+    }
+
+    public function getSearchableFieldsExtended ()
+    {
+        return $this->_call('get_searchable_fields_extended');
+    }
+
+    public function getSearchResultExtended ($aParams, $iStart = 0, $iPerPage = 0)
+    {
+        return $this->_call('get_search_result_extended', $aParams, $iStart, $iPerPage);
+    }
+
+    protected function _call($sMethod)
+    {
+        if(!BxDolRequest::serviceExists($this->_sSystem, $sMethod))
+            return false;
+
+        return BxDolService::call($this->_sSystem, $sMethod, array_slice(func_get_args(), 1), 'Module', $this->_bIgnoreCacheInServiceCalls);
+    }
+}
+
+/** @} */

+ 1807 - 0
upgrade/files/11.0.0-11.0.1/files/inc/classes/BxDolForm.php

@@ -0,0 +1,1807 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaCore UNA Core
+ * @{
+ */
+
+define('BX_DOL_FORM_METHOD_GET', 'get');
+define('BX_DOL_FORM_METHOD_POST', 'post');
+define('BX_DOL_FORM_METHOD_SPECIFIC', 'specific');
+define('BX_DOL_FORM_METHOD_DEFAULT', BX_DOL_FORM_METHOD_POST);
+
+define('BX_DATA_LISTS_KEY_PREFIX', '#!');
+define('BX_DATA_VALUES_DEFAULT', 'LKey'); ///< Use default values for data items, @see BxDolForm::getDataItems
+define('BX_DATA_VALUES_ADDITIONAL', 'LKey2'); ///< Use additional values for data items, @see BxDolForm::getDataItems
+define('BX_DATA_VALUES_ALL', 'all'); ///< Return all available info for data items, @see BxDolForm::getDataItems
+
+/**
+ * Forms allows to display forms from data stored in DB tables, before it was possible to display forms from PHP arrays only.
+ *
+ *
+ * The Form Objects have the following main advantages:
+ * - Minimal coding is needed to create different forms
+ * - Easy forms alterations
+ * - Multiple representations of the same form
+ * - Automated data inserting into database
+ * - Automated data updating
+ * - Automated data checking
+ * - Automatic security checking
+ * - Automatic spam filter
+ *
+ *
+ * Forms API uses several definitions:
+ * - Form or Form Object - record in sys_objects_form table, or instance of Form class.
+ * - Form Display - set of some form inputs in particular order, displayed for some purpose; one form can have several displays, for example add and edit displays.
+ * - Form Input or Form Field - form input field, like textarea, checkbox or set of radio buttons.
+ *
+ *
+ * @section form_creating_object Creating the Form Object
+ *
+ * 1. Create Form Object, add record to sys_objects_form table:
+ * - object - name of the Form Object, in the format: vendor prefix, underscore, module prefix, underscore, internal identifier or nothing; for example: bx_group - for group data processing, like group adding or editing.
+ * - title - Form Object title to display in studio forms builder.
+ * - action - url to submit form to, if url is not full and not empty, then site url is added automatically.
+ * - form_attrs - serialized array of additional form attributes.
+ * - submit_name - name of form field with submit button, it is used to determine if form is submitted.
+ * - table - DB table name (for automatic saving/updating).
+ * - key - DB table field with unique ID (for automatic updating).
+ * - uri - DB table field with URI (for automatic URI generation, aling with uri_title).
+ * - uri_title - DB table field with data title (for automatic URI generation, aling with uri).
+ * - params - serialized array of additional form parameters:
+ * - checker_helper - name of custom BxDolFormCheckerHelper class.
+ * - csrf - array of Cross-site request forgery attack prevention parameters, for now only one boolean parameter is supported - disabled, so it can be disabled for particular form.
+ * - active - 1 or 0, if form is inactive then it can not be used anywhere.
+ * - override_class_name - user defined class name which is derived from BxTemplFormView.
+ * - override_class_file - the location of the user defined class, leave it empty if class is located in system folders.
+ *
+ *
+ * 2. Create Form Displays, add records to sys_form_displays table:
+ * - display_name - name of the Form Display, in the format:
+ *                  form object name, underscore, internal identifier or nothing;
+ *                  for example: bx_group_add - for displaying group adding form, bx_group_edit - for displaying group editing form
+ * - module - module name this display belongs to, it must be associated with name field in sys_modules table.
+ * - object - form object name from sys_objects_form table this Form Display belongs to.
+ * - title - Form Display title to display in studio forms builder.
+ * - view_mode - display form as read-only.
+ *
+ *
+ * 3. Create Form Fields, add records to sys_form_inputs table:
+ * - object - form object name from sys_objects_form table this Form Field belongs to.
+ * - module - module name this field belongs to.
+ * - name - unique Form Field name in particular From Object.
+ * - value - default value, or empty if there is no default value.
+ * - values - possible values, for certain form field types.
+ * - checked - 0 or 1, it determines if form field is checked, for certain form field types.
+ * - type - form field type, for now the following types are supported:
+ *      - text - text input field.
+ *      - password - password input field.
+ *      - textarea - multiline input field.
+ *      - number - number input field.
+ *      - select - select one from all available values.
+ *      - select_multiple - select one, multiple or all items from all available values.
+ *      - switcher - on/off switcher.
+ *      - checkbox - one checkbox.
+ *      - checkbox_set - set of checkboxes.
+ *      - radio_set - set of radio buttons.
+ *      - slider - select some numeric value within the range using slider control.
+ *      - doublerange - select range values within the range using slider control.
+ *      - datepicker - date selection control.
+ *      - datetime - date/time selection control.
+ *      - captcha - image captcha.
+ *      - hidden - hidden input field.
+ *      - file - file upload input.
+ *      - button - button control.
+ *      - image - form image button.
+ *      - reset - form reset button.
+ *      - submit - form submit button.
+ *      - value - just displaying value without any crontol.
+ *      - block_header - start group of field.
+ *      - custom - custom control.
+ *      - input_set - set of other form controls.
+ *      detailed description of every type will be described below.
+ * - caption - input title.
+ * - info - some info to help user to input data into the field, it's better to specify format and limits here.
+ * - required - indicate that the input is required by displaying asterisk near the field,
+ *              please note that this field don't perform any checking automatically,
+ *              since you mark field as required you need to specify checked function which will check entered value.
+ * - collapsed - display section as collapsed by default, for block_header field type only.
+ * - html - display visual editor of certain type, for textarea field type only.
+ *      - 0 - no visual editor, leave textarea field as it is.
+ *      - 1 - standard(default) visual editor, @see BxDolEditor.
+ *      - 2 - full visual editor, @see BxDolEditor.
+ *      - 3 - mini visual editor, @see BxDolEditor.
+ * - attrs - serialized array of additional input attributes.
+ * - attrs_tr - serialized array of additional attributes for the whole input row.
+ * - attrs_wrapper - serialized array of additional attributes for input wrapper.
+ * - checker_func - checked function, if you marked field as required in textarea field you need to point one of the following checked functions:
+ *      - Length - check value length, additional params must contain min and/or max values for checking.
+ *      - Date - check if date is entered correctly.
+ *      - DateTime - check if datetime is entered correctly.
+ *      - Preg - check value with provided regular expression in checker_params field.
+ *      - Avail - just check if value isn't 0 or empty string, additional function parameters are not used.
+ *      - Email - check if value is written in valid email format.
+ *      - Captcha - check if captcha is entered correctly, for captcha field type only.
+ *      You can inherit BxDolFormCheckerHelper class and add own checker functions, you will need to point your custom class in Form Object params array.
+ * - checker_params - serialized array of checker_func parameters.
+ * - checker_error - error message to show in case of checking function returns false.
+ * - db_pass - function to pass value through before saving to database and after restoring from database (for example when date need to be converted from/to timestamp value),
+ *              available values are the following:
+ *      - Int - convert value to integer.
+ *      - Float - convert value to floating point number.
+ *      - Date,DateUtc - convert value to timestamp value before saving to database, and convert from timespamp value after restoring from database.
+ *      - DateTime,DateTimeUtc - convert value to timestamp value before saving to database, and convert from timespamp value after restoring from database.
+ *      - Xss - it warns you that this text can contain XSS vulnerabilities and you need to be extra careful with this, and always use Forms engine to output string to the browser or use bx_process_output if going to output text manually.
+ *      - XssHtml - this text cam have HTML tags, so perform XSS vulnerabilies cleaning before saving to database.
+ *      - All - do not perform any conversion and pass text as it is, be careful with this, use it only when no other function can be used, and make all necessary security checking by yourself.
+ *      - Preg - perform regular expression on the text before saving data to database, regular expression can be provided in db_params field.
+ *      - Boolean - this is used for checkboxes with 'on' value which need to be converted into boolean value.
+ *      - Set - convert set of values into bit integer before saving to database, and restore bit integer into array of values upon restoration from database, it can be used for select_multiple and checkbox_set field types.
+ *      Please note that values for this field must be 1,2,4,8,... (values of power of 2); the max number of values are 31 for 32bit hardware and 63 for 64bit hardware.
+ *      You can inherit BxDolFormCheckerHelper class and add own pass functions, you will need to point your custom class in Form Object params array.
+ * - db_params - serialized array of db_pass parameters.
+ * - editable - allow to edit this field from admin forms builder.
+ * - deletable - allow to delete this field from admin forms builder.
+ *
+ *
+ * 4. Add Form Fields and Form Displays associations, add records to sys_form_display_inputs table:
+ * - display_name - name of the Form Display from sys_form_displays table.
+ * - input_name - name of the Form Field from sys_form_inputs table.
+ * - visible_for_levels - bit field with set of member level ids. To use member level id in bit field the level id minus 1 is used as power of 2, for example:
+ *      - user level id = 1 -> 2^(1-1) = 1
+ *      - user level id = 2 -> 2^(2-1) = 2
+ *      - user level id = 3 -> 2^(3-1) = 4
+ *      - user level id = 4 -> 2^(4-1) = 8
+ * - active - 1 - form field displayed on form, or 0 - isn't displayed.
+ * - order - fields are displayed in this order.
+ *
+ *
+ * @section form_field_types Form Field Types
+ *
+ * Detailed description of Form Field Types.
+ *
+ * Almost all fields have the following common parameters:
+ * - object
+ * - name
+ * - type
+ * - caption
+ * - info
+ * - required
+ * - attrs
+ * - attrs_tr
+ * - attrs_wrapper
+ *
+ * We will not describe above list of parameters in every type, since they work the same way for all types.
+ *
+ * The list below are field types with their unique parameters, which are designed especially for this field, or some parameters which work differently for the specified field type.
+ *
+ * text - text input field. It is displayed as regular single line text input.
+ *      Parameters:
+ *      - value - default value, or empty if there is no default value.
+ *      - values - not applicable here.
+ *      - checked - not applicable here.
+ *      - collapsed - not applicable here.
+ *      - html - not applicable here.
+ *      - checker_func
+ *          Can be used here: Length, Preg, Avail, Email
+ *          Make no sense to use it here: Date, DateTime, Captcha
+ *      - db_pass
+ *          Can be used here: Int, Float, Xss, All, Preg
+ *          Make no sense to use it here: Date, DateTime, XssHtml, Boolean, Set
+ *
+ * password - password input field. It is displayed as HTML input element with invisible input.
+ *      Parameters:
+ *      - value - default value, or empty if there is no default value.
+ *      - values - not applicable here.
+ *      - checked - not applicable here.
+ *      - collapsed - not applicable here.
+ *      - html - not applicable here.
+ *      - checker_func
+ *          Can be used here: Length, Preg, Avail.
+ *          Make no sense to use it here: Date, DateTime, Captcha, Email.
+ *      - db_pass
+ *          Can be used here: Xss, All.
+ *          Make no sense to use it here: Int, Float, Date, DateTime, XssHtml, Boolean, Set, Preg.
+ *
+ * textarea - multiline input field. It can be displayed as regular textarea field or as visual HTML editor.
+ *      Parameters:
+ *      - value - default value, or empty if there is no default value.
+ *      - values - not applicable here.
+ *      - checked - not applicable here.
+ *      - collapsed - not applicable here.
+ *      - html - use visual editor or not.
+ *          - 0 - no visual editor, leave textarea field as it is.
+ *          - 1 - standard(default) visual editor, @see BxDolEditor.
+ *          - 2 - full visual editor, @see BxDolEditor.
+ *          - 3 - mini visual editor, @see BxDolEditor.
+ *      - checker_func
+ *          Can be used here: Length, Preg, Avail
+ *          Make no sense to use it here: Email, Date, DateTime, Captcha
+ *      - db_pass
+ *          Can be used here: Int, Float, Xss, XssHtml, All, Preg
+ *          Make no sense to use it here: Date, DateTime, Boolean, Set
+ *
+ * number - number input field. It is displayed as HTL text input, but with limited width. Also some browsers can add additional controls to this field.
+ *      Parameters:
+ *      - value - default value, or empty if there is no default value.
+ *      - values - not applicable here.
+ *      - checked - not applicable here.
+ *      - collapsed - not applicable here.
+ *      - html - not applicable here.
+ *      - checker_func
+ *          Can be used here: Length, Preg, Avail
+ *          Make no sense to use it here: Email, Date, DateTime, Captcha
+ *      - db_pass
+ *          Can be used here: Int, Float
+ *          Make no sense to use it here: Xss, XssHtml, All, Preg, Date, DateTime, Boolean, Set
+ *
+ * select - select one from all available values. It is displayed as HTML combo-box.
+ *      Parameters:
+ *      - value - default value (array index of selected item from values array), or empty - if there is no default value.
+ *      - values - serialized array of available values, or reference to predefined set of values.
+ *      - checked - not applicable here.
+ *      - collapsed - not applicable here.
+ *      - html - not applicable here.
+ *      - checker_func
+ *          Can be used here: Length, Preg, Avail
+ *          Make no sense to use it here: Email, Date, DateTime, Captcha
+ *      - db_pass
+ *          Can be used here: Int, Float, Xss, All, Preg
+ *          Make no sense to use it here: Date, DateTime, XssHtml, Boolean, Set
+ *
+ * select_multiple - select one, multiple or all items from all available values. It is displayed as HTML multiple selection input.
+ *      Parameters:
+ *      - value - default value (bit integer of array indexes of selected items from values array), or empty - if there is no default value.
+ *      - values - serialized array of available values, or reference to predefined set of values. Array index must be power of 2. Max number of values is 31 on 32bit hardware or 63 on 64bit hardware.
+ *      - checked - not applicable here.
+ *      - collapsed - not applicable here.
+ *      - html - not applicable here.
+ *      - checker_func
+ *          Can be used here: Length, Preg, Avail
+ *          Make no sense to use it here: Email, Date, DateTime, Captcha
+ *      - db_pass
+ *          Can be used here: Int
+ *          Make no sense to use it here: Float, Xss, All, Preg, Date, DateTime, XssHtml, Boolean, Set
+ *
+ * switcher - on/off switcher. It is displayed as custom HTML element with own styles, but on background it works as regular HTML checkbox element.
+ *      Parameters:
+ *      - value - the value which will be submitted if switcher is on, if switcher is off - nothing is submitted.
+ *      - values - not applicable here.
+ *      - checked - if set to 1 then switcher is on by default, 0 - it is off by default.
+ *      - collapsed - not applicable here.
+ *      - html - not applicable here.
+ *      - checker_func
+ *          Can be used here: Length, Preg, Avail
+ *          Make no sense to use it here: Email, Date, DateTime, Captcha
+ *      - db_pass
+ *          Can be used here: Int, Float, Xss, All, Preg, Boolean
+ *          Make no sense to use it here: Date, DateTime, XssHtml, Set
+ *
+ * checkbox - one checkbox. Displayed as HTML checkbox input element.
+ *      Parameters:
+ *      - value - the value which will be submitted if checkbox is checked, if checkbox isn't checked - nothing is submitted.
+ *      - values - not applicable here.
+ *      - checked - if set to 1 then checkbox is checked by default, 0 - it is unchecked by default.
+ *      - collapsed - not applicable here.
+ *      - html - not applicable here.
+ *      - checker_func
+ *          Can be used here: Length, Preg, Avail
+ *          Make no sense to use it here: Email, Date, DateTime, Captcha
+ *      - db_pass
+ *          Can be used here: Int, Float, Xss, All, Preg, Boolean
+ *          Make no sense to use it here: Date, DateTime, XssHtml, Set
+ *
+ * checkbox_set - set of checkboxes. It is displayed as set of checkboxes. It is displayed in one row if number of items is equal or less than 3 or every item is displayed on new line if there is more than 3 items in the set.
+ *      Parameters:
+ *      - value - default value (bit integer of array indexes of selected items from values array), or empty - if there is no default value.
+ *      - values - serialized array of available values, or reference to predefined set of values. Array index must be power of 2. Max number of values is 31 on 32bit hardware or 63 on 64bit hardware.
+ *      - checked - not applicable here.
+ *      - collapsed - not applicable here.
+ *      - html - not applicable here.
+ *      - checker_func
+ *          Can be used here: Length, Preg, Avail
+ *          Make no sense to use it here: Email, Date, DateTime, Captcha
+ *      - db_pass
+ *          Can be used here: Int
+ *          Make no sense to use it here: Float, Xss, All, Preg, Date, DateTime, XssHtml, Boolean, Set
+ *
+ * radio_set - set of radio buttons. It is displayed as set of radio buttons. It is displayed in one row if number of items is equal or less than 3 or every item is displayed on new line if there is more than 3 items in the set.
+ *      Parameters:
+ *      - value - default value (array index of selected radio button from values array), or empty - if there is no default value.
+ *      - values - serialized array of available values, or reference to predefined set of values.
+ *      - checked - not applicable here.
+ *      - collapsed - not applicable here.
+ *      - html - not applicable here.
+ *      - checker_func
+ *          Can be used here: Length, Preg, Avail
+ *          Make no sense to use it here: Email, Date, DateTime, Captcha
+ *      - db_pass
+ *          Can be used here: Int, Float, Xss, All, Preg
+ *          Make no sense to use it here: Date, DateTime, XssHtml, Boolean, Set
+ *
+ * slider - select some numeric value within the range using slider control. It is displayed as jQuery UI HTML control, but on background it works as regular HTML text input element.
+ *      Parameters:
+ *      - value - default value in the format, or empty if there is no default value.
+ *      - values - not applicable here.
+ *      - checked - not applicable here.
+ *      - collapsed - not applicable here.
+ *      - html - not applicable here.
+ *      - attrs - the following additional attributes can be used here:
+ *      - min - minimal value, default is 0.
+ *      - max - maximal value, default is 100.
+ *      - step - value can be changed by this step only, default is 1.
+ *      - checker_func
+ *          Can be used here: Length, Avail
+ *          Make no sense to use it here: Preg, Email, Date, DateTime, Captcha
+ *      - db_pass
+ *          Can be used here: Int, Float
+ *          Make no sense to use it here: Xss, XssHtml, All, Preg, Date, DateTime, Boolean, Set
+ *
+ * doublerange - select range values within the range using slider control.
+ *      Parameters:
+ *      - value - default value in the format [min value]-[max value], for example 16-99, or empty if there is no default value.
+ *      - values - not applicable here.
+ *      - checked - not applicable here.
+ *      - collapsed - not applicable here.
+ *      - html - not applicable here.
+ *      - attrs - the following additional attributes can be used here:
+ *          - min - minimal value, default is 0.
+ *          - max - maximal value, default is 100.
+ *          - step - value can be changed by this step only, default is 1.
+ *      - checker_func
+ *          Can be used here: Length, Avail
+ *          Make no sense to use it here: Preg, Email, Date, DateTime, Captcha
+ *      - db_pass
+ *          Can be used here: Xss, All, Preg
+ *          Make no sense to use it here: Int, Float, XssHtml, Date, DateTime, Boolean, Set
+ *
+ * datepicker - date selection control. It is displayed as HTML text input control, when clicking on this input then popup with date selector control is appeared.
+ *      Parameters:
+ *      - value - default value, in the format YYYY-MMM-DD, or empty if there is no default value.
+ *      - values - not applicable here.
+ *      - checked - not applicable here.
+ *      - collapsed - not applicable here.
+ *      - html - not applicable here.
+ *      - checker_func
+ *          Can be used here: Date
+ *          Make no sense to use it here: Length, Preg, Avail, Email, DateTime, Captcha
+ *      - db_pass
+ *          Can be used here: Date, DateUtc
+ *          Make no sense to use it here: Int, Float, Xss, All, Preg, DateTime, XssHtml, Boolean, Set
+ *
+ * datetime - date/time selection control.
+ *      Parameters:
+ *      - value - default value, in the format YYYY-MMM-DD HH:MM:SS, or empty if there is no default value.
+ *      - values - not applicable here.
+ *      - checked - not applicable here.
+ *      - collapsed - not applicable here.
+ *      - html - not applicable here.
+ *      - checker_func
+ *          Can be used here: DateTime
+ *          Make no sense to use it here: Length, Preg, Avail, Email, Date, Captcha
+ *      - db_pass
+ *          Can be used here: DateTime, DateTimeUtc
+ *          Make no sense to use it here: Int, Float, Xss, All, Preg, Date, XssHtml, Boolean, Set
+ *
+ * captcha - image captcha. Displayed as image with some text along with HTML text input for entering displayed on the image text.
+ *      Parameters:
+ *      - value - not applicable here.
+ *      - values - not applicable here.
+ *      - checked - not applicable here.
+ *      - collapsed - not applicable here.
+ *      - html - not applicable here.
+ *      - checker_func
+ *          Can be used here: Captcha
+ *          Make no sense to use it here: Length, Preg, Avail, Email, Date, DateTime
+ *      - db_pass
+ *          Can be used here: Xss, All, Preg
+ *          Make no sense to use it here: Int, Float, Date, DateTime, XssHtml, Boolean, Set
+ *
+ * hidden - hidden input field. Displayed as hidden HTML input.
+ *      Parameters:
+ *      - value - hidden input value.
+ *      - values - not applicable here.
+ *      - checked - not applicable here.
+ *      - collapsed - not applicable here.
+ *      - html - not applicable here.
+ *      - checker_func
+ *          Can be used here: Length, Preg, Avail, Email, Date, DateTime
+ *          Make no sense to use it here: Captcha
+ *      - db_pass
+ *          Can be used here: Int, Float, Xss, All, Preg, Date, DateUtc, DateTime, DateTimeUtc, XssHtml, Boolean
+ *          Make no sense to use it here: Set
+ *
+ * file - file upload input. Displayed as file upload HTML input.
+ *      Parameters:
+ *      - value - not applicable here.
+ *      - values - not applicable here.
+ *      - checked - not applicable here.
+ *      - collapsed - not applicable here.
+ *      - html - not applicable here.
+ *      - checker_func
+ *          File name is passed for checking.
+ *          Can be used here: Avail, Length, Preg
+ *          Make no sense to use it here: Email, Date, DateTime, Captcha
+ *      - db_pass
+ *          File can't be stored in the database, so this field isn't applicable here.
+ *
+ * files - files upload input. Displayed as complex uploading HTML control.
+ *          This control is too complex to describe it using default set of database fields, you need to use custom class to display this control.
+ *
+ * button - button control. Displayes as HTML button element.
+ *      Parameters:
+ *      - value - translatable button caption.
+ *      - values - not applicable here.
+ *      - checked - not applicable here.
+ *      - collapsed - not applicable here.
+ *      - html - not applicable here.
+ *      - checker_func - not applicable here.
+ *      - db_pass - not applicable here.
+ *
+ * image - form image button. It is displayed as HTML form image input element.
+ *      Parameters:
+ *      - value - not applicable here.
+ *      - values - not applicable here.
+ *      - checked - not applicable here.
+ *      - collapsed - not applicable here.
+ *      - html - not applicable here.
+ *      - attrs - the following mandatory attribute must be specified here:
+ *          - src - image URL.
+ *      - checker_func - not applicable here.
+ *      - db_pass - not applicable here.
+ *
+ * reset - form reset button. Displayed as HTML form reset input button. By clicking on this button the form is reset to its default state.
+ *      Parameters:
+ *      - value - translatable button caption.
+ *      - values - not applicable here.
+ *      - checked - not applicable here.
+ *      - collapsed - not applicable here.
+ *      - html - not applicable here.
+ *      - checker_func - not applicable here.
+ *      - db_pass - not applicable here.
+ *
+ * submit - form submit button. Displayed as HTML form submit input button. This button have the primary button style to distinguish it from other buttons. By clicking on this button the form is submitted.
+ *      Parameters:
+ *      - value - translatable button caption.
+ *      - values - not applicable here.
+ *      - checked - not applicable here.
+ *      - collapsed - not applicable here.
+ *      - html - not applicable here.
+ *      - checker_func - not applicable here.
+ *      - db_pass - not applicable here.
+ *
+ * value - just displaying value without any control.
+ *      Parameters:
+ *      - value - the value to display.
+ *      - values - not applicable here.
+ *      - checked - not applicable here.
+ *      - collapsed - not applicable here.
+ *      - html - not applicable here.
+ *      - checker_func - not applicable here.
+ *      - db_pass - not applicable here.
+ *
+ * block_header - start group of fields. Displayed as form fields divider with caption - then it can be collapsible or without caption - then it is just divider without any functionality.
+ *      Parameters:
+ *      - value - not applicable here.
+ *      - values - not applicable here.
+ *      - checked - not applicable here.
+ *      - collapsed - display group of field collapsed by default, 1 - the group is collapsed, 0 - expanded (default value).
+ *      - html - not applicable here.
+ *      - checker_func - not applicable here.
+ *      - db_pass - not applicable here.
+ *
+ * custom - custom control. You need custom class to display this control, so the exact used values are determined by particular realisation.
+ *
+ * input_set - set of other form controls.
+ *      Parameters:
+ *      - value - not applicable here.
+ *      - values - comma separated list of field names (by name field) of fields to display here.
+ *      - checked - not applicable here.
+ *      - collapsed - not applicable here.
+ *      - html - not applicable here.
+ *      - checker_func - not applicable here.
+ *      - db_pass - not applicable here.
+ *
+ *
+ * @section form_using_own_class Using own class for custom behavior
+ *
+ * It is possible to provide own class for displaying and processing the form.
+ * To do this you need to point it in override_class_name and override_class_file fields in sys_objects_form table.
+ * Your custom class must be inherited from BxTemplFormView class or its descendants.
+ *
+ *
+ * @section form_display_custom_control Displaying custom field control
+ *
+ * It is possible to leave form field with default caption and override only the form field control.
+ * To override some field you need to define the following function:
+ * @code
+ *      protected function genCustomInput[field name] ($aInput).
+ * @endcode
+ * Where [field name] is form field name.
+ * For example:
+ *
+ * @code
+ *     protected function genCustomInputCustom ($aInput) {
+ *         return
+ *         'r: <input type="text" size="2" value="'.(isset($aInput['value'][0]) ? $aInput['value'][0] : '').'" name="'.$aInput['name'].'[]" />' .
+ *         'g: <input type="text" size="2" value="'.(isset($aInput['value'][1]) ? $aInput['value'][1] : '').'" name="'.$aInput['name'].'[]" />' .
+ *         'b: <input type="text" size="2" value="'.(isset($aInput['value'][2]) ? $aInput['value'][2] : '').'" name="'.$aInput['name'].'[]" />';
+ *     }
+ * @endcode
+ *
+ *
+ * @section form_display_custom_row Displaying custom field row
+ *
+ * Form row consists of caption and control, by default it is displayed with default design and functionality.
+ * If you need to display some field with custom header and control you need to declare the following function:
+ * @code
+ *      protected function genCustomRow[field name] ($aInput).
+ * @endcode
+ * Where [field name] is form field name.
+ *
+ *
+ * @section example Example of usage
+ *
+ * Printing the form for adding new record to the database:
+ *
+ * @code
+ *      $oForm = BxDolForm::getObjectInstance('sample_form_objects', 'sample_form_objects_add'); // get form instance for specified form object and display
+ *      if (!$oForm)
+ *          die('"sample_form_objects_add" form object or "sample_form_objects_add" display is not defined');
+ *      $oForm->initChecker(); // init form checker witout any data - adding new record
+ *      if ($oForm->isSubmittedAndValid())
+ *          echo 'inserted id: ' . $oForm->insert (); // add new record to the database
+ *      echo $oForm->getCode(); // display form
+ * @endcode
+ *
+ * Printing the form for editing existing record in the database:
+ *
+ * @code
+ *      // $iEditId - ID of edited row, for example from _GET parameter
+ *      $oDb = BxDolDb::getInstance();
+ *      $sQuery = $oDb->prepare("SELECT * FROM `sample_input_types` WHERE id = ?", $iEditId);
+ *      $aRecord = $oDb->getRow();
+ *      if (!$aRecord)
+ *          die("$iEditId record wasn't found.");
+ *
+ *      $oForm = BxDolForm::getObjectInstance('sample_form_objects', 'sample_form_objects_edit'); // get form instance for specified form object and display
+ *      if (!$oForm)
+ *          die('"sample_form_objects_edit" form object or "sample_form_objects_edit" display is not defined');
+ *      $oForm->initChecker($aRecord); // init form checker with edited data
+ *      if ($oForm->isSubmittedAndValid())
+ *          echo 'updated: ' . $oForm->update ($iEditId); // update database
+ *      echo $oForm->getCode(); // display form
+ * @endcode
+ *
+ * Example of custom form class and custom checking helper class:
+ *
+ * @code
+ *      class BxSampleForm extends BxTemplFormView {
+ *
+ *          public function __construct ($aInfo, $oTemplate = false) {
+ *              parent::__construct ($aInfo, $oTemplate);
+ *          }
+ *
+ *
+ *          // display input with 'custom' name
+ *          protected function genCustomInputCustom ($aInput) {
+ *              return
+ *              'r: <input type="text" size="2" value="'.(isset($aInput['value'][0]) ? $aInput['value'][0] : '').'" name="'.$aInput['name'].'[]" />' .
+ *              'g: <input type="text" size="2" value="'.(isset($aInput['value'][1]) ? $aInput['value'][1] : '').'" name="'.$aInput['name'].'[]" />' .
+ *              'b: <input type="text" size="2" value="'.(isset($aInput['value'][2]) ? $aInput['value'][2] : '').'" name="'.$aInput['name'].'[]" />';
+ *          }
+ *
+ *      }
+ *
+ *      class BxSampleFormCheckerHelper extends BxDolFormCheckerHelper {
+ *
+ *          protected $_sDiv = ',';
+ *
+ *          // prepare RBG values to save to the DB
+ *          function passRgb ($s) {
+ *              if (!is_array($s))
+ *                  return false;
+ *
+ *              $sRet = '';
+ *              foreach ($s as $k => $v)
+ *                  $sRet .= (int)trim($v) . $this->_sDiv;
+ *
+ *              return trim($sRet, $this->_sDiv);
+ *          }
+ *
+ *          // prepare RGB values to output to the screen
+ *          function displayRgb ($s) {
+ *              return explode($this->_sDiv, $s);
+ *          }
+ *      }
+ * @endcode
+ *
+ * The recommended way is to define forms in database, if it is impossible for some reasons you can init form object from array, there is an example:
+ *
+ * @code
+ *      $aForm = array(
+ *            'form_attrs' => array(
+ *                'name'     => 'form_my',
+ *                'method'   => 'post',
+ *            ),
+ *
+ *            'params' => array (
+ *                'db' => array(
+ *                    'table' => 'table_name', // table name
+ *                    'key' => 'ID', // key field name
+ *                    'uri' => 'EntryUri', // uri field name
+ *                    'uri_title' => 'Title', // title field to generate uri from
+ *                    'submit_name' => 'submit_form', // some filed name with non empty value to determine if the for was submitted,
+ *                                                       in most cases it is submit button name
+ *                ),
+ *                'csrf' => array(
+ *                      'disable' => true, //if it wasn't set or has some other value then CSRF checking is enabled for current form, take a look at sys_security_form_token_enable to disable CSRF checking completely.
+ *                )
+ *              ),
+ *
+ *            'inputs' => array(
+ *
+ *                'Title' => array(
+ *                    'type' => 'text',
+ *                    'name' => 'Title', // the same as key and database field name
+ *                    'caption' => 'Some caption',
+ *                    'required' => true,
+ *
+ *                    // checker params
+ *                    'checker' => array (
+ *                        'func' => 'length', // see BxDolFormCheckerHelper class for all check* functions
+ *                        'params' => array(3,100),
+ *                        'error' => 'length must be from 3 to 100 characters',
+ *                    ),
+ *                    // database params
+ *                    'db' => array (
+ *                        'pass' => 'Xss',  // do XSS clear before getting this value, see BxDolFormCheckerHelper class for all pass* functions
+ *                    ),
+ *                ),
+ *
+ *                'Description' => array(
+ *                    'type' => 'textarea',
+ *                    'name' => 'Description', // the same as key and database field name
+ *                    'caption' => 'Some caption',
+ *                    'required' => true,
+ *
+ *                    // checker params
+ *                    'checker' => array (
+ *                        'func' => 'length',
+ *                        'error' => 'enter at least 3 characters',
+ *                        'params' => array(3,64000),
+ *                    ),
+ *                    'db' => array (
+ *                        'pass' => 'XssHtml',  // do XSS clear, but keep HTML before getting this value
+ *                    ),
+ *                ),
+ *            );
+ * @endcode
+ *
+ * Using of above array:
+ *
+ * @code
+ *        $oForm = new BxTemplFormView ($aForm);
+ *        $oForm->initChecker();
+ *
+ *        if ($oForm->isSubmittedAndValid ()) {
+ *
+ *            // add additional vars to database, in this case creation date field is added
+ *            $aValsAdd = array (
+ *                'Date' => time(),
+ *            );
+ *
+ *            echo 'insert last id: ' . $oForm->insert ($aValsAdd); // insert validated data to database
+ *
+ *        } else {
+ *
+ *            echo $oForm->getCode (); // show form
+ *
+ *        }
+ *
+ * @endcode
+ *
+ */
+class BxDolForm extends BxDol implements iBxDolReplaceable
+{
+    static $TYPES_SKIP = array('files' => 1, 'location' => 1);
+    static $TYPES_CHECKBOX = array('checkbox' => 1, 'switcher' => 1);
+    static $TYPES_TEXT = array('text' => 1, 'textarea' => 1);
+    static $TYPES_FILE = array('file' => 1);
+
+    static $FUNC_SKIP_DOMAIN_CHECK = array('email' => 1, 'emails' => 1, 'emailexist' => 1, 'emailuniq' => 1, 'emailexistorempty' => 1, 'hostdomain' => 1, 'hostdomainchat' => 1, 'emailorempty' => 1);
+    static $FUNC_SKIP_MACROS_CHECK = array();
+
+    static $LOCATION_INDEXES = array ('lat', 'lng', 'country', 'state', 'city', 'zip', 'street', 'street_number');
+
+    protected $_aMarkers = array ();
+
+    protected $oTemplate;
+
+    protected $_isValid = true;
+    protected $_sChecker;
+    protected $_sCheckerHelper;
+    protected $_aSpecificValues;
+
+    public $aFormAttrs; ///< form html element attributes
+    public $aInputs; ///< form inputs
+    public $aParams; ///< additional form parameters
+    public $id; ///< Form element id
+
+    protected $_aFieldsCheckForSpam = array(); ///< additional fields names to check for spam(profanity filter), now only fields with 'textarea' and 'text' are checked for spam, 'textarea' fields are checked for spam and filter for profanity, while 'text' fields are filtetered for profanity only
+
+    public function __construct ($aInfo, $oTemplate)
+    {
+        parent::__construct();
+
+        if ($oTemplate)
+            $this->oTemplate = $oTemplate;
+        else
+            $this->oTemplate = BxDolTemplate::getInstance();
+
+        $this->aFormAttrs    = isset($aInfo['form_attrs'])   ? $aInfo['form_attrs']  : array();
+        $this->aInputs       = isset($aInfo['inputs'])       ? $aInfo['inputs']      : array();
+        $this->aParams       = isset($aInfo['params'])       ? $aInfo['params']      : array();
+
+        // get form element id
+        $this->id = $this->aFormAttrs['id'] = (!empty($this->aFormAttrs['id']) ? $this->aFormAttrs['id'] : (!empty($this->aFormAttrs['name']) ? $this->aFormAttrs['name'] : 'form_advanced'));
+
+        // set default method
+        if (!isset($this->aFormAttrs['method']))
+            $this->aFormAttrs['method'] = BX_DOL_FORM_METHOD_DEFAULT;
+
+        // set default action
+        if (!isset($this->aFormAttrs['action']))
+            $this->aFormAttrs['action'] = '';
+
+        $this->_sChecker = isset($this->aParams['checker']) ? $this->aParams['checker'] : 'BxDolFormChecker';
+        $this->_sCheckerHelper = isset($this->aParams['checker_helper']) ? $this->aParams['checker_helper'] : '';
+    }
+
+    /**
+     * Get form object instance by object name
+     * @param $sObject object name
+     * @return object instance or false on error
+     */
+    static public function getObjectInstance($sObject, $sDisplayName, $oTemplate = false, $sParam = '')
+    {
+        $sKey = 'BxDolForm!'.$sObject.'!'.$sDisplayName.'!'.$sParam;
+
+        if (isset($GLOBALS['bxDolClasses'][$sKey]))
+            return $GLOBALS['bxDolClasses'][$sKey];
+
+        $aObject = BxDolFormQuery::getFormArray($sObject, $sDisplayName);
+        if (!$aObject || !is_array($aObject))
+            return false;
+
+        $sClass = 'BxTemplFormView';
+        if (!empty($aObject['override_class_name'])) {
+            $sClass = $aObject['override_class_name'];
+            if (!empty($aObject['override_class_file']))
+                require_once(BX_DIRECTORY_PATH_ROOT . $aObject['override_class_file']);
+        }
+
+        $o = new $sClass($aObject, $oTemplate);
+
+        return ($GLOBALS['bxDolClasses'][$sKey] = $o);
+    }
+
+    /**
+     * Get data items array
+     * @param $sKey data items identifier
+     * @param $isUseForSet convert data items keys to use in set fields, make it power of 2 (1,2,4,8,16,etc).
+     * @param $sUseValues use default(BX_DATA_VALUES_DEFAULT) or additional(BX_DATA_VALUES_ADDITIONAL) value titles, if additinal value title is missing default title is used
+     * @return data items array
+     */
+    public static function getDataItems($sKey, $isUseForSet = false, $sUseValues = BX_DATA_VALUES_DEFAULT)
+    {
+        return BxDolFormQuery::getDataItems($sKey, $isUseForSet, $sUseValues);
+    }
+
+    function initChecker ($aValues = array (), $aSpecificValues = array())
+    {
+        $this->_aSpecificValues = $aSpecificValues;
+
+        $oChecker = new $this->_sChecker($this->_sCheckerHelper);
+        $oChecker->setFormMethod($this->aFormAttrs['method'], $aSpecificValues);
+        $oChecker->setFormParams($this->aParams);
+        $oChecker->setFieldsCheckForSpam($this->_aFieldsCheckForSpam);
+
+        // init form with default values
+
+        $aValuesDef = array ();
+        foreach ($this->aInputs as $k => $a) {
+            if (!isset($a['value']) || !isset($a['db']['pass']) || isset(self::$TYPES_CHECKBOX[$a['type']]) || isset(self::$TYPES_SKIP[$a['type']]))
+                continue;
+            $aValuesDef[$k] = $a['value'];
+        }
+
+        $oChecker->fillWithValues($this->aInputs, $aValuesDef);
+
+        // init form with provided values
+
+        if ($aValues)
+            $oChecker->fillWithValues($this->aInputs, $aValues);
+
+
+        if ($this->isSubmitted ()) {
+
+            // init form with submitted data, overwrite prevously declared values
+
+            $oChecker->enableFormCsrfChecking(isset($this->aParams['csrf']['disable']) && $this->aParams['csrf']['disable'] === true ? false : true);
+
+            $this->_isValid = $oChecker->check($this->aInputs, $aValues);
+
+            if (!$this->_initCheckerNestedForms ())
+                $this->_isValid = false;
+
+        }
+    }
+
+    function getKeyValuesPair ()
+    {
+        $aRet = array ();
+        foreach ($this->aInputs as $k => $a) {
+            if (!isset($a['value']) || isset(self::$TYPES_CHECKBOX[$a['type']]) || isset(self::$TYPES_SKIP[$a['type']]))
+                continue;
+            $aRet[$k] = $a['value'];
+        }
+        return $aRet;
+    }
+
+    function insert ($aValsToAdd = array(), $isIgnore = false)
+    {
+        $oChecker = new $this->_sChecker($this->_sCheckerHelper);
+        $oChecker->setFormMethod($this->aFormAttrs['method'], $this->_aSpecificValues);
+        $sSql = $oChecker->dbInsert($this->aParams['db'], $this->aInputs, $aValsToAdd, $isIgnore);
+        if (!$sSql)
+            return false;
+        $oDb = BxDolDb::getInstance();
+        if ($oDb->query($sSql))
+            return $oDb->lastId();
+        return false;
+    }
+
+    function update ($val, $aValsToAdd = array(), &$aTrackTextFieldsChanges = null)
+    {
+        $oChecker = new $this->_sChecker($this->_sCheckerHelper);
+        $oChecker->setFormMethod($this->aFormAttrs['method'], $this->_aSpecificValues);
+        $sSql = $oChecker->dbUpdate($val, $this->aParams['db'], $this->aInputs, $aValsToAdd, $aTrackTextFieldsChanges);
+        if (!$sSql)
+            return false;
+        return BxDolDb::getInstance()->query($sSql);
+    }
+
+    function delete ($val)
+    {
+        $oChecker = new $this->_sChecker($this->_sCheckerHelper);
+        $oChecker->setFormMethod($this->aFormAttrs['method'], $this->_aSpecificValues);
+        $sSql = $oChecker->dbDelete($val, $this->aParams['db'], $this->aInputs);
+        if (!$sSql)
+            return false;
+        return BxDolDb::getInstance()->query($sSql);
+    }
+
+    function generateUri ()
+    {
+        $f = &$this->aParams['db'];
+        $sUri = $this->getCleanValue ($f['uri_title']);
+        return uriGenerate($sUri, $f['table'], $f['uri']);
+    }
+
+    function getCleanValue ($sName)
+    {
+        $oChecker = new $this->_sChecker($this->_sCheckerHelper);
+        $oChecker->setFormMethod($this->aFormAttrs['method'], $this->_aSpecificValues);
+        $a = isset($this->aInputs[$sName]) ? $this->aInputs[$sName] : false;
+        if ($a && isset($a['db']['pass']))
+            return $oChecker->get ($a['name'], $a['db']['pass'], isset($a['db']['params']) && $a['db']['params'] ? $a['db']['params'] : array());
+        else
+           return $oChecker->get ($sName);
+    }
+
+    function isSubmitted ()
+    {
+        $aSubmitName = $this->aParams['db']['submit_name'];
+        if(!is_array($aSubmitName))
+            $aSubmitName = array($aSubmitName);
+
+        foreach($aSubmitName as $sSubmitName)
+            if(BxDolForm::getSubmittedValue($sSubmitName, $this->aFormAttrs['method'], $this->_aSpecificValues))
+                return true;
+        
+        return false;
+    }
+
+    function getId ()
+    {
+        return $this->id;
+    }
+
+    function setId ($sId)
+    {
+        $this->id = $sId;
+        $this->aFormAttrs['id'] = $sId;
+    }
+
+    function setName($sName)
+    {
+        $this->aFormAttrs['name'] = $sName;
+    }
+
+    function setValid ($isValid)
+    {
+        $this->_isValid = $isValid;
+    }
+
+    function isValid ()
+    {
+        return $this->_isValid;
+    }
+
+    function isSubmittedAndValid ()
+    {
+        return ($this->isSubmitted() && $this->isValid());
+    }
+
+    function getSpecificValues()
+    {
+        return $this->_aSpecificValues;
+    }
+
+    public static function getSubmittedValue($sKey, $sMethod, &$aSpecificValues = false)
+    {
+        $aData = array();
+        if($sMethod == BX_DOL_FORM_METHOD_GET) {
+            $aData = &$_GET;
+        } else if($sMethod == BX_DOL_FORM_METHOD_POST) {
+            $aData = &$_POST;
+        } else if($sMethod == BX_DOL_FORM_METHOD_SPECIFIC) {
+            $aData = &$aSpecificValues;
+        }
+
+        return isset($aData[$sKey]) ? $aData[$sKey] : false;
+    }
+
+    public static function setSubmittedValue($sKey, $mixedValue, $sMethod, &$aSpecificValues = false)
+    {
+        $aData = array();
+        if($sMethod == BX_DOL_FORM_METHOD_GET) {
+            $aData = &$_GET;
+        } else if($sMethod == BX_DOL_FORM_METHOD_POST) {
+            $aData = &$_POST;
+        } else if($sMethod == BX_DOL_FORM_METHOD_SPECIFIC) {
+            $aData = &$aSpecificValues;
+        }
+
+        return $aData[$sKey] = $mixedValue;
+    }
+
+    /**
+     * Check if field exists
+     * @param $sName field name 
+     * @param $bCheckAllFields true - check all fields; false - all fields except: password, captcha, hidden, file, button, image, reset, submit, block_header, input_set
+     * @return true if field exists or false otherwise
+     */
+    public function isFieldExist($sName, $bCheckAllFields = false)
+    {
+        if (!isset($this->aInputs[$sName]))
+            return false;
+        return $bCheckAllFields ? true : !in_array($this->aInputs[$sName]['type'], array('password', 'captcha', 'hidden', 'button', 'reset', 'submit', 'block_header', 'input_set', 'location'));
+    }
+
+
+    // Static Methods related to CSRF Tocken
+    public static function genCsrfToken($bReturn = false)
+    {
+        // TODO: remove
+    }
+    public static function getCsrfToken()
+    {
+        if (getParam('sys_security_form_token_enable') != 'on')
+            return false;
+
+        if (!($oKeys = BxDolKey::getInstance()))
+            return false;
+
+        return $oKeys->getNewKey (false, (int)getParam('sys_security_form_token_lifetime'));
+    }
+    public static function isCsrfTokenValid($s)
+    {
+        if (getParam('sys_security_form_token_enable') != 'on')
+            return true;
+
+        if (!($oKeys = BxDolKey::getInstance()))
+            return true;
+
+        if ($oKeys->isKeyExists($s)) {
+            $oKeys->removeKey($s);
+            return true;
+        }
+        return false;
+    }
+
+    function _initCheckerNestedForms ()
+    {
+        $isValid = true;
+
+        // process nested forms
+        foreach ($this->aInputs as $sKey => $aInput) {
+
+            if (!isset($aInput['ghost_template']))
+                continue;
+
+            if (!(is_array($aInput['ghost_template']) && isset($aInput['ghost_template']['inputs'])) && !(is_object($aInput['ghost_template']) && $aInput['ghost_template'] instanceof BxDolFormNested))
+                continue;
+
+            $sName = $aInput['name'];
+            $aIds = $this->getSubmittedValue($sName, $this->aFormAttrs['method']);
+            if (!$aIds)
+                continue;
+
+            $aNestedForms = array ();
+            foreach ($aIds as $i => $iFileId) {
+
+                // create separate form instance for each file
+                $oFormNested = false;
+                if (is_object($aInput['ghost_template'])) {
+                    $oFormNested = clone($aInput['ghost_template']);
+                } else {
+                    $oFormNested = new BxDolFormNested($aInput['name'], $aInput['ghost_template'], $this->aParams['db']['submit_name'], $this->oTemplate);
+                }
+                if ($iFileId && 0 != $iFileId)
+                    $aNestedForms[$iFileId] = $oFormNested;
+                else
+                    $aNestedForms[] = $oFormNested;
+
+                // collect nested form values
+                $aSpecificValues = array ();
+                if (isset($this->aParams['db']['submit_name']))
+                    $aSpecificValues = array ($this->aParams['db']['submit_name'] => 1);
+                foreach ($oFormNested->aInputs as $r) {
+                    $sName = str_replace('[]', '', $r['name']);
+                    $aValue = $this->getSubmittedValue($sName, $this->aFormAttrs['method']);
+                    $aSpecificValues[$sName] = $aValue[$i];
+                }
+                $oFormNested->initChecker(array(), $aSpecificValues);
+
+                // if nested form in invalid - then the whole worm is failed
+                if (!$oFormNested->isValid ())
+                    $isValid = false;
+            }
+
+            if ($aNestedForms)
+                $this->aInputs[$sKey]['ghost_template'] = $aNestedForms;
+
+        }
+
+        return $isValid;
+    }
+
+    /**
+     * Check if form field is visible.
+     * @param $aInput form field array
+     * @return boolean
+     */
+    static public function isVisible ($aInput)
+    {
+        return isset($aInput['visible_for_levels']) ? BxDolAcl::getInstance()->isMemberLevelInSet($aInput['visible_for_levels']) : true;
+    }
+
+    protected function _genMethodName ($s)
+    {
+        return bx_gen_method_name($s);
+    }
+
+    /**
+     * Add replace markers. Curently markers are replaced in action, form_attrs fields.
+     * @param $a array of markers as key => value
+     * @return true on success or false on error
+     */
+    public function addMarkers ($a)
+    {
+        if (empty($a) || !is_array($a))
+            return false;
+        $this->_aMarkers = array_merge ($this->_aMarkers, $a);
+        return true;
+    }
+
+    /**
+     * Replace provided markers in form array
+     * @param $a form description array
+     * @return array where markes are replaced with real values
+     */
+    protected function _replaceMarkers ($a)
+    {
+        return bx_replace_markers($a, $this->_aMarkers);
+    }
+
+    public function getFormErrors ()
+    {
+        $s = '';
+        foreach ($this->aInputs as $k => $a) {
+            if (isset($this->aInputs[$k]['error']) && $this->aInputs[$k]['error'])
+                $s .= $k . ': ' . $this->aInputs[$k]['error'] . " \n";
+        }
+        return $s;
+    }
+}
+
+class BxDolFormChecker
+{
+    protected $_oChecker;
+    protected $_sFormMethod;
+    protected $_aFormParams;
+    protected $_bFormCsrfChecking;
+    protected $_aSpecificValues;
+    protected $_aFieldsCheckForSpam;
+
+    function __construct ($sHelper = '')
+    {
+        $this->_sFormMethod = BX_DOL_FORM_METHOD_GET;
+        $this->_bFormCsrfChecking = true;
+
+        $sCheckerName = !empty($sHelper) ? $sHelper : 'BxDolFormCheckerHelper';
+        $this->_oChecker = new $sCheckerName();
+    }
+
+    function setFormMethod($sMethod, $aSpecificValues = array())
+    {
+        $this->_sFormMethod = $sMethod;
+        $this->_aSpecificValues = $aSpecificValues;
+    }
+
+    function setFormParams($aParams)
+    {
+        $this->_aFormParams = $aParams;
+    }
+
+    function setFieldsCheckForSpam($a)
+    {
+        $this->_aFieldsCheckForSpam = $a;
+    }
+    
+    function enableFormCsrfChecking($bFormCsrfChecking)
+    {
+        $this->_bFormCsrfChecking = $bFormCsrfChecking;
+    }
+
+    // check function
+    function check (&$aInputs, $aValues = array())
+    {
+        $oChecker = $this->_oChecker;
+        $iErrors = 0;
+
+        $sSubmitName = false;
+
+        foreach ($aInputs as $k => $a) {
+            if (isset($a['visible_for_levels']) && !BxDolForm::isVisible($a))
+                continue;
+
+            if (empty($a['name']) || 'submit' == $a['type'] || 'reset' == $a['type'] || 'button' == $a['type'] || 'value' == $a['type']) {
+                if (isset($a['type']) && 'submit' == $a['type'])
+                    $sSubmitName = $k;
+                continue;
+            }
+
+            if ('input_set' == $a['type'])
+                foreach ($a as $r)
+                    if (isset($r['type']) && 'submit' == $r['type'])
+                        $sSubmitName = $k;
+
+            $a['name'] = str_replace('[]', '', $a['name']);
+
+            $sMethodGetSubmittedValue = 'getSubmittedValue' . bx_gen_method_name($a['name']);
+            if(method_exists($this, $sMethodGetSubmittedValue))
+                $val = $this->$sMethodGetSubmittedValue($a['name'], $this->_sFormMethod, $this->_aSpecificValues);
+            else
+                $val = BxDolForm::getSubmittedValue($a['name'], $this->_sFormMethod, $this->_aSpecificValues);
+
+            if (isset(BxDolForm::$TYPES_FILE[$a['type']]))
+                $val = isset($_FILES[$a['name']]['name']) ? $_FILES[$a['name']]['name'] : '';
+
+            // check for unique
+            if(isset($aInputs[$k]['unique']) && $aInputs[$k]['unique'] && !empty($this->_aFormParams['db']['key']) && !empty($this->_aFormParams['db']['table'])) {
+                $sKey = $this->_aFormParams['db']['key'];
+                $sTable = $this->_aFormParams['db']['table'];
+                $sError = _t('_sys_form_err_not_unique_value');
+
+                if(!empty($aValues[$sKey])) {
+                    $sValDb = BxDolFormQuery::fieldGetValue($sTable, $aInputs[$k]['name'], $sKey, $aValues[$sKey]);
+                    if($val != $sValDb && !BxDolFormQuery::fieldCheckUnique($sTable, $aInputs[$k]['name'], $val)) {
+                        ++$iErrors;
+                        $aInputs[$k]['error'] = $sError;
+                    }
+                }
+                else if(!BxDolFormQuery::fieldCheckUnique($sTable, $aInputs[$k]['name'], $val)) {
+                    ++$iErrors;
+                    $aInputs[$k]['error'] = $sError;
+                }
+            }
+
+            // check for links in text fields
+            $sCheckerFunc = isset($a['checker']['func']) ? strtolower($a['checker']['func']) : '';
+            if (
+                !isset(BxDolForm::$FUNC_SKIP_DOMAIN_CHECK[$sCheckerFunc]) && 
+                (!isset($a['skip_domain_check']) || false == $a['skip_domain_check']) && 
+                isset(BxDolForm::$TYPES_TEXT[$a['type']]) && 
+                bx_is_url_in_content($val) && 
+                !isAdmin())
+            {
+                $aCheck = checkActionModule(bx_get_logged_profile_id(), 'post links', 'system');
+                if ($aCheck[CHECK_ACTION_RESULT] !== CHECK_ACTION_RESULT_ALLOWED) {
+                    ++$iErrors;
+                    $aInputs[$k]['error'] = $aCheck[CHECK_ACTION_MESSAGE];
+                }
+                else {
+                    checkActionModule(bx_get_logged_profile_id(), 'post links', 'system', true);
+                }
+            }
+
+            // check for macros in text fields
+            $sCheckerFunc = isset($a['checker']['func']) ? strtolower($a['checker']['func']) : '';
+            if (
+                !isset(BxDolForm::$FUNC_SKIP_MACROS_CHECK[$sCheckerFunc]) && 
+                (!isset($a['skip_macros_check']) || false == $a['skip_macros_check']) && 
+                isset(BxDolForm::$TYPES_TEXT[$a['type']]) && 
+                bx_is_macros_in_content($val) && 
+                !isAdmin())
+            {
+                $aCheck = checkActionModule(bx_get_logged_profile_id(), 'use macros', 'system');
+                if ($aCheck[CHECK_ACTION_RESULT] !== CHECK_ACTION_RESULT_ALLOWED) {
+                    ++$iErrors;
+                    $aInputs[$k]['error'] = $aCheck[CHECK_ACTION_MESSAGE];
+                }
+                else {
+                    checkActionModule(bx_get_logged_profile_id(), 'use macros', 'system', true);
+                }
+            }
+
+            if (!isset ($a['checker']))  {
+                if (isset(BxDolForm::$TYPES_CHECKBOX[$a['type']]))
+                    $aInputs[$k]['checked'] = (isset($aInputs[$k]['value']) && $aInputs[$k]['value'] == $val);
+                elseif (!isset(BxDolForm::$TYPES_FILE[$a['type']]) && !isset(BxDolForm::$TYPES_SKIP[$a['type']]))
+                    $aInputs[$k]['value'] = bx_process_input($val);
+                continue;
+            }
+
+            $sCheckFunction = array($oChecker, 'check' . bx_gen_method_name($a['checker']['func']));
+
+            if (is_callable($sCheckFunction))
+                $bool = call_user_func_array ($sCheckFunction, !empty($a['checker']['params']) ? array_merge(array($val), $a['checker']['params']) : array ($val));
+            else
+                $bool = true;
+
+            if (is_string($bool)) {
+                ++$iErrors;
+                $aInputs[$k]['error'] = $bool;
+            } elseif (!$bool) {
+                ++$iErrors;
+				if (isset($a['checker']['error']))
+                	$aInputs[$k]['error'] = $a['checker']['error'];
+            }
+
+            if (isset(BxDolForm::$TYPES_CHECKBOX[$a['type']]))
+                $aInputs[$k]['checked'] = ($aInputs[$k]['value'] == $val);
+            elseif (!isset(BxDolForm::$TYPES_FILE[$a['type']]) && !isset(BxDolForm::$TYPES_SKIP[$a['type']]))
+                $aInputs[$k]['value'] = bx_process_input($val);
+        }
+
+        // check CSRF token if it's needed.
+        if (getParam('sys_security_form_token_enable') == 'on' && $this->_bFormCsrfChecking === true) {
+            $mixedCsrfTokenUsr = BxDolForm::getSubmittedValue('csrf_token', $this->_sFormMethod, $this->_aSpecificValues);
+            unset($aInputs['csrf_token']);
+
+            if ($mixedCsrfTokenUsr === false || !BxDolForm::isCsrfTokenValid($mixedCsrfTokenUsr)) {
+                $aInputs[$sSubmitName]['error'] = _t('_sys_txt_form_submission_error_csrf_expired');
+                return false;
+            }
+        }
+
+        // check for spam
+        if (!$iErrors) {
+
+            foreach ($aInputs as $k => $a) {
+
+                if (!isset($a['name']))
+                    continue;
+
+                $a['name'] = str_replace('[]', '', $a['name']);
+
+                if ($a['type'] != 'textarea' && $a['type'] != 'text' && !in_array($a['name'], $this->_aFieldsCheckForSpam))
+                    continue;
+                
+                $val = BxDolForm::getSubmittedValue($a['name'], $this->_sFormMethod, $this->_aSpecificValues);
+                if (!$val)
+                    continue;
+
+                if (!$oChecker->checkIsSpam($val, $a['type'])){
+					BxDolForm::setSubmittedValue($a['name'], $val, $this->_sFormMethod, $this->_aSpecificValues);
+                    continue;
+				}
+                
+                ++$iErrors;
+
+                $sErr = _t('_sys_spam_detected');
+                if (BxDolRequest::serviceExists('bx_contact', 'get_contact_page_url') && ($sUrl = BxDolService::call('bx_contact', 'get_contact_page_url')))
+                    $sErr = _t('_sys_spam_detected_contact', $sUrl);
+                $aInputs[$k]['error'] = $sErr;
+            }
+        }
+
+        // add error message near submit button
+        if ($iErrors && $sSubmitName)
+            $aInputs[$sSubmitName]['error'] = _t('_sys_txt_form_submission_error');
+
+        return $iErrors ? false : true;
+    }
+
+    // get clean value from GET/POST
+    function get ($sName, $sPass = 'Xss', $aParams = array())
+    {
+        if (!$sPass)
+            $sPass = 'Xss';
+        $this->_oChecker;
+        $val = BxDolForm::getSubmittedValue($sName, $this->_sFormMethod, $this->_aSpecificValues);
+        return call_user_func_array (array($this->_oChecker, 'pass'.ucfirst($sPass)), $aParams ? array_merge(array($val), $aParams) : array ($val));
+    }
+
+    // db functions
+    function serializeDbValues (&$aInputs, &$aValsToAdd, &$aTrackTextFieldsChanges = null, $aUnset = null)
+    {
+        $aValsToUpdate = array();
+
+        if (null !== $aTrackTextFieldsChanges && isset($aTrackTextFieldsChanges['data']))
+            $aTrackTextFieldsChanges['changed_fields'] = array();
+
+        // get values from form description array
+        foreach ($aInputs as $k => $a) {
+            if (!isset ($a['db']) || !BxDolForm::isVisible($a)) continue;
+            $sKey = str_replace('[]', '', $a['name']);
+            $valClean = $this->get ($sKey, $a['db']['pass'], !empty($a['db']['params']) ? $a['db']['params'] : array());
+            $aValsToUpdate[$sKey] = $valClean;
+            $aInputs[$k]['db']['value'] = $valClean;
+
+            if (null !== $aTrackTextFieldsChanges && isset(BxDolForm::$TYPES_TEXT[$aInputs[$k]['type']]) && isset($aTrackTextFieldsChanges['data'][$a['name']]) && $aTrackTextFieldsChanges['data'][$a['name']] != $valClean)
+                $aTrackTextFieldsChanges['changed_fields'][] = $a['name'];
+        }
+
+        // get values which are provided manually
+        foreach ($aValsToAdd as $k => $val) {
+            $aValsToUpdate[$k] = $val;
+        }
+
+        // don't update id field for update statement
+        if ($aUnset) {
+            foreach ($aUnset as $sKey) {
+                if (isset($aValsToUpdate[$sKey]))
+                    unset($aValsToUpdate[$sKey]);
+            }
+        }
+
+        // build SQL query part
+        return BxDolDb::getInstance()->arrayToSQL($aValsToUpdate);
+    }
+
+    function dbInsert (&$aDb, &$aInputs, $aValsToAdd = array(), $isIgnore = false)
+    {
+        if (!$aDb['table'])
+            return '';
+
+        $sFields = $this->serializeDbValues ($aInputs, $aValsToAdd);
+        if (!$sFields)
+            return '';
+
+        return "INSERT " . ($isIgnore ? 'IGNORE' : '') . " INTO `{$aDb['table']}` SET $sFields";
+    }
+
+    function dbUpdate ($val, &$aDb, &$aInputs, $aValsToAdd = array(), &$aTrackTextFieldsChanges = null)
+    {
+        if (!$aDb['table'] || !$aDb['key'])
+            return '';
+
+        $oDb = BxDolDb::getInstance();
+
+        if (null !== $aTrackTextFieldsChanges && !isset($aTrackTextFieldsChanges['data'])) {
+            // get row values to compare old and new values
+            $sQuery = $oDb->prepare("SELECT * FROM `{$aDb['table']}` WHERE `{$aDb['key']}` = ?", $val);
+            $aTrackTextFieldsChanges['data'] = $oDb->getRow($sQuery);
+        }
+
+        $sFields = $this->serializeDbValues ($aInputs, $aValsToAdd, $aTrackTextFieldsChanges, array($aDb['key']));
+        if (!$sFields)
+            return '';
+
+        return $oDb->prepare("UPDATE `{$aDb['table']}` SET $sFields WHERE `{$aDb['key']}` = ?", $val);
+    }
+
+    function dbDelete ($val, &$aDb, &$aInputs)
+    {
+        if (!$aDb['table'] || !$aDb['key'])
+            return '';
+
+        $oDb = BxDolDb::getInstance();
+
+        return $oDb->prepare("DELETE FROM `{$aDb['table']}` WHERE `{$aDb['key']}` = ?", $val);
+    }
+
+    function fillWithValues (&$aInputs, &$aValues)
+    {
+        foreach ($aInputs as $k => $a) {
+            if (!isset($aValues[$k]) || isset(BxDolForm::$TYPES_SKIP[$aInputs[$k]['type']])) 
+                continue;
+
+            if (isset(BxDolForm::$TYPES_CHECKBOX[$aInputs[$k]['type']])) {
+                $aInputs[$k]['checked'] = isset($aInputs[$k]['value']) ? ($aInputs[$k]['value'] == $aValues[$k]) : false;
+            } else {
+                $sMethod = 'display' . (isset($a['db']['pass']) ? ucfirst($a['db']['pass']) : 'Undefined');
+                if (method_exists($this->_oChecker, $sMethod))
+                    $aInputs[$k]['value'] = call_user_func_array (array($this->_oChecker, $sMethod), !empty($a['db']['params']) ? array_merge(array($aValues[$k]), $a['db']['params']) : array ($aValues[$k]));
+                else
+                    $aInputs[$k]['value'] = $aValues[$k];
+            }
+        }
+    }
+}
+
+class BxDolFormCheckerHelper
+{
+    // check functions - check values for limits or patterns
+
+    static public function checkLength ($s, $iLenMin, $iLenMax)
+    {
+        if (is_array($s)) {
+            foreach ($s as $k => $v) {
+                $iLen = get_mb_len ($v);
+                if ($iLen < $iLenMin || $iLen > $iLenMax)
+                    return false;
+            }
+            return true;
+        }
+        $iLen = get_mb_len ($s);
+        return $iLen >= $iLenMin && $iLen <= $iLenMax ? true : false;
+    }
+    static public function checkDate ($s)
+    {
+        return self::checkPreg ($s, '#^\d+\-\d+\-\d+$#');
+    }
+    static public function checkDateRange ($s, $iMin, $iMax, $bRequired = false)
+    {
+        if (!$bRequired && (empty($s) || '0000-00-00' == $s))
+            return true;   
+        if (self::checkPreg ($s, '#^\d+\-\d+\-\d+$#')){
+            $dDate = new DateTime($s);
+            $dDateMax = new DateTime("-" . $iMin . " years");
+            $dDateMin = new DateTime("-" . $iMax . " years");
+            if ($dDate > $dDateMin && $dDate < $dDateMax){
+                return true;
+            }
+        }
+        return false;
+    }
+    static public function checkDateTime ($s)
+    {
+        // remove unnecessary opera's input value;
+        $s = str_replace('T', ' ', $s);
+        $s = str_replace('Z', ':00', $s);
+
+        return self::checkPreg ($s, '#^\d+\-\d+\-\d+[\sT]{1}\d+:\d+[:\d+]$#');
+    }
+    static public function checkPreg ($s, $r)
+    {
+        if (is_array($s)) {
+            foreach ($s as $k => $v)
+                if (!preg_match($r, $v))
+                    return false;
+            return true;
+        }
+        return preg_match($r, $s) ? true : false;
+    }
+    static public function checkAvail ($s)
+    {
+        if (is_array($s)) {
+            return !self::_isEmptyArray($s);
+        }
+        return $s ? true : false;
+    }
+
+    static public function checkLocation($s, $sName, $sMethod = BX_DOL_FORM_METHOD_DEFAULT, $bAll = false)
+    {
+        if(!self::checkAvail($s))
+            return false;
+
+        $aIndexes = BxDolForm::$LOCATION_INDEXES;
+        foreach($aIndexes as $sIndex) {
+            $sValue = BxDolForm::getSubmittedValue($sName . '_' . $sIndex, $sMethod);
+            $bValue = self::checkAvail($sValue);
+
+            if($bAll && !$bValue)
+                return false;
+            if(!$bAll && $bValue)
+                return true;
+        }
+
+        return $bAll ? true : false;
+    }
+
+    static public function checkEmailOrEmpty($s)
+    {
+        if (empty($s))
+            return true;
+        return self::checkEmail($s);
+    }
+    static public function checkEmail($s)
+    {
+        if (false === strpos($s, '@')) // simple check
+            return false;
+
+        if (!preg_match("/^[\pL\pNd]/u", $s)) // must start with letter or number
+            return false;
+
+        if (!preg_match("/@[\pL\pNd\.\-]+$/u", $s)) // validate domain
+            return false;
+
+        $s = str_replace(array('@', '.', '-', '+', '_'), '', $s); // allowed symbols
+        if (preg_match("/[^\pL^\pNd]/u", $s)) // check for undesirable chars
+            return false;
+
+        return true;
+        // return self::checkPreg ($s, "/(([A-Za-z]{3,9}:(?:\/\/)?)(?:[\-;:&=\+\$,\w]+@)?[A-Za-z0-9\.\-]+|(?:www\.|[\-;:&=\+\$,\w]+@)[A-Za-z0-9\.\-]+)((?:\/[\+~%\/\.\w\-_]*)?\??(?:[\-\+=&;%@\.\w_]*)#?(?:[\.\!\/\\\w]*))?/");
+    }
+    static public function checkCaptcha($s)
+    {
+        $oCaptcha = BxDolCaptcha::getObjectInstance();
+        if (!$oCaptcha)
+            return true;
+        return $oCaptcha->check ();
+    }
+    static public function checkIsSpam(&$val, $sType = 'textarea')
+    {
+        $bSpam = false;
+        bx_alert('system', 'check_spam', 0, getLoggedId(), array('is_spam' => &$bSpam, 'content' => &$val, 'where' => 'form', 'type' => $sType));
+        return $bSpam;
+    }
+
+    // pass functions, prepare values to insert to database
+    static public function passInt ($s)
+    {
+        if (is_array($s)) {
+            $a = array ();
+            foreach ($s as $k => $v) {
+                $a[$k] = (int)trim($v);
+            }
+            return $a;
+        }
+        return (int)$s;
+    }
+    static public function passFloat ($s)
+    {
+        if (is_array($s)) {
+            $a = array ();
+            foreach ($s as $k => $v) {
+                $a[$k] = (float)$v;
+            }
+            return $a;
+        }
+        return (float)$s;
+    }
+    static public function passDate ($s)
+    {
+        if (is_array($s)) {
+            $a = array ();
+            foreach ($s as $k => $v) {
+                $a[$k] = self::_passDate ($v, BX_DATA_DATE);
+            }
+            return $a;
+        }
+        return self::_passDate ($s, BX_DATA_DATE);
+    }
+    static public function passDateTs ($s)
+    {
+        if (is_array($s)) {
+            $a = array ();
+            foreach ($s as $k => $v) {
+                $a[$k] = self::_passDate ($v, BX_DATA_DATE_TS);
+            }
+            return $a;
+        }
+        return self::_passDate ($s, BX_DATA_DATE_TS);
+    }
+    static public function passDateUtc ($s)
+    {
+        if (is_array($s)) {
+            $a = array ();
+            foreach ($s as $k => $v) {
+                $a[$k] = self::_passDate ($v, BX_DATA_DATE_TS_UTC);
+            }
+            return $a;
+        }
+        return self::_passDate ($s, BX_DATA_DATE_TS_UTC);
+    }
+    static public function passDateTime ($s)
+    {
+        if (is_array($s)) {
+            $a = array ();
+            foreach ($s as $k => $v) {
+                $a[$k] = self::_passDate ($v, BX_DATA_DATETIME);
+            }
+            return $a;
+        }
+        return self::_passDate ($s, BX_DATA_DATETIME);
+    }
+    static public function passDateTimeTs ($s)
+    {
+        if (is_array($s)) {
+            $a = array ();
+            foreach ($s as $k => $v) {
+                $a[$k] = self::_passDate ($v, BX_DATA_DATETIME_TS);
+            }
+            return $a;
+        }
+        return self::_passDate ($s, BX_DATA_DATETIME_TS);
+    }
+    static public function passDateTimeUtc ($s)
+    {
+        if (is_array($s)) {
+            $a = array ();
+            foreach ($s as $k => $v) {
+                $a[$k] = self::_passDate ($v, BX_DATA_DATETIME_TS_UTC);
+            }
+            return $a;
+        }
+        return self::_passDate ($s, BX_DATA_DATETIME_TS_UTC);
+    }
+    static public function _passDate ($s, $iFormat)
+    {
+        $iRet = bx_process_input ($s, $iFormat);
+        if (false === $iRet)
+            return 0;
+        return $iRet;
+    }    
+    static public function passXss ($s)
+    {
+        if (is_array($s)) {
+            $a = array ();
+            foreach ($s as $k => $v) {
+                $a[$k] = bx_process_input ($v, BX_DATA_TEXT); // "strip tags" option was here in 7.0
+            }
+            return $a;
+        }
+        return bx_process_input ($s, BX_DATA_TEXT); // "strip tags" option was here in 7.0
+    }
+
+	static public function passXssMultiline ($s)
+    {
+        if (is_array($s)) {
+            $a = array ();
+            foreach ($s as $k => $v) {
+                $a[$k] = bx_process_input ($v, BX_DATA_TEXT_MULTILINE);
+            }
+            return $a;
+        }
+        return bx_process_input ($s, BX_DATA_TEXT_MULTILINE);
+    }
+
+    static public function passXssHtml ($s)
+    {
+        if (is_array($s)) {
+            $a = array ();
+            foreach ($s as $k => $v) {
+                $a[$k] = bx_process_input ($v, BX_DATA_HTML);
+            }
+            return $a;
+        }
+        return bx_process_input ($s, BX_DATA_HTML);
+    }
+
+    static public function passAll ($s)
+    {
+        if (is_array($s)) {
+            $a = array ();
+            foreach ($s as $k => $v) {
+                $a[$k] = bx_process_input ($v);
+            }
+            return $a;
+        }
+        return bx_process_input ($s);
+    }
+
+    static public function passPreg ($s, $r)
+    {
+        if (is_array($s)) {
+            $a = array ();
+            foreach ($s as $k => $v) {
+                $a[$k] = self::_passPreg ($v, $r);
+            }
+            return $a;
+        }
+        return self::_passPreg($s, $r);
+    }
+    static public function _passPreg ($s, $r)
+    {
+        if (preg_match ($r, $s, $m)) {
+            return $m[1];
+        }
+        return '';
+    }
+    static public function passBoolean ($s)
+    {
+        if (is_array($s)) {
+            $a = array ();
+            foreach ($s as $k => $v) {
+                $a[$k] = $v == 'on' ? true : false;
+            }
+            return $a;
+        }
+        return $s == 'on' ? true : false;
+    }
+
+    static public function passSet ($s)
+    {
+        if (is_array($s)) {
+            $i = 0;
+            foreach ($s as $v)
+                $i |= pow (2, $v - 1);
+            return $i;
+        }
+        return (int)$s;
+    }
+
+    // display functions, prepare values to output to the screen
+
+    static public function displayDate ($i)
+    {
+        return bx_process_output ($i, BX_DATA_DATE);
+    }
+    static public function displayDateTs ($i)
+    {
+        return bx_process_output ($i, BX_DATA_DATE_TS);
+    }
+    static public function displayDateUtc ($i)
+    {
+        return bx_process_output ($i, BX_DATA_DATE_TS_UTC);
+    }    
+    static public function displayDateTime ($i)
+    {
+        return bx_process_output ($i, BX_DATA_DATETIME);
+    }
+    static public function displayDateTimeTs ($i)
+    {
+        return bx_process_output ($i, BX_DATA_DATETIME_TS);
+    }
+    static public function displayDateTimeUtc ($i)
+    {
+        return bx_process_output ($i, BX_DATA_DATETIME_TS_UTC);
+    }    
+    static public function displaySet ($i)
+    {
+        $i = (int)$i;
+
+        $bit = 1;
+        $bitpos = 1;
+        $a = array();
+        while ($bit < BX_DOL_INT_MAX && $bit > 0) {
+            if ($bit & $i)
+                $a[] = $bitpos;
+            $bit <<= 1;
+            $bitpos++;
+        }
+        return $a;
+    }
+
+    // for internal usage only
+
+    static public function _isEmptyArray ($a)
+    {
+        return bx_is_empty_array($a);
+    }
+
+    static public function _isFullArray ($a)
+    {
+        return bx_is_full_array($a);
+    }
+}
+
+/** @} */

+ 183 - 0
upgrade/files/11.0.0-11.0.1/files/inc/classes/BxDolGridConnections.php

@@ -0,0 +1,183 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaCore UNA Core
+ * @{
+ */
+
+require_once(BX_DIRECTORY_PATH_INC . "design.inc.php");
+
+class BxDolGridConnections extends BxTemplGrid
+{
+    protected $_bInit = false;
+    protected $_bOwner = false;
+    protected $_sObjectConnections = 'sys_profiles_friends';
+    protected $_oProfile;
+    protected $_oConnection;
+
+    public function __construct ($aOptions, $oTemplate = false)
+    {
+        parent::__construct ($aOptions, $oTemplate);
+        $this->_sDefaultSortingOrder = 'DESC';
+
+        $this->_aQueryAppendExclude[] = 'join_connections';
+
+        if(($iProfileId = bx_get('profile_id')) !== false)
+            $this->setProfile($iProfileId);
+    }
+
+    public function init()
+    {
+        if(!$this->_oProfile)
+            return false;
+
+        if ($this->_oProfile->id() == bx_get_logged_profile_id())
+            $this->_bOwner = true;
+
+        $this->_oConnection = BxDolConnection::getObjectInstance($this->_sObjectConnections);
+        if (!$this->_oConnection)
+            return false;
+
+        $aSQLParts = $this->_oConnection->getConnectedInitiatorsAsSQLParts('p', 'id', $this->_oProfile->id(), $this->_bOwner ? false : true);
+
+        $this->addMarkers(array(
+            'profile_id' => $this->_oProfile->id(),
+            'join_connections' => $aSQLParts['join']
+        ));
+    }
+
+    public function setProfile($iProfileId)
+    {
+        $this->_oProfile = BxDolProfile::getInstance((int)$iProfileId);
+
+        $this->_bInit = $this->init();
+    }
+
+    /**
+     * 'accept' action handler
+     */
+    public function performActionAccept()
+    {
+        list ($iId, $iViewedId) = $this->_prepareIds();
+
+        if (!$iId) {
+            echoJson(array('msg' => _t('_sys_txt_error_occured')));
+            exit;
+        }
+
+        $a = $this->_oConnection->actionAdd($iId, $iViewedId);
+        if (isset($a['err']) && $a['err'])
+            echoJson(array('msg' => $a['msg']));
+        else
+            echoJson(array('grid' => $this->getCode(false), 'blink' => $iId));
+    }
+
+    /**
+     * 'add friend' action handler
+     */
+    public function performActionAddFriend()
+    {
+        return $this->performActionAccept();
+    }
+
+    protected function _delete ($mixedId)
+    {
+        list ($iId, $iViewedId) = $this->_prepareIds();
+
+        if ($this->_oConnection->isConnected($iViewedId, $iId, true))
+            $a = $this->_oConnection->actionRemove($iId, $iViewedId);
+        else
+            $a = $this->_oConnection->actionReject($iId, $iViewedId);
+
+        if (isset($a['err']) && $a['err'])
+            return false;
+
+        return true;
+    }
+
+    protected function _getCellName ($mixedValue, $sKey, $aField, $aRow)
+    {
+        $oProfile = BxDolProfile::getInstance($aRow['id']);
+        if (!$oProfile)
+            return _t('_sys_txt_error_occured');
+
+        $sBadges = bx_srv($oProfile->getModule(), 'get_badges', array($oProfile->getContentId()));
+        return parent::_getCellDefault($oProfile->getUnit(0, array('template' => array('vars' => array('addon' => $sBadges)))), $sKey, $aField, $aRow);
+    }
+
+    protected function _getCellInfo ($mixedValue, $sKey, $aField, $aRow)
+    {
+        $s = '';
+
+        // for friend requests display mutual friends
+        if ($this->_bOwner && !$aRow['mutual']) {
+            $a = $this->_oConnection->getCommonContent($aRow['id'], bx_get_logged_profile_id(), true);
+            $i = count($a);
+            if (1 == $i) {
+                $iProfileId = array_pop($a);
+                $oProfile = BxDolProfile::getInstance($iProfileId);
+                $s = _t('_sys_txt_one_mutual_friend', $oProfile->getUrl(), $oProfile->getDisplayName());
+            } elseif ($i) {
+                $s = _t('_sys_txt_n_mutual_friends', $i);
+            }
+        }
+
+        // display friends number if no other info is available
+        if (!$s) {
+            $a = $this->_oConnection->getConnectedContent($aRow['id'], true);
+            $i = count($a);
+            $s = _t('_sys_txt_n_friends', $i);
+        }
+
+        return parent::_getCellDefault ($s, $sKey, $aField, $aRow);
+    }
+
+    protected function _getActionDelete ($sType, $sKey, $a, $isSmall = false, $isDisabled = false, $aRow = array())
+    {
+        if (!$this->_bOwner)
+            return '';
+        return parent::_getActionDefault ($sType, $sKey, $a, $isSmall, $isDisabled, $aRow);
+    }
+
+    protected function _getActionAccept ($sType, $sKey, $a, $isSmall = false, $isDisabled = false, $aRow = array())
+    {
+        if (!$this->_bOwner || $aRow['mutual'])
+            return '';
+
+        return parent::_getActionDefault ($sType, $sKey, $a, $isSmall, $isDisabled, $aRow);
+    }
+
+    protected function _getActionAddFriend ($sType, $sKey, $a, $isSmall = false, $isDisabled = false, $aRow = array())
+    {
+        if (!isLogged() || $this->_bOwner || $aRow['id'] == bx_get_logged_profile_id())
+            return '';
+
+        if ($this->_oConnection->isConnected($aRow['id'], bx_get_logged_profile_id()) || $this->_oConnection->isConnected(bx_get_logged_profile_id(), $aRow['id']))
+            return '';
+
+        return parent::_getActionDefault ($sType, $sKey, $a, $isSmall, $isDisabled, $aRow);
+    }
+
+    protected function _prepareIds ()
+    {
+        $iViewedId = false;
+        $iId = 0;
+        $aIds = bx_get('ids');
+        if ($aIds && is_array($aIds))
+            $mixedId = array_pop($aIds);
+
+        if (false === strpos($mixedId, ':')) {
+            $iId = (int)$mixedId;
+        }
+        else {
+            list ($iId, $iViewedId) = explode (':', $mixedId);
+            $iId = (int)$iId;
+            $iViewedId = (int)$iViewedId;
+        }
+        return array($iId, $iViewedId);
+    }
+}
+
+/** @} */

+ 376 - 0
upgrade/files/11.0.0-11.0.1/files/inc/classes/BxDolPayments.php

@@ -0,0 +1,376 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaCore UNA Core
+ * @{
+ */
+
+class BxDolPayments extends BxDolFactory implements iBxDolSingleton
+{
+	protected $_oDb;
+
+	protected $_aObjects;
+	protected $_sActive;
+
+    protected function __construct()
+    {
+        parent::__construct();
+
+        $this->_oDb = new BxDolPaymentsQuery();
+
+        $this->_aObjects = $this->_oDb->getObjects();
+        $this->_sActive = getParam('sys_default_payment');
+    }
+
+	static public function getInstance()
+    {
+        if(!isset($GLOBALS['bxDolClasses']['BxDolPayments']))
+        	$GLOBALS['bxDolClasses']['BxDolPayments'] = new BxDolPayments();
+
+		return $GLOBALS['bxDolClasses']['BxDolPayments'];
+    }
+
+	public function setActive($sActive)
+    {
+		$this->_sActive = $sActive;
+    }
+
+    public function getActive()
+    {
+    	return $this->_sActive;
+    }
+
+    public function isActive()
+    {
+    	if(empty($this->_sActive))
+    		return false;
+
+    	if(!BxDolModuleQuery::getInstance()->isModuleByName($this->_sActive))
+    		return false;
+
+    	return true;
+    }
+
+	public function getDetailsUrl()
+    {
+    	if(!BxDolRequest::serviceExists($this->_sActive, 'get_details_url', 'Details'))
+    		return '';
+
+    	return BxDolService::call($this->_sActive, 'get_details_url', array(), 'Details');
+    }
+
+    public function isAcceptingPayments($iVendorId, $sPaymentType = '')
+    {
+    	if(empty($this->_sActive) || !BxDolRequest::serviceExists($this->_sActive, 'is_accepting_payments'))
+    		return false;
+
+    	$aSrvParams = array($iVendorId, $sPaymentType);
+        return BxDolService::call($this->_sActive, 'is_accepting_payments', $aSrvParams);
+    }
+
+    /**
+     * @deprecated since version 11.0.0
+     * 
+     * @see BxDolPayments::isProviderOptions
+     */
+    public function isPaymentProvider($iVendorId, $sVendorProvider, $sPaymentType = '')
+    {
+    	return $this->isProviderOptions($iVendorId, $sVendorProvider);
+    }
+
+    /**
+     * @deprecated since version 11.0.0
+     * 
+     * @see BxDolPayments::getProviderOptions
+     */
+    public function getPaymentProvider($iVendorId, $sVendorProvider, $sPaymentType = '')
+    {
+    	return $this->getProviderOptions($iVendorId, $sVendorProvider, $sPaymentType);
+    }
+
+    public function isProviderOptions($iVendorId, $sVendorProvider, $sPaymentType = '')
+    {
+        if(empty($this->_sActive) || !BxDolRequest::serviceExists($this->_sActive, 'is_provider_options'))
+            return false;
+
+        $aSrvParams = array($iVendorId, $sVendorProvider, $sPaymentType);
+        return BxDolService::call($this->_sActive, 'is_provider_options', $aSrvParams);
+    }
+
+    public function getProvider($sProvider, $mixedVendorId = BX_PAYMENT_EMPTY_ID)
+    {
+        if(empty($this->_sActive) || !BxDolRequest::serviceExists($this->_sActive, 'get_provider'))
+            return false;
+
+        $aSrvParams = array($sProvider, $mixedVendorId);
+        return BxDolService::call($this->_sActive, 'get_provider', $aSrvParams);
+    }
+
+    public function getProviderOptions($iVendorId, $sVendorProvider, $sPaymentType = '')
+    {
+        if(empty($this->_sActive) || !BxDolRequest::serviceExists($this->_sActive, 'get_provider_options'))
+            return false;
+
+        $aSrvParams = array($iVendorId, $sVendorProvider, $sPaymentType);
+        return BxDolService::call($this->_sActive, 'get_provider_options', $aSrvParams);
+    }
+
+    public function getPayments()
+    {
+        $aPayments = array(
+			'' => _t('_Select_one')
+        );
+		foreach($this->_aObjects as $aObject) {
+			if(empty($aObject) || !is_array($aObject))
+				continue;
+
+			$aPayments[$aObject['object']] = _t($aObject['title']);
+		}
+
+        return $aPayments;
+    }
+
+    public function updateDependentModules($sModule = 'all', $bInstall = true)
+    {
+    	if(!BxDolRequest::serviceExists($this->_sActive, 'update_dependent_modules'))
+    		return;
+
+		BxDolService::call($this->_sActive, 'update_dependent_modules', array($sModule, $bInstall));
+    }
+
+    public function getProvidersCart($iVendorId)
+    {
+    	if(!BxDolRequest::serviceExists($this->_sActive, 'get_providers_cart'))
+    		return array();
+
+    	$aSrvParams = array($iVendorId);
+        return BxDolService::call($this->_sActive, 'get_providers_cart', $aSrvParams);
+    }
+
+	public function getOption($sOption)
+    {
+    	if(!BxDolRequest::serviceExists($this->_sActive, 'get_option'))
+    		return '';
+
+    	return BxDolService::call($this->_sActive, 'get_option', array($sOption));
+    }
+
+    public function getOrdersUrl()
+    {
+    	if(!BxDolRequest::serviceExists($this->_sActive, 'get_orders_url', 'Orders'))
+            return '';
+
+    	return BxDolService::call($this->_sActive, 'get_orders_url', array(), 'Orders');
+    }
+
+    public function getOrdersCount($sType)
+    {
+        if(!BxDolRequest::serviceExists($this->_sActive, 'get_orders_count', 'Orders'))
+            return array();
+
+        $aSrvParams = array($sType);
+        return BxDolService::call($this->_sActive, 'get_orders_count', $aSrvParams, 'Orders');
+    }
+
+    public function getOrdersInfo($aConditions)
+    {
+        if(!BxDolRequest::serviceExists($this->_sActive, 'get_orders_info', 'Orders'))
+            return array();
+
+        $aSrvParams = array($aConditions);
+        return BxDolService::call($this->_sActive, 'get_orders_info', $aSrvParams, 'Orders');
+    }
+
+    public function getPendingOrdersInfo($aConditions)
+    {
+        if(!BxDolRequest::serviceExists($this->_sActive, 'get_pending_orders_info', 'Orders'))
+            return array();
+
+        $aSrvParams = array($aConditions);
+        return BxDolService::call($this->_sActive, 'get_pending_orders_info', $aSrvParams, 'Orders');
+    }
+
+    public function getSubscriptionOrdersInfo($aConditions)
+    {
+        if(!BxDolRequest::serviceExists($this->_sActive, 'get_subscription_orders_info', 'Subscriptions'))
+            return array();
+
+        $aSrvParams = array($aConditions);
+        return BxDolService::call($this->_sActive, 'get_subscription_orders_info', $aSrvParams, 'Subscriptions');
+    }
+
+    public function getSubscriptionsInfo($aConditions, $bCheckInProvider = false)
+    {
+        if(!BxDolRequest::serviceExists($this->_sActive, 'get_subscriptions_info', 'Subscriptions'))
+            return array();
+
+        $aSrvParams = array($aConditions, $bCheckInProvider);
+        return BxDolService::call($this->_sActive, 'get_subscriptions_info', $aSrvParams, 'Subscriptions');
+    }
+
+    public function getCartUrl($iVendor = 0)
+    {
+    	if(!BxDolRequest::serviceExists($this->_sActive, 'get_cart_url', 'Cart'))
+    		return '';
+
+    	return BxDolService::call($this->_sActive, 'get_cart_url', array($iVendor), 'Cart');
+    }
+
+    public function getCartItemsCount()
+    {
+    	if(!BxDolRequest::serviceExists($this->_sActive, 'get_cart_items_count', 'Cart'))
+    		return 0;
+
+		$aSrvParams = array();
+		return BxDolService::call($this->_sActive, 'get_cart_items_count', $aSrvParams, 'Cart');
+    }
+    
+    public function getCartItems($iVendorId, $iModuleId)
+    {
+    	if(!BxDolRequest::serviceExists($this->_sActive, 'get_cart_items', 'Cart'))
+    		return 0;
+
+		$aSrvParams = array($iVendorId, $iModuleId);
+		return BxDolService::call($this->_sActive, 'get_cart_items', $aSrvParams, 'Cart');
+    }
+
+    public function getCartItemDescriptor($iVendorId, $iModuleId, $iItemId, $iItemCount)
+    {
+    	if(!BxDolRequest::serviceExists($this->_sActive, 'get_cart_item_descriptor', 'Cart'))
+    		return '';
+
+    	$aSrvParams = array($iVendorId, $iModuleId, $iItemId, $iItemCount);
+		return BxDolService::call($this->_sActive, 'get_cart_item_descriptor', $aSrvParams, 'Cart');
+    }
+
+    /**
+     * Returns cart JavaScript code which should be included in the page to make "Add To Cart" and "Subscribe" buttons work properly.
+     */
+    public function getCartJs($sType = '', $iVendorId = 0)
+    {
+    	if(!BxDolRequest::serviceExists($this->_sActive, 'get_cart_js', 'Cart'))
+			return '';
+
+		$aSrvParams = array($sType, $iVendorId);
+		return BxDolService::call($this->_sActive, 'get_cart_js', $aSrvParams, 'Cart');
+    }
+
+    /**
+     * Adds an item to cart in background mode.
+     */
+	public function addToCart($iVendorId, $mixedModuleId, $iItemId, $iItemCount, $aCustom = array())
+    {
+    	if(!BxDolRequest::serviceExists($this->_sActive, 'add_to_cart', 'Cart'))
+			return array();
+
+		$aSrvParams = array($iVendorId, $mixedModuleId, $iItemId, $iItemCount, $aCustom);
+		return BxDolService::call($this->_sActive, 'add_to_cart', $aSrvParams, 'Cart');
+    }
+
+    /**
+     * Returns "Add To Cart" JavaScript code to use in onclick attribute.
+     */
+    public function getAddToCartJs($iVendorId, $mixedModuleId, $iItemId, $iItemCount, $bNeedRedirect = false, $aCustom = array())
+    {
+    	if(!BxDolRequest::serviceExists($this->_sActive, 'get_add_to_cart_js', 'Cart'))
+			return array();
+
+		$aSrvParams = array($iVendorId, $mixedModuleId, $iItemId, $iItemCount, $bNeedRedirect, $aCustom);
+		return BxDolService::call($this->_sActive, 'get_add_to_cart_js', $aSrvParams, 'Cart');
+    }
+
+    /**
+     * Returns the whole "Add To Cart" link including HTML tag BUTTON and cart JavaScript code.
+     */
+    public function getAddToCartLink($iVendorId, $mixedModuleId, $iItemId, $iItemCount, $bNeedRedirect = false, $aCustom = array())
+    {
+		if(!BxDolRequest::serviceExists($this->_sActive, 'get_add_to_cart_link', 'Cart'))
+			return '';
+
+		$aSrvParams = array($iVendorId, $mixedModuleId, $iItemId, $iItemCount, $bNeedRedirect, $aCustom);
+		return BxDolService::call($this->_sActive, 'get_add_to_cart_link', $aSrvParams, 'Cart');
+    }
+
+    public function getSubscriptionsUrl()
+    {
+    	if(!BxDolRequest::serviceExists($this->_sActive, 'get_subscriptions_url', 'Subscriptions'))
+    		return '';
+
+    	return BxDolService::call($this->_sActive, 'get_subscriptions_url', array(), 'Subscriptions');
+    }
+
+	public function getSubscribeUrl($iVendorId, $sVendorProvider, $mixedModuleId, $iItemId, $iItemCount = 1)
+    {
+    	if(!BxDolRequest::serviceExists($this->_sActive, 'get_subscribe_url', 'Subscriptions'))
+    		return '';
+
+    	return BxDolService::call($this->_sActive, 'get_subscribe_url', array($iVendorId, $sVendorProvider, $mixedModuleId, $iItemId, $iItemCount), 'Subscriptions');
+    }
+
+	/**
+     * Returns "Subscribe" JavaScript code to use in onclick attribute.
+     */
+    public function getSubscribeJs($iVendorId, $sVendorProvider, $mixedModuleId, $iItemId, $iItemCount = 1, $sRedirect = '', $aCustom = array())
+    {
+    	if(!BxDolRequest::serviceExists($this->_sActive, 'get_subscribe_js', 'Subscriptions'))
+			return array();
+
+		$aSrvParams = array($iVendorId, $sVendorProvider, $mixedModuleId, $iItemId, $iItemCount, $sRedirect, $aCustom);
+		return BxDolService::call($this->_sActive, 'get_subscribe_js', $aSrvParams, 'Subscriptions');
+    }
+
+    /**
+     * Returns the whole "Subscribe" link including HTML tag BUTTON and cart JavaScript code.
+     */
+    public function getSubscribeLink($iVendorId, $sVendorProvider, $mixedModuleId, $iItemId, $iItemCount = 1, $sRedirect = '', $aCustom = array())
+    {
+        if(!BxDolRequest::serviceExists($this->_sActive, 'get_subscribe_link', 'Subscriptions'))
+            return '';
+
+        $aSrvParams = array($iVendorId, $sVendorProvider, $mixedModuleId, $iItemId, $iItemCount, $sRedirect, $aCustom);
+        return BxDolService::call($this->_sActive, 'get_subscribe_link', $aSrvParams, 'Subscriptions');
+    }
+
+    public function sendSubscriptionExpirationLetters($iPendingId, $sOrderId)
+    {
+        if(!BxDolRequest::serviceExists($this->_sActive, 'send_subscription_expiration_letters', 'Subscriptions'))
+            return '';
+
+        $aSrvParams = array($iPendingId, $sOrderId);
+        return BxDolService::call($this->_sActive, 'send_subscription_expiration_letters', $aSrvParams, 'Subscriptions');
+    }
+
+    public function initializeCheckout($iVendorId, $sProvider, $aItems = array())
+    {
+        if(!BxDolRequest::serviceExists($this->_sActive, 'initialize_checkout'))
+            return '';
+
+        $aSrvParams = array($iVendorId, $sProvider, $aItems);
+        return BxDolService::call($this->_sActive, 'initialize_checkout', $aSrvParams);
+    }
+
+    /**
+     * Isn't used anywhere for now. Is needed when license 'prolong' is requested from license keeping module like Market and ACL.  
+     */
+    public function prolongSubscription($sOrderId)
+    {
+        if(!BxDolRequest::serviceExists($this->_sActive, 'prolong_subscription'))
+            return '';
+
+        $aSrvParams = array($sOrderId);
+        return BxDolService::call($this->_sActive, 'prolong_subscription', $aSrvParams);
+    }
+
+    public function generateLicense()
+    {
+        if(!BxDolRequest::serviceExists($this->_sActive, 'generate_license'))
+            return '';
+
+        $aSrvParams = array();
+        return BxDolService::call($this->_sActive, 'generate_license', $aSrvParams, 'Module', true);
+    }
+}
+
+/** @} */

+ 608 - 0
upgrade/files/11.0.0-11.0.1/files/inc/classes/BxDolProfile.php

@@ -0,0 +1,608 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaCore UNA Core
+ * @{
+ */
+
+define('BX_PROFILE_ACTION_AUTO', 0); ///< automatic action without any checking
+define('BX_PROFILE_ACTION_MANUAL', 1); ///< manual action performed by human
+define('BX_PROFILE_ACTION_ROBOT', 2); ///< action peformed by some robot based on some conditions
+define('BX_PROFILE_ACTION_EXTERNAL', 2); ///< action peformed by external service, like join using OAuth 
+
+class BxDolProfile extends BxDolFactory implements iBxDolProfile
+{
+    protected $_iProfileID;
+    protected $_aProfile;
+    protected $_oQuery;
+
+    /**
+     * Constructor
+     */
+    protected function __construct ($iProfileId)
+    {
+        $iProfileId = (int)$iProfileId;
+        $sClass = get_class($this) . '_' . $iProfileId;
+        if (isset($GLOBALS['bxDolClasses'][$sClass]))
+            trigger_error ('Multiple instances are not allowed for the class: ' . get_class($this), E_USER_ERROR);
+
+        parent::__construct();
+
+        $this->_iProfileID = $iProfileId; // since constructor is protected $iProfileId is always valid
+        $this->_oQuery = BxDolProfileQuery::getInstance();
+    }
+
+    /**
+     * Prevent cloning the instance
+     */
+    public function __clone()
+    {
+        $sClass = get_class($this) . '_' . $this->_iProfileID;
+        if (isset($GLOBALS['bxDolClasses'][$sClass]))
+            trigger_error('Clone is not allowed for the class: ' . get_class($this), E_USER_ERROR);
+    }
+
+    /**
+     * Get singleton instance of Account Profile by account id
+     */
+    public static function getInstanceAccountProfile($iAccountId = false, $bClearCache = false)
+    {
+        if (!$iAccountId)
+            $iAccountId = getLoggedId();
+        $oQuery = BxDolProfileQuery::getInstance();
+        $aProfile = $oQuery->getProfileByContentTypeAccount($iAccountId, 'system', $iAccountId);
+        if (!$aProfile)
+            return false;
+        return self::getInstance($aProfile['id'], $bClearCache);
+    }
+
+    /**
+     * Get singleton instance of Profile by account id, content id and type
+     */
+    public static function getInstanceByContentTypeAccount($iContent, $sType, $iAccountId = false)
+    {
+        if (!$iAccountId)
+            $iAccountId = getLoggedId();
+        $oQuery = BxDolProfileQuery::getInstance();
+        $aProfile = $oQuery->getProfileByContentTypeAccount($iContent, $sType, $iAccountId);
+        if (!$aProfile)
+            return false;
+        return self::getInstance($aProfile['id']);
+    }
+
+    /**
+     * Get singleton instance of Profile by content id and type
+     */
+    public static function getInstanceByContentAndType($iContent, $sType, $bClearCache = false)
+    {
+        $oQuery = BxDolProfileQuery::getInstance();
+        $aProfile = $oQuery->getProfileByContentAndType($iContent, $sType, $bClearCache);
+        if (!$aProfile)
+            return false;
+        return self::getInstance($aProfile['id']);
+    }
+
+    /**
+     * Get singleton instance of Profile by Account id (currently active profile is returned)
+     */
+    public static function getInstanceByAccount($iAccountId = false, $bClearCache = false)
+    {
+        $oQuery = BxDolProfileQuery::getInstance();
+        $mixedProfileId = $oQuery->getCurrentProfileByAccount($iAccountId, $bClearCache);
+
+        return self::getInstance($mixedProfileId);
+    }
+
+    /**
+     * Get singleton instance of Profile by profile id, if profile isn't found it returns instance of BxDolProfileAnonymous or BxDolProfileUndefined
+     */
+    public static function getInstanceMagic($mixedProfileId = false, $bClearCache = false)
+    {
+        if ($mixedProfileId < 0)
+            return BxDolProfileAnonymous::getInstance($mixedProfileId);
+
+        if (0 == $mixedProfileId || !($oProfile = self::getInstance($mixedProfileId, $bClearCache)))
+            return BxDolProfileUndefined::getInstance();
+        
+        return $oProfile;
+    }
+
+    /**
+     * Get singleton instance of Profile by profile id
+     */
+    public static function getInstance($mixedProfileId = false, $bClearCache = false)
+    {
+        if (!$mixedProfileId) {
+            $oQuery = BxDolProfileQuery::getInstance();
+            $mixedProfileId = $oQuery->getCurrentProfileByAccount(getLoggedId(), $bClearCache);
+        }
+
+        $iProfileId = self::getID($mixedProfileId);
+        if (!$iProfileId)
+            return false;
+
+        $sClass = __CLASS__ . '_' . $iProfileId;
+        if (!isset($GLOBALS['bxDolClasses'][$sClass]))
+            $GLOBALS['bxDolClasses'][$sClass] = new BxDolProfile($iProfileId);
+
+        return $GLOBALS['bxDolClasses'][$sClass];
+    }
+
+    /**
+     * Get profile id
+     */
+    public function id()
+    {
+        $aProfile = $this->getInfo($this->_iProfileID);
+        return isset($aProfile['id']) ? $aProfile['id'] : false;
+    }
+
+    /**
+     * Get account id associated with the profile
+     */
+    public function getAccountId()
+    {
+        $aInfo = $this->getInfo();
+        return $aInfo['account_id'];
+    }
+
+    /**
+     * Get account object associated with the profile
+     */
+    public function getAccountObject()
+    {
+        return BxDolAccount::getInstance($this->getAccountId());
+    }
+
+    /**
+     * Get content id associated with the profile
+     */
+    public function getContentId()
+    {
+        $aInfo = $this->getInfo();
+        return $aInfo['content_id'];
+    }
+
+    /**
+     * Check if profile status is active
+     */
+    public function isActive($iProfileId = false)
+    {
+        return BX_PROFILE_STATUS_ACTIVE == $this->getStatus($iProfileId) && ($this->getAccountObject()->isConfirmed() || !getParam('sys_account_hide_unconfirmed_accounts'));
+    }
+
+    /**
+     * Is profile online
+     */
+    public function isOnline($iProfileId = false)
+    {
+        $iProfileId = (int)$iProfileId ? $iProfileId : $this->_iProfileID;
+        return $this->_oQuery->isOnline($iProfileId);
+    }
+
+    /**
+     * Is profile can 'Act as Profile'
+     */
+    public function isActAsProfile($iProfileId = false)
+    {
+        $aInfo = $this->_oQuery->getInfoById((int)$iProfileId ? $iProfileId : $this->_iProfileID);
+        return BxDolService::call($aInfo['type'], 'act_as_profile');
+    }
+
+    /**
+     * Get profile status
+     */
+    public function getStatus($iProfileId = false)
+    {
+        $aInfo = $this->_oQuery->getInfoById((int)$iProfileId ? $iProfileId : $this->_iProfileID);
+        return $aInfo['status'];
+    }
+
+    /**
+     * Get profile module name
+     */
+    public function getModule($iProfileId = false)
+    {
+        $aInfo = $this->_oQuery->getInfoById((int)$iProfileId ? $iProfileId : $this->_iProfileID);
+        return $aInfo['type'];
+    }
+
+    /**
+     * Get profile info
+     */
+    public function getInfo($iProfileId = 0)
+    {
+        if ($iProfileId && $iProfileId != $this->_iProfileID)
+            return $this->_oQuery->getInfoById((int)$iProfileId ? $iProfileId : $this->_iProfileID);
+
+        if ($this->_aProfile)
+            return $this->_aProfile;
+        
+        $this->_aProfile = $this->_oQuery->getInfoById((int)$iProfileId ? $iProfileId : $this->_iProfileID);
+        return $this->_aProfile;
+    }
+
+    /**
+     * Validate profile id.
+     * @param $s - profile id
+     * @return profile id or false if profile was not found
+     */
+    static public function getID($s)
+    {
+        $iId = BxDolProfileQuery::getInstance()->getIdById((int)$s);
+        return $iId ? $iId : false;
+    }
+
+    /**
+     * Get name to display in thumbnail
+     */
+    public function getDisplayName($iProfileId = 0)
+    {
+        $aInfo = $this->getInfo($iProfileId);
+        $sDisplayName = BxDolService::call($aInfo['type'], 'profile_name', array($aInfo['content_id']));
+        bx_alert('profile', 'profile_name', $iProfileId, 0, array('info' => $aInfo, 'display_name' => &$sDisplayName));
+        return $sDisplayName;
+    }
+
+    /**
+     * Get profile url
+     */
+    public function getUrl($iProfileId = 0)
+    {
+        $aInfo = $this->getInfo($iProfileId);
+        return BxDolService::call($aInfo['type'], 'profile_url', array($aInfo['content_id']));
+    }
+
+    /**
+     * Get profile unit
+     */
+    public function getUnit($iProfileId = 0, $aParams = array())
+    {
+        $aInfo = $this->getInfo($iProfileId);
+        return BxDolService::call($aInfo['type'], 'profile_unit', array($aInfo['content_id'], $aParams));
+    }
+    
+    /**
+     * Get badges
+     */
+    public function getBadges($iProfileId = 0)
+    {
+        $aInfo = $this->getInfo($iProfileId);
+        return BxDolService::call($aInfo['type'], 'get_badges', array($aInfo['content_id'], true, true));
+    }
+
+	/**
+     * Check whether a profile has real image uploaded by user.
+     */
+    public function hasImage($iProfileId = 0)
+    {
+        $aInfo = $this->getInfo($iProfileId);
+        return BxDolService::call($aInfo['type'], 'has_image', array($aInfo['content_id']));
+    }
+
+    /**
+     * Get picture url
+     */
+    public function getPicture($iProfileId = 0)
+    {
+        $aInfo = $this->getInfo($iProfileId);
+        return BxDolService::call($aInfo['type'], 'profile_picture', array($aInfo['content_id']));
+    }
+
+    /**
+     * Get avatar url
+     */
+    public function getAvatar($iProfileId = 0)
+    {
+        $aInfo = $this->getInfo($iProfileId);
+        return BxDolService::call($aInfo['type'], 'profile_avatar', array($aInfo['content_id']));
+    }
+
+    /**
+     * Get big (2x) avatar url
+     */
+    public function getAvatarBig($iProfileId = 0)
+    {
+        $aInfo = $this->getInfo($iProfileId);
+        return BxDolService::call($aInfo['type'], 'profile_avatar_big', array($aInfo['content_id']));
+    }
+
+    /**
+     * Get cover url
+     */
+    public function getCover($iProfileId = 0)
+    {
+        $aInfo = $this->getInfo($iProfileId);
+        return BxDolService::call($aInfo['type'], 'profile_cover', array($aInfo['content_id']));
+    }
+
+	/**
+     * Get unit cover url
+     */
+    public function getUnitCover($iProfileId = 0)
+    {
+        $aInfo = $this->getInfo($iProfileId);
+        return BxDolService::call($aInfo['type'], 'profile_unit_cover', array($aInfo['content_id']));
+    }
+
+    /**
+     * Get thumbnail url
+     */
+    public function getThumb($iProfileId = 0)
+    {
+        $aInfo = $this->getInfo($iProfileId);
+        return BxDolService::call($aInfo['type'], 'profile_thumb', array($aInfo['content_id']));
+    }
+
+    /**
+     * Get icon url
+     */
+    public function getIcon($iProfileId = 0)
+    {
+        $aInfo = $this->getInfo($iProfileId);
+        return BxDolService::call($aInfo['type'], 'profile_icon', array($aInfo['content_id']));
+    }
+
+    /**
+     * Get module icon
+     */
+    public function getIconModule($iProfileId = 0)
+    {
+        $aInfo = $this->getInfo($iProfileId);
+        return BxDolService::call($aInfo['type'], 'module_icon');
+    }
+
+    /**
+     * get profile edit page url
+     */
+    public function getEditUrl($iProfileId = 0)
+    {
+        $aInfo = $this->getInfo($iProfileId);
+        return BxDolService::call($aInfo['type'], 'profile_edit_url', array($aInfo['content_id']));
+    }
+
+    /**
+     * @see iBxDolProfile::checkAllowedProfileView
+     */
+    public function checkAllowedProfileView($iProfileId = 0)
+    {
+        $aInfo = $this->getInfo($iProfileId);
+        return BxDolService::call($aInfo['type'], 'check_allowed_profile_view', array($aInfo['content_id']));
+    }
+
+    /**
+     * @see iBxDolProfile::checkAllowedProfileContact
+     */
+    public function checkAllowedProfileContact($iProfileId = 0)
+    {
+        $aInfo = $this->getInfo($iProfileId);
+        return BxDolService::call($aInfo['type'], 'check_allowed_profile_contact', array($aInfo['content_id']));
+    }
+    
+    
+    /**
+     * @see iBxDolProfile::checkAllowedPostInProfile
+     */
+    public function checkAllowedPostInProfile($iProfileId = 0)
+    {
+        $aInfo = $this->getInfo($iProfileId);
+        return BxDolService::call($aInfo['type'], 'check_allowed_post_in_profile', array($aInfo['content_id']));
+    }
+    
+    /**
+     * Delete profile.
+     * @param $ID - optional profile id to delete
+     * @param $bDeleteWithContent - delete profile with all its contents
+     * @param $bForceDelete - force deletetion is case of account profile deletion
+     * @return false on error, or true on success
+     */
+    function delete($ID = false, $bDeleteWithContent = false, $bForceDelete = false)
+    {
+        $ID = (int)$ID;
+        if (!$ID)
+            $ID = $this->_iProfileID;
+
+        $aProfileInfo = $this->_oQuery->getInfoById($ID);
+        if (!$aProfileInfo)
+            return false;
+
+        // delete system profiles (accounts) is not allowed, instead - delete whole account
+        if (!$bForceDelete && 'system' == $aProfileInfo['type'])
+            return false;
+
+        // delete actual profile
+        if ($sErrorMsg = BxDolService::call($aProfileInfo['type'] , 'delete_entity_service', array($aProfileInfo['content_id'], $bDeleteWithContent)))
+            return false;
+
+        // switch profile context if deleted profile is active profile context
+        $oAccount = BxDolAccount::getInstance ($aProfileInfo['account_id']);
+        $aAccountInfo = $oAccount->getInfo();
+        if (!$bForceDelete && $ID == $aAccountInfo['profile_id'])
+            $oAccount->updateProfileContextAuto($ID);
+
+        // create system event before deletion
+        $isStopDeletion = false;
+        bx_alert('profile', 'before_delete', $ID, 0, array('delete_with_content' => $bDeleteWithContent, 'stop_deletion' => &$isStopDeletion));
+        if ($isStopDeletion)
+            return false;
+
+        // delete associated content
+        if($bDeleteWithContent) {
+	        BxDolCmts::onAuthorDelete($ID);
+
+	        BxDolReport::onAuthorDelete($ID);
+
+	        BxDolVote::onAuthorDelete($ID);
+
+	        BxDolFavorite::onAuthorDelete($ID);
+
+	        BxDolView::onAuthorDelete($ID);
+        }
+
+        // delete connections
+        $oConn = BxDolConnection::getObjectInstance('sys_profiles_friends');
+        $oConn->onDeleteInitiatorAndContent($ID);
+
+        $oConn = BxDolConnection::getObjectInstance('sys_profiles_subscriptions');
+        $oConn->onDeleteInitiatorAndContent($ID);
+
+        // delete profile's acl levels
+        BxDolAcl::getInstance()->onProfileDelete($ID);
+
+        // delete profile
+        if (!$this->_oQuery->delete($ID))
+            return false;
+
+        // create system event
+        bx_alert('profile', 'delete', $ID, 0, array('delete_with_content' => $bDeleteWithContent));
+
+        // unset class instance to prevent creating the instance again
+        $this->_iProfileID = 0;
+        $sClass = get_class($this) . '_' . $ID;
+        unset($GLOBALS['bxDolClasses'][$sClass]);
+
+        return true;
+    }
+
+    /**
+     * Insert account and content id association. Also if currect profile id is not defined - it updates current profile id in account.
+     * @param $iAccountId account id
+     * @param $iContentId content id
+     * @param $sStatus profile status
+     * @param $sType profile content type
+     * @return inserted profile's id
+     */
+    static public function add ($iAction, $iAccountId, $iContentId, $sStatus, $sType = 'system')
+    {
+        $oQuery = BxDolProfileQuery::getInstance();
+        if (!($iProfileId = $oQuery->insertProfile ($iAccountId, $iContentId, $sStatus, $sType)))
+            return false;
+        bx_alert('profile', 'add', $iProfileId, 0, array('module' => $sType, 'content' => $iContentId, 'account' => $iAccountId, 'status' => $sStatus, 'action' => $iAction, 'profile_id' => &$iProfileId));
+        return $iProfileId;
+    }
+
+    /**
+     * Change profile status to 'Active'
+     */
+    public function activate($iAction, $iProfileId = 0, $bSendEmailNotification = true)
+    {
+        $sStatus = $this->getStatus($iProfileId);
+        return $this->changeStatus(BX_PROFILE_STATUS_ACTIVE, BX_PROFILE_STATUS_PENDING == $sStatus ? 'approve' : 'activate', $iAction, $iProfileId, $bSendEmailNotification);
+    }
+
+    /**
+     * Change profile status from 'Pending' to the next level - 'Active'
+     */
+    public function approve($iAction, $iProfileId = 0, $bSendEmailNotification = true)
+    {
+        return $this->changeStatus(BX_PROFILE_STATUS_ACTIVE, 'approve', $iAction, $iProfileId, $bSendEmailNotification);
+    }
+
+    /**
+     * Change profile status to 'Pending'
+     */
+    public function disapprove($iAction, $iProfileId = 0, $bSendEmailNotification = true)
+    {
+        return $this->changeStatus(BX_PROFILE_STATUS_PENDING, 'disapprove', $iAction, $iProfileId, $bSendEmailNotification);
+    }
+
+    public function doAudit($sAction, $aData = array())
+    {
+        bx_audit(
+            $this->getContentId(), 
+            $this->getModule(), 
+            $sAction,  
+            array('content_title' => $this->getDisplayName(), 'data' => $aData)
+        );  
+    }
+    
+    /**
+     * Change profile status to 'Suspended'
+     */
+    public function suspend($iAction, $iProfileId = 0, $bSendEmailNotification = true)
+    {
+        return $this->changeStatus(BX_PROFILE_STATUS_SUSPENDED, 'suspend', $iAction, $iProfileId, $bSendEmailNotification);
+    }
+
+    protected function changeStatus($sStatus, $sAlertActionName, $iAction, $iProfileId = 0, $bSendEmailNotification = true)
+    {
+        if (!$iProfileId)
+            $iProfileId = $this->_iProfileID;
+
+        // get account and profile objects
+        $oProfile = BxDolProfile::getInstance($iProfileId);
+        $oAccount = $oProfile->getAccountObject();
+        if (!$oProfile || !$oAccount)
+            return false;
+
+        // change status
+        if (!$this->_oQuery->changeStatus($iProfileId, $sStatus))
+            return false;
+
+        $this->_aProfile = array();
+
+        // alert about status changing
+        bx_alert('profile', $sAlertActionName, $iProfileId, false, array('action' => $iAction));
+        
+        $this->doAudit('_sys_audit_action_set_status_' . $sStatus);
+        
+        // send email to member about status change
+        if ($bSendEmailNotification)
+            sendMailTemplate('t_ChangeStatus' . ucfirst($sStatus), $oAccount->id(), $iProfileId, array('status' => $sStatus), BX_EMAIL_SYSTEM);
+
+        return true;
+    }
+    
+    /**
+     * Display informer message if it is possible to switch to this profile
+     */
+    public function checkSwitchToProfile($oTemplate = null, $iViewerAccountId = false, $iViewerProfileId = false)
+    {
+        if (false === $iViewerAccountId)
+            $iViewerAccountId = getLoggedId();
+        if (false === $iViewerProfileId)
+            $iViewerProfileId = bx_get_logged_profile_id();
+
+        if (!$iViewerAccountId || !$iViewerProfileId)
+            return;
+
+        $aCheck = checkActionModule($iViewerProfileId, 'switch to any profile', 'system', false);
+        $bAllowSwitchToAnyProfile = $aCheck[CHECK_ACTION_RESULT] === CHECK_ACTION_RESULT_ALLOWED;
+        
+        $iSwitchToAccountId = $this->getAccountId();
+        $iSwitchToProfileId = $this->id();
+        $bCanSwitch = ($iSwitchToAccountId == $iViewerAccountId || $bAllowSwitchToAnyProfile);
+        bx_alert('account', 'check_switch_context', $iSwitchToAccountId, $iViewerProfileId, array('switch_to_profile' => $iSwitchToProfileId, 'viewer_account' => $iViewerAccountId, 'override_result' => &$bCanSwitch));
+
+        if (!$bCanSwitch ||  $iViewerProfileId == $iSwitchToProfileId)
+            return;
+
+        $oInformer = BxDolInformer::getInstance($oTemplate);
+        if ($oInformer){
+            if ($iSwitchToAccountId == $iViewerAccountId){
+                $oInformer->add('sys-switch-profile-context', _t('_sys_txt_account_profile_context_change_suggestion', BxDolPermalinks::getInstance()->permalink('page.php?i=account-profile-switcher', array('switch_to_profile' => $this->id(), 'redirect_back' => 1))), BX_INFORMER_INFO);
+            }
+            else{
+                $oInformer->add('sys-switch-profile-context', _t('_sys_txt_account_profile_context_change_to_another_suggestion', BxDolPermalinks::getInstance()->permalink('page.php?i=account-profile-switcher', array('switch_to_profile' => $this->id(), 'redirect_back' => 1))), BX_INFORMER_INFO);
+            }
+        }
+    }
+
+	/**
+     * Add permament messages.
+     */
+    public function addInformerPermanentMessages ($oInformer)
+    {
+    	$aInfo = $this->getInfo();
+    	$aProfiles = $this->_oQuery->getProfilesByAccount($aInfo['account_id']);
+
+        if ($aInfo['type'] == 'system' && count($aProfiles) == 1) {
+            $sUrl = BxDolPermalinks::getInstance()->permalink('page.php?i=account-profile-switcher');
+            $oInformer->add('sys-account-profile-system', _t('_sys_txt_account_profile_system', $sUrl), BX_INFORMER_ALERT);
+        }
+    }
+}
+
+/** @} */

+ 395 - 0
upgrade/files/11.0.0-11.0.1/files/inc/classes/BxDolProfileQuery.php

@@ -0,0 +1,395 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaCore UNA Core
+ * @{
+ */
+
+/**
+ * All queries related to profiles
+ */
+class BxDolProfileQuery extends BxDolDb implements iBxDolSingleton
+{
+    protected function __construct()
+    {
+        if (isset($GLOBALS['bxDolClasses'][get_class($this)]))
+            trigger_error ('Multiple instances are not allowed for the class: ' . get_class($this), E_USER_ERROR);
+
+        parent::__construct();
+    }
+
+    /**
+     * Prevent cloning the instance
+     */
+    public function __clone()
+    {
+        if (isset($GLOBALS['bxDolClasses'][get_class($this)]))
+            trigger_error('Clone is not allowed for the class: ' . get_class($this), E_USER_ERROR);
+    }
+
+    /**
+     * Get singleton instance of the class
+     */
+    public static function getInstance()
+    {
+        if(!isset($GLOBALS['bxDolClasses'][__CLASS__]))
+            $GLOBALS['bxDolClasses'][__CLASS__] = new BxDolProfileQuery();
+
+        return $GLOBALS['bxDolClasses'][__CLASS__];
+    }
+
+    /**
+     * Get all account profiles.
+     * @param  string  $iAccountId account id
+     * @return profile array
+     */
+    public function getProfilesByAccount ($iAccountId)
+    {
+        if (!$iAccountId)
+            return array();
+
+        $oAccountQuery = BxDolAccountQuery::getInstance();
+        $aAccountInfo = $oAccountQuery->getInfoById($iAccountId);
+
+        $sSql = $this->prepare("SELECT * FROM `sys_profiles` WHERE `account_id` = ? ORDER BY `id` = ? DESC", $iAccountId, $aAccountInfo['profile_id']);
+        return $this->getAllWithKey($sSql, 'id');
+    }
+
+    /**
+     * Get profile by content id, type and account.
+     * @param  string $iAccountId account id
+     * @return array  if aprofile ids, key is profile id
+     */
+    public function getProfileByContentTypeAccount ($iContentId, $sType, $iAccountId)
+    {
+        $sSql = $this->prepare("SELECT * FROM `sys_profiles` WHERE `account_id` = ? AND `type` = ? AND `content_id` = ?", $iAccountId, $sType, $iContentId);
+        return $this->getRow($sSql);
+    }
+
+    /**
+     * Get profile by content id and type.
+     * @param  string $iAccountId account id
+     * @return array  if aprofile ids, key is profile id
+     */
+    public function getProfileByContentAndType ($iContentId, $sType, $bClearCache = false)
+    {
+        if (!$iContentId)
+            return false;
+
+        $sKey = 'BxDolProfileQuery::getProfileByContentAndType' . $iContentId . $sType;
+        if ($bClearCache)
+            $this->cleanMemory($sKey);
+
+        $sSql = $this->prepare("SELECT * FROM `sys_profiles` WHERE `content_id` = ? AND `type` = ?", $iContentId, $sType);        
+        $mixedResult = $this->fromMemory($sKey, 'getRow', $sSql);
+        if (!$mixedResult)
+            $this->cleanMemory($sKey);
+        return $mixedResult;
+    }
+
+    public function getConnectedProfilesByType ($aSqlParts, $sType, $iStart, $iLimit)
+    {
+        if(empty($aSqlParts['join']))
+            return array();
+
+        $aBindings = array();
+
+        $sSelectClause = '';
+        if(!empty($aSqlParts['fields']))
+            foreach($aSqlParts['fields'] as $sName => $sField)
+                $sSelectClause .= ', ' . $sField;
+
+        $sWhereClause = '';
+        if(!empty($sType)) {
+            $sWhereClause = ' AND `sys_profiles`.`type`=:type';
+
+            $aBindings['type'] = $sType;
+        }
+
+        $sOrderClause = '';
+        if(!empty($aSqlParts['fields']['added']))
+            $sOrderClause = ' ORDER BY ' . $aSqlParts['fields']['added'] . ' DESC';
+
+        $sLimitClause = '';
+        if(!empty($iLimit))
+            $sLimitClause = $this->prepareAsString(' LIMIT ?, ?', $iStart, $iLimit);
+
+        return $this->getAllWithKey("SELECT `sys_profiles`.*" . $sSelectClause . " FROM `sys_profiles` " . $aSqlParts['join'] . " WHERE 1" . $sWhereClause . $sOrderClause . $sLimitClause, 'id', $aBindings);
+    }
+
+    /**
+     * Insert account and content id association. Also if currect profile id is not defined - it updates current profile id in account.
+     * @param $iAccountId account id
+     * @param $iContentId content id
+     * @param $sStatus profile status
+     * @param $sType profile content type
+     * @return inserted profile's id
+     */
+    public function insertProfile ($iAccountId, $iContentId, $sStatus, $sType = 'system')
+    {
+        $sSql = $this->prepare("INSERT INTO `sys_profiles` SET `account_id` = ?, `type` = ?, `content_id` = ?, `status` = ?", $iAccountId, $sType, $iContentId, $sStatus);
+        if (!$this->query($sSql))
+            return false;
+        $iProfileId = $this->lastId();
+        $this->getCurrentProfileByAccount($iAccountId); // it updates current profile id automatically if it is not defined
+        return $iProfileId;
+    }
+
+    /**
+     * Update profile's status
+     * @param $iProfileId profile id
+     * @param $sStatus profile status
+     * @return true on success or false otherwise
+     */
+    public function changeStatus ($iProfileId, $sStatus)
+    {
+        return $this->_updateField ($iProfileId, 'status', $sStatus);
+    }
+
+    /**
+     * Get current account profile.
+     * @param  string  $iAccountId account id
+     * @return current account's profile id
+     */
+    public function getCurrentProfileByAccount ($iAccountId, $bClearCache = false)
+    {
+        if (!$iAccountId)
+            return false;
+
+        $sKey = 'BxDolProfileQuery::getCurrentProfileByAccount' . $iAccountId;
+        if ($bClearCache)
+            $this->cleanMemory($sKey);
+
+        $sSql = $this->prepare("SELECT `profile_id` FROM `sys_accounts` WHERE `id` = ? LIMIT 1", $iAccountId);        
+        $iProfileId = $this->fromMemory($sKey, 'getOne', $sSql);
+        if (!$iProfileId) {
+            $this->cleanMemory($sKey);
+            $sSql = $this->prepare("SELECT `id` FROM `sys_profiles` WHERE `account_id` = ? LIMIT 1", $iAccountId);
+            $iProfileId = $this->getOne($sSql);
+            if (!$iProfileId)
+                return false;
+
+            if (!BxDolAccountQuery::getInstance()->updateCurrentProfile($iAccountId, $iProfileId))
+                return false;
+        }
+        return $iProfileId;
+    }
+
+    /**
+     * Get profile by specified field name and value.
+     * It is for internal usage only.
+     * Use other funtions to get profile info, like getInfoById, etc.
+     * @param  string $sField database field name
+     * @param  mixed  $sValue database field value
+     * @return array  with porfile info
+     */
+    protected function _getDataByField ($sField, $sValue)
+    {
+        $sSql = $this->prepare("SELECT * FROM `sys_profiles` WHERE `$sField` = ? LIMIT 1", $sValue);
+        return $this->getRow($sSql);
+    }
+
+    /**
+     * get profile id by id
+     */
+    public function getIdById($iId, $bClearCache = false)
+    {
+        if (!$iId)
+            return false;
+
+        $sKey = 'BxDolProfileQuery::getIdById' . $iId;
+        if ($bClearCache)
+            $this->cleanMemory($sKey);
+        
+        $sSql = $this->prepare("SELECT `id` FROM `sys_profiles` WHERE `id` = ? LIMIT 1", $iId);
+        $mixedResult = $this->fromMemory($sKey, 'getOne', $sSql);
+        if (!$mixedResult)
+            $this->cleanMemory($sKey);
+        return $mixedResult;
+    }
+
+    /**
+     * get account info by profile id
+     */
+    public function getAccountInfoByProfileId($iId)
+    {
+        $sSql = $this->prepare("SELECT `a`.* FROM `sys_accounts` AS `a` INNER JOIN `sys_profiles` AS `p` ON (`p`.`account_id` = `a`.`id`) WHERE `p`.`id` = ? LIMIT 1", $iId);
+        return $this->getRow($sSql);
+    }
+
+    /**
+     * get account email by profile id
+     */
+    public function getEmailById($iId)
+    {
+        $a = $this->getAccountInfoByProfileId($iId);
+        if (!$a || empty($a['email']))
+            return false;
+        return $a['email'];
+    }
+
+    /**
+     * Get profile info by id
+     * @param  int   $iID profile id
+     * @return array with profile info
+     */
+    public function getInfoById( $iID )
+    {
+        return $this->_getDataByField('id', (int)$iID);
+    }
+
+	/**
+     * Is profile online by id
+     * @param  int $iId profile id
+     * @return profile online status
+     */
+    public function isOnline($iId)
+    {
+        $sSql = $this->prepare("SELECT 
+        		`tp`.`id` 
+        	FROM `sys_profiles` AS `tp` 
+        	INNER JOIN `sys_accounts` AS `ta` ON `tp`.`account_id`=`ta`.`id` 
+        	INNER JOIN `sys_sessions` AS `ts` ON `tp`.`account_id`=`ts`.`user_id` 
+        	WHERE 
+        		`tp`.`id` = ? AND 
+        		`ta`.`profile_id`=`tp`.`id` AND 
+        		`ts`.`date` > (UNIX_TIMESTAMP() - 60 * ?) 
+        	LIMIT 1", $iId, (int)getParam('sys_account_online_time'));
+        return (int)$this->getOne($sSql) > 0;
+    }
+    
+    /**
+     * Get online profiles count 
+     * @return online profiles count 
+     */
+    public function getOnlineCount()
+    {
+        $sSql = $this->prepare("SELECT 
+        		COUNT(`tp`.`id`) 
+        	FROM `sys_profiles` AS `tp` 
+        	INNER JOIN `sys_accounts` AS `ta` ON `tp`.`account_id`=`ta`.`id` 
+        	INNER JOIN `sys_sessions` AS `ts` ON `tp`.`account_id`=`ts`.`user_id` 
+        	WHERE 
+        		`ta`.`profile_id`=`tp`.`id` AND 
+        		`ts`.`date` > (UNIX_TIMESTAMP() - 60 * ?)", (int)getParam('sys_account_online_time'));
+        return (int)$this->getOne($sSql);
+    }
+
+    public function getProfileQuota($iProfileId)
+    {
+        if (!$iProfileId) // for guests and storages without owner don't count per-user quota
+            return array('current_size' => 0, 'current_number' => 0, 'quota_size' => 0, 'quota_number' => 0, 'max_file_size' => 0);
+
+        $sQuery = $this->prepare("SELECT `current_size`, `current_number`, 0 as `quota_size`, 0 as `quota_number`, 0 as `max_file_size` FROM `sys_storage_user_quotas` WHERE `profile_id` = ?", $iProfileId);
+        $a = $this->getRow($sQuery);
+        if (!is_array($a) || !$a)
+            $a = array ('current_size' => 0, 'current_number' => 0, 'quota_size' => 0, 'quota_number' => 0, 'max_file_size' => 0);
+
+        // get quota_number and quota_size from user's acl/membership
+        $aMembershipInfo = BxDolAcl::getInstance()->getMemberMembershipInfo($iProfileId);
+        if ($aMembershipInfo) {
+            if (isset($aMembershipInfo['quota_size']))
+                $a['quota_size'] = $aMembershipInfo['quota_size'];
+            if (isset($aMembershipInfo['quota_number']))
+                $a['quota_number'] = $aMembershipInfo['quota_number'];
+            if (isset($aMembershipInfo['quota_max_file_size']))
+                $a['max_file_size'] = $aMembershipInfo['quota_max_file_size'];
+        }
+
+        return $a;
+    }
+
+    public function updateProfileQuota($iProfileId, $iSize, $iNumber = 1)
+    {
+        if (!$iProfileId) // for guests and storages without owner don't update per-user quota
+            return true;
+
+        $iTime = time();
+        $sQuery = $this->prepare("
+            INSERT INTO `sys_storage_user_quotas`
+            SET `profile_id` = ?, `current_size` = `current_size` + ?, `current_number` = `current_number` + ?, `ts` = ?
+            ON DUPLICATE KEY UPDATE `current_size` = `current_size` + ?, `current_number` = `current_number` + ?, `ts` = ?",
+            $iProfileId, $iSize, $iNumber, $iTime, $iSize, $iNumber, $iTime
+        );
+        if ($this->query($sQuery))
+            return true;
+        else
+            return false;
+    }
+
+    /**
+     * Get profile field by specified field name and value.
+     * In most cases it is for internal usage only.
+     * Use other funtions to get profile info, like getIdByEmail, etc.
+     * @param  string    $sFieldRequested database field name to return
+     * @param  string    $sFieldSearch    database field name to search for
+     * @param  mixed     $sValue          database field value
+     * @return specified profile field value
+     */
+    protected function _getFieldByField ($sFieldRequested, $sFieldSearch, $sValue)
+    {
+        $sSql = $this->prepare("SELECT `$sFieldRequested` FROM `sys_profiles` WHERE `$sFieldSearch` = ? LIMIT 1", $sValue);
+        return $this->getOne($sSql);
+    }
+
+    /**
+     * Update some field by profile id
+     * In most cases it is for internal usage only.
+     * Use other funtions to get profile info, like updateLogged, etc.
+     * @param  string    $sFieldRequested database field name to return
+     * @param  string    $sFieldSearch    database field name to search for
+     * @param  mixed     $sValue          database field value
+     * @return specified profile field value
+     */
+    protected function _updateField ($iId, $sFieldForUpdate, $sValue)
+    {
+        $sSql = $this->prepare("UPDATE `sys_profiles` SET `$sFieldForUpdate` = ? WHERE `id` = ? LIMIT 1", $sValue, $iId);
+        return $this->query($sSql);
+    }
+
+    /**
+     * Delete profile info by id
+     * @param  int      $iID profile id
+     * @return affected rows
+     */
+    public function delete($iID)
+    {
+        $aInfo = $this->getInfoById($iID);
+        $sSql = $this->prepare("DELETE FROM `sys_profiles` WHERE `id` = ? LIMIT 1", $iID);
+        if ($res = $this->query($sSql)) {
+            $this->getProfileByContentAndType($aInfo['content_id'], $aInfo['type'], true);
+            $this->getCurrentProfileByAccount($aInfo['account_id'], true);
+            $this->getIdById($iID, true);
+        }
+        return $res;
+    }
+
+    /**
+     * Reset deleted profile ids and assign system profile ids.
+     * Should be called after profiles module deletion.
+     * It can be called automatically if 
+     * @code
+     * 'process_deleted_profiles' => 1 
+     * @endcode
+     * is specified in module config.php file in 'uninstall' section.
+     */
+    public function processDeletedProfiles ()
+    {
+        $bResult = true;
+
+        // reset deleted profiles
+        $bResult &= $this->query("UPDATE `sys_accounts` AS `a` LEFT OUTER JOIN `sys_profiles` AS `p` ON `a`.`profile_id`=`p`.`id` SET `a`.`profile_id`='0' WHERE `p`.`id` IS NULL") !== false;
+
+        // try to assign another non-system profile to reset accounts
+        $bResult &= $this->query("UPDATE `sys_accounts` AS `a` INNER JOIN `sys_profiles` AS `p` ON `a`.`id`=`p`.`account_id` AND `p`.`type`<>'system' AND `a`.`profile_id`='0' SET `a`.`profile_id`=`p`.`id`") !== false;
+
+        // assign system profile to reset accounts
+        $bResult &= $this->query("UPDATE `sys_accounts` AS `a` INNER JOIN `sys_profiles` AS `p` ON `a`.`id`=`p`.`account_id` AND `p`.`type`='system' AND `a`.`profile_id`='0' SET `a`.`profile_id`=`p`.`id`") !== false;
+
+        return $bResult;
+    }
+}
+
+/** @} */

+ 392 - 0
upgrade/files/11.0.0-11.0.1/files/inc/classes/BxDolScore.php

@@ -0,0 +1,392 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaCore UNA Core
+ * @{
+ */
+
+bx_import('BxDolAcl');
+
+define('BX_DOL_SCORE_USAGE_BLOCK', 'block');
+define('BX_DOL_SCORE_USAGE_INLINE', 'inline');
+define('BX_DOL_SCORE_USAGE_DEFAULT', BX_DOL_SCORE_USAGE_BLOCK);
+
+define('BX_DOL_SCORE_DO_UP', 'up');
+define('BX_DOL_SCORE_DO_DOWN', 'down');
+
+/**
+ * Score for any content
+ *
+ * Related classes:
+ * - BxDolScoreQuery - vote database queries
+ * - BxBaseScore - vote base representation
+ * - BxTemplScore - custom template representation
+ *
+ * AJAX vote for any content. Stars and Plus based representations are supported.
+ *
+ * To add vote section to your feature you need to add a record to 'sys_objects_vote' table:
+ *
+ * - ID - autoincremented id for internal usage
+ * - Name - your unique module name, with vendor prefix, lowercase and spaces are underscored
+ * - TableMain - table name where summary votigs are stored
+ * - TableTrack - table name where each vote is stored
+ * - PostTimeout - number of seconds to not allow duplicate vote
+ * - MinValue - min vote value, 1 by default
+ * - MaxValue - max vote value, 5 by default
+ * - IsUndo - is Undo enabled for Plus based votes
+ * - IsOn - is this vote object enabled
+ * - TriggerTable - table to be updated upon each vote
+ * - TriggerFieldId - TriggerTable table field with unique record id, primary key
+ * - TriggerFieldRate - TriggerTable table field with average rate
+ * - TriggerFieldRateCount - TriggerTable table field with votes count
+ * - ClassName - your custom class name, if you overrride default class
+ * - ClassFile - your custom class path
+ *
+ * You can refer to BoonEx modules for sample record in this table.
+ *
+ *
+ *
+ * @section example Example of usage:
+ * To get Star based vote you need to have different values for MinValue and MaxValue (for example 1 and 5)
+ * and IsUndo should be equal to 0. To get Plus(Like) based vote you need to have equal values
+ * for MinValue and MaxValue (for example 1) and IsUndo should be equal to 1. After filling the other
+ * paramenters in the table you can show vote in any place, using the following code:
+ * @code
+ * $o = BxDolScore::getObjectInstance('system object name', $iYourEntryId);
+ * if (!$o->isEnabled()) return '';
+ *     echo $o->getElementBlock();
+ * @endcode
+ *
+ *
+ * @section acl Memberships/ACL:
+ * - vote
+ *
+ *
+ *
+ * @section alerts Alerts:
+ * Alerts type/unit - every module has own type/unit, it equals to ObjectName.
+ * The following alerts are rised:
+ *
+ * - rate - comment was posted
+ *      - $iObjectId - entry id
+ *      - $iSenderId - rater user id
+ *      - $aExtra['rate'] - rate
+ *
+ */
+
+class BxDolScore extends BxDolObject
+{
+    protected $_aElementDefaults;
+
+    protected function __construct($sSystem, $iId, $iInit = true, $oTemplate = false)
+    {
+        parent::__construct($sSystem, $iId, $iInit, $oTemplate);
+        if(empty($this->_sSystem))
+            return;
+
+        $this->_oQuery = new BxDolScoreQuery($this);
+    }
+
+    /**
+     * get votes object instanse
+     * @param $sSys vote object name
+     * @param $iId associated content id, where vote is available
+     * @param $iInit perform initialization
+     * @return null on error, or ready to use class instance
+     */
+    public static function getObjectInstance($sSys, $iId, $iInit = true, $oTemplate = false)
+    {
+        $sKey = 'BxDolScore!' . $sSys . $iId . ($oTemplate ? $oTemplate->getClassName() : '');
+        if(isset($GLOBALS['bxDolClasses'][$sKey]))
+            return $GLOBALS['bxDolClasses'][$sKey];
+
+        $aSystems = self::getSystems();
+        if(!isset($aSystems[$sSys]))
+            return null;
+
+        $sClassName = 'BxTemplScore';
+        if(!empty($aSystems[$sSys]['class_name'])) {
+            $sClassName = $aSystems[$sSys]['class_name'];
+            if(!empty($aSystems[$sSys]['class_file']))
+                require_once(BX_DIRECTORY_PATH_ROOT . $aSystems[$sSys]['class_file']);
+        }
+
+        $o = new $sClassName($sSys, $iId, $iInit, $oTemplate);
+        return ($GLOBALS['bxDolClasses'][$sKey] = $o);
+    }
+
+    public static function &getSystems()
+    {
+        $sKey = 'bx_dol_cache_memory_score_systems';
+
+        if(!isset($GLOBALS[$sKey]))
+            $GLOBALS[$sKey] = BxDolDb::getInstance()->fromCache('sys_objects_score', 'getAllWithKey', '
+                SELECT
+                    `id` as `id`,
+                    `name` AS `name`,
+                    `table_main` AS `table_main`,
+                    `table_track` AS `table_track`,
+                    `post_timeout` AS `post_timeout`,
+                    `is_on` AS `is_on`,
+                    `trigger_table` AS `trigger_table`,
+                    `trigger_field_id` AS `trigger_field_id`,
+                    `trigger_field_author` AS `trigger_field_author`,
+                    `trigger_field_score` AS `trigger_field_score`,
+                    `trigger_field_cup` AS `trigger_field_cup`,
+                    `trigger_field_cdown` AS `trigger_field_cdown`,
+                    `class_name` AS `class_name`,
+                    `class_file` AS `class_file`
+                FROM `sys_objects_score`', 'name');
+
+        return $GLOBALS[$sKey];
+    }
+
+    public static function onAuthorDelete ($iAuthorId)
+    {
+        $aSystems = self::getSystems();
+        foreach($aSystems as $sSystem => $aSystem)
+            self::getObjectInstance($sSystem, 0)->getQueryObject()->deleteAuthorEntries($iAuthorId);
+
+        return true;
+    }
+
+    public function isPerformed($iObjectId, $iAuthorId, $iAuthorIp = 0)
+    {
+        return parent::isPerformed($iObjectId, $iAuthorId) && !$this->_oQuery->isPostTimeoutEnded($iObjectId, $iAuthorId, $iAuthorIp);        
+    }
+
+	public function getObjectAuthorId($iObjectId = 0)
+    {
+    	if(empty($this->_aSystem['trigger_field_author']))
+    		return 0;
+
+        return $this->_oQuery->getObjectAuthorId($iObjectId ? $iObjectId : $this->getId());
+    }
+
+
+    /**
+     * Interface functions for outer usage
+     */
+    public function getStatCounterUp()
+    {
+        $aScore = $this->_oQuery->getScore($this->getId());
+        return $aScore['count_up'];
+    }
+
+    public function getStatCounterDown()
+    {
+        $aScore = $this->_oQuery->getScore($this->getId());
+        return $aScore['count_down'];
+    }
+
+    public function getStatScore()
+    {
+        $aScore = $this->_oQuery->getScore($this->getId());
+        return $aScore['score'];
+    }
+
+
+    /**
+     * Actions functions
+     */
+    public function actionVoteUp()
+    {
+        return $this->_doVote(BX_DOL_SCORE_DO_UP);
+    }
+
+    public function actionVoteDown()
+    {
+        return $this->_doVote(BX_DOL_SCORE_DO_DOWN);
+    }
+
+    public function actionGetVotedBy()
+    {
+        if (!$this->isEnabled())
+           return '';
+
+        $aParams = $this->_getRequestParamsData();
+        return $this->_getVotedBy($aParams);
+    }
+
+
+    /**
+     * Permissions functions
+     */
+    public function isAllowedVote($isPerformAction = false)
+    {
+        if(isAdmin())
+            return true;
+
+        return $this->checkAction('vote', $isPerformAction);
+    }
+
+    public function msgErrAllowedVote()
+    {
+        return $this->checkActionErrorMsg('vote');
+    }
+
+    public function isAllowedVoteView($isPerformAction = false)
+    {
+        if(isAdmin())
+            return true;
+
+        return $this->checkAction('vote_view', $isPerformAction);
+    }
+
+    public function msgErrAllowedVoteView()
+    {
+        return $this->checkActionErrorMsg('vote_view');
+    }
+
+    public function isAllowedVoteViewVoters($isPerformAction = false)
+    {
+        if(isAdmin())
+            return true;
+
+        return $this->checkAction('vote_view_voters', $isPerformAction);
+    }
+
+    public function msgErrAllowedVoteViewVoters()
+    {
+        return $this->checkActionErrorMsg('vote_view_voters');
+    }
+
+
+    /**
+     * Internal functions
+     */
+    protected function _doVote($sType)
+    {
+        if(!$this->isEnabled())
+            return echoJson(array('code' => 1, 'message' => _t('_sys_score_err_not_enabled')));
+
+        if(!$this->isAllowedVote(true))
+            return echoJson(array('code' => 2, 'message' => $this->msgErrAllowedVote()));
+
+        $iObjectId = $this->getId();
+        $iObjectAuthorId = $this->getObjectAuthorId($iObjectId);
+        $iAuthorId = $this->_getAuthorId();
+        $iAuthorIp = $this->_getAuthorIp();
+
+        $bVoted = $this->isPerformed($iObjectId, $iAuthorId, $iAuthorIp);
+        if($bVoted)
+            return echoJson(array('code' => 3, 'message' => _t('_sys_score_err_duplicate_vote')));
+
+        $iId = $this->_oQuery->putVote($iObjectId, $iAuthorId, $iAuthorIp, $sType);
+        if($iId === false)
+            return echoJson(array('code' => 5));
+
+        $this->_trigger();
+
+        $sTypeUc = ucfirst($sType);
+        bx_alert($this->_sSystem, 'doVote' . $sTypeUc, $iObjectId, $iAuthorId, array('score_id' => $iId, 'score_author_id' => $iAuthorId, 'object_author_id' => $iObjectAuthorId));
+        bx_alert('score', 'do' . $sTypeUc, $iId, $iAuthorId, array('object_system' => $this->_sSystem, 'object_id' => $iObjectId, 'object_author_id' => $iObjectAuthorId));
+
+        $aParams = $this->_getRequestParamsData();
+        $aParams['show_script'] = false;
+
+        $aScore = $this->_oQuery->getScore($iObjectId);
+        $iCup = (int)$aScore['count_up'];
+        $iCdown = (int)$aScore['count_down'];
+        echoJson(array(
+            'code' => 0,
+            'type' => $sType,
+            'score' => $aScore['score'],
+            'scoref' => $iCup > 0 || $iCdown > 0 ? $this->_getCounterLabel($aScore['score'], $aParams) : '',
+            'cup' => $iCup,
+            'cdown' => $iCdown,
+            'counter' => $this->getCounter($aParams),
+            'label_icon' => $this->_getIconDo($sType),
+            'label_title' => _t($this->_getTitleDo($sType)),
+            'disabled' => !$bVoted,
+        ));
+    }
+
+    /**
+     * Note. By default image based controls aren't used.
+     * Therefore it can be overwritten in custom template.
+     */
+    protected function _getImageDo($sType)
+    {
+        $sResult = '';
+
+        switch($sType) {
+            case BX_DOL_SCORE_DO_UP:
+                $sResult = '';
+                break;
+            case BX_DOL_SCORE_DO_DOWN:
+                $sResult = '';
+                break;
+        }
+
+    	return $sResult;
+    }
+
+    protected function _getIconDo($sType = '')
+    {
+        $sResult = '';
+
+        switch($sType) {
+            case BX_DOL_SCORE_DO_UP:
+                $sResult = 'arrow-up';
+                break;
+
+            case BX_DOL_SCORE_DO_DOWN:
+                $sResult = 'arrow-down';
+                break;
+
+            default:
+                $sResult = 'arrows-alt-v';
+        }
+
+    	return $sResult;
+    }
+
+    protected function _getTitleDo($sType)
+    {
+        $sResult = '';
+
+        switch($sType) {
+            case BX_DOL_SCORE_DO_UP:
+                $sResult = '_sys_score_do_up';
+                break;
+            case BX_DOL_SCORE_DO_DOWN:
+                $sResult = '_sys_score_do_down';
+                break;
+        }
+
+    	return $sResult;
+    }
+
+    protected function _getTitleDoBy()
+    {
+    	return '_sys_score_do_by';
+    }
+
+    protected function _encodeElementParams($aParams)
+    {
+        if(empty($aParams) || !is_array($aParams))
+            return '';
+
+        return urlencode(base64_encode(serialize($aParams)));
+    }
+
+    protected function _decodeElementParams($sParams, $bMergeWithDefaults = true)
+    {
+        $aParams = array();
+        if(!empty($sParams))
+            $aParams = unserialize(base64_decode(urldecode($sParams)));
+
+        if(empty($aParams) || !is_array($aParams))
+            $aParams = array();
+
+        if($bMergeWithDefaults)
+            $aParams = array_merge($this->_aElementDefaults, $aParams);
+
+        return $aParams;
+    }
+}
+
+/** @} */

+ 155 - 0
upgrade/files/11.0.0-11.0.1/files/inc/classes/BxDolScoreQuery.php

@@ -0,0 +1,155 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaCore UNA Core
+ * @{
+ */
+
+/**
+ * @see BxDolScore
+ */
+class BxDolScoreQuery extends BxDolObjectQuery
+{
+	protected $_sTriggerFieldScore;
+	protected $_sTriggerFieldCup;
+	protected $_sTriggerFieldCdown;
+
+    protected $_iPostTimeout;
+
+    public function __construct(&$oModule)
+    {
+        parent::__construct($oModule);
+
+        $aSystem = $this->_oModule->getSystemInfo();
+        $this->_sTriggerFieldScore = $aSystem['trigger_field_score'];
+        $this->_sTriggerFieldCup = $aSystem['trigger_field_cup'];
+        $this->_sTriggerFieldCdown = $aSystem['trigger_field_cdown'];
+
+        $this->_iPostTimeout = (int)$aSystem['post_timeout'];
+
+        $this->_sMethodGetEntry = 'getScore';
+    }
+
+    public function getPerformedBy($iObjectId)
+    {
+        $sQuery = "SELECT 
+            	`author_id` AS `id`, 
+            	`type` AS `vote_type`, 
+            	`date` AS `vote_date` 
+			FROM `{$this->_sTableTrack}` 
+			WHERE `object_id`=:object_id";
+
+        return $this->getAll($sQuery, array(
+        	'object_id' => $iObjectId
+        ));
+    }
+
+    public function isPostTimeoutEnded($iObjectId, $iAuthorId, $sAuthorIp)
+    {
+        if($this->_iPostTimeout == 0)
+            return true;
+
+        $aBindings = array(
+            'object_id' => $iObjectId,
+            'date' => time() - $this->_iPostTimeout
+        );
+        $sWhereClause = " AND `object_id` = :object_id AND `date` > :date";
+
+        if(!empty($iAuthorId)) {
+            $aBindings['author_id'] = $iAuthorId;
+
+            $sWhereClause .= " AND `author_id` = :author_id";
+        }
+        else {
+            $aBindings['author_nip'] = ip2long($sAuthorIp);
+
+            $sWhereClause .= " AND `author_nip` = :author_nip";
+        }
+
+        return (int)$this->getOne("SELECT `object_id` FROM `" . $this->_sTableTrack . "` WHERE 1" . $sWhereClause, $aBindings) == 0;
+    }
+
+    public function getScore($iObjectId)
+    {
+        $aResult = $this->getRow("SELECT `count_up`, `count_down`, `count_up` - `count_down` AS `score` FROM {$this->_sTable} WHERE `object_id` = :object_id LIMIT 1", array(
+            'object_id' => $iObjectId
+        ));
+
+        if(empty($aResult) || !is_array($aResult))
+            $aResult = array('count_up' => 0, 'count_down' => 0, 'score' => 0);
+
+        return $aResult;
+    }
+
+    public function putVote($iObjectId, $iAuthorId, $sAuthorIp, $sType)
+    {
+        $bExists = (int)$this->getOne("SELECT `object_id` FROM `{$this->_sTable}` WHERE `object_id` = :object_id LIMIT 1", array('object_id' => $iObjectId)) != 0;
+
+        if(!$bExists)
+            $sQuery = $this->prepare("INSERT INTO {$this->_sTable} SET `object_id` = ?, `count_" . $sType . "` = '1'", $iObjectId);
+        else
+            $sQuery = $this->prepare("UPDATE `{$this->_sTable}` SET `count_" . $sType . "` = `count_" . $sType . "` + 1 WHERE `object_id` = ?", $iObjectId);
+
+        if((int)$this->query($sQuery) == 0)
+            return false;
+
+        if((int)$this->query("INSERT INTO `{$this->_sTableTrack}` SET " . $this->arrayToSQL(array(
+            'object_id' => $iObjectId, 
+            'author_id' => $iAuthorId, 
+            'author_nip' => ip2long($sAuthorIp), 
+            'type' => $sType, 
+            'date' => time()
+        ))) > 0)
+        	return $this->lastId();
+
+        return false;
+    }
+
+    public function getLegend($iObjectId)
+    {
+    	$sQuery = $this->prepare("SELECT `type` AS `type`, COUNT(`type`) AS `count` FROM `{$this->_sTableTrack}` WHERE `object_id` = ? GROUP BY `type`", $iObjectId);
+
+    	return $this->getAllWithKey($sQuery, 'type');
+    }
+
+    public function getSqlParts($sMainTable, $sMainField)
+    {
+    	$aResult = parent::getSqlParts($sMainTable, $sMainField);
+        if(empty($aResult))
+            return $aResult;
+
+		$aResult['fields'] = ", `{$this->_sTable}`.`count_up` AS `score_cup`, `{$this->_sTable}`.`count_down` AS `score_cdown`, (`{$this->_sTable}`.`count_up` - `{$this->_sTable}`.`count_down`) AS `score` ";
+        return $aResult;
+    }
+
+	protected function _updateTriggerTable($iObjectId, $aEntry)
+    {
+        $aSet = array(
+            $this->_sTriggerFieldScore => $aEntry['score'],
+            $this->_sTriggerFieldCup => $aEntry['count_up'],
+            $this->_sTriggerFieldCdown => $aEntry['count_down']
+        );
+
+        return (int)$this->query("UPDATE `{$this->_sTriggerTable}` SET " . $this->arrayToSQL($aSet) . " WHERE `{$this->_sTriggerFieldId}` = :object_id", array(
+        	'object_id' => $iObjectId
+        )) > 0;
+    }
+
+    protected function _deleteAuthorEntriesTableMain($aTrack)
+    {
+        return $this->query("UPDATE `{$this->_sTable}` SET `count_" . $aTrack['type'] . "`=`count_" . $aTrack['type'] . "`-1 WHERE `object_id`=:object_id", array(
+        	'object_id' => $aTrack['object_id']
+        ));
+    }
+
+    protected function _deleteAuthorEntriesTableTrigger($aTrack)
+    {
+        $aScore = $this->getScore($aTrack['object_id']);
+
+        return $this->_updateTriggerTable($aTrack['object_id'], $aScore);
+    }
+}
+
+/** @} */

+ 1067 - 0
upgrade/files/11.0.0-11.0.1/files/inc/classes/BxDolSearch.php

@@ -0,0 +1,1067 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaCore UNA Core
+ * @{
+ */
+
+define('BX_DOL_SEARCH_RESULTS_PER_PAGE_DEFAULT', 10);
+define('BX_DOL_SEARCH_KEYWORD_PAGE', 'site-search-page');
+
+/**
+ * Search class for different content.
+ *
+ * Allows present content in main search area for all modules.
+ *
+ * To add taking part content from your module to search area you need add a record to `sys_objects_search` table:
+ * @code
+ *   ID - autoincremented id for internal usage
+ *   ObjectName - your unique module name, with vendor prefix, lowercase and spaces are underscored
+ *   Title - language key for module's choicer in global search form
+ *   ClassName - class name which is responsible for search process ad showing result for found results
+ *   ClassPath - file where your ClassName is stored (if class is stored in template folder then you can use key {tmpl})
+ * @endcode
+ *
+ * For using this class you should have unit of BxDolSearchResult class (see description below) with method processing which is responsible for processing of search request.
+ *
+ * Example of usage can be seen in the BoonEx modules, for example: Posts
+ *
+ * Memberships/ACL:
+ * Doesn't depend on user's membership.
+ *
+ * Alerts:
+ * no alerts available
+ *
+ */
+class BxDolSearch extends BxDol
+{
+    protected $aClasses = array(); ///< array of all search classes
+    protected $aChoice  = array(); ///< array of current search classes which were choosen in search area
+    protected $_bRawProcessing = false; ///< display search results without design box and paginate
+    protected $_bDataProcessing = false; ///< return result as array of data
+
+    protected $_bLiveSearch = false;
+    protected $_sMetaType = '';
+    protected $_sCategoryObject = ''; ///< by default = object from sys_objects_category table, for multicategories set to 'multi' string 
+    protected $_aCustomSearchCondition = array();
+    protected $_aCustomCurrentCondition = array();
+    protected $_sUnitTemplate = '';
+
+    /**
+     * Constructor
+     * @param array $aChoice - array of choosen classes (will take a part only existing in `sys_objects_search` table)
+     */
+    public function __construct ($aChoice = '')
+    {
+        parent::__construct();
+
+        $this->aClasses = BxDolDb::getInstance()->fromCache('sys_objects_search', 'getAllWithKey',
+           'SELECT `ID` as `id`,
+                   `Title` as `title`,
+                   `ClassName` as `class`,
+                   `ClassPath` as `file`,
+                   `ObjectName`,
+                   `GlobalSearch`
+            FROM `sys_objects_search`
+            ORDER BY `Order` ASC', 'ObjectName'
+        );
+
+        if (is_array($aChoice) && !empty($aChoice)) {
+            foreach ($aChoice as $sValue) {
+                if (isset($this->aClasses[$sValue]))
+                    $this->aChoice[$sValue] = $this->aClasses[$sValue];
+            }
+        } else {
+            $this->aChoice = $this->aClasses;
+        }
+    }
+
+    /**
+     * create units for all classes and calling their processing methods
+     */
+    public function response ()
+    {
+        $sCode = $this->_bDataProcessing ? array() : '';
+
+        $bSingle = count($this->aChoice) == 1;
+        foreach ($this->aChoice as $sKey => $aValue) {
+            if (!$this->_sMetaType && !$aValue['GlobalSearch'])
+                continue;
+
+        	$sClassName = 'BxTemplSearchResult';
+	        if(!empty($aValue['class'])) {
+	            $sClassName = $aValue['class'];
+	            if(!empty($aValue['file']))
+	                require_once(BX_DIRECTORY_PATH_ROOT . $aValue['file']);
+	        }
+
+            $oEx = new $sClassName();            
+            if ($this->_sMetaType && !$oEx->isMetaEnabled($this->_sMetaType))
+                continue;
+            $oEx->setId($aValue['id']);
+            $oEx->setLiveSearch($this->_bLiveSearch);
+            $oEx->setMetaType($this->_sMetaType);
+            $oEx->setCategoryObject($this->_sCategoryObject);
+            $oEx->setCenterContentUnitSelector(false);
+            $oEx->setSingleSearch($bSingle);
+            $oEx->setCustomSearchCondition($this->_aCustomSearchCondition);
+            $oEx->aCurrent = array_merge($oEx->aCurrent, $this->_aCustomCurrentCondition);
+            if ($this->_sUnitTemplate)
+                $oEx->setUnitTemplate($this->_sUnitTemplate);
+
+            if ($this->_bDataProcessing)
+                $sCode[$sKey] = $oEx->getSearchData();
+            else
+                $sCode .= $this->_bRawProcessing ? $oEx->processingRaw() : $oEx->processing();
+        }
+
+        return $sCode;
+    }
+
+    public function getEmptyResult ()
+    {
+        $sKey = _t('_Empty');
+        return DesignBoxContent($sKey, MsgBox($sKey), 1);
+    }
+
+    protected function getKeyTitlesPairs ()
+    {
+        $a = array();
+        foreach ($this->aChoice as $sKey => $r)
+            if ($this->_sMetaType || $r['GlobalSearch'])
+                $a[$sKey] = _t($r['title']);
+        return $a;
+    }
+
+    public function setLiveSearch($bLiveSearch)
+    {
+        $this->_bLiveSearch = $bLiveSearch;
+    }
+
+    public function setMetaType($s)
+    {
+        $aMetaTypes = array('location_country', 'location_country_city', 'location_country_state', 'mention', 'keyword');
+        if (in_array($s, $aMetaTypes))
+            $this->_sMetaType = $s;
+    }
+    
+    public function setCategoryObject($s)
+    {
+        $this->_sCategoryObject = $s;
+    }
+
+    /**
+     * Set custom search condition to use istead of GET/POST variables
+     * @param $a array of params, such as 'keyword', 'state', 'city'
+     * @return nothing
+     */
+    public function setCustomSearchCondition($a)
+    {
+        $this->_aCustomSearchCondition = $a;
+    }
+
+    /**
+     * Set custom data for aCurrent array
+     * @param $a array of params
+     * @return nothing
+     */
+    public function setCustomCurrentCondition($a)
+    {
+        $this->_aCustomCurrentCondition = $a;
+    }
+
+    /**
+     * Set custom unit template
+     * @param $s template name
+     * @return nothing
+     */
+    public function setUnitTemplate($s)
+    {
+        $this->_sUnitTemplate = $s;
+    }
+    
+    /**
+     * Display search results without design box and paginate
+     */ 
+    public function setRawProcessing($b)
+    {
+        $this->_bRawProcessing = $b;
+    }
+
+    /**
+     * Return search result as array of data
+     */ 
+    public function setDataProcessing($b)
+    {
+        $this->_bDataProcessing = $b;
+    }
+}
+
+/*
+ * Search class for processing search requests and displaying search results.
+ *
+ * Allows present content from modules on search params or internal (via fields of class) conditions.
+ *
+ * Example of usage (you can see example in any of default Dolhpin's modules):
+ *
+ * 1. Extends your own search class from this one (or from BxBaseSearchResult or BxBaseSearchResultSharedMedia classes)
+ * 2. Set necessary fields of class (using as example BxFilesSearch):
+ *
+ * @code
+ *
+ *  // main field of search class
+ *  $this->aCurrent = array(
+ *
+ *      // name of module
+ *      'name' => 'bx_files',
+ *
+ *      // language key with name of module
+ *      'title' => '_bx_files',
+ *
+ *      // main content table
+ *      'table' => 'bx_files_main',
+ *
+ *      // array of all fields which can be choosen for select in result query
+ *      'ownFields' => array('ID', 'Title', 'Uri', 'Desc', 'Date', 'Size', 'Ext', 'Views', 'Rate', 'RateCount', 'Type'),
+ *
+ *      // array of fields which take a part in search by keyword (global search and related words only - in other cases leave it blank)
+ *      'searchFields' => array('Title', 'Tags', 'Desc', 'Categories'),
+ *
+ *      // array of join tables
+ *      //   'type' - type of join
+ *      //   'table' - join table
+ *      //   'mainField' - field from main table for 'on' condition
+ *      //   'onField' - field from joining table for 'on' condition
+ *      //   'joinFields' - array of fields from joining table
+ *
+ *      'join' => array(
+ *          'profile' => array(
+ *              'type' => 'left',
+ *              'table' => 'Profiles',
+ *              'mainField' => 'Owner',
+ *              'onField' => 'ID',
+ *              'joinFields' => array('NickName'),
+ *          ),
+ *          // ...
+ *      ),
+ *
+ *      // array of search parameters
+ *      //   'value' - value of search parameter (can be number, string or array)
+ *      //   'field' - field which will take value of search, may be lefy as blank then in search query will be pasted to WHERE conditon via operator AGAINST
+ *      //   'operator' - operator between field and value (can be 'in', 'not in', '>', '<', 'against', 'like' and '=' by default),
+ *      //   'paramName' - GET param which will keep 'value' for pagianation
+ *
+ *      'restriction' => array(
+ *          'activeStatus' => array('value'=>'approved', 'field'=>'Status', 'operator'=>'=', 'paramName' => 'status'),
+ *          'albumType' => array('value'=>'', 'field'=>'Type', 'operator'=>'=', 'paramName'=>'albumType', 'table'=>'sys_albums'),
+ *      ),
+ *
+ *      // array of pagination
+ *      //   'perPage' - units per page
+ *      //   'start' - show search results starting from this number
+ *      //   'num' - number of element to show on current page + 1
+ *
+ *      'paginate' => array('perPage' => 10, 'start' => 0, 'num' => 11),
+ *
+ *      // sort mode - by default it was last (DESC by date)
+ *      'sorting' => 'last',
+ *
+ *      // mode of units presentation in search result, can be 'short' or 'full' view
+ *      'view' => 'full',
+ *
+ *      // field of id
+ *      'ident' => 'ID',
+ *
+ *      // rss feed array
+ *      'rss' => array(
+ *          'title' => '',
+ *          'link' => '',
+ *          'image' => '',
+ *          'profile' => 0,
+ *          'fields' => array (
+ *              'Link' => '',
+ *              'Title' => 'Title',
+ *              'DateTimeUTS' => 'Date',
+ *              'Desc' => 'Desc',
+ *              'Photo' => '',
+ *          ),
+ *      ),
+ *  );
+ *
+ *  // array of fields renamings
+ *  $aPseud - filling in costructor by function _getPseud
+ *
+ *  // unique identificator from `sys_objects_search` table
+ *  $id;
+ *
+ * @endcode
+ *
+ * Memberships/ACL:
+ * Doesn't depend on user's membership.
+ *
+ * Alerts:
+ * no alerts available
+ *
+ */
+
+class BxDolSearchResult implements iBxDolReplaceable
+{
+    public $aCurrent; ///< search results configuration
+
+    protected $aPseud; ///< array of fields renamings
+    protected $id; ///< unique identificator of block on mixed search results page
+    protected $bDisplayEmptyMsg = false; ///< display empty message instead of nothing, when no results
+    protected $sDisplayEmptyMsgKey = ''; ///< custom empty message language key, instead of default "empty" message
+    protected $bProcessPrivateContent = true; ///< check each item for privacy, if view isn't allowed then display private view instead
+    protected $aPrivateConditionsIndexes = array('restriction' => array(), 'join' => array()); ///< conditions indexes for bProcessPrivateContent
+    protected $bForceAjaxPaginate = false; ///< force ajax paginate
+    protected $iPaginatePerPage = BX_DOL_SEARCH_RESULTS_PER_PAGE_DEFAULT; ///< default 'per page' value for paginate.
+
+    protected $_bSingleSearch = true;
+    protected $_bLiveSearch = false;
+    protected $_sMetaType = '';
+    protected $_sMode = '';
+    protected $_sCategoryObject = '';
+    protected $_aCustomSearchCondition = array();
+
+    protected $_aMarkers = array (); ///< markers to replace somewhere, usually title and browse url (defined in custom class)
+    
+    /**
+     * constructor
+     * filling identificator field
+     */
+    function __construct ()
+    {
+        if (isset($this->aPseud['id']))
+            $this->aCurrent['ident'] = $this->aPseud['id'];
+    }
+
+    public function getId()
+    {
+        return $this->id;
+    }
+
+    public function setId($sId)
+    {
+        $this->id = $sId;
+    }
+
+    public function setAjaxPaginate($b = true)
+    {
+        $this->bForceAjaxPaginate = $b;
+    }
+
+    public function setPaginatePerPage($iPerPage)
+    {
+        $this->iPaginatePerPage = $iPerPage;
+    }
+
+    public function setSingleSearch($bSingleSearch)
+    {
+        $this->_bSingleSearch = $bSingleSearch;
+    }
+
+    public function setLiveSearch($bLiveSearch)
+    {
+        $this->_bLiveSearch = $bLiveSearch;
+    }
+
+    public function setMetaType($s)
+    {
+        $this->_sMetaType = $s;
+    }
+
+    public function isMetaEnabled($s)
+    {
+        if (empty($this->aCurrent['object_metatags']))
+            return false;
+            
+        if (!($o = BxDolMetatags::getObjectInstance($this->aCurrent['object_metatags'])))
+            return false;
+
+        switch ($s) {
+            case 'location_country_city':
+            case 'location_country_state':            
+            case 'location_country':
+                return $o->locationsIsEnabled();
+            case 'mention':
+                return $o->mentionsIsEnabled();
+            case 'keyword':
+                return $o->keywordsIsEnabled();
+        }
+
+        return false;
+    }
+    
+    public function setCategoryObject($s)
+    {
+        $this->_sCategoryObject = $s;
+    }
+
+    public function setCustomSearchCondition($a)
+    {
+        $this->_aCustomSearchCondition = $a;
+    }
+    
+    public function setCategoriesCondition($sKeyword)
+    {
+        $this->aCurrent['join']['multicat'] = array(
+            'type' => 'INNER',
+            'table' => 'sys_categories2objects',
+            'mainField' => 'id',
+            'onField' => 'object_id',
+            'joinFields' => array(),
+        );
+        $this->aCurrent['join']['multicat2'] = array(
+           'type' => 'INNER',
+           'table' => 'sys_categories',
+           'mainField' => 'category_id',
+           'mainTable' => 'sys_categories2objects',
+           'onField' => 'id',
+           'joinFields' => array(),
+       );
+       $this->aCurrent['restriction']['multicat'] = array('value' => $sKeyword, 'field' => 'value', 'operator' => '=', 'table' => 'sys_categories');
+    }
+    
+    /**
+     * Display empty message if there is no content, custom empty message can be used.
+     * @param $b - boolan value to enable or disable 'empty' message
+     * @param $sLangKey [optional] - custom 'empty' message
+     */
+    public function setDisplayEmptyMsg($b, $sLangKey = '')
+    {
+        $this->bDisplayEmptyMsg = $b;
+        if ($sLangKey)
+           $this->sDisplayEmptyMsgKey = $sLangKey;
+    }
+
+    /**
+     * Perform privacy checking for every unit
+     * @param $b - boolan value to enable or disable privacy checking
+     */
+    public function setProcessPrivateContent ($b)
+    {
+        $this->bProcessPrivateContent = $b;
+        if ($b) {
+            // unset condition which was set when 'bProcessPrivateContent' was set to 'false'
+            foreach ($this->aPrivateConditionsIndexes['restriction'] as $sKey) 
+                unset($this->aCurrent['restriction'][$sKey]);
+            foreach ($this->aPrivateConditionsIndexes['join'] as $sKey)
+                unset($this->aCurrent['join'][$sKey]);
+        }
+    }
+
+    /**
+     * Get search results without design box and paginate
+     * @return html code
+     */
+    function processingRaw ()
+    {
+        $sCode = $this->displayResultBlock();
+        return $this->aCurrent['paginate']['num'] > 0 ? $sCode : '';
+    }
+    
+    /**
+     * Get html box of search results (usually used in grlobal search)
+     * @return html code
+     */
+    function processing ()
+    {
+        $sCode = $this->displayResultBlock();
+        if ($this->aCurrent['paginate']['num'] > 0) {
+            $sPaginate = $this->showPagination();
+            $sCode = $this->displaySearchBox($sCode, $sPaginate);
+        } else {
+            $sCode = $this->bDisplayEmptyMsg ? $this->displaySearchBox(MsgBox(_t($this->sDisplayEmptyMsgKey ? $this->sDisplayEmptyMsgKey : '_Empty'))) : '';
+        }
+        return $sCode;
+    }
+
+    /**
+     * Get html output of search result
+     * @return html code
+     */
+    function displayResultBlock ()
+    {
+        $aData = $this->getSearchData();
+
+        $sCode = '';
+        if (count($aData) > 0) {
+            $sCode = $this->addCustomParts();
+            foreach ($aData as $iKey => $aValue) {
+                $sCode .= $this->displaySearchUnit($aValue);
+            }
+        }
+        return $sCode;
+    }
+
+    /**
+     * Add different code before html output (usually redeclared)
+     * no return result
+     */
+    function addCustomParts ()
+    {
+
+    }
+
+    /**
+     * Get XML string for rss output
+     */
+    function rss ()
+    {
+        if (!isset($this->aCurrent['rss']['fields']) || !isset($this->aCurrent['rss']['link']))
+            return '';
+
+        $aData = $this->getSearchData();
+        $f = &$this->aCurrent['rss']['fields'];
+        if ($aData) {
+            foreach ($aData as $k => $a) {
+                $aData[$k][$f['Link']] = $this->getRssUnitLink ($a);
+
+                if(isset($f['Image']))
+                    $aData[$k][$f['Image']] = $this->getRssUnitImage ($a, $f['Image']);
+            }
+        }
+
+        $oRss = new BxDolRssFactory ();
+
+        return $oRss->GenRssByCustomData(
+            $aData,
+            isset($this->aCurrent['rss']['title']) && $this->aCurrent['rss']['title'] ? $this->aCurrent['rss']['title'] : $this->aCurrent['title'],
+            $this->aCurrent['rss']['link'],
+            $this->aCurrent['rss']['fields'],
+            isset($this->aCurrent['rss']['image']) ? $this->aCurrent['rss']['image'] : '',
+            isset($this->aCurrent['rss']['profile']) ? $this->aCurrent['rss']['profile'] : 0
+        );
+    }
+
+    /**
+     * Output RSS XML with XML header
+     */
+    function outputRSS ()
+    {
+        header('Content-Type: text/xml; charset=UTF-8');
+        echo $this->rss();
+    }
+
+    /**
+     * Return rss unit link (redeclared)
+     */
+    function getRssUnitLink (&$a)
+    {
+        // override this functions to return permalink to rss unit
+    }
+
+	/**
+     * Return rss unit image (redeclared)
+     */
+    function getRssUnitImage (&$a, $sField)
+    {
+        // override this functions to return image for rss unit
+    }
+
+    /**
+     * Naming fields in query's body
+     * @param string  $sFieldName  name of field
+     * @param string  $sTableName  name of field's table
+     *                             $param string $sOperator of field's calculation (like MAX)
+     * @param boolean $bRenameMode indicator for renaming and unsetting fields from field of class $this->aPseud
+     *                             return $sqlUnit sql code and unsetting elements from aPseud field
+     */
+    function setFieldUnit ($sFieldName, $sTableName, $sOperator = '', $bRenameMode = true)
+    {
+        if (!empty($sOperator))
+            $sqlUnit  = "$sOperator(`$sTableName`.`$sFieldName`)";
+        else
+            $sqlUnit  = "`$sTableName`.`$sFieldName`";
+
+        if (isset($this->aPseud) && $bRenameMode !== false) {
+            $sKey = array_search($sFieldName, $this->aPseud);
+            if ($sKey !== false) {
+                $sqlUnit .= " as `$sKey`";
+                unset($this->aPseud[$sKey]);
+            }
+        }
+        return $sqlUnit . ', ';
+    }
+
+    /**
+     * Get html code of of every search unit
+     * @param array $aData array of every search unit
+     *                     return html code
+     */
+    function displaySearchUnit ($aData)
+    {
+
+    }
+
+    /**
+     * Get html code of search box with search results
+     * @param string $sCode html code of search results
+     *                      $param $sPaginate html code of paginate
+     *                      return html code
+     */
+    function displaySearchBox ($sCode, $sPaginate = '')
+    {
+
+    }
+
+    /**
+     * Get html code of pagination
+     */
+    function showPagination ($bAdmin = false, $bChangePage = true, $bPageReload = true)
+    {
+
+    }
+
+    /**
+     * Get array of data with search results
+     * return array with data
+     */
+    function getSearchData ()
+    {
+        $this->aPseud = $this->_getPseud();
+        $this->setConditionParams();
+        if ($this->aCurrent['paginate']['num'] > 0) {
+            $aData = $this->getSearchDataByParams();
+            return $aData;
+        }
+        return array();
+    }
+
+    /**
+     * Get array with code for sql elements
+     * @param $bRenameMode indicator of renmaing fields
+     * return array with joinFields, ownFields, groupBy and join elements
+     */
+    function getJoins ($bRenameMode = true)
+    {
+        $aSql = array();
+        // joinFields & join
+        if (isset($this->aCurrent['join']) && is_array($this->aCurrent['join'])) {
+            $aSql = array('join'=>'', 'ownFields'=>'', 'joinFields'=>'', 'groupBy'=>'');
+            foreach ($this->aCurrent['join'] as $sKey => $aValue) {
+                $sAlias = isset($aValue['table_alias']) ? $aValue['table_alias'] : $aValue['table'];
+                $sTableAlias = isset($aValue['table_alias']) ? " AS {$aValue['table_alias']} " : '';
+
+                if (is_array($aValue['joinFields'])) {
+                    foreach ($aValue['joinFields'] as $sValue) {
+                        $aSql['joinFields'] .= $this->setFieldUnit($sValue, $sAlias, isset($aValue['operator']) ? $aValue['operator'] : null, $bRenameMode);
+                    }
+                }
+                // group by
+                if (isset($aValue['groupTable']))
+                    $aSql['groupBy'] =  "GROUP BY `{$aValue['groupTable']}`.`{$aValue['groupField']}`, ";
+                $sOn = isset($aValue['mainTable']) ? $aValue['mainTable'] : $this->aCurrent['table'];
+                $aSql['join'] .= " {$aValue['type']} JOIN `{$aValue['table']}` $sTableAlias ON `{$sAlias}`.`{$aValue['onField']}`=`$sOn`.`{$aValue['mainField']}`";
+                $aSql['ownFields'] .= $this->setFieldUnit($aValue['mainField'], $sOn, '', $bRenameMode);
+            }
+            $aSql['joinFields'] = trim($aSql['joinFields'], ', ');
+            $aSql['groupBy'] = trim($aSql['groupBy'], ', ');
+        }
+        return $aSql;
+    }
+
+    /**
+     * Concat sql parts of query, run it and return result array
+     * @param $aParams addon param
+     * return $aData multivariate array
+     */
+    function getSearchDataByParams ($aParams = '')
+    {
+        $aSql = array('ownFields'=>'', 'joinFields'=>'', 'order'=>'');
+
+        // searchFields
+        foreach ($this->aCurrent['ownFields'] as $sValue)
+            $aSql['ownFields'] .= $this->setFieldUnit($sValue, $this->aCurrent['table']);
+
+        // joinFields & join
+        $aJoins = $this->getJoins();
+        if (!empty($aJoins)) {
+            $aSql['ownFields'] .= $aJoins['ownFields'];
+            $aSql['ownFields'] .= $aJoins['joinFields'];
+            $aSql['join'] = $aJoins['join'];
+            $aSql['groupBy'] = $aJoins['groupBy'];
+        } 
+
+        $aSql['ownFields'] = trim($aSql['ownFields'], ', ');
+
+        // from
+        $aSql['from'] = " FROM `{$this->aCurrent['table']}`";
+
+        // where
+        $aSql['where'] = $this->getRestriction();
+
+        // limit
+        $aSql['limit'] = $this->getLimit();
+
+        // sorting
+        $this->setSorting();
+
+        $aSort = $this->getSorting($this->aCurrent['sorting']);
+        foreach ($aSort as $sKey => $sValue)
+            $aSql[$sKey] .= $sValue;
+
+        // execution
+        $sqlQuery = "SELECT " . $aSql['ownFields'];
+
+        $sqlQuery .= ' ' . $aSql['from'];
+
+        if (isset($aSql['join']))
+            $sqlQuery .= ' ' . $aSql['join'];
+
+        if (isset($aSql['where']))
+            $sqlQuery .= ' ' . $aSql['where'];
+
+        if (isset($aSql['groupBy']))
+            $sqlQuery .= ' ' . $aSql['groupBy'];
+
+        if (isset($aSql['order']))
+            $sqlQuery .= ' ' . $aSql['order'];
+
+        if (isset($aSql['limit']))
+            $sqlQuery .= ' ' . $aSql['limit'];
+
+        // echoDbg($sqlQuery);
+        $aRes = BxDolDb::getInstance()->getAll($sqlQuery);
+        return $aRes;
+    }
+
+    /**
+     * Set class fields condition params and paginate array
+     */
+    function setConditionParams()
+    {
+        // keyword
+        $sKeyword = bx_process_input(isset($this->_aCustomSearchCondition['keyword']) ? $this->_aCustomSearchCondition['keyword'] : bx_get('keyword'));
+        if ($sKeyword !== false) {
+            $this->aCurrent['restriction']['keyword'] = array(
+                'value' => $sKeyword,
+                'field' => '',
+                'operator' => 'against'
+            );
+            // for search results we need to show all items, not only public content
+            $this->setProcessPrivateContent(true);
+        }
+
+        // owner
+        if (isset($_GET['ownerName'])) {
+            $sName = bx_process_input($_GET['ownerName']);
+            $iUser = (int)BxDolProfileQuery::getInstance()->getIdByNickname($sName);
+            BxDolMenu::getInstance()->setCurrentProfileID($iUser);
+        } elseif (isset($_GET['userID']))
+            $iUser = bx_process_input($_GET['userID'], BX_DATA_INT);
+
+        if (!empty($iUser))
+            $this->aCurrent['restriction']['owner']['value'] = $iUser;
+        
+        // meta info
+        if ($this->_sMetaType && !empty($this->aCurrent['object_metatags'])) {
+            $o = BxDolMetatags::getObjectInstance($this->aCurrent['object_metatags']);
+            if ($o) {
+                unset($this->aCurrent['restriction']['keyword']);
+                switch ($this->_sMetaType) {
+                    case 'location_country':
+                        $o->locationsSetSearchCondition($this, $sKeyword);
+                        break;
+                    case 'location_country_state':
+                        $o->locationsSetSearchCondition($this, $sKeyword, bx_process_input(isset($this->_aCustomSearchCondition['state']) ? $this->_aCustomSearchCondition['state'] : bx_get('state')));
+                        break;
+                    case 'location_country_city':
+                        $o->locationsSetSearchCondition($this, $sKeyword, bx_process_input(isset($this->_aCustomSearchCondition['state']) ? $this->_aCustomSearchCondition['state'] : bx_get('state')), bx_process_input(isset($this->_aCustomSearchCondition['city']) ? $this->_aCustomSearchCondition['city'] : bx_get('city')));
+                        break;
+                    case 'mention':
+                        $oCmts = !empty($this->sModuleObjectComments) ? BxDolCmts::getObjectInstance($this->sModuleObjectComments, 0, false) : false;
+                        $o->mentionsSetSearchCondition($this, $sKeyword, $oCmts ? $oCmts->getSystemId() : 0);
+                        break;
+                    case 'keyword':
+                        $oCmts = !empty($this->sModuleObjectComments) ? BxDolCmts::getObjectInstance($this->sModuleObjectComments, 0, false) : false;
+                        $o->keywordsSetSearchCondition($this, $sKeyword, $oCmts ? $oCmts->getSystemId() : 0);
+                        break;
+                }
+            }
+        }
+
+        // category
+        if ($this->_sCategoryObject){
+            if(($o = BxDolCategory::getObjectInstance($this->_sCategoryObject)) && $this->_bSingleSearch) {
+                unset($this->aCurrent['restriction']['keyword']);
+                $o->setSearchCondition($this, $sKeyword);
+            }
+            if ($this->_sCategoryObject == 'multi'){
+                unset($this->aCurrent['restriction']['keyword']);
+                $this->setCategoriesCondition($sKeyword);
+            }
+        }
+
+        $this->setPaginate();
+        $iNum = $this->getNum();
+        if ($iNum > 0) {
+            $this->aCurrent['paginate']['num'] = $iNum;
+        } else {
+           $this->aCurrent['paginate']['num'] = 0;
+        }
+    }
+
+    /**
+     * Check number of records on current page
+     * return number of records on current page + 1
+     */
+    function getNum ()
+    {
+        $aJoins = $this->getJoins(false);
+        $sqlQuery =  "SELECT * FROM `{$this->aCurrent['table']}` " . (isset($aJoins['join']) ? $aJoins['join'] : '' ). $this->getRestriction() . (isset($aJoins['groupBy']) ? $aJoins['groupBy'] : '') . ' ' . $this->getLimit(true);
+        return count(BxDolDb::getInstance()->getAll($sqlQuery));
+    }
+
+    /**
+     * Check restriction params and make condition part of query
+     * return $sqlWhere sql code of query for WHERE part
+     */
+    function getRestriction ()
+    {
+        $oDb = BxDolDb::getInstance();
+        $sqlWhere = '';
+        if (isset($this->aCurrent['restriction'])) {
+            $aWhere[] = '1';
+            foreach ($this->aCurrent['restriction'] as $sKey => $aValue) {
+                $sqlCondition = '';
+                if (isset($aValue['operator']) && isset($aValue['value']) && $aValue['value'] !== '' && $aValue['value'] !== false && $aValue['value'] !== null) {
+                   $sFieldTable = isset($aValue['table']) ? $aValue['table'] : $this->aCurrent['table'];
+                   $sqlCondition = "`{$sFieldTable}`.`{$aValue['field']}` ";
+                   switch ($aValue['operator']) {
+                       case 'empty value':
+                            $sqlCondition .= " = '' ";
+                            break;
+                       case 'not empty value':
+                            $sqlCondition .= " != '' ";
+                            break;
+                       case 'nothing':
+                            $sqlCondition = " 0 ";
+                            break;
+                       case 'against':
+                            $aCond = isset($aValue['field']) && strlen($aValue['field']) > 0 ? $aValue['field'] : $this->aCurrent['searchFields'];
+                            $sqlCondition = !empty($aCond) ? $this->getSearchFieldsCond($aCond, $aValue['value']) : "";
+                            break;
+                       case 'like':
+                            $sqlCondition .= "LIKE " . $oDb->escape('%' . $aValue['value'] . '%');
+                            break;
+                       case 'in':
+                       case 'not in':
+                            $sValuesString = $this->getMultiValues($aValue['value']);
+                            $sqlCondition .= strtoupper($aValue['operator']) . '(' . $sValuesString . ')';
+                            break;
+                       default:
+                               $sqlCondition .= $aValue['operator'] . (isset($aValue['no_quote_value']) && $aValue['no_quote_value'] ?  $aValue['value'] : $oDb->escape($aValue['value']));
+                       break;
+                    }
+                }
+                if (strlen($sqlCondition) > 0)
+                    $aWhere[] = $sqlCondition;
+            }
+            $sqlWhere .= "WHERE ". implode(' AND ', $aWhere) . (isset($this->aCurrent['restriction_sql']) ? $this->aCurrent['restriction_sql'] : '');
+        }
+
+        return $sqlWhere;
+    }
+
+    /**
+     * Get limit part of query
+     * return $sqlFrom code for limit part pf query
+     */
+    function getLimit ($isAddPlusOne = false)
+    {
+        if (!isset($this->aCurrent['paginate']))
+            return;
+
+        $sqlFrom = (int)$this->aCurrent['paginate']['start'] > 0 ? (int)$this->aCurrent['paginate']['start'] : 0;
+        $sqlTo = $this->aCurrent['paginate']['perPage'] + ($isAddPlusOne ? 1 : 0);
+        if ($sqlTo > 0)
+            return 'LIMIT ' . $sqlFrom . ', ' . $sqlTo;
+    }
+
+    /**
+     * Set sorting field of class
+     */
+    function setSorting ()
+    {
+        $this->aCurrent['sorting'] = isset($_GET[$this->aCurrent['name'] . '_mode']) ? $_GET[$this->aCurrent['name'] . '_mode'] : $this->aCurrent['sorting'];
+    }
+
+    /**
+     * Get sorting part of query according current sorting mode
+     * @param string $sSortType sorting type
+     *                          return array with sql elements order and ownFields
+     */
+    function getSorting ($sSortType = 'last')
+    {
+        $aOverride = $this->getAlterOrder();
+        if (is_array($aOverride) && !empty($aOverride))
+            return $aOverride;
+
+       $aSql = array();
+       switch ($sSortType) {
+           case 'rand':
+                $aSql['order'] = "ORDER BY RAND()";
+                break;
+           case 'score':
+                if (is_array($this->aCurrent['restriction']['keyword'])) {
+                    $sPseud = '';
+                    if ('on' == getParam('useLikeOperator')) {
+                        $aSql['order'] = "ORDER BY `score` DESC";
+                        $sPseud = 'score';
+                    }
+                    $aSql['ownFields'] .= $this->getSearchFieldsCond($this->aCurrent['searchFields'], $this->aCurrent['restriction']['keyword']['value'], $sPseud) . ',';
+                }
+                break;
+           case 'none':
+                $aSql['order'] = "";
+                break;
+           default:
+                $aSql['order'] = "ORDER BY `date` DESC";
+        }
+        return $aSql;
+    }
+
+    /**
+     * Return own varaint for sorting (redeclare if necessary)
+     * return array of sql elements
+     */
+    function getAlterOrder ()
+    {
+        return array();
+    }
+
+    /**
+     * Set paginate fields of class according GET params 'start' and 'per_page'
+     * forcePage is need for setting most important number of current page
+     */
+    function setPaginate ()
+    {
+        $iPerPage = 0;
+
+        //--- Check in aCurrent (low priority).
+        if(isset($this->aCurrent['paginate']['perPage']) && (int)$this->aCurrent['paginate']['perPage'] != 0)
+            $iPerPage = (int)$this->aCurrent['paginate']['perPage'];
+
+        //--- Check in GET params (high priority).
+        if(isset($_GET['per_page']) && (int)$_GET['per_page'] != 0)
+            $iPerPage = (int)$_GET['per_page'];
+
+        //--- Trying to get from System settings.
+        if (empty($iPerPage))
+            $iPerPage = (int)getParam('sys_per_page_search_keyword_' . ($this->_bSingleSearch ? 'single' : 'plural'));
+
+        //--- Use default value in case of emergency.
+        if (empty($iPerPage))
+            $iPerPage = BX_DOL_SEARCH_RESULTS_PER_PAGE_DEFAULT;
+
+        $this->aCurrent['paginate']['perPage'] = $iPerPage;
+            
+        $this->aCurrent['paginate']['start'] = isset($this->aCurrent['paginate']['forceStart'])
+            ? (int)$this->aCurrent['paginate']['forceStart']
+            : (empty($_GET['start']) ? 0 : (int)$_GET['start']);
+
+        if ($this->aCurrent['paginate']['start'] < 0)
+            $this->aCurrent['paginate']['start'] = 0;
+    }
+
+    function unsetPaginate()
+    {
+    	unset($this->aCurrent['paginate']);
+    }
+
+    /**
+     * Get sql where condition for search fields
+     * @param array of search fields
+     * @param string $sKeyword keyword value for search
+     * @param string sPseud for setting new name for generated set of fields in query
+     *                         return sql code of WHERE part in query
+     */
+    function getSearchFieldsCond ($aFields, $sKeyword, $sPseud = '')
+    {
+        if (!$sKeyword)
+            return '';
+
+        $sTable = empty($this->aCurrent['tableSearch']) ? $this->aCurrent['table'] : $this->aCurrent['tableSearch'];
+
+        $oDb = BxDolDb::getInstance();
+
+        $bLike = getParam('useLikeOperator');
+
+        if (!is_array($aFields))
+            $aFields = array($aFields);
+
+        if ($bLike == 'on') {
+            $sKeyword = $oDb->escape('%' . preg_replace('/\s+/', '%', $sKeyword) . '%');
+
+            $sSqlWhere = '';
+            foreach ($aFields as $sValue)
+                $sSqlWhere .= "`{$sTable}`.`$sValue` LIKE  " . $sKeyword . " OR ";
+
+            $sSqlWhere = '(' . trim($sSqlWhere, 'OR ') . ')';
+
+        } else {
+        	$sKeyword = $oDb->escape($sKeyword);
+
+            $sSqlWhere = '';
+            foreach ($aFields as $sValue)
+                $sSqlWhere .= "`{$sTable}`.`$sValue`, ";
+
+            $sSqlWhere = trim($sSqlWhere, ', ');
+            $sSqlWhere = " MATCH({$sSqlWhere}) AGAINST (" . $sKeyword . ") ";
+
+            if (!empty($sPseud))
+                $sSqlWhere .= " AS `$sPseud` ";
+        }
+
+        return $sSqlWhere;
+    }
+
+    /**
+     * Get set from several values for 'in' and 'not in' operators
+     * @param $aValues array of values
+     * return sql code for field with operator IN (NOT IN)
+     */
+    function getMultiValues ($aValues)
+    {
+        $oDb = BxDolDb::getInstance();
+        return $oDb->implode_escape($aValues);
+    }
+
+    /**
+     * System method for filling aPseud array.
+     * Fill field aPseud for current class (if you will use own getSearchUnit methods then not necessary to redeclare).
+     */
+    function _getPseud ()
+    {
+
+    }
+
+    /**
+     * Add replace markers. Markers are replaced in titles and browse urls
+     * @param $a array of markers as key => value
+     * @return true on success or false on error
+     */
+    public function addMarkers ($a)
+    {
+        if (empty($a) || !is_array($a))
+            return false;
+        $this->_aMarkers = array_merge ($this->_aMarkers, $a);
+        return true;
+    }
+
+    /**
+     * Replace provided markers in a string
+     * @param $mixed string or array to replace markers in
+     * @return string where all occured markers are replaced
+     */
+    protected function _replaceMarkers ($mixed)
+    {
+        return bx_replace_markers($mixed, $this->_aMarkers);
+    }
+}
+
+/** @} */

+ 348 - 0
upgrade/files/11.0.0-11.0.1/files/inc/classes/BxDolStorageQuery.php

@@ -0,0 +1,348 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaCore UNA Core
+ * @{
+ */
+
+/**
+ * Database queries for File storage class.
+ * @see BxDolStorage
+ */
+class BxDolStorageQuery extends BxDolDb
+{
+    protected $_aObject;
+    protected $_sTableFiles;
+
+    public function __construct($aObject)
+    {
+        parent::__construct();
+        $this->_aObject = $aObject;
+        $this->_sTableFiles = '`' . $aObject['table_files'] . '`';
+    }
+
+    static public function getStorageObject ($sObject)
+    {
+        $oDb = BxDolDb::getInstance();
+        $sQuery = $oDb->prepare("SELECT * FROM `sys_objects_storage` WHERE `object` = ?", $sObject);
+        return $oDb->getRow($sQuery);
+    }
+
+    static public function getStorageObjects ()
+    {
+        $oDb = BxDolDb::getInstance();
+        $sQuery = "SELECT * FROM `sys_objects_storage`";
+        return $oDb->getAll($sQuery);
+    }
+
+    public function changeStorageEngine ($sEngine)
+    {
+        $sQuery = $this->prepare("UPDATE `sys_objects_storage` SET `engine` = ? WHERE `object` = ?", $sEngine, $this->_aObject['object']);
+        return $this->query($sQuery);
+    }
+
+    public function getMimeTypeByExt($sExt)
+    {
+        $sQuery = $this->prepare("SELECT `mime_type` FROM `sys_storage_mime_types` WHERE `ext` = ?", $sExt);
+        return $this->getOne($sQuery);
+    }
+
+    public function getIconByExt($sExt)
+    {
+        $sQuery = $this->prepare("SELECT `icon` FROM `sys_storage_mime_types` WHERE `ext` = ?", $sExt);
+        return $this->getOne($sQuery);
+    }
+
+    public function getIconFontByExt($sExt)
+    {
+        $sQuery = $this->prepare("SELECT `icon_font` FROM `sys_storage_mime_types` WHERE `ext` = ?", $sExt);
+        return $this->getOne($sQuery);
+    }
+    
+    public function clearAllMimeTypes()
+    {
+        return $this->query("TRUNCATE TABLE `sys_storage_mime_types`");
+    }
+
+    public function addMimeType($sMimeType, $sExt, $sIcon = '', $sIconFont = '')
+    {
+        $sQuery = $this->prepare("INSERT IGNORE INTO `sys_storage_mime_types` SET `ext` = ?, `mime_type` = ?, `icon` = ?, `icon_font` = ?", $sExt, $sMimeType, $sIcon, $sIconFont);
+        return $this->query($sQuery);
+    }
+
+    public function getStorageObjectQuota()
+    {
+        return $this->_aObject;
+    }
+
+    public function updateStorageObjectQuota($iSize, $iNumber = 1)
+    {
+        $iTime = time();
+        $sQuery = $this->prepare("
+            UPDATE `sys_objects_storage`
+            SET `current_size` = `current_size` + ?, `current_number` = `current_number` + (?), `ts` = ?
+            WHERE `object` = ?",
+            $iSize, $iNumber, $iTime, $this->_aObject['object']
+        );
+        if ($this->query($sQuery)) {
+            $this->_aObject = $this->getStorageObject($this->_aObject['object']);
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    public function getUserQuota($iProfileId)
+    {
+        return BxDolProfileQuery::getInstance()->getProfileQuota($iProfileId);
+    }
+
+    public function updateUserQuota($iProfileId, $iSize, $iNumber = 1)
+    {
+        return BxDolProfileQuery::getInstance()->updateProfileQuota($iProfileId, $iSize, $iNumber);
+    }
+
+    public function addFile($iProfileId, $sLocalId, $sPath, $aFileName, $sMimeType, $sExt, $iSize, $iTime, $isPrivate)
+    {
+        $sQuery = $this->prepare("INSERT INTO " . $this->_sTableFiles . " SET
+            `profile_id` = ?, `remote_id` = ?, `path` = ?, `file_name` = ?, `mime_type` = ?, `ext` = ?, `size` = ?, `added` = ?, `modified` = ?, `private` = ?",
+            $iProfileId, $sLocalId, $sPath, $aFileName, $sMimeType, $sExt, $iSize, $iTime, $iTime, $isPrivate ? 1 : 0
+        );
+        return $this->query($sQuery);
+    }
+
+    public function modifyFilePrivate($iFileId, $isPrivate)
+    {
+        return $this->modifyCustomField($iFileId, 'private', $isPrivate ? 1 : 0);
+    }
+
+    public function modifyCustomField($iFileId, $sField, $sValue, $isUpdateModifiedField = true)
+    {
+    	$aBindings = array(
+    		$sField => $sValue,
+    		'id' => $iFileId
+    	);
+    	
+        $sAdd = '';
+        if ($isUpdateModifiedField) {
+        	$aBindings['modified'] = time();
+
+            $sAdd = ", `modified` = :modified";
+        }
+
+        $sQuery = "UPDATE " . $this->_sTableFiles . " SET `{$sField}` = :{$sField} {$sAdd} WHERE `id` = :id";
+        return $this->query($sQuery, $aBindings);
+    }
+
+    public function deleteFile($iFileId)
+    {
+        // delete queued record for the file
+        $sQuery = $this->prepare("DELETE FROM `sys_storage_deletions` WHERE `object` = ? AND `file_id` = ?", $this->_aObject['object'], $iFileId);
+        $this->query($sQuery);
+
+        // delete file record
+        $sQuery = $this->prepare("DELETE FROM " . $this->_sTableFiles . " WHERE `id` = ?", $iFileId);
+        if (!$this->query($sQuery))
+            return false;
+
+        // delete any file traces in ghosts table
+        $sQuery = $this->prepare("DELETE FROM `sys_storage_ghosts` WHERE `object` = ? AND `id` = ?", $this->_aObject['object'], $iFileId);
+        $this->query($sQuery);
+
+        return true;
+    }
+
+    public function getFileByFileName($sValue)
+    {
+        return $this->_getFileBy('`file_name`', $sValue);
+    }
+
+    public function getFileById($sValue)
+    {
+        return $this->_getFileBy('`id`', $sValue);
+    }
+
+    public function getFileByRemoteId($sValue)
+    {
+        return $this->_getFileBy('`remote_id`', $sValue);
+    }
+
+    protected function _getFileBy($sField, $sValue)
+    {
+        $sQuery = $this->prepare("SELECT * FROM " . $this->_sTableFiles . " WHERE " . $sField . " = ?", $sValue);
+        return $this->getRow($sQuery);
+    }
+
+    public function isTokenValid($iFileId, $sToken)
+    {
+        $iTime = time();
+        $sQuery = $this->prepare("SELECT `created` FROM `sys_storage_tokens` WHERE `id` = ? AND `object` = ? AND `hash` = ? AND `created` > ?", $iFileId, $this->_aObject['object'], $sToken, $iTime - $this->_aObject['token_life']);
+        return $this->getOne($sQuery) ? true : false;
+    }
+
+    public function genToken($iFileId)
+    {
+        $iTime = time();
+        $sToken = md5($iTime . mt_rand() . BX_DOL_SECRET);
+        $sQuery = $this->prepare("INSERT INTO `sys_storage_tokens` SET `id` = ?, `object` = ?, `hash` = ?, `created` = ?", $iFileId, $this->_aObject['object'], $sToken, $iTime);
+        if ($this->query($sQuery))
+            return $sToken;
+        else
+            return false;
+    }
+
+    public function insertGhosts($mixedFileIds, $iProfileId, $iContentId = 0)
+    {
+        $iTime = time();
+        if (!is_array($mixedFileIds))
+            $mixedFileIds = array($mixedFileIds);
+
+        $iCount = 0;
+        foreach ($mixedFileIds as $iFileId) {
+            $sQuery = $this->prepare("INSERT INTO `sys_storage_ghosts`
+                SET `id` = ?, `object` = ?, `profile_id` = ?, `content_id` = ?, `created` = ?
+                ON DUPLICATE KEY UPDATE `profile_id` = ?, `content_id` = ?, `created` = ?", $iFileId, $this->_aObject['object'], $iProfileId, $iContentId, $iTime, $iProfileId, $iContentId, $iTime);
+            $iCount += $this->query($sQuery);
+        }
+        return $iCount;
+    }
+
+    public function updateGhostsContentId($mixedFileIds, $iProfileId, $iContentId, $aProfiles = array(), $isAdmin = false)
+    {
+        $aBindings = array(
+            'content' => $iContentId,
+    		'profile' => $iProfileId,
+    		'object' => $this->_aObject['object']
+        );
+
+        $sSetAddon = '';
+        $sWhere = " AND `profile_id` = :profile ";
+        if ($aProfiles || $isAdmin) {
+            $sSetAddon = ', `profile_id` = :profile ';
+            $sWhere = $isAdmin ? "" : " AND `profile_id` IN(" . $this->implode_escape($aProfiles) . ")";
+        }
+        
+        $sQuery = $this->prepare("UPDATE `sys_storage_ghosts` SET `content_id` = :content $sSetAddon WHERE `object` = :object $sWhere AND `id` IN (" . $this->implode_escape($mixedFileIds) . ")");
+        return $this->res($sQuery, $aBindings);
+    }
+
+    public function deleteGhosts($mixedFileIds, $iProfileId, $iContentId = false)
+    {
+    	$aBindings = array(
+    		'profile_id' => $iProfileId,
+    		'object' => $this->_aObject['object']
+    	);
+        $sQuery = "DELETE FROM `sys_storage_ghosts` WHERE `profile_id` = :profile_id AND `object` = :object AND `id` IN (" . $this->implode_escape($mixedFileIds) . ")";
+        if (false !== $iContentId) {
+        	$aBindings['content_id'] = $iContentId;
+
+            $sQuery .= " AND `content_id` = :content_id";
+        }
+        $iCount = $this->query($sQuery, $aBindings);
+        if ($iCount)
+            $this->query("OPTIMIZE TABLE `sys_storage_ghosts`");
+        return $iCount;
+    }
+
+    public function getGhosts($mixedProfileId, $iContentId = false, $isAdmin = false)
+    {
+        return $this->getFiles($mixedProfileId, true, $iContentId, $isAdmin);
+    }
+
+    public function getGhost($iFileId)
+    {
+        $sQuery = $this->prepare("SELECT * FROM `sys_storage_ghosts` WHERE `object` = ? AND `id` = ?", $this->_aObject['object'], $iFileId);
+        return $this->getRow($sQuery);
+    }
+
+    public function getFiles($mixedProfileId, $isGhostsOnly = false, $iContentId = false, $isAdmin = false)
+    {
+        $aBindings = array();        
+
+        $sWhere = '';
+        $sJoin = '';
+        if ($isGhostsOnly) {
+            $aBindings['object'] = $this->_aObject['object'];
+
+            $sOnProfile = '';
+            if ($isAdmin && $iContentId) {
+                // don't check profile id for admins, so admin can edit any entry
+            }
+            elseif (is_array($mixedProfileId) && $mixedProfileId) {
+                $sOnProfile = " AND `g`.`profile_id` IN(" . $this->implode_escape($mixedProfileId) . ") ";
+            } 
+            elseif (!is_array($mixedProfileId) && false !== $mixedProfileId) {
+                $aBindings['profile_id'] = $mixedProfileId;
+                
+                $sOnProfile = " AND `g`.`profile_id` = :profile_id ";                
+            }
+            
+            $sJoin .= " INNER JOIN `sys_storage_ghosts` AS `g` ON (`f`.`id` = `g`.`id` AND `g`.`object` = :object " . $sOnProfile;
+            if (false !== $iContentId) {
+                $aBindings['content_id'] = $iContentId;
+
+                $sJoin .= " AND `g`.`content_id` = :content_id";
+            }
+
+            $sJoin .= ')';
+        } 
+        else {
+            if (is_array($mixedProfileId) && $mixedProfileId) {
+                $sWhere = " WHERE `f`.`profile_id` IN(" . $this->implode_escape($mixedProfileId) . ") ";
+            }
+            elseif (!is_array($mixedProfileId) && false !== $mixedProfileId) {
+                $aBindings['profile_id'] = $mixedProfileId;
+
+                $sWhere = " WHERE `f`.`profile_id` = :profile_id ";
+            }
+        }
+
+        $sQuery = "SELECT `f`.* FROM " . $this->_sTableFiles . " AS `f` " . $sJoin . $sWhere;
+        return $this->getAll($sQuery, $aBindings);
+    }
+
+    public function getFilesAll($iStart, $iPerPage)
+    {
+        $sQuery = $this->prepare("SELECT * FROM " . $this->_sTableFiles . " LIMIT ?, ?", (int)$iStart, (int)$iPerPage);
+        return $this->getAll($sQuery);
+    }
+
+    public function prune()
+    {
+        $iTime = time();
+        $sQuery = $this->prepare("DELETE FROM `sys_storage_tokens` WHERE `object` = ? AND `created` < ?", $this->_aObject['object'], $iTime - $this->_aObject['token_life']);
+        $iCount = $this->query($sQuery);
+        if ($iCount)
+            $this->query("OPTIMIZE TABLE `sys_storage_tokens`");
+        return $iCount;
+    }
+
+    public function queueFilesForDeletion ($a)
+    {
+        $iTime = time();
+        $iAdded = 0;
+        foreach ($a as $iFileId) {
+            $sQuery = $this->prepare("INSERT IGNORE INTO `sys_storage_deletions` SET `object` = ?, `file_id` = ?, `requested` = ?", $this->_aObject['object'], (int)$iFileId, $iTime);
+            $iAdded += ($this->query($sQuery) ? 1 : 0);
+        }
+        return $iAdded;
+    }
+
+    public static function getQueuedFilesForDeletion ($iLimit = 1000)
+    {
+        $oDb = BxDolDb::getInstance();
+        $sQuery = $oDb->prepare("SELECT `object`, `file_id` FROM `sys_storage_deletions` ORDER BY `requested` ASC LIMIT ?", $iLimit);
+        return $oDb->getAll($sQuery);
+    }
+
+    public static function isQueuedFilesForDeletion ($sPrefix)
+    {
+        $oDb = BxDolDb::getInstance();
+        $sQuery = $oDb->prepare("SELECT COUNT(*) FROM `sys_storage_deletions` WHERE `object` LIKE ?", $sPrefix . '%');
+        return $oDb->getOne($sQuery);
+    }
+}
+
+/** @} */

+ 900 - 0
upgrade/files/11.0.0-11.0.1/files/inc/classes/BxDolTranscoder.php

@@ -0,0 +1,900 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaCore UNA Core
+ * @{
+ */
+
+define('BX_DOL_QUEUE_PENDING', 'pending'); ///< file is pending for processing
+define('BX_DOL_QUEUE_PROCESSING', 'processing'); ///< file is in the process
+define('BX_DOL_QUEUE_COMPLETE', 'complete'); ///< file is already processed
+define('BX_DOL_QUEUE_FAILED', 'failed'); ///< file processing was failed
+define('BX_DOL_QUEUE_DELETE', 'delete'); ///< file is subject to delete
+
+define('BX_DOL_MAX_RETINA_DIMENSION_MOBILE', 500); ///< max image dimension which will be processes as retine on mobiles
+
+/**
+ * Base class for @see BxDolTranscoderImage and @see BxDolTranscoderVideo transcoders
+ */
+class BxDolTranscoder extends BxDolFactory implements iBxDolFactoryObject
+{
+    protected $_sLog = ''; ///< file conversion log
+    protected $_aObject; ///< object properties
+    protected $_oStorage; ///< storage object, transcoded files are stored here
+    protected $_oDb; ///< database queries object
+    protected $_sRetinaSuffix = '@2x'; ///< handler suffix for retina image file
+    protected $_sQueueTable = ''; ///< table with queued files for transcoding
+    protected $_sQueueStorage = ''; ///< storage object to storage temporary files
+    protected $_iDevicePixelRatio = false; ///< force device pixel ratio, 1 or 2
+
+    /**
+     * constructor
+     */
+    protected function __construct($aObject, $oStorage)
+    {
+        parent::__construct();
+        $this->_aObject = $aObject;
+        $this->_oStorage = $oStorage;
+        $this->_oDb = null;
+    }
+
+    /**
+     * Get transcode object instance.
+     * @param $sObject - name of transcode object.
+     * @return false on error or instance of transcoder class.
+     */
+    public static function getObjectInstance($sObject)
+    {
+        if (isset($GLOBALS['bxDolClasses'][__CLASS__ . '!' . $sObject]))
+            return $GLOBALS['bxDolClasses'][__CLASS__ . '!'.$sObject];
+
+        // get transcode object
+        $aObject = BxDolTranscoderQuery::getTranscoderObject($sObject);
+        if (!$aObject || !is_array($aObject))
+            return false;
+
+        if ($aObject['source_params'])
+            $aObject['source_params'] = unserialize($aObject['source_params']);
+
+        $aObject['filters'] = false; // filters are initialized on demand
+
+        // create storage object to store transcoded data
+        $oStorage = BxDolStorage::getObjectInstance($aObject['storage_object']);
+        if (!$oStorage)
+            return false;
+
+        // create instance
+        $sClass = $aObject['override_class_name'] ? $aObject['override_class_name'] : 'BxDolTranscoderImage';
+        if (!empty($aObject['override_class_file']))
+            require_once(BX_DIRECTORY_PATH_ROOT . $aObject['override_class_file']);
+
+        $o = new $sClass ($aObject, $oStorage);
+
+        return ($GLOBALS['bxDolClasses'][__CLASS__ . '!' . $sObject] = $o);
+    }
+
+    /**
+     * Delete outdated transcoed data from all transcoeeed objects, by last access time.
+     * It called on cron, usually every day.
+     * @return total number of pruned/deleted files
+     */
+    static public function pruning ()
+    {
+        $iCount = 0;
+        $aObjects = self::getTranscoderObjects();
+        foreach ($aObjects as $aObject) {
+            if (!$aObject['atime_tracking'] || !$aObject['atime_pruning'])
+                continue;
+            $oTranscoder = self::getObjectInstance($aObject['object']);
+            if (!$oTranscoder)
+                continue;
+            $iCount += $oTranscoder->prune();
+        }
+        return $iCount;
+    }
+
+    /**
+     * Get transcoder objects array
+     * @return array
+     */
+    static protected function getTranscoderObjects ()
+    {
+        return BxDolTranscoderQuery::getTranscoderObjects ();
+    }
+
+    /**
+     * Process files queued for transcoding
+     * Can be performed on separate server.
+     */
+    static public function processQueue ($bQueuePruning = true)
+    {
+        if ($bQueuePruning)
+            self::pruneQueue();
+
+        $a = BxDolTranscoderQuery::getNextInQueue (gethostname());
+        foreach ($a as $r) {
+            $o = self::getObjectInstance($r['transcoder_object']);
+            if (!$o) {
+                BxDolTranscoderQuery::deleteFromQueueById($r['id']);
+                continue;
+            }
+
+            $aQueue = $o->getDb()->getFromQueue($r['file_id_source']); 
+            if (!$aQueue || $aQueue['status'] != BX_DOL_QUEUE_PENDING)
+                continue;
+            
+            $o->transcode($aQueue['file_id_source'], $aQueue['profile_id']);
+        }
+    }
+
+    static public function pruneQueue () 
+    {
+        $a = BxDolTranscoderQuery::getForDeletionFromQueue (gethostname());
+        if (!$a)
+            return false;
+
+        foreach ($a as $aQueue) {
+            $o = self::getObjectInstance($aQueue['transcoder_object']);
+            if ($o)
+                $o->deleteFromQueue($aQueue['file_id_source']);
+        }
+    }
+
+    /**
+     * Store completed files from tmp queue storage to the final storage
+     * Must be performed on the actual site.
+     */
+    static public function processCompleted ()
+    {
+        $a = BxDolTranscoderQuery::getCompletedFromQueue ();
+        foreach ($a as $r) {
+            $o = self::getObjectInstance($r['transcoder_object']);
+            $aQueue = $o->getDb()->getFromQueue($r['file_id_source']); 
+            if (!$aQueue || $aQueue['status'] != BX_DOL_QUEUE_COMPLETE)
+                continue;
+            
+            if ($aQueue['file_url_result'] && $aQueue['file_id_result']) {
+  
+                // get transcoded file from provided URL              
+                $sFileData = bx_file_get_contents ($aQueue['file_url_result']);
+                if (false === $sFileData) {
+                    $o->getDb()->updateQueueStatus($aQueue['file_id_source'], BX_DOL_QUEUE_FAILED, "download file failed: {$aQueue['file_url_result']}\n");
+                    continue;   
+                }
+                
+                $sTmpFile = $o->getTmpFilename ('.' . $aQueue['file_ext_result']);
+                if (!file_put_contents($sTmpFile, $sFileData)) {
+                    $o->getDb()->updateQueueStatus($aQueue['file_id_source'], BX_DOL_QUEUE_FAILED, "store downloaded file failed\n");
+                    @unlink($sTmpFile);
+                    continue;
+                }
+                
+                // store downloaded file in final storage
+                if ($o->storeTranscodedFile($aQueue['file_id_source'], $sTmpFile, $aQueue['profile_id']))
+                   $o->getDb()->updateQueueStatus($aQueue['file_id_source'], BX_DOL_QUEUE_DELETE); // mark to delete it (deletion is performed on the server where it was transcoded)
+                else
+                    $o->getDb()->updateQueueStatus($aQueue['file_id_source'], BX_DOL_QUEUE_FAILED, "store file failed:\n" . $o->getLog());
+
+                // delete tmp local file
+                @unlink($sTmpFile);
+            }
+        }
+    }
+
+    /**
+     * Register handlers array
+     * It can be called upon module enable event.
+     * @param $mixed array of transcoders objects, or just one object
+     */
+    static public function registerHandlersArray ($mixed)
+    {
+        self::_callFuncForObjectsArray ($mixed, 'registerHandlers');
+    }
+
+    /**
+     * Register system handlers
+     */
+    static public function registerHandlersSystem()
+    {
+        $sPrefix = 'sys_';
+
+        $aResults = array();
+        $aTranscoders = BxDolTranscoderQuery::getTranscoderObjects();
+        foreach($aTranscoders as $aTranscoder) {
+            if(strncasecmp($aTranscoder['object'], $sPrefix, strlen($sPrefix)) !== 0)
+                continue;
+
+            $aResults[] = $aTranscoder['object'];
+        }
+
+        if(!empty($aResults))
+            self::registerHandlersArray($aResults);
+    }
+
+    /**
+     * Unregister handlers array
+     * It can be called upon module disbale event.
+     * @param $mixed array of transcoders objects, or just one object
+     */
+    static public function unregisterHandlersArray ($mixed)
+    {
+        self::_callFuncForObjectsArray ($mixed, 'unregisterHandlers');
+    }
+
+    /**
+     * Cleanup (queue for deletion all resized files)
+     * It can be called upon module disbale event.
+     * @param $mixed array of transcoders objects, or just one object
+     */
+    static public function cleanupObjectsArray ($mixed)
+    {
+        self::_callFuncForObjectsArray ($mixed, 'cleanup');
+    }
+
+    /**
+     * Called automatically, upon local(transcoded) file deletetion.
+     */
+    static public function onAlertResponseFileDeleteLocal ($oAlert, $sObject)
+    {
+        $oTranscoder = self::getObjectInstance($sObject);
+        if (!$oTranscoder)
+            return;
+
+        if ($oAlert->sAction != 'file_deleted')
+            return;
+
+        $oTranscoder->onDeleteFileLocal($oAlert->iObject);
+    }
+
+    /**
+     * Called automatically, upon original file deletetion.
+     */
+    static public function onAlertResponseFileDeleteOrig ($oAlert, $sObject)
+    {
+        $oTranscoder = self::getObjectInstance($sObject);
+        if (!$oTranscoder)
+            return;
+
+        if ($oAlert->sAction != 'file_deleted')
+            return;
+
+        $oTranscoder->onDeleteFileOrig($oAlert->iObject);
+    }
+
+    /**
+     * Called automatically, upon local(transcoded) file deletetion.
+     */
+    public function onDeleteFileLocal($iFileId)
+    {
+        return $this->_oDb->deleteFileTraces($iFileId);
+    }
+
+    /**
+     * Called automatically, upon original file deletetion.
+     */
+    public function onDeleteFileOrig($mixedHandler)
+    {
+        // deleted files queued for transcoding
+        $this->_oDb->deleteFromQueue ($mixedHandler);
+
+        // delete main file
+        $iFileId = $this->_oDb->getFileIdByHandler($mixedHandler);
+        if ($iFileId && !$this->_oStorage->deleteFile($iFileId))
+            return false;
+
+        // delete retina file
+        $iFileId = $this->_oDb->getFileIdByHandler($mixedHandler . $this->_sRetinaSuffix);
+        if ($iFileId && !$this->_oStorage->deleteFile($iFileId))
+            return false;
+
+        return true;
+    }
+
+    /**
+     * Register necessary alert handlers for automatic deletetion of transcoded data if source file is deleted.
+     * Make sure that you call it once, before first usage, for example upon module installation.
+     */
+    public function registerHandlers ()
+    {
+        if (!$this->_oDb->registerHandlers ())
+            return false;
+        return $this->clearCacheDB();
+    }
+
+    /**
+     * Unregister alert handlers for automatic deletetion of transcoded data if source file is deleted.
+     * Make sure that you call it once, for example upon module uninstallation.
+     */
+    public function unregisterHandlers ()
+    {
+        if (!$this->_oDb->unregisterHandlers ())
+            return false;
+        return $this->clearCacheDB();
+    }
+
+    /**
+     * Delete (queue for deletion) all resized files for the current object
+     */
+    public function cleanup ()
+    {
+        return $this->_oStorage->queueFilesForDeletionFromObject();
+    }
+
+    /**
+     * Get storage object where transcoded data is stored
+     */
+    public function getStorage()
+    {
+        return $this->_oStorage;
+    }
+
+    /**
+     * Get database object instance
+     */
+    public function getDb()
+    {
+        return $this->_oDb;
+    }
+
+    /**
+     * check if transcoder suppors given file mime type
+     */ 
+    public function isMimeTypeSupported($sMimeType)
+    {
+        return false;
+    }
+
+    /**
+     * Get transcoded file url.
+     * If transcoded file is ready then direct url to the file is returned.
+     * If there is no transcoded data available, then special url is returned, upon opening this url image is transcoded automatically and redirects to the ready transcoed image.
+     * @param $mixedHandler - file handler
+     * @return file url, or false on error.
+     */
+    public function getFileUrl($mixedHandler)
+    {
+        if ($this->isFileReady($mixedHandler)) {
+
+            $mixedHandler = $this->processHandlerForRetinaDevice($mixedHandler);
+
+            $iFileId = $this->_oDb->getFileIdByHandler($mixedHandler);
+            if (!$iFileId)
+                return false;
+
+            $aFile = $this->_oStorage->getFile($iFileId);
+            if (!$aFile)
+                return false;
+
+            if ($this->_aObject['atime_tracking'])
+                $this->_oDb->updateAccessTime($mixedHandler);
+
+            return $this->_oStorage->getFileUrlById($iFileId);
+        }
+
+        if ($this->_sQueueTable && !$this->addToQueue($mixedHandler)) // add file to queue (when queue is enabled) if it isn't transcoded yet
+            return false;
+
+        return $this->getFileUrlNotReady($mixedHandler);
+    }
+
+    /**
+     * alias for @see getFileUrl
+     */
+    public function getFileUrlById($mixedHandler)
+    {
+        return $this->getFileUrl($mixedHandler);
+    }
+
+    /**
+     * Check if transcoded data is available. No need to call it directly, it is called automatically when it is needed.
+     * @param $mixedHandler - file handler
+     * @param $isCheckOutdated - check if transcoded file is outdated
+     * @return false if there is no ready transcoded file is available or it is outdated, true if file is ready
+     */
+    public function isFileReady ($mixedHandler, $isCheckOutdated = true)
+    {
+        $sMethodFileReady = 'isFileReady_' . $this->_aObject['source_type'];
+        return $this->$sMethodFileReady($mixedHandler, $isCheckOutdated);
+    }
+
+    /**
+     * Transcode file, no need to call it directly, it is called automatically when it is needed.
+     * @param $mixedHandler - file handler
+     * @param $iProfileId - optional profile id, to assign transcoded file ownership to, usually it is NOT assigned to any particular profile, so just leave it default
+     * @return true on success, false on error
+     */
+    public function transcode ($mixedHandler, $iProfileId = 0)
+    {
+        $sExtChange = false;
+        $bRet = true;
+
+        $this->_oDb->updateQueueStatus($mixedHandler, BX_DOL_QUEUE_PROCESSING, '', gethostname());
+
+        do {
+
+            // create tmp file locally
+            $sSuffix = $this->_sQueueStorage ? 'Queue' : $this->_aObject['source_type'];
+            $sMethodStoreFile = 'storeFileLocally_' . $sSuffix;
+            $sTmpFile = $this->$sMethodStoreFile($mixedHandler);
+            if (!$sTmpFile) {
+                $this->_oDb->updateQueueStatus($mixedHandler, BX_DOL_QUEUE_FAILED, "store local tmp file failed ($sMethodStoreFile)");
+                $bRet = false;
+                break;
+            }
+
+            // appply filters to tmp file
+            $this->initFilters ();
+            foreach ($this->_aObject['filters'] as $aParams) {
+                $this->clearLog();
+                $sMethodFilter = 'applyFilter_' . $aParams['filter'];
+                if (!method_exists($this, $sMethodFilter))
+                    continue;
+                if (!$this->$sMethodFilter($sTmpFile, $aParams['filter_params'])) {
+                    unlink($sTmpFile);
+                    $this->_oDb->updateQueueStatus($mixedHandler, BX_DOL_QUEUE_FAILED, "filter failed: $sMethodFilter\n" . $this->getLog());
+                    $bRet = false;
+                    break 2;
+                }
+
+                if (!empty($aParams['filter_params']['force_type'])) {
+                    switch ($aParams['filter_params']['force_type']) {
+                        case 'jpeg':
+                        case 'jpg':
+                            $sExtChange = 'jpg';
+                            break;
+                        default:
+                            $sExtChange = $aParams['filter_params']['force_type'];
+                    }
+                }
+
+                if ($sExtChange) {
+                    $iDotPos = strrpos($sTmpFile, '.');
+                    $sExtOld = false === $iDotPos ? '' : substr($sTmpFile, $iDotPos+1);
+                    if ($sExtOld != $sExtChange) {
+                        $sTmpFileOld = $sTmpFile;
+                        $sTmpFile = false === $iDotPos ? $sTmpFile . '.' . $sExtChange : substr_replace($sTmpFile, $sExtChange, $iDotPos+1, strlen($sExtOld));
+                        if (!rename($sTmpFileOld, $sTmpFile)) {
+                            @unlink($sTmpFileOld);
+                            $this->_oDb->updateQueueStatus($mixedHandler, BX_DOL_QUEUE_FAILED, "change file extension failed: $sTmpFileOld => $sTmpFile");
+                            $bRet = false;
+                            break 2;
+                        }
+                    }
+                }
+            }
+
+            $mixedHandler = $this->processHandlerForRetinaDevice($mixedHandler);
+                    
+            if ($this->_sQueueStorage) {
+
+                $sFileUrlResult = '';
+                if (!($iFileIdResult = $this->storeTranscodedFileInQueueStorage($mixedHandler, $sTmpFile, $sFileUrlResult)))
+                    $this->_oDb->updateQueueStatus($mixedHandler, BX_DOL_QUEUE_FAILED, "store queue tmp file failed:\n" . $this->getLog());
+                else
+                    $this->_oDb->updateQueueStatus($mixedHandler, BX_DOL_QUEUE_COMPLETE, '', '', $iFileIdResult, $sFileUrlResult, $sExtChange);
+
+            } else {
+
+                $bRet = $this->storeTranscodedFile($mixedHandler, $sTmpFile, $iProfileId);
+                
+                if ($bRet)
+                    $this->deleteFromQueue($mixedHandler);
+                else
+                    $this->_oDb->updateQueueStatus($mixedHandler, BX_DOL_QUEUE_FAILED, "store file failed:\n" . $this->getLog());
+                    
+            }
+
+            @unlink($sTmpFile);
+
+        } while (false);
+
+        bx_alert($this->_aObject['object'], 'transcoded', $mixedHandler, false, array(
+            'object' => $this,
+            'ret' => $bRet,
+        ));
+
+        return $bRet;
+    }
+
+    /**
+     * Delete outdated files by last access time.
+     * @return number of pruned/deleted files
+     */
+    public function prune ()
+    {
+        $aFiles = $this->_oDb->getFilesForPruning();
+        if (!$aFiles)
+            return false;
+        $iCount = 0;
+        foreach ($aFiles as $r)
+            $iCount += $this->_oStorage->deleteFile($r['file_id']) ? 1 : 0;
+        return $iCount;
+    }
+
+    public function forceDevicePixelRatio ($i)
+    {
+        if (!$i)
+            $this->_iDevicePixelRatio = false;
+        elseif ($i >= 2) 
+            $this->_iDevicePixelRatio = 2;
+        else
+            $this->_iDevicePixelRatio = 1;
+    }
+
+    public function getDevicePixelRatio ()
+    {   
+        $iRet = 1;
+        if($this->_iDevicePixelRatio)
+            $iRet = $this->_iDevicePixelRatio;
+        else if(!isset($this->_aObject['source_params']['disable_retina']) || !$this->_aObject['source_params']['disable_retina'])
+            $iRet = bx_get_device_pixel_ratio();
+
+        // don't resize images on mobile if they are already big enough
+        if ($iRet > 1 && $this->isMobile() && $this->getMaxResizeDimention() >= BX_DOL_MAX_RETINA_DIMENSION_MOBILE)
+            $iRet = 1;
+
+        return $iRet;
+    }
+
+    public function getOrigFileUrl ($mixedHandler)
+    {
+        $sMethodStoreFile = 'getOrigFileUrl_' . $this->_aObject['source_type'];
+        return $this->$sMethodStoreFile($mixedHandler);
+    }
+
+    /**
+     * Get file url when file isn't transcoded yet
+     */
+    public function getFileUrlNotReady($mixedHandler)
+    {
+        return false;
+    }
+
+    public function getTmpFilename ($sOverrideName = false)
+    {
+        if ($sOverrideName)
+            return BX_DIRECTORY_PATH_TMP . rand(10000, 99999) . $sOverrideName;
+        return tempnam(BX_DIRECTORY_PATH_TMP, $this->_aObject['object']);
+    }
+
+    public function clearLog()
+    {
+        $this->_sLog = '';
+    }
+
+    public function addToLog($s)
+    {
+        $this->_sLog .= $s;
+    }
+
+    public function getLog()
+    {
+        return $this->_sLog;
+    }
+
+    public function deleteFromQueue ($mixedHandler)
+    {
+        $sErrMsg = "delete queue tmp file failed:\n";
+        $a = $this->_oDb->getFromQueue($mixedHandler);
+        if (!$a || $a['server'] != gethostname()) // deletion must be performed on the server where it was transcoded
+            return false;
+        
+        if ($a['file_id_result'] && $this->_sQueueStorage && 0 !== strncmp($a['log'], $sErrMsg, strlen($sErrMsg))) {
+            $oStorage = BxDolStorage::getObjectInstance($this->_sQueueStorage);
+            if (!$oStorage->deleteFile($a['file_id_result'])) {
+                $this->_oDb->updateQueueStatus($mixedHandler, BX_DOL_QUEUE_FAILED, $sErrMsg . $this->getLog());
+                return false;
+            }
+        }
+
+        return $this->_oDb->deleteFromQueue($mixedHandler);
+    }
+
+    public function storeTranscodedFile ($mixedHandler, $sTmpFile, $iProfileId)
+    {
+        $sMethodIsPrivate = 'isPrivate_' . $this->_aObject['source_type'];
+        $isPrivate = $this->$sMethodIsPrivate($mixedHandler);
+        $iFileId = $this->_oStorage->storeFileFromPath ($sTmpFile, $isPrivate, $iProfileId);
+        if (!$iFileId) {
+            $this->addToLog($this->_oStorage->getErrorString());
+            return false;
+        }
+
+        if (!$this->_oDb->updateHandler($iFileId, $mixedHandler)) {
+            $this->_oStorage->deleteFile($iFileId);
+            return false;
+        }
+
+        $this->_oStorage->afterUploadCleanup($iFileId, $iProfileId);
+
+        return true;
+    }
+
+    protected function storeTranscodedFileInQueueStorage ($mixedHandler, $sTmpFile, &$sFileUrlResult)
+    {
+        if (!$this->_sQueueStorage)
+            return false;
+
+        $oStorage = BxDolStorage::getObjectInstance($this->_sQueueStorage);
+
+        $iFileIdResult = $oStorage->storeFileFromPath ($sTmpFile, 1, 0);
+        if (!$iFileIdResult) {
+            $this->addToLog($oStorage->getErrorString());
+            return false;
+        }
+
+        if (!($sFileUrlResult = $oStorage->getFileUrlById($iFileIdResult))) {
+            $this->_oStorage->deleteFile($iFileIdResult);
+            return false;
+        }
+
+        $oStorage->afterUploadCleanup($iFileIdResult, 0);
+
+        return $iFileIdResult;
+    }
+
+    protected function addToQueue($mixedHandler, $iProfileId = 0)
+    {
+        if ($this->_oDb->getFromQueue($mixedHandler))
+            return true;
+        $sFileUrl = $this->getOrigFileUrl($mixedHandler);
+        return $this->_oDb->addToQueue ($mixedHandler, $sFileUrl, $iProfileId);
+    }
+
+    protected function applyFilter_Resize ($sFile, $aParams)
+    {
+        $o = BxDolImageResize::getInstance();
+        $o->removeCropOptions ();
+
+        $o->setSize (isset($aParams['w']) ? round($aParams['w'] * $this->getDevicePixelRatio()) : null, isset($aParams['h']) ? round($aParams['h'] * $this->getDevicePixelRatio()) : null);
+
+        if (isset($aParams['crop_resize']) && $aParams['crop_resize'])
+            $o->setAutoCrop (true);
+        elseif (isset($aParams['square_resize']) && $aParams['square_resize'])
+            $o->setSquareResize (true);
+        else
+            $o->setSquareResize (false);
+
+        if (IMAGE_ERROR_SUCCESS == $o->resize($sFile))
+            return true;
+
+        return false;
+    }
+
+    protected function isFileReady_Folder ($mixedHandlerOrig, $isCheckOutdated = true)
+    {
+        $mixedHandler = $this->processHandlerForRetinaDevice($mixedHandlerOrig);
+
+        $iFileId = $this->_oDb->getFileIdByHandler($mixedHandler);
+        if (!$iFileId)
+            return false;
+
+        $aFile = $this->_oStorage->getFile($iFileId);
+        if (!$aFile)
+            return false;
+
+        if ($isCheckOutdated) { // warning, $isCheckOutdated is partially supported for Folder source type - file modification is not checked
+            if ($this->_aObject['ts'] > $aFile['modified'] || !$this->getFilePath_Folder($mixedHandlerOrig)) { // if we changed transcoder object params or original file is deleted
+                // delete file, so it will be recreated next time
+                if ($this->_oStorage->deleteFile($aFile['id']))
+                    return false;
+            }
+        }
+
+        return true;
+    }
+
+    protected function isFileReady_Storage ($mixedHandlerOrig, $isCheckOutdated = true)
+    {
+        $mixedHandler = $this->processHandlerForRetinaDevice($mixedHandlerOrig);
+
+        $iFileId = $this->_oDb->getFileIdByHandler($mixedHandler);
+        if (!$iFileId)
+            return false;
+
+        $aFile = $this->_oStorage->getFile($iFileId);
+        if (!$aFile)
+            return false;
+
+        if ($isCheckOutdated) {
+            $oStorageOriginal = BxDolStorage::getObjectInstance($this->_aObject['source_params']['object']);
+            if ($oStorageOriginal) {
+                $aFileOriginal = $oStorageOriginal->getFile($mixedHandlerOrig);
+                if (!$aFileOriginal || $aFileOriginal['modified'] > $aFile['modified'] || $this->_aObject['ts'] >= $aFile['modified']) { // if original file was changed OR we changed transcoder object params
+                    // delete file, so it will be recreated next time
+                    if ($this->_oStorage->deleteFile($aFile['id']))
+                        return false;
+                }
+            }
+        }
+
+        return true;
+    }
+
+    protected function isPrivate_Folder ($mixedHandler)
+    {
+        if ('no' == $this->_aObject['private'])
+            return false;
+        return true;
+    }
+
+    protected function isPrivate_Storage ($mixedHandler)
+    {
+        switch ($this->_aObject['private']) {
+            case 'no':
+                return false;
+            case 'yes':
+                return true;
+            default:
+            case 'auto':
+                $oStorageOriginal = BxDolStorage::getObjectInstance($this->_aObject['source_params']['object']);
+                if (!$oStorageOriginal)
+                    return true; // in case of error - make sure that file is not public accidentally
+                $aFile = $oStorageOriginal->getFile($mixedHandler);
+                return $oStorageOriginal->isFilePrivate($aFile['id']);
+        }
+    }
+
+    protected function getFilePath_Folder ($mixedHandler)
+    {
+        $sPath = $this->_aObject['source_params']['path'] . $mixedHandler;
+        if (!file_exists($sPath))
+            $sPath = BX_DIRECTORY_PATH_ROOT . $sPath;
+
+        if (!file_exists($sPath))
+            return false;
+
+        return $sPath;
+    }
+
+    protected function storeFileLocally_Folder ($mixedHandler)
+    {
+        $sPath = $this->getFilePath_Folder($mixedHandler);
+        if (!$sPath)
+            return false;
+
+        $sTmpFile = $this->getTmpFilename ($mixedHandler);
+        if (!copy($sPath, $sTmpFile))
+            return false;
+
+        return $sTmpFile;
+    }
+
+    protected function storeFileLocally_Storage ($mixedHandler)
+    {
+        $oStorageOriginal = BxDolStorage::getObjectInstance($this->_aObject['source_params']['object']);
+        if (!$oStorageOriginal)
+            return false;
+
+        $aFile = $oStorageOriginal->getFile($mixedHandler);
+        if (!$aFile)
+            return false;
+
+        $sUrl = $oStorageOriginal->getFileUrlById($mixedHandler);
+        if (!$sUrl)
+            return false;
+
+        $sFileData = bx_file_get_contents ($sUrl);
+        if (false === $sFileData)
+            return false;
+
+        $sTmpFile = $this->getTmpFilename ($aFile['file_name']);
+        if (!file_put_contents($sTmpFile, $sFileData))
+            return false;
+
+        return $sTmpFile;
+    }
+
+    protected function storeFileLocally_Queue ($mixedHandler)
+    {   
+        $aQueue = $this->_oDb->getFromQueue($mixedHandler);
+        if (!$aQueue || !$aQueue['file_url_source'])
+            return false;
+
+        $sFileData = bx_file_get_contents ($aQueue['file_url_source']);
+        if (false === $sFileData)
+            return false;
+
+        $sTmpFile = $this->getTmpFilename ($mixedHandler);
+        if (!file_put_contents($sTmpFile, $sFileData))
+            return false;
+
+        return $sTmpFile;
+    }
+
+    protected function getOrigFileUrl_Folder ($mixedHandler)
+    {
+        $sPath = $this->_aObject['source_params']['path'] . $mixedHandler;
+        if (!file_exists(BX_DIRECTORY_PATH_ROOT . $sPath))
+            return false;
+        
+        return BX_DOL_URL_ROOT . $sPath;
+    }
+
+    protected function getOrigFileUrl_Storage ($mixedHandler)
+    {
+        $oStorageOriginal = BxDolStorage::getObjectInstance($this->_aObject['source_params']['object']);
+        if (!$oStorageOriginal)
+            return false;
+
+        return $oStorageOriginal->getFileUrlById($mixedHandler);
+    }
+
+    protected function initFilters ()
+    {
+        if (false !== $this->_aObject['filters']) // filters are already initilized
+            return;
+
+        // get transcoder filters
+        $aFilters = $this->_oDb->getTranscoderFilters();
+        $this->_aObject['filters'] = array();
+        foreach ($aFilters as $aFilter) {
+            $aFilter['filter_params'] = $aFilter['filter_params'] ? unserialize($aFilter['filter_params']) : array();
+            $this->_aObject['filters'][] = $aFilter;
+        }
+
+    }
+
+    protected function clearCacheDB()
+    {
+        $oCacheDb = $this->_oDb->getDbCacheObject();
+        return $oCacheDb->removeAllByPrefix('db_');
+    }
+
+    public function getDevicePixelRatioHandlerSuffix ()
+    {
+        $iDevicePixelRatio = $this->getDevicePixelRatio ();
+        if ($iDevicePixelRatio >= 2)
+            return $this->_sRetinaSuffix;
+        return '';
+    }
+
+    protected function processHandlerForRetinaDevice ($mixedHandler)
+    {
+        return '' . $mixedHandler . $this->getDevicePixelRatioHandlerSuffix ();
+    }
+
+    static protected function _callFuncForObjectsArray ($mixed, $sFunc)
+    {
+        if (!is_array($mixed))
+            $mixed = array($mixed);
+        foreach ($mixed as $sObject) {
+            $oTranscoder = self::getObjectInstance($sObject);
+            if ($oTranscoder)
+                $oTranscoder->$sFunc();
+        }
+    }
+
+    /**
+     * Get filter params
+     * @param $sName filter name
+     * @return array of filter params if filter was found, or false if given filter isn't enabled for the transcoder
+     */
+    public function getFilterParams ($sName)
+    {
+        $this->initFilters ();
+        foreach ($this->_aObject['filters'] as $aParams)
+            if ($sName == $aParams['filter'])
+                return $aParams['filter_params'];
+        return false;
+    }
+
+    public function getMaxResizeDimention ()
+    {
+        $a = $this->_oDb->getTranscoderFilters('Resize');
+        if (!$a)
+            return 0;
+        $aFilter = array_pop($a);
+        $aFilterParams = $aFilter['filter_params'] ? unserialize($aFilter['filter_params']) : array();
+        return $aFilterParams ? max(isset($aFilterParams['w']) ? $aFilterParams['w'] : 0, isset($aFilterParams['h']) ? $aFilterParams['h'] : 0) : 0;
+    }
+
+    public function isMobile ()
+    {
+        return bx_is_mobile();
+    }
+}
+
+/** @} */

+ 152 - 0
upgrade/files/11.0.0-11.0.1/files/inc/classes/BxDolVoteQuery.php

@@ -0,0 +1,152 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaCore UNA Core
+ * @{
+ */
+
+/**
+ * @see BxDolVote
+ */
+class BxDolVoteQuery extends BxDolObjectQuery
+{
+    protected $_sTriggerFieldRate;
+
+    protected $_iPostTimeout;
+
+    public function __construct(&$oModule)
+    {
+        parent::__construct($oModule);
+
+        $aSystem = $this->_oModule->getSystemInfo();
+        $this->_sTriggerFieldRate = $aSystem['trigger_field_rate'];
+
+        $this->_iPostTimeout = (int)$aSystem['post_timeout'];
+
+        $this->_sMethodGetEntry = 'getVote';
+    }
+
+    public function isPostTimeoutEnded($iObjectId, $iAuthorId, $sAuthorIp)
+    {
+        if($this->_iPostTimeout == 0)
+            return true;
+
+        $aBindings = array(
+            'object_id' => $iObjectId,
+            'date' => time() - $this->_iPostTimeout
+        );
+        $sWhereClause = " AND `object_id` = :object_id AND `date` > :date";
+
+        if(!empty($iAuthorId)) {
+            $aBindings['author_id'] = $iAuthorId;
+
+            $sWhereClause .= " AND `author_id` = :author_id";
+        }
+        else {
+            $aBindings['author_nip'] = ip2long($sAuthorIp);
+
+            $sWhereClause .= " AND `author_nip` = :author_nip";
+        }
+
+        return (int)$this->getOne("SELECT `object_id` FROM `" . $this->_sTableTrack . "` WHERE 1" . $sWhereClause, $aBindings) == 0;
+    }
+
+    public function getVote($iObjectId)
+    {
+        $sQuery = $this->prepare("SELECT `count` as `count`, `sum` as `sum`, ROUND(`sum` / `count`, 2) AS `rate` FROM {$this->_sTable} WHERE `object_id` = ? LIMIT 1", $iObjectId);
+
+        $aResult = $this->getRow($sQuery);
+        if(empty($aResult) || !is_array($aResult))
+            $aResult = array('count' => 0, 'sum' => 0, 'rate' => 0);
+
+        return $aResult;
+    }
+
+    public function putVote($iObjectId, $iAuthorId, $sAuthorIp, $aData, $bUndo = false)
+    {
+        $sQuery = $this->prepare("SELECT `object_id` FROM `{$this->_sTable}` WHERE `object_id` = ? LIMIT 1", $iObjectId);
+        $bExists = (int)$this->getOne($sQuery) != 0;
+        if(!$bExists && $bUndo)
+            return false;
+
+        $iValue = $aData['value'];
+
+        if(!$bExists)
+            $sQuery = $this->prepare("INSERT INTO {$this->_sTable} SET `object_id` = ?, `count` = '1', `sum` = ?", $iObjectId, $iValue);
+        else
+            $sQuery = $this->prepare("UPDATE `{$this->_sTable}` SET `count` = `count` " . ($bUndo ? "-" : "+") . " 1, `sum` = `sum` " . ($bUndo ? "-" : "+") . " ? WHERE `object_id` = ?", $iValue, $iObjectId);
+
+        if((int)$this->query($sQuery) == 0)
+            return false;
+
+        if($bUndo)
+            return $this->_deleteTrack($iObjectId, $iAuthorId);
+
+        $iNow = time();
+        $iAuthorNip = ip2long($sAuthorIp);
+        $sQuery = $this->prepare("INSERT INTO `{$this->_sTableTrack}` SET `object_id` = ?, `author_id` = ?, `author_nip` = ?, `value` = ?, `date` = ?", $iObjectId, $iAuthorId, $iAuthorNip, $iValue, $iNow);
+        if((int)$this->query($sQuery) > 0)
+            return $this->lastId();
+
+        return false;
+    }
+
+    public function getLegend($iObjectId)
+    {
+    	$sQuery = $this->prepare("SELECT `value` AS `value`, COUNT(`value`) AS `count` FROM `{$this->_sTableTrack}` WHERE `object_id` = ? GROUP BY `value`", $iObjectId);
+
+    	return $this->getAllWithKey($sQuery, 'value');
+    }
+
+    public function getSqlParts($sMainTable, $sMainField)
+    {
+    	$aResult = parent::getSqlParts($sMainTable, $sMainField);
+        if(empty($aResult))
+            return $aResult;
+
+        $aResult['fields'] = ", `{$this->_sTable}`.`count` as `vote_count`, (`{$this->_sTable}`.`sum` / `{$this->_sTable}`.`count`) AS `vote_rate` ";
+        return $aResult;
+    }
+
+    public function updateTriggerTableValue($iObjectId, $iValue)
+    {
+        return false;
+    }
+
+    protected function _deleteTrack($iObjectId, $iAuthorId)
+    {
+        $sQuery = $this->prepare("SELECT `id` FROM `{$this->_sTableTrack}` WHERE `object_id` = ? AND `author_id` = ? LIMIT 1", $iObjectId, $iAuthorId);
+        $iId = (int)$this->getOne($sQuery);
+
+        $sQuery = $this->prepare("DELETE FROM `{$this->_sTableTrack}` WHERE `id` = ? LIMIT 1", $iId);
+        if((int)$this->query($sQuery) > 0)
+            return $iId;
+
+        return false;
+    }
+    
+    protected function _updateTriggerTable($iObjectId, $aEntry)
+    {
+    	$sQuery = $this->prepare("UPDATE `{$this->_sTriggerTable}` SET `{$this->_sTriggerFieldCount}` = ?, `{$this->_sTriggerFieldRate}` = ? WHERE `{$this->_sTriggerFieldId}` = ?", $aEntry['count'], $aEntry['rate'], $iObjectId);
+        return (int)$this->query($sQuery) > 0;
+    }
+
+    protected function _deleteAuthorEntriesTableMain($aTrack)
+    {
+        return $this->query("UPDATE `{$this->_sTable}` SET `count`=`count`-1, `sum`=`sum`-:value WHERE `object_id`=:object_id", array(
+            'object_id' => $aTrack['object_id'],
+            'value' => $aTrack['value']
+        ));
+    }
+
+    protected function _deleteAuthorEntriesTableTrigger($aTrack)
+    {
+        $aVote = $this->getVote($aTrack['object_id']);
+
+        return $this->_updateTriggerTable($aTrack['object_id'], $aVote);
+    }
+}
+
+/** @} */

+ 32 - 0
upgrade/files/11.0.0-11.0.1/files/inc/classes/BxDolVoteStars.php

@@ -0,0 +1,32 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaCore UNA Core
+ * @{
+ */
+
+/**
+ * Star based votes for any content
+ */
+class BxDolVoteStars extends BxTemplVote
+{
+    public function __construct($sSystem, $iId, $iInit = true, $oTemplate = false)
+    {
+        parent::__construct($sSystem, $iId, $iInit, $oTemplate);
+
+        $this->_oQuery = new BxDolVoteStarsQuery($this);
+        $this->_sType = BX_DOL_VOTE_TYPE_STARS;
+    }
+
+    /**
+     * Internal functions
+     */
+    protected function _isDuplicate($iObjectId, $iAuthorId, $iAuthorIp, $bVoted)
+    {
+        return !$this->_oQuery->isPostTimeoutEnded($iObjectId, $iAuthorId, $iAuthorIp);
+    }
+}
+
+/** @} */

+ 164 - 0
upgrade/files/11.0.0-11.0.1/files/inc/js/classes/BxDolMenuMoreAuto.js

@@ -0,0 +1,164 @@
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaCore UNA Core
+ * @{
+ */
+
+function BxDolMenuMoreAuto(options)
+{
+    this._sObject = options.sObject;
+    this._iItemsStatic = undefined == options.iItemsStatic ? 0 : options.iItemsStatic;
+    this._aHtmlIds = undefined == options.aHtmlIds ? {} : options.aHtmlIds;  
+
+    this._sKeyWidth = 'bx-mma-width';
+
+    this._sClassReady = 'bx-mma-ready';
+    this._sClassItem = '.bx-menu-item';
+    this._sClassItemMore = this._sClassItem + '.bx-menu-item-more-auto';
+    this._sClassItemMoreSubmenu = '.bx-menu-submenu-more-auto';
+    this._sClassItemStatic = 'bx-menu-item-static';
+}
+
+BxDolMenuMoreAuto.prototype.init = function() {
+    var $this = this;
+
+    var oMenu = $('#' + this._aHtmlIds['main']);
+    if(!oMenu.length) 
+        return;
+
+    //--- Initialize ---//
+    var oItemMore = oMenu.find(this._sClassItemMore);
+    var oItemMoreSubmenu = oItemMore.find(this._sClassItemMoreSubmenu);
+
+    for(var i = 0; i < this._iItemsStatic; i++)
+        oItemMore.prevAll(this._sClassItem + ':not(.' + this._sClassItemStatic + '):last').addClass(this._sClassItemStatic);
+
+    var iMenu = 0;
+    oMenu.children(this._sClassItem + ':visible').each(function() {
+        iMenu += $this._getWidth($(this));
+    });
+
+    var iParent = oMenu.parent().width();
+    var iItemMore = oItemMore.outerWidth(true);
+
+    oMenu.css('overflow', 'visible');
+
+    if(iMenu >= iParent)
+        this._moveToSubmenu(oMenu, oItemMore, oItemMoreSubmenu, iParent, iItemMore);
+
+    //--- Add event handlers ---//
+    $(window).on('resize', function() {
+       $this.update();
+    });
+
+    oMenu.find(this._sClassItem).on('resize', function() {
+        $this.update(true);
+    });
+};
+
+BxDolMenuMoreAuto.prototype.update = function(bForceCalculate)
+{
+    var $this = this;
+    var oMenu = $('#' + this._aHtmlIds['main']);
+    var oItemMore = oMenu.find(this._sClassItemMore);
+    var oItemMoreSubmenu = oItemMore.find(this._sClassItemMoreSubmenu);
+
+    var iMenu = 0;
+    oMenu.children(this._sClassItem + ':visible').each(function() {
+        iMenu += $this._getWidth($(this), bForceCalculate);
+    });
+
+    var iParent = oMenu.parent().width();
+    var iItemMore = oItemMore.outerWidth(true);
+
+    if(iMenu > iParent)
+        this._moveToSubmenu(oMenu, oItemMore, oItemMoreSubmenu, iParent, iItemMore);
+    if(iMenu < iParent)
+        this._moveFromSubmenu(oMenu, oItemMore, oItemMoreSubmenu, iParent, iMenu);
+};
+
+BxDolMenuMoreAuto.prototype.more = function(oElement)
+{
+    var oElement = $(oElement);
+
+    oElement.parents('li:first').find('#' + this._aHtmlIds['more_auto_popup']).dolPopup({
+        pointer: {
+            el: oElement
+        }, 
+        moveToDocRoot: false
+    });
+}
+
+BxDolMenuMoreAuto.prototype._moveToSubmenu = function(oMenu, oItemMore, oItemMoreSubmenu, iParent, iItemMore)
+{
+    var $this = this;
+
+    var bRelocateOthers = false;
+    var iWidthTotal = iItemMore;
+    var oSubmenuItemFirst = oItemMoreSubmenu.children(this._sClassItem + ':first');
+
+    oMenu.children(this._sClassItem + ':not(' + this._sClassItemMore + ')').each(function() {
+        var oItem = $(this);
+        var iItem = $this._getWidth(oItem);
+        if((bRelocateOthers || iWidthTotal + iItem > iParent) && !oItem.hasClass($this._sClassItemStatic)) {
+            oItem.addClass('bx-def-color-bg-hl-hover');
+
+            if(!oSubmenuItemFirst.length)
+                oItemMoreSubmenu.append(oItem.detach());
+            else
+                oSubmenuItemFirst.before(oItem.detach());
+            bRelocateOthers = true;
+            return;
+        }
+
+        iWidthTotal += iItem;
+    });
+
+    if(oItemMoreSubmenu.find('li').length)
+        oItemMore.show(function() {
+            oMenu.addClass($this._sClassReady).parents('.bx-menu-more-auto-wrapper:first').css('overflow', 'visible');
+        });
+};
+
+BxDolMenuMoreAuto.prototype._moveFromSubmenu = function(oMenu, oItemMore, oItemMoreSubmenu, iParent, iMenu)
+{
+    var $this = this;
+
+    var bStopRelocation = false;
+    var iWidthTotal = iMenu;
+    oItemMoreSubmenu.children(this._sClassItem).each(function() {
+        if(bStopRelocation) 
+            return;
+
+        var oItem = $(this);
+        var iItem = $this._getWidth(oItem);
+        if(iWidthTotal + iItem > iParent) {
+            bStopRelocation = true;
+            return;
+        }
+
+        oItem.removeClass('bx-def-color-bg-hl-hover');
+
+        oItemMore.before(oItem.detach());
+        iWidthTotal += iItem;
+    });
+
+    if(!oItemMoreSubmenu.find('li').length)
+        oItemMore.hide();
+};
+
+BxDolMenuMoreAuto.prototype._getWidth = function(oItem, bForceCalculate)
+{
+    var iItem = parseInt(oItem.attr(this._sKeyWidth));
+    if(!bForceCalculate && iItem)
+        return iItem;
+
+    iItem = oItem.outerWidth(true);
+    if(iItem)
+        oItem.attr(this._sKeyWidth, iItem);
+
+    return iItem;
+}
+/** @} */

+ 327 - 0
upgrade/files/11.0.0-11.0.1/files/inc/js/jquery.webForms.js

@@ -0,0 +1,327 @@
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaCore UNA Core
+ * @{
+ */
+
+(function($) {
+
+    $.fn.addWebForms = function() {
+        if ('undefined' === typeof(glJsLoadOnaddWebForms) || !glJsLoadOnaddWebForms.length) {
+            $.fn.processWebForms.apply(this);
+        }
+        else {
+            var $this = this;
+            bx_get_scripts(glJsLoadOnaddWebForms, function () {
+                $.fn.processWebForms.apply($this);
+            });
+        }
+
+        return this;
+    }
+    
+    $.fn.processWebForms = function() {
+        // switchers
+        $('.bx-switcher-cont', this).each(function() {
+            var eSwitcher = $(this);
+            var eInput = $(this).find('input');
+
+            if (eSwitcher.hasClass('bx-form-switcher-processed'))
+                return;
+            eSwitcher.addClass('bx-form-switcher-processed');
+
+            eSwitcher.on('click', function() {
+                var $this = $(this);
+                if ($this.hasClass('on')) {
+                    $this.find('input').prop('checked', false).trigger('change');
+                    $this.removeClass('on').addClass('off');
+                } else {
+                    $this.removeClass('off').addClass('on');
+                    $this.find('input').prop('checked', true).trigger('change');
+                }
+                return false;
+            });
+
+            eInput.on('enable', function () {
+                if (!$(this).prop('checked'))
+                    $(this).prop('checked', true).trigger('change');
+                if (!eSwitcher.hasClass('on'))
+                    eSwitcher.removeClass('off').addClass('on');
+            });
+
+            eInput.on('disable', function () {
+                if ($(this).prop('checked'))
+                    $(this).prop('checked', false).trigger('change');
+                if (!eSwitcher.hasClass('off'))
+                    eSwitcher.removeClass('on').addClass('off');
+            });
+        });
+
+        // collapsable headers 
+        $('.bx-form-collapsable', this).each(function() {
+
+            var eFormSection = $(this);
+
+            if (eFormSection.hasClass('bx-form-js-processed'))
+                return;
+
+            eFormSection.addClass('bx-form-js-processed');
+
+            var fCallback = function() {
+
+                if (eFormSection.hasClass('bx-form-collapsed')) {
+                    // show
+                    eFormSection.removeClass('bx-form-collapsed');
+
+                    if (eFormSection.hasClass('bx-form-section-hidden')) {
+                        $('.bx-form-section-content:first', eFormSection).hide();
+                        eFormSection.removeClass('bx-form-section-hidden');
+                    }
+
+                    $('.bx-form-section-content:first', eFormSection).slideDown(function () {
+                        eFormSection.removeClass('bx-form-section-hidden');
+
+                        $(this).addWebForms();
+
+                        //--- fire custom event 'bx_show'
+                        eFormSection.trigger('bx_show');
+                    }); 
+
+                } else {
+                    // hide
+                    eFormSection.addClass('bx-form-collapsed');
+
+                    $('.bx-form-section-content:first', eFormSection).slideUp(function () {                    
+                        eFormSection.addClass('bx-form-collapsed bx-form-section-hidden');
+
+                        //--- fire custom event 'bx_hide'
+                        eFormSection.trigger('bx_hide');
+                    }); 
+                }
+
+            };
+
+            $('<u class="bx-form-section-toggler"><i class="sys-icon chevron-right"></i></u>').prependTo($('legend', eFormSection));
+            $('legend:first', eFormSection).click(fCallback);
+        });
+
+        $("select", this).each(function () {
+            // Labels selector
+            var oInput = $(this);
+            if (oInput.hasClass('bx-form-select-labels')) {
+                $(".bx-form-select-labels").select2ToTree();
+            }
+        });
+
+        if (!$(".bx-form-input-wrapper-password").hasClass("bx-inited")) {
+            var sClassFocus = "bx-form-input-focus";
+
+            $(".bx-form-input-wrapper-password input").bind('focus', function() {
+                $(this).parents(".bx-form-input-wrapper-password:first").addClass(sClassFocus);
+            }).bind('blur', function() {
+                $(this).parents(".bx-form-input-wrapper-password:first").removeClass(sClassFocus);
+            });
+
+            $(".bx-form-input-wrapper-password a").on("click", function () {
+                var oIcon = $(this).find("i");
+                var oFld = $(this).parents(".bx-form-input-wrapper-password").find("input");
+                if (oIcon.hasClass("eye")) {
+                    oIcon.removeClass("eye").addClass("eye-slash");
+                    oFld.attr("type", "text");
+                }
+                else {
+                    oIcon.addClass("eye").removeClass("eye-slash");
+                    oFld.attr("type", "password");
+                }
+            });
+            $(".bx-form-input-wrapper-password").addClass("bx-inited");
+        }
+           
+        $("input", this).each(function() {
+        	var oInput = $(this);
+
+            var onCreateRange = function (event, ui) {
+                var eInput = $(this).parent().find('input');
+                var iSliderWidth = eInput.innerWidth() - 160;
+                $(this).css('width',  iSliderWidth + 'px').css('top', (eInput.innerHeight() - $(this).outerHeight())/2 + 1 + 'px');
+                eInput.css('padding-right', (iSliderWidth + parseFloat($(this).css('margin-right')) + 10) + 'px');                        
+            };
+
+            // DoubleRange
+            if (this.getAttribute("type") == 'doublerange') {
+
+                var cur = $(this);
+                if(!cur.is(':visible') || cur.hasClass('bx-form-doublerange-processed'))
+                    return;
+
+                cur.addClass('bx-form-doublerange-processed');
+
+                var $slider = $('<div class="bx-def-margin-right"></div>').insertAfter(cur);
+
+                var iMin = cur.attr("min") ? parseFloat(cur.attr("min"), 10) : 0;
+                var iMax = cur.attr("max") ? parseFloat(cur.attr("max"), 10) : 100;
+                var sRangeDv = cur.attr("range-divider") ? cur.attr("range-divider") : '-';
+
+                var funcGetValues = function (e) {
+
+                    var values = e.val().split(sRangeDv, 2); // get values
+
+                    if (typeof(values[0]) != 'undefined' && values[0].length)
+                        values[0] = parseFloat(values[0]);
+                    else
+                        values[0] = iMin;
+
+                    if (typeof(values[1]) != 'undefined' && values[1].length)
+                        values[1] = parseFloat(values[1]);
+                    else
+                        values[1] = iMax;
+
+                    return values;
+                };
+
+                var onChange = function(e, ui) {
+                    values = ui.values;
+                    cur.val( values[0] + sRangeDv + values[1] );
+                };
+
+                $slider.slider({
+                    range: true,
+                    min: iMin,
+                    max: iMax,
+                    step: parseFloat(cur.attr("step")) ? parseFloat(cur.attr("step")) : 1,
+                    values: funcGetValues(cur),
+                    change: onChange,
+                    slide: onChange,
+                    create: onCreateRange
+                });
+
+                cur.bind('change', function () {
+                    var values = funcGetValues($(this));
+                    $slider.slider("option", "values", values);
+                });
+
+            }
+
+            // Single range or slider
+            else if (this.getAttribute("type") == 'slider') {
+
+                var cur = $(this);
+                if(!cur.is(':visible') || cur.hasClass('bx-form-range-processed'))
+                    return;
+
+                cur.addClass('bx-form-range-processed');
+
+                var $slider = $('<div class="bx-def-margin-right"></div>').insertAfter(cur)
+
+                $slider.css('width', ($slider.parent().innerWidth() - cur.outerWidth() - 50) + 'px');
+
+                var iMin = cur.attr("min") ? parseFloat(cur.attr("min"), 10) : 0;
+                var iMax = cur.attr("max") ? parseFloat(cur.attr("max"), 10) : 100;
+
+                var onChange = function(e, ui) {
+                    cur.val( ui.value );
+                };
+
+                $slider.slider({
+                    min: iMin,
+                    max: iMax,
+                    step: parseFloat(cur.attr("step")) ? parseFloat(cur.attr("step")) : 1,
+                    value: cur.val(),
+                    change: onChange,
+                    slide: onChange,
+                    create: onCreateRange
+                });
+
+                cur.bind('change', function () {
+                    $slider.slider("option", "value", $(this).val());
+                });
+
+            }
+
+			// Date/Time pickers
+			if (this.getAttribute("type") == "date" || this.getAttribute("type") == "date_calendar" || this.getAttribute("type") == "datetime" || this.getAttribute("type") == "date_time" || this.getAttribute("type") == "datepicker") {
+                if ($(this).hasClass('bx-form-datepicker-processed'))
+                    return;
+                $(this).addClass('bx-form-datepicker-processed');
+
+                var iYearMin = '1900';
+                var iYearMax = '2100';
+                var m;
+
+                if ($(this).attr('min') && (m = $(this).attr('min').match(/^(\d{4})/)))
+                    iYearMin = m[1];
+                if ($(this).attr('max') && (m = $(this).attr('max').match(/^(\d{4})/)))
+                    iYearMax = m[1];
+
+                var onBeforeShow = function(oInput, oInstance) {
+                	$(oInstance.dpDiv).addClass('bx-form-datepicker-modal');
+                };
+
+                if (this.getAttribute("type") == "date" || this.getAttribute("type") == "date_calendar" || this.getAttribute("type") == "datepicker") { // Date picker
+
+                    $(this).datepicker({
+                        changeYear: true,
+                        changeMonth: true,
+                        dateFormat: 'yy-mm-dd',
+                        //defaultDate: '-22y',
+                        yearRange: iYearMin + ':' + iYearMax,
+                        beforeShow: onBeforeShow
+                    });
+
+                } else if(this.getAttribute("type") == "datetime" || this.getAttribute("type") == "date_time") { // DateTime picker
+
+                    var oPickerOptions = {
+                        changeYear: true,
+                        changeMonth: true,
+                        dateFormat: 'yy-mm-dd',
+                        beforeShow: onBeforeShow
+                    };
+                    if (1 == $(this).attr('data-utc')) {
+                        var e = $(this).clone();
+                        if ($(this).val().length) {
+                            e.val(moment($(this).val() + ' Z').format("YYYY-MM-DD HH:mm:0 Z"));
+                            $(this).val(moment($(this).val() + ' Z').format("YYYY-MM-DD HH:mm"));
+                        }
+                        e.attr('type', 'hidden').appendTo($(this).parent());
+                        $(this).attr('name', $(this).attr('name') + '_fake');
+                        oPickerOptions['altField'] = e;
+                        oPickerOptions['altFieldTimeOnly'] = false;
+                        oPickerOptions['altTimeFormat'] = 'HH:mm:00 Z';
+                        oPickerOptions['showTimezone'] = 'false';
+                    }
+                    
+                    $(this).datetimepicker(oPickerOptions);
+                }
+
+                if (window.navigator.appVersion.search(/Chrome\/(.*?) /) == -1 || parseInt(window.navigator.appVersion.match(/Chrome\/(\d+)\./)[1], 10) < 24)
+                    if( this.getAttribute("allow_input") == null)
+                        $(this).attr('readonly', 'readonly');
+			}
+
+			// RGB/RGBA pickers
+			if(this.getAttribute('type') == 'text' && (oInput.hasClass('bx-form-input-rgb') || oInput.hasClass('bx-form-input-rgba')) && !oInput.hasClass('minicolors-input')) {
+				oInput.attr('autocomplete', 'off');
+				oInput.minicolors({
+                    control: oInput.attr('data-control') || 'hue',
+                    format: oInput.attr('data-format') || 'rgb',
+                    letterCase: oInput.attr('data-letterCase') || 'lowercase',
+                    opacity: oInput.hasClass('bx-form-input-rgba'),
+                    position: oInput.attr('data-position') || 'bottom left',
+                    theme: 'bootstrap'
+                });
+				
+			}
+        });
+        $('form', this).addClass('bx-form-processed');
+        return this;
+    };
+
+})(jQuery);
+
+$(document).ready(function() {
+    $(this).addWebForms();
+});
+
+/** @} */

+ 1953 - 0
upgrade/files/11.0.0-11.0.1/files/inc/utils.inc.php

@@ -0,0 +1,1953 @@
+<?php defined('BX_DOL') or defined('BX_DOL_INSTALL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaCore UNA Core
+ * @{
+ */
+
+define('BX_DOL_LINK_CLASS', 'bx-link'); ///< class to add to every link in user content
+
+define('BX_DATA_TEXT', 1); ///< regular text data type
+define('BX_DATA_TEXT_MULTILINE', 2); ///< regular multiline text data type
+define('BX_DATA_INT', 3); ///< integer data type
+define('BX_DATA_FLOAT', 4); ///< float data type
+define('BX_DATA_CHECKBOX', 5); ///< checkbox data type, 'on' or empty value
+define('BX_DATA_HTML', 6); ///< HTML data type
+define('BX_DATA_DATE', 7); ///< date data type stored as yyyy-mm-dd
+define('BX_DATA_DATETIME', 12); ///< date/time data type stored as yyyy-mm-dd hh:mm:ss
+define('BX_DATA_DATE_TS', 8); ///< date data type stored as unixtimestamp
+define('BX_DATA_DATETIME_TS', 9); ///< date/time data type stored as unixtimestamp
+define('BX_DATA_DATE_TS_UTC', 10); ///< date data type stored as unixtimestamp from UTC time
+define('BX_DATA_DATETIME_TS_UTC', 11); ///< date/time data type stored as unixtimestamp from UTC time
+
+define('BX_SLASHES_AUTO', 0);
+define('BX_SLASHES_ADD', 1);
+define('BX_SLASHES_STRIP', 2);
+define('BX_SLASHES_NO_ACTION', 3);
+
+define('BX_ESCAPE_STR_AUTO', 0); ///< turn apostropes and quote signs into html special chars, for use in @see bx_js_string and @see bx_html_attribute
+define('BX_ESCAPE_STR_APOS', 1); ///< escape apostrophes only, for js strings enclosed in apostrophes, for use in @see bx_js_string and @see bx_html_attribute
+define('BX_ESCAPE_STR_QUOTE', 2); ///< escape quotes only, for js strings enclosed in quotes, for use in @see bx_js_string and @see bx_html_attribute
+
+define('BX_EMAIL_SYSTEM', 0); ///< system email without unsubscribe link, like forgot password or email verification
+define('BX_EMAIL_NOTIFY', 1); ///< notification message, with unsubscribe link
+define('BX_EMAIL_MASS', 2); ///< mass email, one mesage send to manu users, with unsubscribe link
+
+define('BX_MAINTENANCE_FILE', '.bx_maintenance'); ///< file name to use as mantenance mode indicator
+
+/**
+ * The following two functions are needed to convert title to uri and back.
+ * It usefull when titles are used in URLs, like in Categories and Tags.
+ */
+function title2uri($sValue)
+{
+    return str_replace(
+        array('&', '/', '\\', '"', '+'),
+        array('[and]', '[slash]', '[backslash]', '[quote]', '[plus]'),
+        $sValue
+    );
+}
+function uri2title($sValue)
+{
+    return str_replace(
+        array('[and]', '[slash]', '[backslash]', '[quote]', '[plus]'),
+        array('&', '/', '\\', '"', '+'),
+        $sValue
+    );
+}
+
+/*
+ * functions for limiting maximal text length
+ */
+function strmaxtextlen($sInput, $iMaxLen = 60, $sEllipsisSign = '&#8230;')
+{
+    $sTail = '';
+    $s = trim(strip_tags($sInput));
+    if (mb_strlen($s) > $iMaxLen) {
+        $s = mb_substr($s, 0, $iMaxLen);
+        $sTail = $sEllipsisSign;
+    }
+    return htmlspecialchars_adv($s) . $sTail;
+}
+
+function html2txt($content, $tags = "")
+{
+    while($content != strip_tags($content, $tags)) {
+        $content = strip_tags($content, $tags);
+    }
+
+    return $content;
+}
+
+/**
+ * Functions to process user input.
+ * DON'T use to process data before passing to SQL query - use db prepare instead @see BxDolDb::prepare.
+ * It is ok to use bx_process_input and then db prepare.
+ * @param $mixedData data to process
+ * @param $iDataType how to handle data, possible valies:
+ *      @code
+ *          BX_DATA_INT - integer value
+ *          BX_DATA_FLOAT - float values
+ *          BX_DATA_CHECKBOX - 'on' or empty string
+ *          BX_DATA_TEXT - text data, single line (default)
+ *          BX_DATA_TEXT_MULTILINE - text data, multiple lines
+ *          BX_DATA_HTML - HTML data
+ *          BX_DATA_DATE - date data type stored as yyyy-mm-dd
+ *          BX_DATA_DATETIME - date/time data type stored as yyyy-mm-dd hh:mm:ss
+ *          BX_DATA_DATE_TS' -  date data type stored as unixtimestamp
+ *          BX_DATA_DATETIME_TS - date/time data type stored as unixtimestamp
+ *          BX_DATA_DATE_TS_UTC - date data type stored as unixtimestamp from UTC time
+ *          BX_DATA_DATETIME_TS_UTC - date/time data type stored as unixtimestamp from UTC time
+ *      @endcode
+ * @param $mixedParams optional parameters to pass for validation
+ * @param $isCheckMagicQuotes deprecated
+ * @return the filtered data, or FALSE if the filter fails.
+ */
+function bx_process_input ($mixedData, $iDataType = BX_DATA_TEXT, $mixedParams = false, $isCheckMagicQuotes = true)
+{
+    if (is_array($mixedData)) {
+        foreach ($mixedData as $k => $v)
+            $mixedData[$k] = bx_process_input($v, $iDataType, $mixedParams);
+        return $mixedData;
+    }
+
+    switch ($iDataType) {
+    case BX_DATA_INT:
+        return filter_var(trim($mixedData), FILTER_VALIDATE_INT);
+    case BX_DATA_FLOAT:
+        return filter_var(trim($mixedData), FILTER_VALIDATE_FLOAT);
+    case BX_DATA_CHECKBOX:
+        return 'on' == trim($mixedData) ? 'on' : '';
+
+    case BX_DATA_DATE:
+        // maybe consider using strtotime
+        $mixedData = trim($mixedData);
+        if (!preg_match('/^\d{4}-\d{1,2}-\d{1,2}$/', $mixedData))
+            return false;
+        list($iYear, $iMonth, $iDay) = explode('-', $mixedData); // 1985-10-28
+        $iDay   = intval($iDay);
+        $iMonth = intval($iMonth);
+        $iYear  = intval($iYear);
+        return sprintf("%04d-%02d-%02d", $iYear, $iMonth, $iDay);
+    case BX_DATA_DATETIME:
+        // maybe consider using strtotime
+        $mixedData = trim($mixedData);
+        if (!preg_match('#(\d+)\-(\d+)\-(\d+)[\sT]{1}(\d+):(\d+):(\d+)#', $mixedData, $m) && !preg_match('#(\d+)\-(\d+)\-(\d+)[\sT]{1}(\d+):(\d+)#', $mixedData, $m))
+            return bx_process_input ($mixedData, BX_DATA_DATE, $mixedParams, $isCheckMagicQuotes);
+        $iDay   = intval($m[3]);
+        $iMonth = intval($m[2]);
+        $iYear  = intval($m[1]);
+        $iH = intval($m[4]);
+        $iM = intval($m[5]);
+        $iS = isset($m[6]) ? intval($m[6]) : 0;
+        return sprintf("%04d-%02d-%02d %02d:%02d:%02d", $iYear, $iMonth, $iDay, $iH, $iM, $iS); // 1985-10-28 00:59:35
+    case BX_DATA_DATE_TS:
+    case BX_DATA_DATE_TS_UTC:
+        $mixedData = trim($mixedData);
+        if (!preg_match('/^\d{4}-\d{1,2}-\d{1,2}$/', $mixedData))
+            return false;
+        list($iYear, $iMonth, $iDay) = explode('-', $mixedData);
+        $iDay   = intval($iDay);
+        $iMonth = intval($iMonth);
+        $iYear  = intval($iYear);
+        $sFunc = BX_DATA_DATE_TS_UTC == $iDataType ? 'gmmktime' : 'mktime';
+        $iRet = $sFunc (0, 0, 0, $iMonth, $iDay, $iYear);
+        return $iRet > 0 ? $iRet : false;
+    case BX_DATA_DATETIME_TS:
+    case BX_DATA_DATETIME_TS_UTC:
+        if (!preg_match('#(\d+)\-(\d+)\-(\d+)[\s]{1}(\d+):(\d+):(\d+)[\s]{1}([+\-\d:Z]+)#', $mixedData, $m) && !preg_match('#(\d+)\-(\d+)\-(\d+)[\sT]{1}(\d+):(\d+):(\d+)#', $mixedData, $m) && !preg_match('#(\d+)\-(\d+)\-(\d+)[\sT]{1}(\d+):(\d+)#', $mixedData, $m))
+            return bx_process_input ($mixedData, BX_DATA_DATETIME_TS == $iDataType ? BX_DATA_DATE_TS : BX_DATA_DATE_TS_UTC, $mixedParams, $isCheckMagicQuotes);
+        $iDay   = $m[3];
+        $iMonth = $m[2];
+        $iYear  = $m[1];
+        $iH = $m[4];
+        $iM = $m[5];
+        $iS = isset($m[6]) ? $m[6] : 0;        
+        $iTimezoneOffset = 0;
+        if (isset($m[7])) {
+            $oTz = new DateTimeZone($m[7]);
+            $oUtc = new DateTime(str_replace($m[7], '', $mixedData), new DateTimeZone('UTC'));
+            if ($oTz && $oUtc)
+                $iTimezoneOffset = $oTz->getOffset($oUtc);
+        }
+        $sFunc = BX_DATA_DATETIME_TS_UTC == $iDataType ? 'gmmktime' : 'mktime';
+        $iRet = $sFunc ($iH, $iM, $iS, $iMonth, $iDay, $iYear) - $iTimezoneOffset;
+
+        return $iRet > 0 ? $iRet : false;
+
+    case BX_DATA_HTML:
+        return clear_xss($mixedData);
+    case BX_DATA_TEXT_MULTILINE:
+        return nl2br(htmlspecialchars_adv($mixedData));
+    case BX_DATA_TEXT:
+    default:
+        return $mixedData;
+    }
+}
+
+/*
+ * Functions to process user output.
+ * Always use this function before output data which was entered by user before.
+ * @param $mixedData string to process
+ * @param $iDataType how to handle data, possible valies the same as in bx_process_input function, see bx_process_input.
+ * @param $mixedParams optional parameters to pass for validation
+ * @return the filtered data, or FALSE if the filter fails.
+ */
+function bx_process_output ($mixedData, $iDataType = BX_DATA_TEXT, $mixedParams = false)
+{
+    if (is_array($mixedData)) {
+        foreach ($mixedData as $k => $v)
+            $mixedData[$k] = bx_process_output($v, $iDataType, $mixedParams);
+        return $mixedData;
+    }
+
+    switch ($iDataType) {
+    case BX_DATA_INT:
+        return filter_var($mixedData, FILTER_VALIDATE_INT);
+    case BX_DATA_FLOAT:
+        return filter_var($mixedData, FILTER_VALIDATE_FLOAT);
+    case BX_DATA_CHECKBOX:
+        return 'on' == trim($mixedData) ? 'on' : '';
+
+    case BX_DATA_DATE:
+    case BX_DATA_DATETIME:
+        return $mixedData;
+    case BX_DATA_DATE_TS:
+        return empty($mixedData) ? '' : date("Y-m-d", (int)$mixedData);
+    case BX_DATA_DATE_TS_UTC:
+        return empty($mixedData) ? '' : gmdate("Y-m-d", (int)$mixedData);
+    case BX_DATA_DATETIME_TS:
+        return empty($mixedData) ? '' : date("Y-m-d H:i", (int)$mixedData);
+    case BX_DATA_DATETIME_TS_UTC:
+        return empty($mixedData) ? '' : gmdate("Y-m-d H:i", (int)$mixedData);
+
+    case BX_DATA_HTML:
+        $s = bx_linkify_html($mixedData, 'class="' . BX_DOL_LINK_CLASS . '"');
+        return $mixedParams && is_array($mixedParams) && in_array('no_process_macro', $mixedParams) ? $s : bx_process_macros($s);
+    case BX_DATA_TEXT_MULTILINE:
+        $s = $mixedData;
+        return $mixedParams && is_array($mixedParams) && in_array('no_process_macros', $mixedParams) ? $s : bx_process_macros($s);
+    case BX_DATA_TEXT:
+    default:
+        $s = htmlspecialchars_adv($mixedData);
+        return $mixedParams && is_array($mixedParams) && in_array('no_process_macros', $mixedParams) ? $s : bx_process_macros($s);
+    }
+}
+
+function bx_is_macros_in_content (&$s) 
+{
+    return false === strpos($s, '{{~') ? false : true; 
+}
+
+/**
+ * This function converts macros upon text output. 
+ * Macros represents constructions like this:
+ * @code
+ * {{!module_name:function[param1, "param2"]}}
+ * @endcode
+ * For example, to display some content from module Posts:
+ * @code
+ * {{!bx_posts:get_search_result_unit[3]}}
+ * @endcode
+ * Only users which have "use macros" ACL action enabled can use this functionlity.
+ *
+ * @param $s text to process
+ * @return modified or not modified text
+ */
+function bx_process_macros ($s)
+{
+    if (!bx_is_macros_in_content($s))
+        return $s;
+
+    return preg_replace_callback(
+        "/{{\~(.*?)\~}}/", 
+        function ($aMatches) {
+            return BxDolService::callMacro($aMatches[1]); 
+        }, 
+        $s);
+}
+
+/*
+ * This function apply bx_process_input and then bx_process_output.
+ * Use this function to output data immediately after receiving, without saving to database.
+ * Patams are the same as bx_process_input function - @see bx_process_input
+ */
+function bx_process_pass ($mixedData, $iDataType = BX_DATA_TEXT, $mixedParams = false, $isCheckMagicQuotes = true)
+{
+    return bx_process_output(bx_process_input ($mixedData, $iDataType, $mixedParams, $isCheckMagicQuotes), $iDataType, $mixedParams);
+}
+
+/**
+ * DEPRECATED
+ * use bx_process_input + bx_process_output instead
+ * --------
+ * function for processing pass data
+ *
+ * This function cleans the GET/POST/COOKIE data if magic_quotes_gpc() is on
+ * for data which should be outputed immediately after submit
+ */
+/*
+function process_pass_data( $text, $strip_tags = 0 )
+{
+    if ( $strip_tags )
+        $text = strip_tags($text);
+
+    if ( !get_magic_quotes_gpc() )
+        return $text;
+    else
+        return stripslashes($text);
+}
+*/
+
+/*
+ * function for output data from database into html
+ */
+function htmlspecialchars_adv( $string )
+{
+    return htmlspecialchars($string, ENT_COMPAT, 'UTF-8', false);
+}
+
+/**
+ * Send mail to user by parsing email template
+ */
+function sendMailTemplate($sTemplateName, $iAccountId = 0, $iProfileId = 0, $aReplaceVars = array(), $iEmailType = BX_EMAIL_NOTIFY, $bAddToQueue = false)
+{
+    $oProfile = BxDolProfile::getInstance($iProfileId);
+
+    $oAccount = $iAccountId ? BxDolAccount::getInstance($iAccountId) : ($oProfile ? $oProfile->getAccountObject() : null);
+
+    $oEmailTemplates = BxDolEmailTemplates::getInstance();
+
+    if (!$oAccount || !$oProfile || !$oEmailTemplates)
+        return false;
+
+    $aTemplate = $oEmailTemplates->parseTemplate($sTemplateName, $aReplaceVars, $oAccount ? $oAccount->id() : 0, (int)$iProfileId);
+    if (!$aTemplate)
+        return false;
+
+    return sendMail($oAccount->getEmail(), $aTemplate['Subject'], $aTemplate['Body'], 0, array(), $iEmailType, 'html', false, array(), $bAddToQueue);
+}
+
+/**
+ * Send system email 
+ */
+function sendMailTemplateSystem($sTemplateName, $aReplaceVars = array(), $iEmailType = BX_EMAIL_SYSTEM, $bAddToQueue = false)
+{
+    $oEmailTemplates = BxDolEmailTemplates::getInstance();
+
+    if (!$oEmailTemplates)
+        return false;
+
+    $aTemplate = $oEmailTemplates->parseTemplate($sTemplateName, $aReplaceVars);
+    if (!$aTemplate)
+        return false;
+
+    return sendMail(getParam('site_email'), $aTemplate['Subject'], $aTemplate['Body'], 0, array(), $iEmailType, 'html', false, array(), $bAddToQueue);
+}
+/**
+ * Send email function
+ *
+ * @param $sRecipientEmail - Email where email should be send
+ * @param $sMailSubject - subject of the message
+ * @param $sMailBody - Body of the message
+ * @param $iRecipientID - ID of recipient profile
+ * @param $aPlus - Array of additional information
+ * @param $iEmailType - email message type: BX_EMAIL_SYSTEM, BX_EMAIL_NOTIFY or BX_EMAIL_MASS
+ * @param $sEmailFlag - use 'html' for HTML email message
+ * @param $isDisableAlert - disable alert
+ * @param $aCustomHeaders - custom email headers
+ * @param $bAddToQueue - add message to email queue
+ * @return true if message was send or false otherwise
+ */
+function sendMail($sRecipientEmail, $sMailSubject, $sMailBody, $iRecipientID = 0, $aPlus = array(), $iEmailType = BX_EMAIL_NOTIFY, $sEmailFlag = 'html', $isDisableAlert = false, $aCustomHeaders = array(), $bAddToQueue = false)
+{
+    // make sure that recipient's email is valid and message isn't empty
+    if (!$sMailBody || !$sRecipientEmail || preg_match('/\(2\)$/', $sRecipientEmail))
+        return false;
+
+    // get recipient account
+    $oAccount = !$isDisableAlert ? BxDolAccount::getInstance($sRecipientEmail) : null;
+    $aAccountInfo = $oAccount ? $oAccount->getInfo() : false;
+
+    // don't send bulk emails if user didn't subscribed to site news or email is unconfirmed
+    if ($aAccountInfo && BX_EMAIL_MASS == $iEmailType && (!$aAccountInfo['email_confirmed'] || !$aAccountInfo['receive_news']))
+        return false;
+
+    // don't send email notifications if user didn't subscribed to notifications or email is unconfirmed
+    if ($aAccountInfo && BX_EMAIL_NOTIFY == $iEmailType && (!$aAccountInfo['email_confirmed'] || !$aAccountInfo['receive_updates']))
+        return false;
+
+    if($bAddToQueue && BxDolQueueEmail::getInstance()->add($sRecipientEmail, $sMailSubject, $sMailBody, $iRecipientID, $aPlus, $iEmailType, $sEmailFlag, $isDisableAlert, $aCustomHeaders))
+        return true;
+
+    // if profile id is provided - get profile's info
+    $aRecipientInfo = false;
+    if ($iRecipientID) {
+        $oProfile = BxDolProfile::getInstance($iRecipientID);
+        if ($oProfile)
+            $aRecipientInfo = $oProfile->getInfo();
+    }
+
+    // get site vars
+    $sEmailNotify = !$isDisableAlert ? getParam('site_email_notify') : $sRecipientEmail;
+    $sSiteTitle = !$isDisableAlert ? getParam('site_title') : 'UNA ' . BX_DOL_VERSION;
+
+    // add unsubscribe link
+    if (!$isDisableAlert && empty($aPlus['unsubscribe'])) {
+        $aPlus['unsubscribe'] = '';
+        if ($oAccount && (BX_EMAIL_MASS == $iEmailType || BX_EMAIL_NOTIFY == $iEmailType))
+            $aPlus['unsubscribe'] = ($sLink = $oAccount->getUnsubscribeLink($iEmailType)) ? '<a href="' . BX_DOL_URL_ROOT . $sLink . '">' . _t('_sys_et_txt_unsubscribe') . '</a>' : '';
+    }
+
+    // parse template
+    if ($aPlus || $iRecipientID || $oAccount) {
+        if(!is_array($aPlus))
+            $aPlus = array();
+        $oEmailTemplates = BxDolEmailTemplates::getInstance();
+        $sMailSubject = $oEmailTemplates->parseContent($sMailSubject, $aPlus, !$iRecipientID && $oAccount ? $oAccount->id() : 0, $iRecipientID);
+        $sMailBody = $oEmailTemplates->parseContent($sMailBody, $aPlus, !$iRecipientID && $oAccount ? $oAccount->id() : 0, $iRecipientID);
+    }
+
+    // email message headers
+    $sMailHeader = '';
+    foreach ($aCustomHeaders as $sHeaderName => $sHeaderValue)
+        $sMailHeader = "$sHeaderName: $sHeaderValue\r\n" . $sMailHeader;
+
+    if (!isset($aCustomHeaders['From']))
+        $sMailHeader = "From: =?UTF-8?B?" . base64_encode( $sSiteTitle ) . "?= <{$sEmailNotify}>";
+
+    if (!isset($aCustomHeaders['MIME-Version']))
+        $sMailHeader = "MIME-Version: 1.0\r\n" . $sMailHeader;
+
+    $sMailParameters = isset($aCustomHeaders['Sender']) ? "-f{$aCustomHeaders['Sender']}" : "-f{$sEmailNotify}";
+
+    if (isset($aCustomHeaders['Subject']))
+        $sMailSubject = $aCustomHeaders['Subject'];
+
+    // build data for alert handler
+    $bResult = null;
+    $aAlert = array(
+        'email' => $sRecipientEmail,
+        'subject' => $sMailSubject,
+        'body' => $sMailBody,
+        'header' => $sMailHeader,
+        'params' => $sMailParameters,
+        'recipient' => $aRecipientInfo,
+        'html' => 'html' == $sEmailFlag ? true : false,
+        'custom_headers' => $aCustomHeaders,
+        'override_result' => &$bResult,
+    );
+
+    // system alert
+    if (!$isDisableAlert) {
+        bx_alert('system', 'before_send_mail', (isset($aRecipientInfo['ID']) ? $aRecipientInfo['ID'] : 0), '', $aAlert);
+        if ($bResult !== null)
+            return $bResult;
+        unset($aAlert['override_result']);
+    }
+
+    // prepare HTML/Plain message
+    if($sEmailFlag == 'html')
+        $sMailHeader = "Content-type: text/html; charset=UTF-8\r\n" . $sMailHeader;
+    else {
+        $sMailHeader = "Content-type: text/plain; charset=UTF-8\r\n" . $sMailHeader;
+        $sMailBody = html2txt($sMailBody);
+    }
+
+    // encode subject
+    if (0 !== strncasecmp($sMailSubject, '=?UTF-8?B?', 10))
+        $sMailSubject = '=?UTF-8?B?' . base64_encode($sMailSubject) . '?=';
+
+    // send mail or put it into queue
+    $bResult = mail($sRecipientEmail, $sMailSubject, $sMailBody, $sMailHeader, $sMailParameters);
+
+    // system alert
+    if (!$isDisableAlert)
+        bx_alert('system', 'send_mail', (isset($aRecipientInfo['ID']) ? $aRecipientInfo['ID'] : 0), '', $aAlert);
+
+    return $bResult;
+}
+
+/*
+ * Getting an array with Templates' Names
+ */
+function get_templates_array($bEnabledOnly = true, $bShortInfo = true)
+{
+    $oDb = BxDolDb::getInstance();
+
+    $sWhereAddon = $bEnabledOnly ? " AND `enabled`='1'" : "";
+
+    if($bShortInfo)
+        return $oDb->getPairs("SELECT `uri`, `title` FROM `sys_modules` WHERE 1 AND `type`='" . BX_DOL_MODULE_TYPE_TEMPLATE . "'" . $sWhereAddon, "uri", "title");
+    else
+        return $oDb->getAllWithKey("SELECT * FROM `sys_modules` WHERE 1 AND `type`='" . BX_DOL_MODULE_TYPE_TEMPLATE . "'" . $sWhereAddon, "uri");
+}
+
+function bx_get_image_exif_and_size($oStorage, $oTranscoder, $iContentId){
+    $sData = '';
+    $sExif = '';
+    $aExif = false;
+    $aPhoto = $oStorage->getFile($iContentId);
+    if ($oTranscoder->isMimeTypeSupported($aPhoto['mime_type'])) {
+        $oImageReize = BxDolImageResize::getInstance();
+        
+        $a = $oImageReize->getImageSize($oTranscoder->getFileUrl($iContentId));
+        $sData = isset($a['w']) && isset($a['h']) ? $a['w'] . 'x' . $a['h'] : '';
+        
+        if ($aExif = $oImageReize->getExifInfo($oStorage->getFileUrlById($iContentId))) {
+            $a = array('Make', 'Model', 'FocalLength', 'ShutterSpeedValue', 'ExposureTime', 'ISOSpeedRatings', 'Orientation', 'Artist', 'Copyright', 'Flash', 'WhiteBalance', 'DateTimeOriginal', 'DateTimeDigitized', 'ExifVersion', 'COMPUTED', 'GPSLatitudeRef', 'GPSLatitude', 'GPSLongitudeRef', 'GPSLongitude', 'GPSAltitudeRef', 'GPSAltitude', 'GPSTimeStamp', 'GPSImgDirectionRef', 'GPSImgDirection', 'GPSDateStamp');
+            $aExifFiltered = array();
+            foreach ($a as $sIndex)
+                if (isset($aExif[$sIndex]))
+                    $aExifFiltered[$sIndex] = $aExif[$sIndex];
+            $sExif = serialize($aExifFiltered);
+        }
+    }
+    return array('exif' => $sExif, 'size' => $sData);
+}
+
+function extFileExists( $sFileSrc )
+{
+    return (file_exists( $sFileSrc ) && is_file( $sFileSrc )) ? true : false;
+}
+
+function getVisitorIP()
+{
+    $ip = "0.0.0.0";
+    if (isset($_SERVER['HTTP_X_FORWARDED_FOR']) && !empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
+        $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
+    } elseif (isset( $_SERVER['HTTP_CLIENT_IP']) && !empty($_SERVER['HTTP_CLIENT_IP'])) {
+        $ip = explode(".",$_SERVER['HTTP_CLIENT_IP']);
+        $ip = $ip[3].".".$ip[2].".".$ip[1].".".$ip[0];
+    } elseif (!isset( $_SERVER['HTTP_X_FORWARDED_FOR']) || empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
+        if (!isset( $_SERVER['HTTP_CLIENT_IP']) && empty($_SERVER['HTTP_CLIENT_IP']) && isset($_SERVER['REMOTE_ADDR']))
+            $ip = $_SERVER['REMOTE_ADDR'];
+    }
+    return $ip;
+}
+
+function genFlag($sLang = '', $oTemplate = null)
+{
+    if (!$oTemplate)
+        $oTemplate = BxDolTemplate::getInstance();
+    $oTemplate->addCss(BX_DIRECTORY_PATH_PLUGINS_PUBLIC . 'flag-icon-css/css/|flag-icon.min.css');
+    $sFlag = BxDolLanguages::getInstance()->getLangFlag($sLang);
+    return '<span title="' . $sFlag . '" class="flag-icon flag-icon-' . $sFlag . '"></span>';
+}
+
+// print debug information ( e.g. arrays )
+function echoDbg( $what, $desc = '' )
+{
+    if ( $desc )
+        echo "<b>$desc:</b> ";
+    echo "<pre>";
+        print_r( $what );
+    echo "</pre>\n";
+}
+
+function echoDbgLog($mWhat, $sDesc = '', $sFileName = 'debug.log')
+{
+    $sCont =
+        '--- ' . date('r') . ' (' . BX_DOL_START . ") ---\n" .
+        $sDesc . "\n" .
+        print_r($mWhat, true) . "\n\n\n";
+
+    $rFile = fopen(BX_DIRECTORY_PATH_LOGS . $sFileName, 'a');
+    fwrite($rFile, $sCont);
+    fclose($rFile);
+}
+
+function dbgTiming($sStartMicrotime)
+{
+    $i1 = explode(' ', microtime ());
+    $i2 = explode(' ', $sStartMicrotime);
+    $iTime = ($i1[0]+$i1[1]) - ($i2[0]+$i2[1]);
+    return round($iTime, 6) . ' sec';
+}
+
+function echoJson($a)
+{
+	header('Content-type: text/html; charset=utf-8');
+
+	echo json_encode($a);
+}
+
+function clear_xss($val)
+{
+    // HTML Purifier plugin
+    global $oHtmlPurifier;
+    if (!isset($oHtmlPurifier) && !$GLOBALS['logged']['admin']) {
+
+        require_once( BX_DIRECTORY_PATH_PLUGINS . 'htmlpurifier/HTMLPurifier.standalone.php' );
+
+        HTMLPurifier_Bootstrap::registerAutoload();
+
+        $oConfig = HTMLPurifier_Config::createDefault();
+
+        $oConfig->set('Cache.SerializerPath', rtrim(BX_DIRECTORY_PATH_CACHE, '/'));
+        $oConfig->set('Cache.SerializerPermissions', BX_DOL_DIR_RIGHTS);
+
+        $oConfig->set('HTML.SafeObject', 'true');
+        $oConfig->set('Output.FlashCompat', 'true');
+        $oConfig->set('HTML.FlashAllowFullScreen', 'true');
+        $oConfig->set('Attr.AllowedFrameTargets', array('_blank', '_self', '_parent', '_top'));
+
+        if (getParam('sys_add_nofollow')) {
+            $sHost = parse_url(BX_DOL_URL_ROOT, PHP_URL_HOST);
+            $oConfig->set('URI.Host', $sHost);
+            $oConfig->set('HTML.Nofollow', 'true');
+        }
+
+        $oConfig->set('Filter.Custom', array (new HTMLPurifier_Filter_YouTube(), new HTMLPurifier_Filter_YoutubeIframe(), new HTMLPurifier_Filter_AddBxLinksClass(), new HTMLPurifier_Filter_LocalIframe()));
+
+	    $oConfig->set('HTML.DefinitionID', 'html5-definitions');
+		$oConfig->set('HTML.DefinitionRev', 1);
+		if ($def = $oConfig->maybeGetRawHTMLDefinition()) {
+		    $def->addElement('section', 'Block', 'Flow', 'Common');
+		    $def->addElement('nav',     'Block', 'Flow', 'Common');
+		    $def->addElement('article', 'Block', 'Flow', 'Common');
+		    $def->addElement('aside',   'Block', 'Flow', 'Common');
+		    $def->addElement('header',  'Block', 'Flow', 'Common');
+		    $def->addElement('footer',  'Block', 'Flow', 'Common');
+		    $def->addElement('video', 'Block', 'Optional: (source, Flow) | (Flow, source) | Flow', 'Common', array(
+		        'src' => 'URI',
+		        'type' => 'Text',
+		        'width' => 'Length',
+		        'height' => 'Length',
+		        'poster' => 'URI',
+		        'preload' => 'Enum#auto,metadata,none',
+		        'controls' => 'Bool',
+		    ));
+		    $def->addElement('source', 'Block', 'Flow', 'Common', array(
+		        'src' => 'URI',
+		        'type' => 'Text',
+            ));
+            $def->addAttribute('a', 'data-profile-id', 'Number');
+		}
+
+        $oHtmlPurifier = new HTMLPurifier($oConfig);
+    }
+
+    if (!$GLOBALS['logged']['admin'])
+        $val = $oHtmlPurifier->purify($val);
+
+    bx_alert('system', 'clear_xss', 0, 0, array('oHtmlPurifier' => $oHtmlPurifier, 'return_data' => &$val));
+
+    return $val;
+}
+
+//--------------------------------------- friendly permalinks --------------------------------------//
+//------------------------------------------- main functions ---------------------------------------//
+function uriGenerate ($s, $sTable, $sField, $sEmpty = '-', $sDivider = '-')
+{
+    $s = uriFilter($s, $sEmpty, $sDivider);
+    if(uriCheckUniq($s, $sTable, $sField))
+        return $s;
+
+    // cut off redundant part
+    if(get_mb_len($s) > 240)
+        $s = get_mb_substr($s, 0, 240);
+
+    // try to add date
+    $s .= $sDivider . date('Y-m-d');
+    if(uriCheckUniq($s, $sTable, $sField))
+        return $s;
+
+    // try to add number
+    for($i = 0 ; $i < 999 ; ++$i)
+        if(uriCheckUniq($s . $sDivider . $i, $sTable, $sField))
+            return ($s . $sDivider . $i);
+
+    return rand(0, 999999999);
+}
+
+function uriFilter ($s, $sEmpty = '-', $sDivider = '-')
+{
+    if (BxTemplConfig::getInstance()->bAllowUnicodeInPreg)
+        $s = get_mb_replace ('/[^\pL^\pN]+/u', $sDivider, $s); // unicode characters
+    else
+        $s = get_mb_replace ('/([^\d^\w]+)/u', $sDivider, $s); // latin characters only
+
+    $s = get_mb_replace ('/([' . $sDivider . '^]+)/', $sDivider, $s);
+    $s = get_mb_replace ('/([' . $sDivider . ']+)$/', '', $s); // remove trailing dash
+    if (!$s) $s = $sEmpty;
+    return $s;
+}
+
+function uriCheckUniq ($s, $sTable, $sField)
+{
+    $oDb = BxDolDb::getInstance();
+
+    $sSql = $oDb->prepare("SELECT 1 FROM `$sTable` WHERE `$sField`=? LIMIT 1", $s);
+    return !$oDb->query($sSql);
+}
+
+function get_mb_replace ($sPattern, $sReplace, $s)
+{
+    return preg_replace ($sPattern, $sReplace, $s);
+}
+
+function get_mb_len ($s)
+{
+    return (function_exists('mb_strlen')) ? mb_strlen($s) : strlen($s);
+}
+
+function get_mb_substr ($s, $iStart, $iLen)
+{
+    return (function_exists('mb_substr')) ? mb_substr ($s, $iStart, $iLen) : substr ($s, $iStart, $iLen);
+}
+
+function bx_mb_substr_replace($s, $sReplace, $iPosStart, $iLength)
+{
+    return mb_substr($s, 0, $iPosStart) . $sReplace . mb_substr($s, $iPosStart + $iLength);
+}
+
+function bx_mb_strpos ($s, $sReplacement, $iStart = 0)
+{
+    return mb_strpos($s, $sReplacement, $iStart);
+}
+
+/**
+ * Import class file, it automatically detects class path by its prefix or module array/name
+ *
+ * @param $sClassName - full class name or class postfix(withoit prefix) in the case of module class
+ * @param $mixedModule - module array or module name in the case of module class
+ */
+function bx_import($sClassName, $mixedModule = array())
+{
+    if (class_exists($sClassName, false))
+        return;
+
+    $aModule = false;
+    if ($mixedModule) {
+        if (is_array($mixedModule)) {
+            $aModule = $mixedModule;
+        } elseif (is_string($mixedModule)) {
+            $o = BxDolModule::getInstance($mixedModule);
+            $aModule = $o->_aModule;
+        } elseif (is_bool($mixedModule) && true === $mixedModule) {
+            $aModule = $GLOBALS['aModule'];
+        }
+    }
+
+    if ($aModule) {
+        if (class_exists($aModule['class_prefix'] . $sClassName, false))
+            return;
+        require_once (BX_DIRECTORY_PATH_MODULES . $aModule['path'] . 'classes/' . $aModule['class_prefix'] . $sClassName . '.php');
+        return;
+    }
+
+    if (0 === strncmp($sClassName, 'BxDol', 5)) {
+        if (0 === strncmp($sClassName, 'BxDolStudio', 11))
+            require_once(BX_DOL_DIR_STUDIO_CLASSES . $sClassName . '.php');
+        else
+            require_once(BX_DIRECTORY_PATH_CLASSES . $sClassName . '.php');
+        return;
+    }
+
+    if (0 === strncmp($sClassName, 'BxBase', 6)) {
+        if (0 === strncmp($sClassName, 'BxBaseMod', 9)) {
+            $aMatches = array();
+            if (preg_match('/BxBaseMod([A-Z][a-z]+)/', $sClassName, $aMatches)) {
+                require_once(BX_DIRECTORY_PATH_MODULES . 'base/' . strtolower($aMatches[1]) . '/classes/' . $sClassName . '.php');
+                return;
+            }
+        } if (0 === strncmp($sClassName, 'BxBaseStudio', 12)) {
+            require_once(BX_DOL_DIR_STUDIO_BASE . 'scripts/' . $sClassName . '.php');
+            return;
+        } else {
+            require_once(BX_DIRECTORY_PATH_BASE . 'scripts/' . $sClassName . '.php');
+            return;
+        }
+    }
+
+    if (0 === strncmp($sClassName, 'BxTempl', 7)) {
+        if(0 === strncmp($sClassName, 'BxTemplStudio', 13)) {
+            $sPath = BX_DIRECTORY_PATH_MODULES . BxDolStudioTemplate::getInstance()->getPath() . 'data/template/studio/scripts/' . $sClassName . '.php';
+        } else {
+            $sPath = BX_DIRECTORY_PATH_MODULES . BxDolTemplate::getInstance()->getPath() . 'data/template/system/scripts/' . $sClassName . '.php';
+        }
+
+        if (file_exists($sPath)) {
+            require_once($sPath);
+            return;
+        }
+
+        trigger_error ("bx_import fatal error: class (" . $sClassName . ") wasn't found", E_USER_ERROR);
+    }
+}
+
+/**
+ * used in spl_autoload_register() function, so no need to call bx_import for system classes
+ */
+function bx_autoload($sClassName)
+{
+    if (0 === strncmp($sClassName, 'BxDol', 5) || 0 === strncmp($sClassName, 'BxBase', 6) || 0 === strncmp($sClassName, 'BxTempl', 7))
+        bx_import($sClassName);
+}
+
+/**
+ * Gets an instance of class pathing necessary parameters if it's necessary.
+ *
+ * @param string $sClassName class name.
+ * @param array $aParams an array of parameters to be pathed to the constructor of the class.
+ * @param array $mixedModule an array with module description. Is used when the requested class is located in some module.
+ * @return unknown
+ */
+function bx_instance($sClassName, $aParams = array(), $mixedModule = array())
+{
+    if(isset($GLOBALS['bxDolClasses'][$sClassName]))
+        return $GLOBALS['bxDolClasses'][$sClassName];
+
+    if ($mixedModule) {
+        if (!is_array($mixedModule)) {
+            $o = BxDolModule::getInstance($mixedModule);
+            $mixedModule = $o->_aModule;
+        }
+        $sClassName = bx_ltrim_str($sClassName, $mixedModule['class_prefix']);
+        bx_import($sClassName, $mixedModule);
+        $sClassName = $mixedModule['class_prefix'] . $sClassName;
+    }
+
+    $oClass = new ReflectionClass($sClassName);
+
+    $GLOBALS['bxDolClasses'][$sClassName] = empty($aParams) ? $oClass->newInstance() : $oClass->newInstanceArgs($aParams);
+
+    return $GLOBALS['bxDolClasses'][$sClassName];
+}
+
+
+/**
+ * Escapes string/array ready to pass to js script with filtered symbols like ', " etc
+ *
+ * @param $mixedInput - string/array which should be filtered
+ * @param $iQuoteType - string escaping method: BX_ESCAPE_STR_AUTO(default), BX_ESCAPE_STR_APOS or BX_ESCAPE_STR_QUOTE
+ * @return converted string / array
+ */
+function bx_js_string ($mixedInput, $iQuoteType = BX_ESCAPE_STR_AUTO)
+{
+    $aUnits = array(
+        "\n" => "\\n",
+        "\r" => "",
+    );
+    if (BX_ESCAPE_STR_APOS == $iQuoteType) {
+        $aUnits["'"] = "\\'";
+        $aUnits['<script'] = "<scr' + 'ipt";
+        $aUnits['</script>'] = "</scr' + 'ipt>";
+    } elseif (BX_ESCAPE_STR_QUOTE == $iQuoteType) {
+        $aUnits['"'] = '\\"';
+        $aUnits['<script'] = '<scr" + "ipt';
+        $aUnits['</script>'] = '</scr" + "ipt>';
+    } else {
+        $aUnits['"'] = '&quot;';
+        $aUnits["'"] = '&apos;';
+        $aUnits["<script>"] = '&lt;script&gt;';
+        $aUnits["</script>"] = '&lt;/script&gt;';
+    }
+    return str_replace(array_keys($aUnits), array_values($aUnits), $mixedInput);
+}
+
+/**
+ * Return input string/array ready to pass to html attribute with filtered symbols like ', " etc
+ *
+ * @param $mixedInput - string/array which should be filtered
+ * @param $iQuoteType - string escaping BX_ESCAPE_STR_AUTO, BX_ESCAPE_STR_APOS or BX_ESCAPE_STR_QUOTE
+ * @return converted string / array
+ */
+function bx_html_attribute ($mixedInput, $iQuoteType = BX_ESCAPE_STR_AUTO)
+{
+    $aUnits = array ();
+    if (BX_ESCAPE_STR_APOS == $iQuoteType)
+        $aUnits["'"] = "\\'";
+    elseif (BX_ESCAPE_STR_QUOTE == $iQuoteType)
+        $aUnits['"'] = '&quot;';
+    else
+        $aUnits = array("\"" => "&quot;", "'" => "&apos;");
+
+    return str_replace(array_keys($aUnits), array_values($aUnits), $mixedInput);
+}
+
+/**
+ * Escapes string/array ready to pass to php script with filtered symbols like ', " etc
+ *
+ * @param mixed $mixedInput - string/array which should be filtered
+ * @return converted string / array
+ */
+function bx_php_string_apos ($mixedInput)
+{
+    return str_replace("'", "\\'", $mixedInput);
+}
+function bx_php_string_quot ($mixedInput)
+{
+    return str_replace('"', '\\"', $mixedInput);
+}
+
+/**
+ * Gets file contents by URL.
+ *
+ * @param string $sFileUrl - file URL to be read.
+ * @param array $aParams - an array of parameters to be pathed with URL.
+ * @param string $sMethod - post or get.
+ * @param array $aHeaders - custom headers.
+ * @param string $sHttpCode - HTTP code to return
+ * @param array $aBasicAuth - array with 'user' and 'password' for Basic HTTP Auth
+ * @return string the file's contents.
+ */
+function bx_file_get_contents($sFileUrl, $aParams = array(), $sMethod = 'get', $aHeaders = array(), &$sHttpCode = null, $aBasicAuth = array(), $iTimeout = 0, $aCustomCurlParams = array())
+{
+    if ('post' != $sMethod)
+    	$sFileUrl = bx_append_url_params($sFileUrl, $aParams);
+
+    $sResult = '';
+    if(function_exists('curl_init')) {
+        $rConnect = curl_init();
+
+        curl_setopt($rConnect, CURLOPT_USERAGENT, 'UNA ' . BX_DOL_VERSION);
+        curl_setopt($rConnect, CURLOPT_TIMEOUT, 10);
+        curl_setopt($rConnect, CURLOPT_URL, $sFileUrl);
+        curl_setopt($rConnect, CURLOPT_HEADER, NULL === $sHttpCode ? false : true);
+        curl_setopt($rConnect, CURLOPT_RETURNTRANSFER, 1);
+
+        if (0 !== $iTimeout) {
+            curl_setopt($rConnect, CURLOPT_CONNECTTIMEOUT, $iTimeout);
+            curl_setopt($rConnect, CURLOPT_TIMEOUT, $iTimeout);
+        }
+
+        if (!ini_get('open_basedir'))
+            curl_setopt($rConnect, CURLOPT_FOLLOWLOCATION, 1);
+
+        if ($aHeaders)
+            curl_setopt($rConnect, CURLOPT_HTTPHEADER, $aHeaders);
+
+        if ($aBasicAuth)
+            curl_setopt($rConnect, CURLOPT_USERPWD, $aBasicAuth['user'] . ':' . $aBasicAuth['password']);
+
+        if ('post' == $sMethod) {
+            curl_setopt($rConnect, CURLOPT_POST, true);
+            curl_setopt($rConnect, CURLOPT_POSTFIELDS, http_build_query($aParams));
+        }
+
+        $sAllCookies = '';
+        foreach($_COOKIE as $sKey=>$sValue){
+            $sAllCookies .= $sKey . '=' . $sValue . ';';
+        }
+        curl_setopt($rConnect, CURLOPT_COOKIE, $sAllCookies);
+
+        if ($aCustomCurlParams)
+            foreach ($aCustomCurlParams as $sName => $mixedValue)
+                curl_setopt($rConnect, constant($sName), $mixedValue);
+
+        $sResult = curl_exec($rConnect);
+
+        if (curl_errno($rConnect) == 60) { // CURLE_SSL_CACERT
+            curl_setopt($rConnect, CURLOPT_CAINFO, BX_DIRECTORY_PATH_PLUGINS . 'curl/cacert.pem');
+            $sResult = curl_exec($rConnect);
+        }
+
+        if (NULL !== $sHttpCode)
+            $sHttpCode = curl_getinfo($rConnect, CURLINFO_HTTP_CODE);
+
+        curl_close($rConnect);
+    }
+    else {
+
+        $iSaveTimeout = false;
+        if (0 !== $iTimeout) {
+            $iSaveTimeout = ini_get('default_socket_timeout');
+            ini_set('default_socket_timeout', $iTimeout);
+        }
+
+        $sResult = @file_get_contents($sFileUrl);
+
+        if (0 !== $iTimeout && false !== $iSaveTimeout) {
+            ini_set('default_socket_timeout', $iSaveTimeout);
+        }
+    }
+
+    return $sResult;
+}
+
+function bx_get_site_info($sSourceUrl, $aProcessAdditionalTags = array())
+{
+    $aResult = array();
+    $sContent = bx_file_get_contents($sSourceUrl);
+
+    if ($sContent) {
+        $sCharset = '';
+        preg_match("/<meta.+charset=([A-Za-z0-9-]+).+>/i", $sContent, $aMatch);
+        if (isset($aMatch[1]))
+            $sCharset = $aMatch[1];
+
+        $sContent = preg_replace("/<script[^>]*>(.*?)<\/script>/i", '', $sContent);
+        $sContent = preg_replace("/<style[^>]*>(.*?)<\/style>/i", '', $sContent);
+        if (preg_match("/<title[^>]*>(.*?)<\/title>/i", $sContent, $aMatch))
+            $aResult['title'] = strip_tags($aMatch[1]);
+        else
+            $aResult['title'] = parse_url($sSourceUrl, PHP_URL_HOST);
+
+        $aResult['description'] = bx_parse_html_tag($sContent, 'meta', 'name', 'description', 'content', $sCharset);
+        $aResult['keywords'] = bx_parse_html_tag($sContent, 'meta', 'name', 'keywords', 'content', $sCharset);
+
+        if ($aProcessAdditionalTags) {
+
+            foreach ($aProcessAdditionalTags as $k => $a) {
+                $aResult[$k] = bx_parse_html_tag(
+                    $sContent, 
+                    isset($a['tag']) ? $a['tag'] : 'meta', 
+                    isset($a['name_attr']) ? $a['name_attr'] : 'itemprop', 
+                    isset($a['name']) ? $a['name'] : $k, 
+                    isset($a['content_attr']) ? $a['content_attr'] : 'content', 
+                    $sCharset,
+                    isset($a['specialchars_decode']) ? $a['specialchars_decode'] : true); 
+
+                if ((isset($a['name']) && 'og:image' == $a['name']) || (isset($a['tag']) && 'link' == $a['tag'])) {
+                    $aResult[$k] = bx_get_site_info_fix_relative_url ($sSourceUrl, $aResult[$k]);
+                }
+            }
+
+        }
+    }
+
+    return $aResult;
+}
+
+/**
+ * Fix relative URL to make it absolute
+ * @param $sSourceUrl main URL
+ * @param $s URL to fix
+ * @return absolute URL or URL wothout changes
+ */ 
+function bx_get_site_info_fix_relative_url ($sSourceUrl, $s)
+{
+    if (0 === stripos($s, 'http://') || 0 === stripos($s, 'https://') || 0 === stripos($s, 'ftp://') || !$s)
+        return $s;
+
+    if ('/' == $s[0]) {
+        $a = parse_url($sSourceUrl);
+        return $a['scheme'] . '://' . $a['host'] . (empty($a['port']) || 80 == $a['port'] || 443 == $a['port'] ? '' : ':' . $a['port']) . $s;
+    }
+
+    return $sSourceUrl . ('/' === substr($sSourceUrl, -1) ? $s : '/' . $s);
+}
+
+function bx_parse_html_tag ($sContent, $sTag, $sAttrNameName, $sAttrNameValue, $sAttrContentName, $sCharset = false, $bSpecialCharsDecode = true)
+{
+    if (!preg_match("/<{$sTag}\s+{$sAttrNameName}[='\" ]+{$sAttrNameValue}['\"]\s+{$sAttrContentName}[='\" ]+([^'>\"]*)['\"][^>]*>/i", $sContent, $aMatch) || !isset($aMatch[1]))
+        preg_match("/<{$sTag}\s+{$sAttrContentName}[='\" ]+([^'>\"]*)['\"]\s+{$sAttrNameName}[='\" ]+{$sAttrNameValue}['\"][^>]*>/i", $sContent, $aMatch);
+
+    $s = isset($aMatch[1]) ? $aMatch[1] : '';
+
+    if ($s && $sCharset)
+        $s = mb_convert_encoding($s, 'UTF-8', $sCharset);
+
+    if ($bSpecialCharsDecode)
+        $s = htmlspecialchars_decode($s);
+        
+    return $s;
+}
+
+// calculation ini_get('upload_max_filesize') in bytes as example
+function return_bytes($val)
+{
+    $val = trim($val);
+    $last = strtolower($val[strlen($val)-1]);
+    $val = (int)$val;
+    switch($last) {
+        // The 'G' modifier is available since PHP 5.1.0
+        case 'k':
+            $val *= 1024;
+            break;
+        case 'm':
+            $val *= 1024 * 1024;
+            break;
+        case 'g':
+            $val *= 1024 * 1024 * 1024;
+            break;
+    }
+    return $val;
+}
+
+// Generate Random Password
+function genRndPwd($iLength = 8, $bSpecialCharacters = true)
+{
+    $sPassword = '';
+    $sChars = "abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789";
+
+    if($bSpecialCharacters === true)
+        $sChars .= "!?=/&+,.";
+
+    for($i = 0; $i < $iLength; $i++) {
+        $x = mt_rand(0, strlen($sChars) -1);
+        $sPassword .= $sChars[$x];
+    }
+
+    return $sPassword;
+}
+
+// Generate Random Salt for Password encryption
+function genRndSalt()
+{
+    return genRndPwd(8, true);
+}
+
+/**
+ * Encrypt user password.
+ * When BX_PWD_ALGO is 'custom', then bx_custom_pwd_encrypt function with custom algorythm must be defined in inc/header.inc.php file.
+ * @param $sPwd clear password
+ * @param $sSalt salt
+ * @return password hash
+ */ 
+function encryptUserPwd($sPwd, $sSalt)
+{
+	$sAlgo = defined('BX_PWD_ALGO') ? BX_PWD_ALGO : '';
+
+    switch ($sAlgo) {
+        
+        case 'custom':
+            return bx_custom_pwd_encrypt($sPwd, $sSalt); 
+
+    	case 'crypt':
+            return crypt($sPwd, BX_PWD_ALGO_SALT);
+
+        case 'sha1_crypt_salt':
+            return sha1(crypt($sPwd, BX_PWD_ALGO_SALT) . $sSalt);
+
+        case 'sha1_md5_salt':
+        default:
+            return sha1(md5($sPwd) . $sSalt);
+    }
+}
+
+/**
+ * Hash profile ID
+ */
+function encryptUserId($sId)
+{
+    if (!($oProfile = BxDolProfile::getInstance($sId)))
+        return false;
+
+    if (!($oAccount = $oProfile->getAccountObject()))
+        return false;
+
+    $aAccountInfo = $oAccount->getInfo();
+    return sha1(md5($sId) . md5($aAccountInfo['salt']) . BX_DOL_SECRET);
+}
+
+function bx_get ($sName, $sMethod = false)
+{
+    if (isset($_GET[$sName]) && ('get' == $sMethod || !$sMethod))
+        return $_GET[$sName];
+    elseif (isset($_POST[$sName]) && ('post' == $sMethod || !$sMethod))
+        return $_POST[$sName];
+    else
+        return false;
+}
+
+function bx_set ($sName, $sValue, $sMethod = false)
+{
+    if(!$sMethod)
+        $sMethod = 'get';
+
+    $bResult = true;
+    switch($sMethod) {
+        case 'get':
+            $_GET[$sName] = $sValue;
+            break;
+        
+        case 'post':
+            $_POST[$sName] = $sValue;
+            break;
+
+        default:
+            $bResult = false;
+    }
+
+    return $bResult;
+}
+
+function bx_get_with_prefix ($sPrefix, $sMethod = false)
+{
+    $aSources = array('get' => &$_GET, 'post' => &$_POST);
+
+    $aFiltered = array();
+    foreach($aSources as $sName => $aSource)
+        if($sMethod == $sName || !$sMethod)
+            $aFiltered = array_merge($aFiltered, array_filter($aSource, function($sKey) use ($sPrefix) {
+                return strpos($sKey, $sPrefix) === 0;
+            }, ARRAY_FILTER_USE_KEY));
+
+    $aUpdated = array();
+    array_walk($aFiltered, function($sValue, $sKey) use ($sPrefix, &$aUpdated) {
+        $aUpdated[trim(str_replace($sPrefix, '', $sKey), '_-')] = $sValue;
+    });
+
+    return $aUpdated;
+}
+
+function bx_get_base_url_inline($aParams = array())
+{
+    $aBaseLink = parse_url(BX_DOL_URL_ROOT);
+    $sPageLink = (!empty($aBaseLink['scheme']) ? $aBaseLink['scheme'] : 'http') . '://' . $aBaseLink['host'] . $_SERVER['REQUEST_URI'];
+
+    list($sPageLink, $aPageParams) = bx_get_base_url($sPageLink);
+
+    if(!empty($_SERVER['QUERY_STRING'])) {
+        $aPageParamsAdd = array();
+        parse_str($_SERVER['QUERY_STRING'], $aPageParamsAdd);
+        if(!empty($aPageParamsAdd) && is_array($aPageParamsAdd))
+            $aPageParams = array_merge($aPageParams, $aPageParamsAdd);
+    }
+
+    if(!empty($aParams) && is_array($aParams))
+        $aPageParams = array_merge($aPageParams, $aParams);
+
+    return array($sPageLink, $aPageParams);
+}
+
+function bx_get_base_url_popup($aParams = array())
+{
+    list($sPageLink, $aPageParams) = bx_get_base_url($_SERVER['HTTP_REFERER']);
+
+    if(!empty($aParams) && is_array($aParams))
+        $aPageParams = array_merge($aPageParams, $aParams);
+
+    return array($sPageLink, $aPageParams);
+}
+
+function bx_get_base_url($sPageLink)
+{
+    $sPageLink = BxDolPermalinks::getInstance()->unpermalink($sPageLink, false);
+
+    $sPageParams = '';
+    if(strpos($sPageLink, '?') !== false)
+        list($sPageLink, $sPageParams) = explode('?', $sPageLink);
+
+    $aPageParams = array();
+    if(!empty($sPageParams))
+        parse_str($sPageParams, $aPageParams);
+
+    return array($sPageLink, $aPageParams);
+}
+
+function bx_get_location_bounds_latlng($fLatitude, $fLongitude, $iRadiusInKm)
+{
+    $fEquatorLatInKm = 111.321;
+    $aRv = array();
+    $aRv['max_lat'] = $fLatitude + $iRadiusInKm / $fEquatorLatInKm;
+    $aRv['min_lat'] = $fLatitude - ($aRv['max_lat'] - $fLatitude);
+    $aRv['max_lng'] = $fLongitude + $iRadiusInKm / (cos($aRv['min_lat'] * M_PI / 180) * $fEquatorLatInKm);
+    $aRv['min_lng'] = $fLongitude - ($aRv['max_lng'] - $fLongitude);
+    return $aRv;
+    
+}
+
+function bx_encode_url_params ($a, $aExcludeKeys = array (), $aOnlyKeys = false)
+{
+    $s = '';
+    foreach ($a as $sKey => $sVal) {
+        if (in_array($sKey, $aExcludeKeys))
+            continue;
+        if (false !== $aOnlyKeys && !in_array($sKey, $aOnlyKeys))
+            continue;
+        if (is_array($sVal)) {
+            foreach ($sVal as $sSubVal) {
+                $s .= rawurlencode($sKey) . '[]=' . rawurlencode(is_array($sSubVal) ? 'array' : $sSubVal) . '&';
+            }
+        } else {
+            $s .= rawurlencode($sKey) . '=' . rawurlencode($sVal) . '&';
+        }
+    }
+    return $s;
+}
+
+function bx_append_url_params ($sUrl, $mixedParams)
+{
+    if (!$mixedParams)
+        return $sUrl;
+
+    $sParams = false == strpos($sUrl, '?') ? '?' : '&';
+
+    if (is_array($mixedParams)) {
+        foreach($mixedParams as $sKey => $sValue) {
+            if (!is_array($sValue)) {
+                $sParams .= $sKey . '=' . $sValue . '&';
+            }
+            else {
+                foreach($sValue as $sSubValue)
+                    $sParams .= $sKey . '[]=' . $sSubValue . '&';
+            }
+        }
+        $sParams = substr($sParams, 0, -1);
+    } else {
+        $sParams .= $mixedParams;
+    }
+    return $sUrl . $sParams;
+}
+
+function bx_rrmdir($directory)
+{
+    if (substr($directory,-1) == "/")
+        $directory = substr($directory,0,-1);
+
+    if (!file_exists($directory) || !is_dir($directory))
+        return false;
+    elseif (!is_readable($directory))
+        return false;
+
+    if (!($directoryHandle = opendir($directory)))
+        return false;
+
+    while ($contents = readdir($directoryHandle)) {
+        if ($contents != '.' && $contents != '..') {
+            $path = $directory . "/" . $contents;
+
+            if (is_dir($path))
+                bx_rrmdir($path);
+            else
+                unlink($path);
+        }
+    }
+
+    closedir($directoryHandle);
+
+    if (!rmdir($directory))
+        return false;
+
+    return true;
+}
+
+function bx_clear_folder ($sPath, $aExts = array ())
+{
+    if (substr($$sPath,-1) == "/")
+        $sPath = substr($sPath,0,-1);
+
+    if (!file_exists($sPath) || !is_dir($sPath))
+        return false;
+    elseif (!is_readable($sPath))
+        return false;
+
+    if (!($h = opendir($sPath)))
+        return false;
+
+    while ($sFile = readdir($h)) {
+        if ('.' == $sFile || '..' == $sFile)
+            continue;
+
+        $sFullPath = $sPath . '/' . $sFile;
+
+        if (is_dir($sFullPath))
+            continue;
+
+        if (!$aExts || (($sExt = pathinfo($sFullPath, PATHINFO_EXTENSION)) && in_array($sExt, $aExts)))
+            @unlink($sFullPath);
+    }
+
+    closedir($h);
+
+    return true;
+}
+
+function bx_ltrim_str ($sString, $sPrefix, $sReplace = '')
+{
+    if ($sReplace && substr($sString, 0, strlen($sReplace)) == $sReplace)
+        return $sString;
+    if (substr($sString, 0, strlen($sPrefix)) == $sPrefix)
+        return $sReplace . substr($sString, strlen($sPrefix));
+    return $sString;
+}
+
+function bx_rtrim_str ($sString, $sPrefix, $sReplace = '')
+{
+    if ($sReplace && substr($sString, -strlen($sReplace)) == $sReplace)
+        return $sString;
+    if (substr($sString, -strlen($sPrefix)) == $sPrefix)
+        return substr($sString, 0, -strlen($sPrefix)) . $sReplace;
+    return $sString;
+}
+
+/**
+ * Strip all lines with no information for example: <p></p>, <br /><br />
+ */ 
+function bx_trim_nl_duplicates($s)
+{
+    $sStrip = implode('', array_keys(get_html_translation_table(HTML_ENTITIES)));
+    return implode('', array_filter(mb_split("[\n\r]", $s), function($s) use ($sStrip) {
+        return trim(strip_tags($s, '<button><canvas><embed><hr><iframe><img><input><object><select><svg><video>'), $sStrip) !== '';
+    }));
+}
+
+/**
+ * Convert array to attributes string
+ *
+ * Example:
+ * @code
+ * $a = array('name' => 'test', 'value' => 5);
+ * $s = bx_convert_array2attrs($a);
+ * echo $s; // outputs: name="test" value="5"
+ * @endcode
+ *
+ * @param $a - array of attributes
+ * @param $sClasses - classes to merge with 'class' attribute
+ * @param $sStyles - styles to merge with 'style' attribute
+ * @return string
+ */
+function bx_convert_array2attrs ($a, $sClasses = false, $sStyles = false)
+{
+    $sRet = '';
+
+    if (!$a || !is_array($a))
+        $a = array();
+
+    if ($sClasses) {
+        $sClasses = trim($sClasses);
+        $a['class'] = $sClasses . (!empty($a['class']) ? ' ' . $a['class'] : '');
+    }
+
+    if ($sStyles) {
+        $sStyles = trim($sStyles);
+        if (';' != $sStyles[strlen($sStyles)-1])
+            $sStyles .= ';';
+        $a['style'] = $sStyles . (!empty($a['style']) ? ' ' . $a['style'] : '');
+    }
+
+    foreach ($a as $sKey => $sValue) {
+        if(is_null($sValue)) // pass NULL values
+            continue;
+
+        $sValueC = bx_html_attribute($sValue, BX_ESCAPE_STR_QUOTE);
+
+        $sRet .= " $sKey=\"$sValueC\"";
+    }
+
+    return $sRet;
+}
+
+function bx_unicode_urldecode($s)
+{
+    preg_match_all('/%u([[:alnum:]]{4})/', $s, $a);
+
+    foreach ($a[1] as $uniord) {
+        $dec = hexdec($uniord);
+        $utf = '';
+
+        if ($dec < 128) {
+            $utf = chr($dec);
+        } else if ($dec < 2048) {
+            $utf = chr(192 + (($dec - ($dec % 64)) / 64));
+            $utf .= chr(128 + ($dec % 64));
+        } else {
+            $utf = chr(224 + (($dec - ($dec % 4096)) / 4096));
+            $utf .= chr(128 + ((($dec % 4096) - ($dec % 64)) / 64));
+            $utf .= chr(128 + ($dec % 64));
+        }
+
+        $s = str_replace('%u'.$uniord, $utf, $s);
+    }
+
+    return urldecode($s);
+}
+
+/**
+ * Raise an audit event
+ * @param int $iContentId - content id
+ * @param string $sContentModule - module name
+ * @param string $sAction - system action key
+ * @param array $aParams - array of parameters 
+ */
+function bx_audit($iContentId, $sContentModule, $sAction, $aParams)
+{
+    if (!getParam('sys_audit_enable') || getParam('sys_audit_acl_levels') == '')
+        return;
+    
+    $iProfileId = bx_get_logged_profile_id();
+    if (!BxDolAcl::getInstance()->isMemberLevelInSet(explode(',', getParam('sys_audit_acl_levels')), $iProfileId))
+        return;
+    
+    $sContentTitle = $sContentInfoObject = $sContextProfileTitle = $sData = $sActionParams = '';
+    $iContextProfileId = 0;
+    
+    if (isset($aParams['profile_id']))
+        $iProfileId = (int)$aParams['profile_id'];
+    
+    if (isset($aParams['content_title']))
+        $sContentTitle = $aParams['content_title'];
+    
+    if (isset($aParams['context_profile_id']))
+        $iContextProfileId = (int)$aParams['context_profile_id'];
+    
+    if (isset($aParams['context_profile_title']))
+        $sContextProfileTitle = $aParams['context_profile_title'];
+    
+    if (isset($aParams['action_params']) && is_array($aParams['action_params']) && count($aParams['action_params']))
+        $sActionParams = serialize($aParams['action_params']);
+    
+    if (isset($aParams['content_info_object']))
+        $sContentInfoObject = $aParams['content_info_object'];
+     
+    if (isset($aParams['data']) && is_array($aParams['data']) && count($aParams['data']))
+        $sData = serialize($aParams['data']);
+    
+    $sProfileTitle = BxDolProfile::getInstance($iProfileId)->getDisplayName();
+    
+    $oDb = BxDolDb::getInstance();
+    $sSql = $oDb->prepare("INSERT INTO `sys_audit`(`added`, `profile_id`, `profile_title`, `content_id`, `content_title`, `content_module`, `context_profile_id`, `context_profile_title`, `action_lang_key`, `action_lang_key_params`, `content_info_object`, `extras`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
+     ,time(), $iProfileId, $sProfileTitle, $iContentId, $sContentTitle, $sContentModule, $iContextProfileId, $sContextProfileTitle, $sAction, $sActionParams ,$sContentInfoObject, $sData);
+    return !$oDb->query($sSql);
+}
+
+/**
+ * Raise an alert
+ * @param string $sUnit - system type
+ * @param string $sAction - system action
+ * @param int $iObjectId - object id
+ * @param int $iSender - sender (action's author) profile id, if it is false - then currectly logged in profile id is used
+ * @param array $aExtras - extra parameters 
+ */
+function bx_alert($sUnit, $sAction, $iObjectId, $iSender = false, $aExtras = array())
+{
+    $o = new BxDolAlerts($sUnit, $sAction, $iObjectId, $iSender, $aExtras);
+    $o->alert();
+}
+
+/**
+ * Perform serice call
+ * @param $mixed module name or module id
+ * @param $sMethod service method name in format 'method_name', corresponding class metod is serviceMethodName
+ * @param $aParams params to pass to service method
+ * @param $sClass class to search for service method, by default it is main module class
+ * @return service call result
+ */
+function bx_srv($mixed, $sMethod, $aParams = array(), $sClass = 'Module', $bIgnoreCache = false)
+{
+    return BxDolService::call($mixed, $sMethod, $aParams, $sClass, $bIgnoreCache);
+}
+
+function bx_replace_markers($mixed, $aMarkers)
+{
+    if (empty($aMarkers))
+        return $mixed;
+
+    if (is_array($mixed)) {
+        foreach ($mixed as $sKey => $sValue)
+            $mixed[$sKey] = bx_replace_markers ($sValue, $aMarkers);
+    } else {
+        foreach ($aMarkers as $sKey => $sValue)
+            $mixed = str_replace('{' . $sKey . '}', $sValue, $mixed);
+    }
+
+    return $mixed;
+}
+
+function bx_site_hash($sSalt = '', $isSkipVersion = false)
+{
+    return md5($sSalt . ($isSkipVersion ? '' : bx_get_ver() . getParam('sys_revision')) . BX_DOL_SECRET . BX_DOL_URL_ROOT);
+}
+
+/**
+ * Transform string to method name string, for example it changes 'some_method' string to 'SomeMethod' string
+ * @param $s string where words are separated with underscore
+ * @param $aWordsDelimiter word delimeters
+ * @return string where every word begins with capital letter
+ */
+function bx_gen_method_name ($s, $aWordsDelimiter = array('_'))
+{
+    return str_replace(' ', '', ucwords(str_replace($aWordsDelimiter, ' ', $s)));
+}
+
+/**
+ * Trigger user error
+ * @param $sMsg message to display
+ * @param $iNumLevelsBack add additional debug backtracing N levels back
+ */
+function bx_trigger_error ($sMsg, $iNumLevelsBack = 0)
+{
+    $a = debug_backtrace();
+    $sMsgAdd = "<br />\n related code in <b>{$a[$iNumLevelsBack]['file']}</b> on line <b>{$a[$iNumLevelsBack]['line']}</b> <br />\n";
+    trigger_error ($sMsg . $sMsgAdd, E_USER_ERROR);
+}
+
+/**
+ * Get system DB version, for files version @see BX_DOL_VERSION, these versions must match
+ */
+function bx_get_ver ($bInvalidateCache = false)
+{
+    $oDb = BxDolDb::getInstance();
+
+    if ($bInvalidateCache)
+        $oDb->cleanMemory('sys_version');
+    
+    $sQuery = $oDb->prepare("SELECT `version` FROM `sys_modules` WHERE `name` = 'system'");
+    return $oDb->fromMemory('sys_version', 'getOne', $sQuery);
+}
+
+/**
+ * Check if site maintetance mode is enabled.
+ * Maintetance mode is enabled when '.bx_maintenance' file exists in the script root folder, 
+ * please note that this is hidden file and some file managers don't show it.
+ * @param $bShowHttpError show 503 HTTP error if site is in mainenance mode
+ * @return true if site is in maintenance mode, or false otherwise
+ */
+function bx_check_maintenance_mode ($bShowHttpError = false)
+{
+    $bMaintetance = file_exists(BX_DIRECTORY_PATH_ROOT . BX_MAINTENANCE_FILE) && !defined('BX_DOL_UPGRADING');
+
+    if ($bMaintetance && $bShowHttpError)
+        bx_show_service_unavailable_error_and_exit ('Site is temporarily unavailable due to scheduled maintenance, please try again in a minute.', 600);
+
+    return $bMaintetance;
+}
+
+/**
+ * Check for minimal requirements.
+ * if BX_DISABLE_REQUIREMENTS_CHECK is defined then this requirements checking is skipped.
+ * @param $bShowHttpError show 503 HTTP error if site doesn't meet minimal requirements
+ * @return false if requirements are met, or array of errors of requirements aren't met
+ */
+function bx_check_minimal_requirements ($bShowHttpError = false)
+{
+    if (defined('BX_DISABLE_REQUIREMENTS_CHECK'))
+        return false;
+
+    $aErrors = array();
+
+    $aErrors[] = (ini_get('register_globals') == 0) ? '' : '<b>register_globals</b> is on (you need to disable it, or your site will be unsafe)';
+    $aErrors[] = (ini_get('safe_mode') == 0) ? '' : '<b>safe_mode</b> is on (you need to disable it)';
+    $aErrors[] = (version_compare(PHP_VERSION, '5.3.0', '<')) ? 'PHP version is too old (please update to <b>PHP 5.3.0</b> at least)' : '';
+    $aErrors[] = (!extension_loaded( 'mbstring')) ? '<b>mbstring</b> extension not installed (the script cannot work without it)' : '';
+    $aErrors[] = (ini_get('allow_url_include') == 0) ? '' : '<b>allow_url_include</b> is on (you need to disable it, or your site will be unsafe)';
+
+    $aErrors = array_diff($aErrors, array('')); // delete empty
+
+    $bFailedMinimalRequirements = !empty($aErrors);
+
+    if ($bFailedMinimalRequirements && $bShowHttpError) {
+        $sErrors = implode(" <br /> ", $aErrors);
+        bx_show_service_unavailable_error_and_exit($sErrors);
+    }
+
+    return $bFailedMinimalRequirements ? $aErrors : false;
+}
+
+/**
+ * Check if redirect to the correct hostname is required, for example redirect from site.com to www.site.com
+ * @param $bProcessRedirect process redirect and exit if needed
+ */
+function bx_check_redirect_to_correct_hostname ($bProcessRedirect = false)
+{
+    $aUrl = parse_url(BX_DOL_URL_ROOT);
+    $iPortDefault = 'https' == $aUrl['scheme'] ? '443' : '80';
+
+    $bRedirectRequired = isset($_SERVER['HTTP_HOST']) && 0 !== strcasecmp($_SERVER['HTTP_HOST'], $aUrl['host']) && 0 !== strcasecmp($_SERVER['HTTP_HOST'], $aUrl['host'] . ':' . (!empty($aUrl['port']) ? $aUrl['port'] : $iPortDefault));
+
+    if ($bRedirectRequired && $bProcessRedirect) {
+        $sPort = empty($aUrl['port']) || 80 == $aUrl['port'] || 443 == $aUrl['port'] ? '' : ':' . $aUrl['port'];
+        header("Location:{$aUrl['scheme']}://{$aUrl['host']}{$sPort}{$_SERVER['REQUEST_URI']}", true, 301);
+        exit;
+    }
+    
+    return $bRedirectRequired;
+}
+
+/**
+ * Check if redirect to remove install folder.
+ * If BX_SKIP_INSTALL_CHECK is defined then this redirect checking is skipped.
+ * @param $bProcessRedirect process redirect and exit if needed
+ */
+function bx_check_redirect_to_remove_install_folder ($bProcessRedirect = false)
+{
+    $bRemoveInstallFolder = !defined ('BX_SKIP_INSTALL_CHECK') && file_exists(BX_DIRECTORY_PATH_ROOT . 'install');
+
+    if ($bRemoveInstallFolder && $bProcessRedirect) {
+        header('Location:' . BX_DOL_URL_ROOT . 'install/index.php?action=remove_install');
+        exit;
+    }
+
+    return $bRemoveInstallFolder;
+}
+
+/**
+ * Show HTTP 503 service unavailable error and exit
+ */
+function bx_show_service_unavailable_error_and_exit ($sMsg = false, $iRetryAfter = 86400)
+{
+    header('HTTP/1.0 503 Service Unavailable', true, 503);
+    header('Retry-After: 600');
+    echo $sMsg ? $sMsg : 'Service temporarily unavailable';
+    exit;
+}
+
+/**
+ * The function is sumilar to php readfile, but it send all required headers and can send file by chunks and suports file seek
+ * @param $sPath path to file to output to the browser
+ * @param $sFilename filename without path, ig file is saved from browser, then this name is used, not used(empty) by default
+ * @param $sMimeType file mime type, by default 'application/octet-stream'
+ * @param $iCacheAge file cache age, by default 0
+ * @param $sCachePrivacy cache privacy 'public' (default value) or 'private'
+ * @return true on success or false on error
+ */
+function bx_smart_readfile($sPath, $sFilename = '', $sMimeType = 'application/octet-stream', $iCacheAge = 0, $sCachePrivacy = 'public')
+{
+    if (!file_exists($sPath))
+        return  false;
+
+    $fp = @fopen($sPath, 'rb');
+
+    $size   = filesize($sPath);
+    $length = $size;
+    $start  = 0;
+    $end    = $size - 1;
+
+    header('Content-type: ' . $sMimeType);
+    header('Cache-Control: ' . $sCachePrivacy . ', must-revalidate, max-age=' . $iCacheAge);
+    header("Accept-Ranges: 0-$length");
+    if ($sFilename)
+        header('Content-Disposition: inline; filename="' . $sFilename . '"');
+
+    if (isset($_SERVER['HTTP_RANGE'])) {
+
+        $c_start = $start;
+        $c_end   = $end;
+
+        list(, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2);
+        if (strpos($range, ',') !== false) {
+            header('HTTP/1.1 416 Requested Range Not Satisfiable');
+            header("Content-Range: bytes $start-$end/$size");
+            return false;
+        }
+        if ($range == '-') {
+            $c_start = $size - substr($range, 1);
+        }else{
+            $range  = explode('-', $range);
+            $c_start = $range[0];
+            $c_end   = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $size;
+        }
+        $c_end = ($c_end > $end) ? $end : $c_end;
+        if ($c_start > $c_end || $c_start > $size - 1 || $c_end >= $size) {
+            header('HTTP/1.1 416 Requested Range Not Satisfiable');
+            header("Content-Range: bytes $start-$end/$size");
+            return false;
+        }
+        $start  = $c_start;
+        $end    = $c_end;
+        $length = $end - $start + 1;
+        fseek($fp, $start);
+        header('HTTP/1.1 206 Partial Content');
+    }
+    header("Content-Range: bytes $start-$end/$size");
+    header("Content-Length: ".$length);
+
+
+    $buffer = 1024 * 8;
+    while(!feof($fp) && ($p = ftell($fp)) <= $end) {
+
+        if ($p + $buffer > $end) {
+            $buffer = $end - $p + 1;
+        }
+        set_time_limit(0);
+        echo fread($fp, $buffer);
+        flush();
+    }
+
+    fclose($fp);
+
+    return true;
+}
+
+/**
+ * Wrap in A tag links in TEXT string
+ * @param $text - text string without tags
+ * @param $sAttrs - attributes string to add to the added A tag
+ * @param $bHtmlSpecialChars - apply htmlspecialchars before processing
+ * @return string where all links are wrapped in A tag
+ */
+function bx_linkify($text, $sAttrs = '', $bHtmlSpecialChars = false)
+{
+    if ($bHtmlSpecialChars)
+        $text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
+
+    $re = "@\b((https?://)|(www\.))(([0-9a-zA-Z_!~*'().&=+$%-]+:)?[0-9a-zA-Z_!~*'().&=+$%-]+\@)?(([0-9]{1,3}\.){3}[0-9]{1,3}|([0-9a-zA-Z_!~*'()-]+\.)*([0-9a-zA-Z][0-9a-zA-Z-]{0,61})?[0-9a-zA-Z]\.[a-zA-Z]{2,16})(:[0-9]{1,4})?((/[0-9a-zA-Z_!~*'().;?:\@&=+$,%#-]+)*/?)@";
+    preg_match_all($re, $text, $matches, PREG_OFFSET_CAPTURE);
+
+    $matches = $matches[0];
+
+    if ($i = count($matches))
+        $bAddNofollow = getParam('sys_add_nofollow') == 'on';
+
+    while ($i--)
+    {
+        $sAttrsLocal = $sAttrs;
+        $url = $matches[$i][0];
+        if (!preg_match('@^https?://@', $url))
+            $url = 'http://'.$url;
+
+        if (strncmp(BX_DOL_URL_ROOT, $url, strlen(BX_DOL_URL_ROOT)) !== 0) {
+            $sAttrsLocal .= ' target="_blank" ';
+            if ($bAddNofollow)
+                $sAttrsLocal .= ' rel="nofollow" ';
+        }
+
+        $text = substr_replace($text, '<a ' . $sAttrsLocal . ' href="'.$url.'">'.$matches[$i][0].'</a>', $matches[$i][1], strlen($matches[$i][0]));
+    }
+	
+	// email pattern
+	$mail_pattern = "/([A-z0-9\._-]+\@[A-z0-9_-]+\.)([A-z0-9\_\-\.]{1,}[A-z])/";
+	$text = preg_replace($mail_pattern, '<a href="mailto:$1$2">$1$2</a>', $text);
+	
+    return $text;
+}
+
+/**
+ * Wrap in A tag links in HTML string, which aren't wrapped in A tag yet
+ * @param $sHtmlOrig - HTML string
+ * @param $sAttrs - attributes string to add to the added A tag
+ * @return modified HTML string, in case of errror original string is returned
+ */
+function bx_linkify_html($sHtmlOrig, $sAttrs = '') 
+{
+    if (!trim($sHtmlOrig))
+        return $sHtmlOrig;
+
+    $sId = 'bx-linkify-' . md5(microtime());
+    $dom = new DOMDocument();
+    @$dom->loadHTML('<?xml encoding="UTF-8"><div id="' . $sId . '">' . $sHtmlOrig . '</div>');
+    $xpath = new DOMXpath($dom);
+
+    foreach ($xpath->query('//text()') as $text) {
+        if (!empty($text->parentNode) && !empty($text->parentNode->tagName) && 'a' == $text->parentNode->tagName)
+            continue;
+        $frag = $dom->createDocumentFragment();
+        @$frag->appendXML(bx_linkify($text->nodeValue, $sAttrs, true));
+        $text->parentNode->replaceChild($frag, $text);
+    }
+
+    if (version_compare(PHP_VERSION, '5.3.6') >= 0)
+        $s = $dom->saveHTML($dom->getElementById($sId));
+    else
+        $s = $dom->saveXML($dom->getElementById($sId), LIBXML_NOEMPTYTAG);
+
+    if (false === $s) // in case of error return original string
+        return $sHtmlOrig;
+
+    if (false !== ($iPos = mb_strpos($s, '<html><body>')) && $iPos < mb_strpos($s, $sId))
+        $s = mb_substr($s, $iPos + 12, -15); // strip <html><body> tags and everything before them
+
+    return mb_substr($s, 54, -6); // strip added tags
+}
+
+/**
+ * Returns current site protocol http:// or https://
+ */
+function bx_proto()
+{
+    return 0 === strncmp('https', BX_DOL_URL_ROOT, 5) ? 'https' : 'http';
+}
+
+/**
+ * Checks protocol in the link
+ */
+function bx_has_proto($sLink)
+{
+    return preg_match('@^https?://@', $sLink);    
+}
+
+function bx_is_empty_array ($a)
+{
+    if (!is_array($a))
+        return true;
+    if (empty($a))
+        return true;
+    foreach ($a as $k => $v)
+        if ($v)
+            return false;
+    return true;
+}
+
+function bx_is_full_array ($a)
+{
+    if (!is_array($a))
+        return false;
+    if (empty($a))
+        return false;
+    foreach ($a as $k => $v)
+        if (empty($v))
+            return false;
+    return true;
+}
+
+function bx_is_url_in_content ($sContent)
+{
+    $a = array(
+        'http://',
+        'https://',
+        'https/',
+        'http/',
+        'www.',
+        '.com'
+    );
+    foreach ($a as $s)
+        if (false !== strpos($sContent, $s))
+            return true;
+
+    return false;
+}
+
+function bx_is_dynamic_request ()
+{
+    return bx_get('dynamic') || (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && 'XMLHttpRequest' == $_SERVER['HTTP_X_REQUESTED_WITH']);
+}
+
+function bx_idn_to_utf8($sUrl, $bReturnDomain = false)
+{
+    return bx_idn_to('idn_to_utf8', $sUrl, $bReturnDomain);
+}
+
+function bx_idn_to_ascii($sUrl, $bReturnDomain = false)
+{
+    return bx_idn_to('idn_to_ascii', $sUrl, $bReturnDomain);
+}
+
+function bx_idn_to($sMethod, $sUrl, $bReturnDomain = false)
+{
+    $aUrl = parse_url($sUrl);
+    if($aUrl === false)
+        return $sUrl;
+
+    $sResult = $aUrl['host'];
+    if(function_exists($sMethod))
+        $sResult = $sMethod($sResult, IDNA_DEFAULT, (defined('INTL_IDNA_VARIANT_UTS46') ? INTL_IDNA_VARIANT_UTS46 : INTL_IDNA_VARIANT_2003));
+
+    if($bReturnDomain)
+        return $sResult;
+
+    $sResult = (!empty($aUrl['scheme']) ? $aUrl['scheme'] . '://' : '' ) . $sResult;
+    $sResult = $sResult . (!empty($aUrl['path']) ? $aUrl['path'] : '');
+    $sResult = $sResult . (!empty($aUrl['query']) ? '?' . $aUrl['query'] : '');
+
+    return $sResult;
+}
+
+function bx_is_mobile()
+{
+    if(!isset($_SERVER['HTTP_USER_AGENT']))
+        return false;
+
+    $s = $_SERVER['HTTP_USER_AGENT'];
+    return preg_match('/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i', $s) || preg_match('/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i', substr($s, 0, 4));
+}
+
+function bx_get_device_pixel_ratio()
+{
+    $iRatio = 1;
+    if(!isset($_COOKIE['devicePixelRatio']))
+        return $iRatio;
+
+    $iDevicePixelRatio = intval($_COOKIE['devicePixelRatio']);
+    if($iDevicePixelRatio >= 2)
+        $iRatio = 2;
+
+    return $iRatio;
+}
+
+/** @} */

+ 12 - 0
upgrade/files/11.0.0-11.0.1/files/inc/version.inc.php

@@ -0,0 +1,12 @@
+<?php defined('BX_DOL') or defined('BX_DOL_INSTALL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaCore UNA Core
+ * @{
+ */
+
+define('BX_DOL_VERSION', '11.0.1'); ///< system files version, for DB version @see bx_get_ver, DB and files version must match
+
+/** @} */

+ 533 - 0
upgrade/files/11.0.0-11.0.1/files/modules/base/general/classes/BxBaseModGeneralFormEntry.php

@@ -0,0 +1,533 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    BaseGeneral Base classes for modules
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+/**
+ * Create/edit entry form
+ */
+class BxBaseModGeneralFormEntry extends BxTemplFormView
+{
+    protected static $_isCssJsGeneralModuleAdded = false;
+        
+    protected $MODULE;
+
+    protected $_oModule;
+
+    protected $_aMetatagsFieldsWithKeywords = array();
+    protected $_oMetatagsObject = null;
+    protected $_oMetatagsContentId = 0;
+
+    protected $_sGhostTemplate = 'form_ghost_template.html';
+    
+    protected $_aTrackFieldsChanges;
+
+    public function __construct($aInfo, $oTemplate = false)
+    {
+        parent::__construct($aInfo, $oTemplate);
+
+        $this->_oModule = BxDolModule::getInstance($this->MODULE);
+
+        $this->_aTrackFieldsChanges = array();
+
+        $CNF = &$this->_oModule->_oConfig->CNF;
+        
+        if (isset($CNF['FIELD_ADDED']) && isset($this->aInputs[$CNF['FIELD_ADDED']])) {
+            $this->aInputs[$CNF['FIELD_ADDED']]['date_filter'] = BX_DATA_INT;
+            $this->aInputs[$CNF['FIELD_ADDED']]['date_format'] = BX_FORMAT_DATE;
+        }
+
+        if (isset($CNF['FIELD_CHANGED']) && isset($this->aInputs[$CNF['FIELD_CHANGED']])) {
+            $this->aInputs[$CNF['FIELD_CHANGED']]['date_filter'] = BX_DATA_INT;
+            $this->aInputs[$CNF['FIELD_CHANGED']]['date_format'] = BX_FORMAT_DATE;
+        }
+
+        if (isset($CNF['FIELD_TEXT']) && isset($this->aInputs[$CNF['FIELD_TEXT']]) && isset($CNF['FIELD_TEXT_ID'])) {
+            $this->aInputs[$CNF['FIELD_TEXT']]['attrs'] = array_merge (
+                array ('id' => $CNF['FIELD_TEXT_ID']),
+                is_array($this->aInputs[$CNF['FIELD_TEXT']]['attrs']) ? $this->aInputs[$CNF['FIELD_TEXT']]['attrs'] : array ()
+            );
+        }
+
+        if (isset($CNF['FIELD_LABELS']) && isset($this->aInputs[$CNF['FIELD_LABELS']]) && !empty($CNF['OBJECT_METATAGS']) && ($oMetatags = BxDolMetatags::getObjectInstance($CNF['OBJECT_METATAGS'])) && $oMetatags->keywordsIsEnabled()) {
+            $this->aInputs[$CNF['FIELD_LABELS']]['meta_object'] = $CNF['OBJECT_METATAGS'];
+        }
+
+        if (isset($CNF['FIELD_LOCATION_PREFIX']) && isset($this->aInputs[$CNF['FIELD_LOCATION_PREFIX']])) {
+            $this->aInputs[$CNF['FIELD_LOCATION_PREFIX']]['manual_input'] = true;
+        }
+
+        if (isset($CNF['FIELD_PHOTO']) && isset($this->aInputs[$CNF['FIELD_PHOTO']])) {
+            $this->aInputs[$CNF['FIELD_PHOTO']]['storage_object'] = $CNF['OBJECT_STORAGE'];
+            $this->aInputs[$CNF['FIELD_PHOTO']]['uploaders'] = !empty($this->aInputs[$CNF['FIELD_PHOTO']]['value']) ? unserialize($this->aInputs[$CNF['FIELD_PHOTO']]['value']) : $CNF['OBJECT_UPLOADERS'];
+            $this->aInputs[$CNF['FIELD_PHOTO']]['images_transcoder'] = $CNF['OBJECT_IMAGES_TRANSCODER_PREVIEW'];
+            $this->aInputs[$CNF['FIELD_PHOTO']]['storage_private'] = 0;
+            $this->aInputs[$CNF['FIELD_PHOTO']]['multiple'] = true;
+            $this->aInputs[$CNF['FIELD_PHOTO']]['content_id'] = 0;
+            $this->aInputs[$CNF['FIELD_PHOTO']]['ghost_template'] = '';
+        }
+
+        $aPrivacyFields = $this->_getPrivacyFields();
+        foreach($aPrivacyFields as $sField => $sObject)
+            $this->_preparePrivacyField($sField, $sObject);
+    }
+
+    function getCode($bDynamicMode = false)
+    {
+        if(!$bDynamicMode && bx_is_dynamic_request())
+            $bDynamicMode = true;
+
+        $aPrivacyFields = $this->_getPrivacyFields();
+        foreach($aPrivacyFields as $sField => $sObject)
+            $this->_addCssJsPrivacyField($sField, $sObject, $bDynamicMode);
+
+        return parent::getCode($bDynamicMode);
+    }
+
+    /**
+     * Add field(s) which will be tracked during content update. 
+     */
+    public function addTrackFields($mixedFields, $mixedContent)
+    {
+        if(empty($mixedContent) || empty($mixedFields))
+            return;
+
+        if(!is_array($mixedContent)) {
+            $mixedContent = $this->_oModule->_oDb->getContentInfoById((int)$mixedContent);
+            if(empty($mixedContent) || !is_array($mixedContent))
+                return;
+        }
+
+        if(!is_array($mixedFields))
+            $mixedFields = array($mixedFields);
+
+        foreach($mixedFields as $sField) {
+            if(!isset($mixedContent[$sField]))
+                continue;
+
+            $this->_aTrackFieldsChanges[$sField] = array(
+                'old' => $mixedContent[$sField],
+            );
+        }
+    }
+
+    /**
+     * Checks if the field was changed.
+     */
+    public function isTrackFieldChanged($sField, $bReturnValues = false)
+    {
+        if(!isset($this->_aTrackFieldsChanges[$sField]) || $this->_aTrackFieldsChanges[$sField] === false)
+            return false;
+
+        return $bReturnValues ? $this->_aTrackFieldsChanges[$sField] : true;
+    }
+
+    function initChecker ($aValues = array (), $aSpecificValues = array())
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+        
+        if (!empty($CNF['FIELD_LOCATION_PREFIX']) && isset($this->aInputs[$CNF['FIELD_LOCATION_PREFIX']]) && isset($aValues[$CNF['FIELD_ID']]) && !empty($CNF['OBJECT_METATAGS']) && ($oMetatags = BxDolMetatags::getObjectInstance($CNF['OBJECT_METATAGS'])) && $oMetatags->locationsIsEnabled())
+            $this->aInputs[$CNF['FIELD_LOCATION_PREFIX']]['value'] = $oMetatags->locationsString($aValues[$CNF['FIELD_ID']], false);
+
+        if (isset($CNF['FIELD_PHOTO']) && isset($this->aInputs[$CNF['FIELD_PHOTO']])) {
+
+            $aContentInfo = false;
+            if ($aValues && !empty($aValues['id'])) {
+                $aContentInfo = $this->_oModule->_oDb->getContentInfoById ($aValues['id']);
+                $this->aInputs[$CNF['FIELD_PHOTO']]['content_id'] = $aValues['id'];
+            }
+
+            $this->aInputs[$CNF['FIELD_PHOTO']]['ghost_template'] = $this->_oModule->_oTemplate->parseHtmlByName($this->_sGhostTemplate, $this->_getPhotoGhostTmplVars($aContentInfo));
+        }
+
+        if (isset($CNF['FIELD_LABELS']) && isset($this->aInputs[$CNF['FIELD_LABELS']]) && !empty($CNF['OBJECT_METATAGS']) && ($oMetatags = BxDolMetatags::getObjectInstance($CNF['OBJECT_METATAGS'])) && $oMetatags->keywordsIsEnabled() && !empty($aValues['id'])) {
+            $this->aInputs[$CNF['FIELD_LABELS']]['content_id'] = $aValues['id'];
+            if(($aLabels = $oMetatags->keywordsGet($aValues['id'])))
+                $this->aInputs[$CNF['FIELD_LABELS']]['value'] = array_intersect($aLabels, BxDolLabel::getInstance()->getLabels(array('type' => 'values')));
+        }
+
+        if (isset($CNF['FIELD_ANONYMOUS']) && isset($this->aInputs[$CNF['FIELD_ANONYMOUS']]) && isset($CNF['FIELD_AUTHOR']) && isset($aValues[$CNF['FIELD_AUTHOR']])) {
+            $this->aInputs[$CNF['FIELD_ANONYMOUS']]['checked'] = $aValues[$CNF['FIELD_AUTHOR']] < 0;
+        }
+
+        $aPrivacyFields = $this->_getPrivacyFields();
+        foreach($aPrivacyFields as $sField => $sObject)
+            $this->_preloadPrivacyField($sField, $sObject, $aValues);
+
+        parent::initChecker ($aValues, $aSpecificValues);
+    }
+    
+    public function insert ($aValsToAdd = array(), $isIgnore = false)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if (isset($CNF['FIELD_AUTHOR']) && empty($aValsToAdd[$CNF['FIELD_AUTHOR']]))
+            $aValsToAdd[$CNF['FIELD_AUTHOR']] = (isset($CNF['FIELD_ANONYMOUS']) && isset($this->aInputs[$CNF['FIELD_ANONYMOUS']]) && $this->getCleanValue($CNF['FIELD_ANONYMOUS']) ? -1 : 1) * bx_get_logged_profile_id ();
+
+        if (isset($CNF['FIELD_ADDED']) && empty($aValsToAdd[$CNF['FIELD_ADDED']]) && empty($this->getCleanValue($CNF['FIELD_ADDED'])))
+            $aValsToAdd[$CNF['FIELD_ADDED']] = time();
+
+        if (isset($CNF['FIELD_CHANGED']) && empty($aValsToAdd[$CNF['FIELD_CHANGED']]) && empty($this->getCleanValue($CNF['FIELD_CHANGED'])))
+            $aValsToAdd[$CNF['FIELD_CHANGED']] = time();
+
+        if (CHECK_ACTION_RESULT_ALLOWED === $this->_oModule->checkAllowedSetThumb() && isset($CNF['FIELD_THUMB'])) {
+            $aThumb = isset($_POST[$CNF['FIELD_THUMB']]) ? bx_process_input ($_POST[$CNF['FIELD_THUMB']], BX_DATA_INT) : false;
+            $aValsToAdd[$CNF['FIELD_THUMB']] = 0;
+            if (!empty($aThumb) && is_array($aThumb) && ($iFileThumb = array_pop($aThumb)))
+                $aValsToAdd[$CNF['FIELD_THUMB']] = $iFileThumb;
+        }
+
+        if(!empty($CNF['OBJECT_METATAGS']))
+            $this->_processMetas($aValsToAdd);
+
+        if(isset($CNF['FIELD_STATUS']) && !empty($CNF['FIELDS_DELAYED_PROCESSING']) && !empty($CNF['OBJECT_VIDEOS_TRANSCODERS']['mp4'])) {
+            $oTranscoder = BxDolTranscoder::getObjectInstance($CNF['OBJECT_VIDEOS_TRANSCODERS']['mp4']);
+
+            $aFields = $CNF['FIELDS_DELAYED_PROCESSING'];
+            if(is_string($aFields))
+                $aFields = explode(',', $aFields);
+
+            foreach($aFields as $sField) {
+                $mixedFieldValues = $this->getCleanValue($sField);
+                if(empty($mixedFieldValues)) 
+                    continue;
+
+                $oStorage = BxDolStorage::getObjectInstance($this->aInputs[$sField]['storage_object']);
+                if(!$oStorage)
+                    continue;
+
+                if(!is_array($mixedFieldValues))
+                    $mixedFieldValues = array($mixedFieldValues);
+
+                foreach($mixedFieldValues as $mixedFieldValue) {
+                    $aInfo = $oStorage->getFile((int)$mixedFieldValue);
+                    if(empty($aInfo) || !is_array($aInfo))
+                        continue;
+
+                    if(strncmp($aInfo['mime_type'], 'video/', 6) != 0) 
+                        continue;
+
+                    if($oTranscoder->isFileReady((int)$mixedFieldValue))
+                        continue;
+
+                    $aValsToAdd[$CNF['FIELD_STATUS']] = 'awaiting';
+                    break 2;
+                }
+            }
+        }
+
+        return parent::insert ($aValsToAdd, $isIgnore);
+    }
+
+    function update ($iContentId, $aValsToAdd = array(), &$aTrackTextFieldsChanges = null)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $aContentInfo = $this->_oModule->_oDb->getContentInfoById($iContentId);
+
+        if(isset($CNF['FIELD_AUTHOR']) && isset($CNF['FIELD_ANONYMOUS']) && isset($this->aInputs[$CNF['FIELD_ANONYMOUS']]))
+            $aValsToAdd[$CNF['FIELD_AUTHOR']] = ($this->getCleanValue($CNF['FIELD_ANONYMOUS']) ? -1 : 1) * abs($aContentInfo[$CNF['FIELD_AUTHOR']]);
+
+        if(isset($CNF['FIELD_CHANGED']) && empty($aValsToAdd[$CNF['FIELD_CHANGED']]) && empty($this->getCleanValue($CNF['FIELD_CHANGED'])))
+            $aValsToAdd[$CNF['FIELD_CHANGED']] = time();
+
+        if(CHECK_ACTION_RESULT_ALLOWED === $this->_oModule->checkAllowedSetThumb($iContentId) && isset($CNF['FIELD_THUMB'])) {
+            $aThumb = bx_process_input (bx_get($CNF['FIELD_THUMB']), BX_DATA_INT);
+            $aValsToAdd[$CNF['FIELD_THUMB']] = 0;
+            if (!empty($aThumb) && is_array($aThumb) && ($iFileThumb = array_pop($aThumb)))
+                $aValsToAdd[$CNF['FIELD_THUMB']] = $iFileThumb;
+        }
+
+        if(!empty($CNF['OBJECT_METATAGS']))
+            $this->_processMetas($aValsToAdd);
+
+        if(isset($CNF['FIELD_STATUS']) && isset($aContentInfo[$CNF['FIELD_STATUS']]) && $aContentInfo[$CNF['FIELD_STATUS']] == 'failed')
+            $aValsToAdd[$CNF['FIELD_STATUS']] = 'active';
+
+        $mixedResult = parent::update($iContentId, $aValsToAdd, $aTrackTextFieldsChanges);
+        if($mixedResult !== false)
+            $this->_processTrackFields($iContentId);
+
+        return $mixedResult;
+    }
+
+    protected function _associalFileWithContent($oStorage, $iFileId, $iProfileId, $iContentId, $sPictureField = '')
+    {
+        $oStorage->updateGhostsContentId ($iFileId, $iProfileId, $iContentId, $this->_isAdmin($iContentId));
+    }
+
+    protected function _getPhotoGhostTmplVars($aContentInfo = array())
+    {
+    	$CNF = &$this->_oModule->_oConfig->CNF;
+
+    	return array (
+			'name' => $this->aInputs[$CNF['FIELD_PHOTO']]['name'],
+			'content_id' => (int)$this->aInputs[$CNF['FIELD_PHOTO']]['content_id'],
+			'editor_id' => isset($CNF['FIELD_TEXT_ID']) ? $CNF['FIELD_TEXT_ID'] : '',
+            'thumb_id' => isset($CNF['FIELD_THUMB']) && isset($aContentInfo[$CNF['FIELD_THUMB']]) ? $aContentInfo[$CNF['FIELD_THUMB']] : 0,
+            'name_thumb' => isset($CNF['FIELD_THUMB']) ? $CNF['FIELD_THUMB'] : '',
+			'bx_if:set_thumb' => array (
+				'condition' => CHECK_ACTION_RESULT_ALLOWED === $this->_oModule->checkAllowedSetThumb($this->aInputs[$CNF['FIELD_PHOTO']]['content_id']),
+				'content' => array (
+					'name_thumb' => isset($CNF['FIELD_THUMB']) ? $CNF['FIELD_THUMB'] : '',
+    				'txt_pict_use_as_thumb' => _t(!empty($CNF['T']['txt_pict_use_as_thumb']) ? $CNF['T']['txt_pict_use_as_thumb'] : '_sys_txt_form_entry_input_picture_use_as_thumb')
+				),
+			),
+		);
+    }
+    
+    function delete ($iContentId, $aContentInfo = array())
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        // delete associated files
+
+        if (!empty($CNF['OBJECT_STORAGE'])) {
+            $oStorage = BxDolStorage::getObjectInstance($CNF['OBJECT_STORAGE']);
+            if ($oStorage)
+                $oStorage->queueFilesForDeletionFromGhosts($aContentInfo[$CNF['FIELD_AUTHOR']], $iContentId);
+        }
+
+        // delete associated objects data
+
+        if (!empty($CNF['OBJECT_VIEWS'])) {
+            $o = BxDolView::getObjectInstance($CNF['OBJECT_VIEWS'], $iContentId);
+            if ($o) $o->onObjectDelete();
+        }
+
+        if (!empty($CNF['OBJECT_VOTES'])) {
+            $o = BxDolVote::getObjectInstance($CNF['OBJECT_VOTES'], $iContentId);
+            if ($o) $o->onObjectDelete();
+        }
+
+        if (!empty($CNF['OBJECT_SCORES'])) {
+            $o = BxDolScore::getObjectInstance($CNF['OBJECT_SCORES'], $iContentId);
+            if ($o) $o->onObjectDelete();
+        }
+
+        if (!empty($CNF['OBJECT_COMMENTS'])) {
+            $o = BxDolCmts::getObjectInstance($CNF['OBJECT_COMMENTS'], $iContentId);
+            if ($o) $o->onObjectDelete();
+        }
+
+        if (!empty($CNF['OBJECT_METATAGS'])) {
+            $o = BxDolMetatags::getObjectInstance($CNF['OBJECT_METATAGS']);
+            if ($o) $o->onDeleteContent($iContentId);
+        }
+
+        // delete db record
+
+        return parent::delete($iContentId);
+    }
+
+    protected function _isAdmin ($iContentId = 0)
+    {
+        return $this->_oModule->_isModerator();
+    }
+
+    public function processFiles ($sFieldFile, $iContentId = 0, $isAssociateWithContent = false)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if (!isset($this->aInputs[$sFieldFile]))
+            return true;
+
+        $mixedFileIds = $this->getCleanValue($sFieldFile);
+        if(!$mixedFileIds)
+            return true;
+
+        $oStorage = BxDolStorage::getObjectInstance($this->aInputs[$sFieldFile]['storage_object']);
+        if (!$oStorage)
+            return false;
+
+        $iProfileId = $this->getContentOwnerProfileId($iContentId);
+
+        $aGhostFiles = $oStorage->getGhosts ($iProfileId, $isAssociateWithContent ? 0 : $iContentId, true, $this->_isAdmin($iContentId));
+        if (!$aGhostFiles)
+            return true;
+
+        foreach ($aGhostFiles as $aFile) {
+            if (is_array($mixedFileIds) && !in_array($aFile['id'], $mixedFileIds))
+                continue;
+
+            if ($aFile['private'])
+                $oStorage->setFilePrivate ($aFile['id'], 1);
+
+            if ($iContentId)
+                $this->_associalFileWithContent($oStorage, $aFile['id'], $iProfileId, $iContentId, $sFieldFile);
+        }
+
+        return true;
+    }
+
+    function _deleteFile ($iFileId, $sStorage = '')
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if (!$iFileId)
+            return true;
+
+		$sStorage = !empty($sStorage) ? $sStorage : $CNF['OBJECT_STORAGE'];
+		$oStorage = BxDolStorage::getObjectInstance($sStorage);
+        if (!$oStorage)
+            return false;
+
+        if (!$oStorage->getFile($iFileId))
+            return true;
+
+        $iProfileId = bx_get_logged_profile_id();
+        return $oStorage->deleteFile($iFileId, $iProfileId);
+    }
+
+    function addCssJs ()
+    {
+        if ((!isset($this->aParams['view_mode']) || !$this->aParams['view_mode']) && !self::$_isCssJsGeneralModuleAdded) {
+            $this->_oModule->_oTemplate->addCss('forms.css');
+            $this->_oModule->_oTemplate->addJs('modules/base/general/js/|forms.js');
+            self::$_isCssJsGeneralModuleAdded = true;
+        }
+
+        return parent::addCssJs ();
+    }
+    
+    function genViewRowValue(&$aInput)
+    {
+        $s = parent::genViewRowValue($aInput);
+
+        if ($this->_oMetatagsObject && in_array($aInput['name'], $this->_aMetatagsFieldsWithKeywords) && $s)
+            $s = $this->_oMetatagsObject->metaParse($this->_oMetatagsContentId, $s);
+
+        return $s;
+    }
+
+    function setMetatagsKeywordsData($iId, $a, $o)
+    {
+        $this->_oMetatagsContentId = $iId;
+        $this->_aMetatagsFieldsWithKeywords = $a;
+        $this->_oMetatagsObject = $o;
+    }
+
+    function getContentOwnerProfileId ($iContentId)
+    {
+        return $this->_oModule->serviceGetContentOwnerProfileId($iContentId);
+    }
+
+    protected function _processTrackFields($mixedContent)
+    {
+        if(empty($mixedContent) || empty($this->_aTrackFieldsChanges))
+            return;
+
+        if(!is_array($mixedContent)) {
+            $mixedContent = $this->_oModule->_oDb->getContentInfoById((int)$mixedContent);
+            if(empty($mixedContent) || !is_array($mixedContent))
+                return;
+        }
+
+        foreach($this->_aTrackFieldsChanges as $sField => $aValues)
+            if($mixedContent[$sField] == $aValues['old'])
+                $this->_aTrackFieldsChanges[$sField] = false;
+            else
+                $this->_aTrackFieldsChanges[$sField]['new'] = $mixedContent[$sField];
+    }
+
+    protected function _processMetas(&$aValsToAdd)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $sKey = 'FIELD_LABELS';
+        if(isset($CNF[$sKey]) && isset($this->aInputs[$CNF[$sKey]]) && empty($aValsToAdd[$CNF[$sKey]])) {
+            $aLabels = $this->getCleanValue($CNF[$sKey]);
+            if(!empty($aLabels) && is_array($aLabels))
+                $aValsToAdd[$CNF[$sKey]] = serialize($aLabels);
+            else
+                $aValsToAdd[$CNF[$sKey]] = '';
+        }
+
+        $sKey1 = 'FIELD_LOCATION';
+        $sKey2 = 'FIELD_LOCATION_PREFIX';
+        if(isset($CNF[$sKey1]) && isset($CNF[$sKey2]) && isset($this->aInputs[$CNF[$sKey1]]) && empty($aValsToAdd[$CNF[$sKey1]])) {               
+            $aLocation = BxDolMetatags::locationsRetrieveFromForm($CNF[$sKey2], $this);
+            if(!empty($aLocation) && is_array($aLocation))
+                $aValsToAdd[$CNF[$sKey1]] = serialize(BxDolMetatags::locationsParseComponents($aLocation));
+        }
+    }
+
+    protected function _getPrivacyFields($aKeysF2O = array())
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if(isset($CNF['PRIVACY_FIELD_TO_OBJECT']) && is_array($CNF['PRIVACY_FIELD_TO_OBJECT']))
+            return $CNF['PRIVACY_FIELD_TO_OBJECT'];
+
+        if(empty($aKeysF2O))
+            $aKeysF2O = array('FIELD_ALLOW_VIEW_TO' => 'OBJECT_PRIVACY_VIEW');
+
+        $aResult = array();
+        foreach($aKeysF2O as $sKeyField => $sKeyObject) {
+            if(!isset($CNF[$sKeyField]) || !isset($this->aInputs[$CNF[$sKeyField]]) || !isset($CNF[$sKeyObject]))
+                continue;
+
+            $aResult[$CNF[$sKeyField]] = $CNF[$sKeyObject];
+        }
+
+        return $aResult;
+    }
+
+    function _addCssJsPrivacyField($sField, $sPrivacyObject, $bDynamicMode = false)
+    {
+        $oPrivacy = BxDolPrivacy::getObjectInstance($sPrivacyObject);
+        if(!$oPrivacy) 
+            return;
+
+        $this->aInputs[$sField]['content'] = $oPrivacy->addCssJs($bDynamicMode) . $this->aInputs[$sField]['content'];
+    }
+
+    protected function _preparePrivacyField($sField, $sPrivacyObject)
+    {
+        $oPrivacy = BxDolPrivacy::getObjectInstance($sPrivacyObject);
+        if(!$oPrivacy) 
+            return;
+
+        $aSave = array('db' => array('pass' => 'Xss'));
+        array_walk($this->aInputs[$sField], function ($a, $k, $aSave) {
+            if (in_array($k, array('info', 'caption', 'value')))
+                $aSave[0][$k] = $a;
+        }, array(&$aSave));
+
+        $aGroupChooser = $oPrivacy->getGroupChooser($sPrivacyObject);
+
+        $this->aInputs[$sField] = array_merge($this->aInputs[$sField], $aGroupChooser, $aSave);
+    }
+
+    protected function _preloadPrivacyField($sField, $sPrivacyObject, $aValues)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $oPrivacy = BxDolPrivacy::getObjectInstance($sPrivacyObject);
+        if(!$oPrivacy) 
+            return;
+
+        $iContentId = !empty($aValues[$CNF['FIELD_ID']]) ? (int)$aValues[$CNF['FIELD_ID']] : 0;
+        $iProfileId = !empty($iContentId) ? (int)$this->getContentOwnerProfileId($iContentId) : bx_get_logged_profile_id();
+        $iGroupId = !empty($aValues[$sField]) ? $aValues[$sField] : 0;
+
+        if(!isset($this->aInputs[$sField]['content']))
+            $this->aInputs[$sField]['content'] = '';
+
+        $this->aInputs[$sField]['content'] .= $oPrivacy->initGroupChooser($sPrivacyObject, $iProfileId, array(
+            'content_id' => $iContentId,
+            'group_id' => $iGroupId,
+            'html_ids' => array(
+                'form' => $this->getId()
+            )
+        ));
+    }
+}
+
+/** @} */

+ 621 - 0
upgrade/files/11.0.0-11.0.1/files/modules/base/general/classes/BxBaseModGeneralFormsEntryHelper.php

@@ -0,0 +1,621 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    BaseGeneral Base classes for modules
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+/**
+ * Entry forms helper functions
+ */
+class BxBaseModGeneralFormsEntryHelper extends BxDolProfileForms
+{
+    protected $_oModule;
+
+    /**
+     * 'Ajax Mode' determines the format of response. If it's TRUE the response 
+     * (a form or an error appeared during form creation) should be returned 
+     * as text during initial loading, while all other responses appeared after 
+     * form submit should be arrays, which are ready to path to echoJson.
+     */
+    protected $_bAjaxMode;
+
+    /**
+     * Use absolute Action URL in generated form object. 
+     * It's needed in Ajax Mode.
+     */
+    protected $_bAbsoluteActionUrl;
+
+    protected $_bDynamicMode;
+
+    public function __construct($oModule)
+    {
+        parent::__construct();
+        $this->_oModule = $oModule;
+
+        $this->_bDynamicMode = false;
+
+        $this->_bAjaxMode = false;
+        $mixedAjaxMode = bx_get('ajax_mode');
+        if($mixedAjaxMode !== false)
+            $this->setAjaxMode($mixedAjaxMode);
+
+        $this->_bAbsoluteActionUrl = false;
+        $mixedAbsoluteActionUrl = bx_get('absolute_action_url');
+        if($mixedAbsoluteActionUrl !== false)
+            $this->setAbsoluteActionUrl($mixedAbsoluteActionUrl);
+    }
+
+    public function setAjaxMode($bAjaxMode)
+    {
+        $this->_bAjaxMode = (bool)$bAjaxMode;
+        if($this->_bAjaxMode)
+            $this->setDynamicMode(true);
+    }
+
+    public function setAbsoluteActionUrl($bAbsoluteActionUrl)
+    {
+        $this->_bAbsoluteActionUrl = (bool)$bAbsoluteActionUrl;
+    }
+
+    public function setDynamicMode($bDynamicMode)
+    {
+        $this->_bDynamicMode = (bool)$bDynamicMode;
+    }
+
+    public function getObjectStorage()
+    {
+        return BxDolStorage::getObjectInstance($this->_oModule->_oConfig->CNF['OBJECT_STORAGE']);
+    }
+
+    public function getObjectFormAdd ($sDisplay = false)
+    {
+    	$CNF = &$this->_oModule->_oConfig->CNF;
+
+        if(false === $sDisplay)
+            $sDisplay = $CNF['OBJECT_FORM_ENTRY_DISPLAY_ADD'];
+
+        $oForm = BxDolForm::getObjectInstance($CNF['OBJECT_FORM_ENTRY'], $sDisplay, $this->_oModule->_oTemplate);
+        if($this->_bAjaxMode)
+            $oForm->setAjaxMode($this->_bAjaxMode);
+
+        if($this->_bAbsoluteActionUrl)
+            $this->_setAbsoluteActionUrl('add', $oForm);
+
+        return $oForm; 
+    }
+
+    public function getObjectFormEdit ($sDisplay = false)
+    {
+    	$CNF = &$this->_oModule->_oConfig->CNF;
+
+        if(false === $sDisplay)
+            $sDisplay = $CNF['OBJECT_FORM_ENTRY_DISPLAY_EDIT'];
+
+        $oForm = BxDolForm::getObjectInstance($CNF['OBJECT_FORM_ENTRY'], $sDisplay, $this->_oModule->_oTemplate);
+        if($this->_bAjaxMode)
+            $oForm->setAjaxMode($this->_bAjaxMode);
+
+        if($this->_bAbsoluteActionUrl)
+            $this->_setAbsoluteActionUrl('edit', $oForm);
+
+        return $oForm;
+    }
+
+    public function getObjectFormView ($sDisplay = false)
+    {
+    	$CNF = &$this->_oModule->_oConfig->CNF;
+
+        if (false === $sDisplay)
+            $sDisplay = $CNF['OBJECT_FORM_ENTRY_DISPLAY_VIEW'];
+
+        return BxDolForm::getObjectInstance($CNF['OBJECT_FORM_ENTRY'], $sDisplay, $this->_oModule->_oTemplate);
+    }
+
+    public function getObjectFormDelete ($sDisplay = false)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if (false === $sDisplay)
+            $sDisplay = $CNF['OBJECT_FORM_ENTRY_DISPLAY_DELETE'];
+
+        $oForm = BxDolForm::getObjectInstance($CNF['OBJECT_FORM_ENTRY'], $sDisplay, $this->_oModule->_oTemplate);
+        if($this->_bAjaxMode)
+            $oForm->setAjaxMode($this->_bAjaxMode);
+
+        if($this->_bAbsoluteActionUrl)
+            $this->_setAbsoluteActionUrl('delete', $oForm);
+
+        return $oForm;
+    }
+
+    public function viewDataEntry ($iContentId)
+    {
+        // get content data and profile info
+        list ($oProfile, $aContentInfo) = $this->_getProfileAndContentData($iContentId);
+        if (!$aContentInfo)
+            return MsgBox(_t('_sys_txt_error_entry_is_not_defined'));
+
+        // check access
+        if (CHECK_ACTION_RESULT_ALLOWED !== ($sMsg = $this->_oModule->checkAllowedView($aContentInfo)))
+            return MsgBox($sMsg);
+
+        return $this->_oModule->_oTemplate->entryText($aContentInfo);
+    }
+
+    public function addData ($iProfile, $aValues, $sDisplay = false)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        // check and display form
+        $oForm = $this->getObjectFormAdd($sDisplay);
+        if (!$oForm)
+            return array('code' => 1, 'message' => '_sys_txt_error_occured');
+
+        $oForm->aFormAttrs['method'] = BX_DOL_FORM_METHOD_SPECIFIC;
+        $oForm->aParams['csrf']['disable'] = true;
+        if(!empty($oForm->aParams['db']['submit_name'])) {            
+            $sSubmitName = false;
+            if (is_array($oForm->aParams['db']['submit_name'])) {
+                foreach ($oForm->aParams['db']['submit_name'] as $sVal) {
+                    if (isset($oForm->aInputs[$sVal])) {
+                        $sSubmitName = $sVal;
+                        break;
+                    }
+                }
+            } 
+            else {
+                $sSubmitName = $oForm->aParams['db']['submit_name'];
+            }
+            if ($sSubmitName && isset($oForm->aInputs[$sSubmitName]))
+                $aValues[$sSubmitName] = $oForm->aInputs[$sSubmitName]['value'];
+        }
+
+        $oForm->initChecker(array(), $aValues);
+        if (!$oForm->isSubmittedAndValid())
+            return array('code' => 2, 'message' => '_sys_txt_error_occured');
+
+        // insert data into database
+        $aValsToAdd = array ();
+        if(isset($CNF['FIELD_AUTHOR']))
+            $aValsToAdd[$CNF['FIELD_AUTHOR']] = $iProfile;
+
+        $iContentId = $oForm->insert($aValsToAdd);
+        if (!$iContentId) {
+            if (!$oForm->isValid())
+                return array('code' => 2, 'message' => '_sys_txt_error_occured');
+            else
+                return array('code' => 3, 'message' => '_sys_txt_error_entry_creation');
+        }
+
+        $sResult = $this->onDataAddAfter(BxDolProfile::getInstance($iProfile)->getAccountId(), $iContentId);
+        if($sResult)
+            return array('code' => 4, 'message' => $sResult);
+
+        list($oProfile, $aContentInfo) = $this->_getProfileAndContentData($iContentId);
+
+        /*
+         * Process metas.
+         * Note. It's essential to process metas a the very end, 
+         * because all data related to an entry should be already
+         * processed and are ready to be passed to alert. 
+         */
+        $this->_oModule->processMetasAdd($iContentId);
+
+        /*
+         * Create alert about the completed action.
+         */
+        $this->_oModule->alertAfterAdd($aContentInfo);
+
+        return array('code' => 0, 'message' => '', 'content' => $aContentInfo);
+    }
+
+    public function addDataForm ($sDisplay = false, $sCheckFunction = false)
+    {
+        if (!$sCheckFunction)
+            $sCheckFunction = 'checkAllowedAdd';
+
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $bAsJson = false;
+
+        // get form object
+        $oForm = $this->getObjectFormAdd($sDisplay);
+        if (!$oForm)
+            return $this->prepareResponse(MsgBox(_t('_sys_txt_error_occured')), $bAsJson, 'msg');
+
+        $bAsJson = $this->_bAjaxMode && $oForm->isSubmitted();
+
+        // check access
+        if (CHECK_ACTION_RESULT_ALLOWED !== ($sMsg = $this->_oModule->$sCheckFunction())) {
+            $oProfile = BxDolProfile::getInstance();
+            if ($oProfile && ($aProfileInfo = $oProfile->getInfo()) && $aProfileInfo['type'] == 'system' && is_subclass_of($this->_oModule, 'BxBaseModProfileModule') && $this->_oModule->serviceActAsProfile()) // special check for system profile is needed, because of incorrect error message
+                return $this->prepareResponse(MsgBox(_t('_sys_txt_access_denied')), $bAsJson, 'msg');
+            else
+                return $this->prepareResponse(MsgBox($sMsg), $bAsJson, 'msg');
+        }
+
+        // check and display form
+        $oForm->initChecker();
+        if (!$oForm->isSubmittedAndValid())
+            return $this->prepareResponse($oForm->getCode($this->_bDynamicMode), $bAsJson, 'form', array(
+            	'form_id' => $oForm->getId()
+            ));
+
+        // insert data into database
+        $aValsToAdd = array ();
+        $iContentId = $oForm->insert ($aValsToAdd);
+        if (!$iContentId) {
+            if (!$oForm->isValid())
+                return $this->prepareResponse($oForm->getCode($this->_bDynamicMode), $bAsJson, 'form', array(
+                    'form_id' => $oForm->getId()
+                ));
+            else
+                return $this->prepareResponse(MsgBox(_t('_sys_txt_error_entry_creation')), $bAsJson, 'msg');
+        }
+
+        $sResult = $this->onDataAddAfter (getLoggedId(), $iContentId);
+        if ($sResult)
+            return $this->prepareResponse($sResult, $bAsJson, 'msg');
+
+        list($oProfile, $aContentInfo) = $this->_getProfileAndContentData($iContentId);
+
+        /*
+         * Process metas.
+         * Note. It's essential to process metas a the very end, 
+         * because all data related to an entry should be already
+         * processed and are ready to be passed to alert. 
+         */
+        $this->_oModule->processMetasAdd($iContentId);
+
+        // Perform ACL action
+        $this->_oModule->$sCheckFunction(true);
+
+        // Create alert about the completed action.
+        $this->_oModule->alertAfterAdd($aContentInfo);
+
+        // Redirect
+        $this->redirectAfterAdd($aContentInfo);
+    }
+
+    public function redirectAfterAdd($aContentInfo)
+    {
+    	$CNF = &$this->_oModule->_oConfig->CNF;
+
+    	$sUrl = 'page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $aContentInfo[$CNF['FIELD_ID']];
+        if($this->_bAjaxMode) {
+            echoJson($this->prepareResponse($sUrl, $this->_bAjaxMode, 'redirect'));
+            exit;
+        }
+        else
+            $this->_redirectAndExit($sUrl);
+    }
+
+    public function editDataForm ($iContentId, $sDisplay = false, $sCheckFunction = false, $bErrorMsg = true)
+    {
+        if (!$sCheckFunction)
+            $sCheckFunction = 'checkAllowedEdit';
+
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        // get content data and profile info
+        list ($oProfile, $aContentInfo) = $this->_getProfileAndContentData($iContentId);
+        if (!$aContentInfo)
+            return $bErrorMsg ? MsgBox(_t('_sys_txt_error_entry_is_not_defined')) : '';
+
+        // check access
+        if (CHECK_ACTION_RESULT_ALLOWED !== ($sMsg = $this->_oModule->$sCheckFunction($aContentInfo)))
+            return $bErrorMsg ? MsgBox($sMsg) : '';
+
+        // check and display form
+        $oForm = $this->getObjectFormEdit($sDisplay);
+        if (!$oForm)
+            return $bErrorMsg ? MsgBox(_t('_sys_txt_error_occured')) : '';
+
+        $aSpecificValues = array();        
+        if (!empty($CNF['OBJECT_METATAGS'])) {
+            $oMetatags = BxDolMetatags::getObjectInstance($CNF['OBJECT_METATAGS']);
+            if ($oMetatags->locationsIsEnabled())
+                $aSpecificValues = $oMetatags->locationGet($iContentId, empty($CNF['FIELD_LOCATION_PREFIX']) ? '' : $CNF['FIELD_LOCATION_PREFIX']);
+        }
+        $oForm->initChecker($aContentInfo, $aSpecificValues);
+
+        if (!$oForm->isSubmittedAndValid())
+            return $oForm->getCode();
+
+        // update data in the DB
+        $aTrackTextFieldsChanges = null;
+
+        $this->onDataEditBefore ($aContentInfo[$CNF['FIELD_ID']], $aContentInfo, $aTrackTextFieldsChanges, $oProfile, $oForm);
+
+        if (!$oForm->update ($aContentInfo[$CNF['FIELD_ID']], array(), $aTrackTextFieldsChanges)) {
+            if (!$oForm->isValid())
+                return $oForm->getCode();
+            else
+                return MsgBox(_t('_sys_txt_error_entry_update'));
+        }
+
+        $sResult = $this->onDataEditAfter ($aContentInfo[$CNF['FIELD_ID']], $aContentInfo, $aTrackTextFieldsChanges, $oProfile, $oForm);
+        if ($sResult)
+            return $sResult;
+
+        /*
+         * Process metas.
+         * Note. It's essential to process metas a the very end, 
+         * because all data related to an entry should be already
+         * processed and are ready to be passed to alert. 
+         */
+        $this->_oModule->processMetasEdit($iContentId, $oForm);
+
+        // Perform ACL action
+        $this->_oModule->$sCheckFunction($aContentInfo, true);
+
+        // Create alert about the completed action.
+        $this->_oModule->alertAfterEdit($aContentInfo);
+        
+        // Redirect
+        $this->redirectAfterEdit($aContentInfo);
+    }
+
+    protected function redirectAfterEdit($aContentInfo)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+        $this->_redirectAndExit('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $aContentInfo[$CNF['FIELD_ID']]);
+    }
+
+    public function deleteDataForm ($iContentId, $sDisplay = false, $sCheckFunction = false)
+    {
+        if (!$sCheckFunction)
+            $sCheckFunction = 'checkAllowedDelete';
+        
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        // get content data and profile info
+        list ($oProfile, $aContentInfo) = $this->_getProfileAndContentData($iContentId);
+        if (!$aContentInfo)
+            return MsgBox(_t('_sys_txt_error_entry_is_not_defined'));
+
+        // check access
+        if (CHECK_ACTION_RESULT_ALLOWED !== ($sMsg = $this->_oModule->$sCheckFunction($aContentInfo)))
+            return MsgBox($sMsg);
+
+        // check and display form
+        $oForm = $this->getObjectFormDelete($sDisplay);
+        if (!$oForm)
+            return MsgBox(_t('_sys_txt_error_occured'));
+
+        $oForm->initChecker($aContentInfo);
+
+        if (!$oForm->isSubmittedAndValid())
+            return $oForm->getCode();
+
+        if ($sError = $this->deleteData($aContentInfo[$CNF['FIELD_ID']], $aContentInfo, $oProfile, $oForm))
+            return MsgBox($sError);
+
+        // perform action
+        $this->_oModule->$sCheckFunction($aContentInfo, true);
+
+        // redirect
+        $this->redirectAfterDelete($aContentInfo);
+    }
+
+    protected function redirectAfterDelete($aContentInfo)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+        $this->_redirectAndExit($CNF['URL_HOME'], true, array(
+            'account_id' => getLoggedId(),
+            'profile_id' => bx_get_logged_profile_id(),
+        ));
+    }
+
+    /**
+     * Delete data entry
+     * @param $iContentId entry id
+     * @param $oForm optional content info array
+     * @param $aContentInfo optional content info array
+     * @param $oProfile optional content author profile
+     * @return error string on error or empty string on success
+     */
+    public function deleteData ($iContentId, $aContentInfo = false, $oProfile = null, $oForm = null)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if (!$aContentInfo || !$oProfile)
+            list ($oProfile, $aContentInfo) = $this->_getProfileAndContentData($iContentId);
+
+        if (!$aContentInfo)
+            return _t('_sys_txt_error_entry_is_not_defined');
+
+        if (!$oForm)
+            $oForm = $this->getObjectFormDelete();
+
+        if (!$oForm->delete ($aContentInfo[$CNF['FIELD_ID']], $aContentInfo))
+            return _t('_sys_txt_error_entry_delete');
+
+        if ($sResult = $this->onDataDeleteAfter ($aContentInfo[$CNF['FIELD_ID']], $aContentInfo, $oProfile))
+            return $sResult;
+
+		// create an alert
+        bx_alert($this->_oModule->getName(), 'deleted', $aContentInfo[$CNF['FIELD_ID']], false, array(
+            'content' => &$aContentInfo
+        ));
+		
+        return '';
+    }
+
+    public function viewDataForm ($iContentId, $sDisplay = false)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        // get content data and profile info
+        list ($oProfile, $aContentInfo) = $this->_getProfileAndContentData($iContentId);
+        if (!$aContentInfo)
+            return MsgBox(_t('_sys_txt_error_entry_is_not_defined'));
+
+        // check access
+        if ($sMsg = $this->_processPermissionsCheckForViewDataForm ($aContentInfo, $oProfile))
+            return MsgBox($sMsg);
+
+        // get form
+        $oForm = $this->getObjectFormView($sDisplay);
+        if (!$oForm)
+            return MsgBox(_t('_sys_txt_error_occured'));
+
+        // process metatags
+        if (!empty($CNF['OBJECT_METATAGS'])) {
+            $oMetatags = BxDolMetatags::getObjectInstance($CNF['OBJECT_METATAGS']);
+            if ($oMetatags->keywordsIsEnabled()) {
+                $aFields = $oMetatags->metaFields($aContentInfo, $CNF, $CNF['OBJECT_FORM_ENTRY_DISPLAY_VIEW']);
+                $oForm->setMetatagsKeywordsData($iContentId, $aFields, $oMetatags);
+            }
+        }        
+
+        // display profile
+        $oForm->initChecker($aContentInfo);
+        return $oForm->getCode();
+    }
+
+    protected function _processPermissionsCheckForViewDataForm ($aContentInfo, $oProfile)
+    {
+        if (CHECK_ACTION_RESULT_ALLOWED !== ($sMsg = $this->_oModule->checkAllowedView($aContentInfo)))
+            return $sMsg;
+
+        return '';
+    }
+
+    public function onDataDeleteAfter ($iContentId, $aContentInfo, $oProfile)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if(($oPrivacy = BxDolPrivacy::getObjectInstance($CNF['OBJECT_PRIVACY_VIEW'])) !== false)
+            $oPrivacy->deleteGroupCustomByContentId($iContentId);
+
+        bx_audit(
+            $iContentId, 
+            $this->_oModule->getName(), 
+            '_sys_audit_action_detete_content',  
+            $this->_oModule->_prepareAuditParams($aContentInfo)
+        );
+        
+        return '';
+    }
+
+    public function onDataEditBefore ($iContentId, $aContentInfo, &$aTrackTextFieldsChanges, &$oProfile, &$oForm)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if(isset($CNF['FIELD_STATUS']) && isset($aContentInfo[$CNF['FIELD_STATUS']]) && $aContentInfo[$CNF['FIELD_STATUS']] == 'failed')
+            $oForm->addTrackFields($CNF['FIELD_STATUS'], $aContentInfo);
+    }
+
+    public function onDataEditAfter ($iContentId, $aContentInfo, $aTrackTextFieldsChanges, $oProfile, $oForm)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        /*
+         * Load update data.
+         */
+        list($oProfile, $aContentInfo) = $this->_getProfileAndContentData($iContentId);
+
+        if(isset($CNF['FIELD_PHOTO']))
+            $oForm->processFiles ($CNF['FIELD_PHOTO'], $iContentId, false);
+
+        if(isset($CNF['FIELD_STATUS']) && ($aTrackResult = $oForm->isTrackFieldChanged($CNF['FIELD_STATUS'], true)) !== false)
+            if($aTrackResult['old'] == 'failed' && $aTrackResult['new'] == 'active')
+                $this->_oModule->alertAfterAdd($aContentInfo);
+
+        if(isset($CNF['FIELD_ALLOW_VIEW_TO']) && !empty($aContentInfo[$CNF['FIELD_ALLOW_VIEW_TO']]) && ($oPrivacy = BxDolPrivacy::getObjectInstance($CNF['OBJECT_PRIVACY_VIEW'])) !== false)
+            $oPrivacy->reassociateGroupCustomWithContent($oProfile->id(), $iContentId, (int)$aContentInfo[$CNF['FIELD_ALLOW_VIEW_TO']]);
+        
+        bx_audit(
+            $iContentId, 
+            $this->_oModule->getName(), 
+            '_sys_audit_action_edit_content',  
+            $this->_oModule->_prepareAuditParams($aContentInfo)
+        );
+        
+        return '';
+    }
+
+    public function onDataAddAfter ($iAccountId, $iContentId)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        list($oProfile, $aContentInfo) = $this->_getProfileAndContentData($iContentId);
+
+        if(($oForm = $this->getObjectFormAdd()) !== false) {
+            if(isset($CNF['FIELD_PHOTO']))
+                $oForm->processFiles($CNF['FIELD_PHOTO'], $iContentId, true);
+        }
+
+        if(isset($CNF['FIELD_ALLOW_VIEW_TO']) && !empty($aContentInfo[$CNF['FIELD_ALLOW_VIEW_TO']]) && ($oPrivacy = BxDolPrivacy::getObjectInstance($CNF['OBJECT_PRIVACY_VIEW'])) !== false)
+            $oPrivacy->associateGroupCustomWithContent($oProfile->id(), $iContentId, (int)$aContentInfo[$CNF['FIELD_ALLOW_VIEW_TO']]);
+
+        bx_audit(
+            $iContentId, 
+            $this->_oModule->getName(), 
+            '_sys_audit_action_add_content',  
+            $this->_oModule->_prepareAuditParams($aContentInfo)
+        );
+        
+        return '';
+    }
+
+    protected function prepareCustomRedirectUrl($s, $aContentInfo)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+        $oProfile = BxDolProfile::getInstanceByContentAndType($aContentInfo[$CNF['FIELD_ID']], $this->_oModule->getName());
+
+        $aMarkers = array(
+            '{profile_id}',
+            '{content_id}',
+            '{module}',
+        );
+        $aReplacements = array(
+            $oProfile ? $oProfile->id() : 0,
+            $aContentInfo[$CNF['FIELD_ID']],
+            $this->_oModule->getName(),
+        );
+        $s = str_replace($aMarkers, $aReplacements, $s);
+
+        $s = BxDolPermalinks::getInstance()->permalink($s);
+
+        if (false === strpos($s, 'http://') && false === strpos($s, 'https://'))
+            $s = BX_DOL_URL_ROOT . $s;
+        
+        return $s;
+    }
+    
+    protected function prepareResponse($mixedResponse, $bAsJson = false, $sKey = 'msg', $aAdditional = array())
+    {
+    	if(!$bAsJson)
+            return $mixedResponse;
+
+        $aResponse = array(
+            $sKey => $mixedResponse,
+            '_dt' => 'json'
+        );
+
+        if(!empty($aAdditional) && is_array($aAdditional))
+            $aResponse = array_merge($aResponse, $aAdditional);
+
+        return $aResponse;
+    }
+    
+    protected function _setAbsoluteActionUrl($sType, &$oForm)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $sKeyUri = 'URI_' . strtoupper($sType) . '_ENTRY';
+        if(empty($CNF[$sKeyUri]))
+            return;
+
+        $oForm->setAbsoluteActionUrl(BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF[$sKeyUri]));
+    }
+}
+
+/** @} */

+ 2551 - 0
upgrade/files/11.0.0-11.0.1/files/modules/base/general/classes/BxBaseModGeneralModule.php

@@ -0,0 +1,2551 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    BaseGeneral Base classes for modules
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+bx_import('BxDolAcl');
+
+/**
+ * Base module class.
+ */
+class BxBaseModGeneralModule extends BxDolModule
+{
+    protected $_iProfileId;
+    protected $_aSearchableNamesExcept;
+    protected $_aFormParams;
+
+    function __construct(&$aModule)
+    {
+        parent::__construct($aModule);
+
+        $this->_iProfileId = bx_get_logged_profile_id();
+        $this->_aSearchableNamesExcept = array(
+            'allow_view_to'
+        );
+
+        $this->_aFormParams = array(
+            'display' => false, 
+            'dynamic_mode' => false, 
+            'ajax_mode' => false, 
+            'absolute_action_url' => false,
+            'visibility_autoselect' => false,
+            'context_id' => 0,
+            'custom' => array()
+        );
+    }
+
+    // ====== ACTIONS METHODS
+
+    public function actionRss ()
+    {
+        $aArgs = func_get_args();
+        $this->_rss($aArgs);
+    }
+    
+    public function actionGetCreatePostForm()
+    {
+        $sName = $this->_oConfig->getName();
+
+    	$aParams = bx_process_input(array_intersect_key($_GET, $this->_aFormParams));
+    	$aParams = array_merge($this->_aFormParams, $aParams);
+        $aParams['context_id'] = (bool)$aParams['context_id'] ? (int)$aParams['context_id'] : false;
+
+    	$mixedResponse = $this->serviceGetCreatePostForm($aParams);
+    	if(empty($mixedResponse))
+            return echoJson(array());
+        else if(is_array($mixedResponse)) {
+            $mixedResponse['module'] = $sName;
+            return echoJson($mixedResponse);
+        }
+
+        return echoJson(array(
+            'module' => $sName,
+            'content' => $mixedResponse
+    	));
+    }
+
+    // ====== SERVICE METHODS
+    
+    public function serviceIsBadgesAvaliable()
+    {
+        $oBadges = BxDolBadges::getInstance();
+		$aBadges = $oBadges->getData(array('type' => 'by_module&object', 'object_id' => 0, 'module' => $this->_oConfig->getName()));
+        return count($aBadges) > 0 ? true : false;
+    }
+
+    public function serviceGetSafeServices()
+    {
+        return array(
+            // other
+            'ModuleIcon' => '',
+            'GetLink' => '',
+            // browse
+            'GetSearchResultUnit' => '',
+            'Browse' => '',
+            'BrowseFeatured' => '',
+            'BrowseFavorite' => '',
+            // forms
+            'GetCreatePostForm' => '',
+            'EntityEdit' => '',
+            'EntityDelete' => '',
+            // page blocks
+            'EntityTextBlock' => '',
+            'EntityInfo' => '',
+            'EntityInfoFull' => '',
+            'EntityInfoExtended' => '',
+            'EntityLocation' => '',
+            'EntityComments' => '',
+            'EntityAttachments' => '',
+            // menu
+            'EntityAllActions' => '',
+            'EntityActions' => '',
+            'EntitySocialSharing' => '',
+            'MyEntriesActions' => '',
+        );
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_base_general Base General
+     * @subsection bx_base_general-other Other
+     * @subsubsection bx_base_general-module_icon module_icon
+     * 
+     * @code bx_srv('bx_posts', 'module_icon', [...]); @endcode
+     * 
+     * Get module icon as CSS class name with FontAwesome icon name class and color class
+     * 
+     * @see BxBaseModGeneralModule::serviceModuleIcon
+     */
+    /** 
+     * @ref bx_base_general-module_icon "module_icon"
+     */
+    public function serviceModuleIcon ()
+    {
+        return isset($this->_oConfig->CNF['ICON']) ? $this->_oConfig->CNF['ICON'] : '';
+    }
+
+    public function serviceGetAuthor ($iContentId)
+    {
+        $mixedResult = $this->_getFieldValue('FIELD_AUTHOR', $iContentId);
+        return $mixedResult !== false ? (int)$mixedResult : 0; 
+    }
+
+    public function serviceGetPrivacyView ($iContentId)
+    {
+        return $this->_getFieldValue('FIELD_ALLOW_VIEW_TO', $iContentId);
+    }
+
+    public function serviceGetDateAdded ($iContentId)
+    {
+        $mixedResult = $this->_getFieldValue('FIELD_ADDED', $iContentId);
+        return $mixedResult !== false ? (int)$mixedResult : 0; 
+    }
+
+    public function serviceGetDateChanged ($iContentId)
+    {
+        $mixedResult = $this->_getFieldValue('FIELD_CHANGED', $iContentId);
+        return $mixedResult !== false ? (int)$mixedResult : 0;
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_base_general Base General
+     * @subsection bx_base_general-other Other
+     * @subsubsection bx_base_general-get_link get_link
+     * 
+     * @code bx_srv('bx_posts', 'get_link', [...]); @endcode
+     * 
+     * Get URL for the specified content.
+     * @param $iContentId content id
+     * 
+     * @see BxBaseModGeneralModule::serviceGetLink
+     */
+    /** 
+     * @ref bx_base_general-get_link "get_link"
+     */
+    public function serviceGetLink ($iContentId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+        if(empty($CNF['URI_VIEW_ENTRY']))
+            return '';
+
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+        if(empty($aContentInfo))
+            return '';
+
+        return BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $aContentInfo[$CNF['FIELD_ID']]);
+    }
+
+    public function serviceGetTitle ($iContentId)
+    {
+        $mixedResult = $this->_getFieldValue('FIELD_TITLE', $iContentId);
+        return $mixedResult !== false ? $mixedResult : '';
+    }
+
+    public function serviceGetText ($iContentId)
+    {
+        $mixedResult = $this->_getFieldValue('FIELD_TEXT', $iContentId);
+        if (false === $mixedResult)
+            return '';
+
+        $CNF = &$this->_oConfig->CNF;
+        if (!empty($CNF['OBJECT_METATAGS']) && is_string($mixedResult)) {
+            $oMetatags = BxDolMetatags::getObjectInstance($CNF['OBJECT_METATAGS']);
+            $mixedResult = $oMetatags->metaParse($iContentId, $mixedResult);
+        }
+
+        return $mixedResult;
+    }
+
+    public function serviceGetInfo ($iContentId, $bSearchableFieldsOnly = true)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aContentInfo = $this->_getFields($iContentId);
+        if(empty($aContentInfo))
+            return array();
+
+        if(!$bSearchableFieldsOnly)
+            return $aContentInfo;
+
+        if(empty($CNF['PARAM_SEARCHABLE_FIELDS']))
+            return array();
+
+        $aFields = explode(',', getParam($CNF['PARAM_SEARCHABLE_FIELDS']));
+        if(empty($aFields))
+            return array();
+
+        $aResult = array();
+        foreach($aFields as $sField)
+            if(isset($aContentInfo[$sField]))
+                $aResult[$sField] = $aContentInfo[$sField];
+
+        return $aResult;
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_base_general Base General
+     * @subsection bx_base_general-browsing Browsing
+     * @subsubsection bx_base_general-get_search_result_unit get_search_result_unit
+     * 
+     * @code bx_srv('bx_posts', 'get_search_result_unit', [3,"unit_gallery.html"]); @endcode
+     * 
+     * Get browsing unit for the specified content
+     * @param $iContentId content id
+     * @param $sUnitTemplate unit template, such as: unit_full.html, unit_gallery.html, unit_live_search.html
+     * 
+     * @see BxBaseModGeneralModule::serviceGetSearchResultUnit
+     */
+    /** 
+     * @ref bx_base_general-get_search_result_unit "get_search_result_unit"
+     */
+    public function serviceGetSearchResultUnit ($iContentId, $sUnitTemplate = '')
+    {
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+        if(empty($aContentInfo))
+            return '';
+
+        if(empty($sUnitTemplate))
+            $sUnitTemplate = 'unit.html';
+
+        return $this->_oTemplate->unit($aContentInfo, true, $sUnitTemplate);
+    }
+
+    public function serviceGetAll ($aParams = array())
+    {
+        if(empty($aParams) || !is_array($aParams))
+            $aParams = array('type' => 'all');
+
+        return $this->_oDb->getEntriesBy($aParams);
+    }
+
+    public function serviceGetAllByAuthor ($iProfileId)
+    {
+        return $this->_oDb->getEntriesByAuthor((int)$iProfileId);
+    }
+
+    public function serviceGetSearchableFieldsExtended($aInputsAdd = array())
+    {
+        $CNF = &$this->_oConfig->CNF;
+        if(empty($CNF['OBJECT_FORM_ENTRY']))
+            return array();
+
+        $aResult = array();
+        if(!empty($CNF['FIELD_AUTHOR']) && !in_array($CNF['FIELD_AUTHOR'], $this->_aSearchableNamesExcept))
+            $aResult[$CNF['FIELD_AUTHOR']] = array(
+                'type' => 'text_auto', 
+                'caption' => $CNF['T']['form_field_author'],
+                'info' => '',
+            	'value' => '',
+                'values' => '',
+                'pass' => ''
+            );
+
+        $aInputs = array();
+        if(!empty($CNF['OBJECT_FORM_ENTRY_DISPLAY_ADD'])) {
+            $oForm = BxDolForm::getObjectInstance($CNF['OBJECT_FORM_ENTRY'], $CNF['OBJECT_FORM_ENTRY_DISPLAY_ADD'], $this->_oTemplate);
+
+            $aInputs = array_merge($aInputs, $oForm->aInputs);
+        }
+        if(!empty($CNF['OBJECT_FORM_ENTRY_DISPLAY_EDIT'])) {
+            $oForm = BxDolForm::getObjectInstance($CNF['OBJECT_FORM_ENTRY'], $CNF['OBJECT_FORM_ENTRY_DISPLAY_EDIT'], $this->_oTemplate);
+
+            $aInputs = array_merge($aInputs, $oForm->aInputs);
+        }
+
+        if(!empty($aInputsAdd) && is_array($aInputsAdd))
+            $aInputs = array_merge($aInputs, $aInputsAdd);
+
+        foreach($aInputs as $aInput){
+            if(in_array($aInput['type'], BxDolSearchExtended::$SEARCHABLE_TYPES) && !in_array($aInput['name'], $this->_aSearchableNamesExcept)) {
+                $aField = array(
+                    'type' => $aInput['type'], 
+                    'caption_system' => $aInput['caption_system_src'],
+                    'caption' => $aInput['caption_src'],
+                    'info' => $aInput['info_src'],
+                    'value' => !empty($aInput['value']) ? $aInput['value'] : '',
+                    'values' => !empty($aInput['values_src']) ? $aInput['values_src'] : '',
+                    'pass' => !empty($aInput['db']['pass']) ? $aInput['db']['pass'] : '',
+                );
+
+                if(isset($aInput['search_type']))
+                    $aField['search_type'] = $aInput['search_type'];
+
+                $aResult[$aInput['name']] = $aField;
+            }
+        }
+        return $aResult;
+    }
+
+    public function serviceGetSearchResultExtended($aParams, $iStart = 0, $iPerPage = 0)
+    {
+        if(empty($aParams) || !is_array($aParams))
+            return array();
+
+        return $this->_oDb->getEntriesBy(array('type' => 'search_ids', 'search_params' => $aParams, 'start' => $iStart, 'per_page' => $iPerPage));
+    }
+
+    public function serviceGetSearchableFields ($aInputsAdd = array())
+    {
+        $CNF = $this->_oConfig->CNF;
+
+        if(!isset($CNF['PARAM_SEARCHABLE_FIELDS']) || !isset($CNF['OBJECT_FORM_ENTRY']) || !isset($CNF['OBJECT_FORM_ENTRY_DISPLAY_ADD']))
+            return array();
+
+        $oForm = BxDolForm::getObjectInstance($CNF['OBJECT_FORM_ENTRY'], $CNF['OBJECT_FORM_ENTRY_DISPLAY_ADD'], $this->_oTemplate);
+        if(!$oForm)
+            return array();
+
+        $aInputs = $oForm->aInputs;
+        if(!empty($aInputsAdd) && is_array($aInputsAdd))
+            $aInputs = array_merge($aInputs, $aInputsAdd);
+
+        $aTextTypes = array('text', 'textarea');
+        $aTextFields = array();
+        foreach($aInputs as $r)
+            if(in_array($r['type'], $aTextTypes))
+                $aTextFields[$r['name']] = $r['caption'];
+
+        return $aTextFields;
+    }
+    
+    public function serviceManageTools($sType = 'common')
+    {
+        $oGrid = BxDolGrid::getObjectInstance($this->_oConfig->getGridObject($sType));
+        if(!$oGrid)
+            return '';
+
+		$CNF = &$this->_oConfig->CNF;
+
+		$sMenu = '';
+		if(BxDolAcl::getInstance()->isMemberLevelInSet(192)) {
+			$oPermalink = BxDolPermalinks::getInstance();
+
+			$aMenuItems = array();
+			if(!empty($CNF['OBJECT_GRID_COMMON']) && !empty($CNF['T']['menu_item_manage_my']))
+				$aMenuItems[] = array('id' => 'manage-common', 'name' => 'manage-common', 'class' => '', 'link' => $oPermalink->permalink($CNF['URL_MANAGE_COMMON']), 'target' => '_self', 'title' => _t($CNF['T']['menu_item_manage_my']), 'active' => 1);
+			if(!empty($CNF['OBJECT_GRID_ADMINISTRATION']) && !empty($CNF['T']['menu_item_manage_all']))
+				$aMenuItems[] = array('id' => 'manage-administration', 'name' => 'manage-administration', 'class' => '', 'link' => $oPermalink->permalink($CNF['URL_MANAGE_ADMINISTRATION']), 'target' => '_self', 'title' => _t($CNF['T']['menu_item_manage_all']), 'active' => 1);
+
+			if(count($aMenuItems) > 1) {
+	            $oMenu = new BxTemplMenu(array(
+	            	'template' => 'menu_vertical.html', 
+	            	'menu_items' => $aMenuItems
+	            ), $this->_oTemplate);
+	            $oMenu->setSelected($this->_aModule['name'], 'manage-' . $sType);
+	            $sMenu = $oMenu->getCode();
+			}
+		}
+
+		if(!empty($CNF['OBJECT_MENU_SUBMENU']) && isset($CNF['URI_MANAGE_COMMON'])) {
+			BxDolMenu::getObjectInstance($CNF['OBJECT_MENU_SUBMENU'])->setSelected($this->_aModule['name'], $CNF['URI_MANAGE_COMMON']);
+		}
+
+        $this->_oTemplate->addCss(array('manage_tools.css'));
+        $this->_oTemplate->addJs(array('manage_tools.js'));
+        $this->_oTemplate->addJsTranslation(array('_sys_grid_search'));
+        return array(
+        	'content' => $this->_oTemplate->getJsCode('manage_tools', array('sObjNameGrid' => $this->_oConfig->getGridObject($sType))) . $oGrid->getCode(),
+        	'menu' => $sMenu
+        );
+    }
+
+    public function serviceGetMenuAddonManageTools()
+    {
+    	return 0;
+    }
+    
+    public function serviceGetMenuAddonManageToolsProfileStats()
+    {
+    	return 0;
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_base_general Base General
+     * @subsection bx_base_general-browsing Browsing
+     * @subsubsection bx_base_general-browse browse
+     * 
+     * @code bx_srv('bx_posts', 'browse', [...]); @endcode
+     * 
+     * Universal browse method.
+     * @param $aParams custom browse params, possible params are the following:
+     *  - mode - browse mode, such as 'recent', 'featured', etc
+     *  - params - custom params to browse method, for example 'unit_view' can be passed here
+     *  - design_box - design box style, @see BxBaseFunctions::DesignBoxContent 
+     *  - empty_message - display or not "empty" message when there is no content
+     *  - ajax_paginate - use AJAX paginate or not
+     * 
+     * @see BxBaseModGeneralModule::serviceBrowse
+     */
+    /** 
+     * @ref bx_base_general-browse "browse"
+     */
+    public function serviceBrowse ($aParams = array())
+    {
+        $aDefaults = array (
+            'mode' => 'recent',
+            'params' => false,
+            'design_box' => BX_DB_PADDING_DEF,
+            'empty_message' => false,
+            'ajax_paginate' => true,
+            'class_search_result' => 'SearchResult'
+        );
+        $aParams = array_merge($aDefaults, $aParams);
+        return $this->_serviceBrowse ($aParams['mode'], $aParams['params'], $aParams['design_box'], $aParams['empty_message'], $aParams['ajax_paginate'], $aParams['class_search_result']);
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_base_general Base General
+     * @subsection bx_base_general-browsing Browsing
+     * @subsubsection bx_base_general-browse_featured browse_featured
+     * 
+     * @code bx_srv('bx_posts', 'browse_featured', [...]); @endcode
+     * 
+     * Display featured entries
+     * @param $sUnitView browsing unity view, 
+     *                   such as: full, extended, gallery, showcase
+     * @param $bEmptyMessage display or not "empty" message when there is no content
+     * @param $bAjaxPaginate use AJAX paginate or not
+     * 
+     * @see BxBaseModGeneralModule::serviceBrowseFeatured
+     */
+    /** 
+     * @ref bx_base_general-browse_featured "browse_featured"
+     */
+    public function serviceBrowseFeatured ($sUnitView = false, $bEmptyMessage = false, $bAjaxPaginate = true)
+    {
+        return $this->_serviceBrowse ('featured', $sUnitView ? array('unit_view' => $sUnitView) : false, BX_DB_PADDING_DEF, $bEmptyMessage, $bAjaxPaginate);
+    }
+	
+    /**
+     * @page service Service Calls
+     * @section bx_base_general Base General
+     * @subsection bx_base_general-browsing Browsing
+     * @subsubsection bx_base_general-browse_favorite browse_favorite
+     * 
+     * @code bx_srv('bx_posts', 'browse_favorite', [...]); @endcode
+     * 
+     * Display entries whoch were added to favorite list by some member
+     * @param $iProfileId profile ID
+     * @param $aParams additional browse params, see BxBaseModGeneralModule::serviceBrowse
+     * 
+     * @see BxBaseModGeneralModule::serviceBrowseFavorite BxBaseModGeneralModule::serviceBrowse
+     */
+    /** 
+     * @ref bx_base_general-browse_favorite "browse_favorite"
+     */
+    public function serviceBrowseFavorite ($iProfileId = 0, $aParams = array())
+    {
+        $oProfile = null;
+        if((int)$iProfileId)
+            $oProfile = BxDolProfile::getInstance($iProfileId);
+        if(!$oProfile && bx_get('profile_id') !== false)
+            $oProfile = BxDolProfile:: getInstance(bx_process_input(bx_get('profile_id'), BX_DATA_INT));
+        if(!$oProfile)
+            $oProfile = BxDolProfile::getInstance();
+        if(!$oProfile)
+            return '';
+
+        $bEmptyMessage = false;
+        if(isset($aParams['empty_message'])) {
+            $bEmptyMessage = (bool)$aParams['empty_message'];
+            unset($aParams['empty_message']);
+        }
+
+        return $this->_serviceBrowse ('favorite', array_merge(array('user' => $oProfile->id()), $aParams), BX_DB_PADDING_DEF, $bEmptyMessage);
+    }
+    
+    /**
+     * Display entries posted into particular context
+     * @return HTML string
+     */
+    public function serviceBrowseContext ($iProfileId = 0, $aParams = array())
+    {
+        return $this->_serviceBrowseWithParam ('context', 'profile_id', $iProfileId, $aParams);
+    }
+    
+    public function _serviceBrowseWithParam ($sParamName, $sParamGet, $sParamVal, $aParams = array())
+    {
+        if(!$sParamVal)
+            $sParamVal = bx_process_input(bx_get($sParamGet), BX_DATA_INT);
+        if(!$sParamVal)
+            return '';
+
+        $bEmptyMessage = true;
+        if(isset($aParams['empty_message'])) {
+            $bEmptyMessage = (bool)$aParams['empty_message'];
+            unset($aParams['empty_message']);
+        }
+
+        $bAjaxPaginate = true;
+        if(isset($aParams['ajax_paginate'])) {
+            $bAjaxPaginate = (bool)$aParams['ajax_paginate'];
+            unset($aParams['ajax_paginate']);
+        }
+
+        return $this->_serviceBrowse ($sParamName, array_merge(array($sParamName => $sParamVal), $aParams), BX_DB_PADDING_DEF, $bEmptyMessage, $bAjaxPaginate);
+    }
+
+    public function serviceFormsHelper ()
+    {
+        bx_import('FormsEntryHelper', $this->_aModule);
+        $sClass = $this->_aModule['class_prefix'] . 'FormsEntryHelper';
+        return new $sClass($this);
+    }
+
+	/**
+     * Add entry using provided fields' values.
+     * @return array with result: 'code' is 0 on success or non-zero on error, 'message' is error message in case of error, 'content' is content info array in case of success
+     */
+    public function serviceEntityAdd ($iProfile, $aValues)
+    {
+        bx_import('FormsEntryHelper', $this->_aModule);
+        $sClass = $this->_aModule['class_prefix'] . 'FormsEntryHelper';
+        $oFormsHelper = new $sClass($this);
+        return $oFormsHelper->addData($iProfile, $aValues);
+    }
+
+	/**
+     * Perform redirect after content creation
+     * @return nothing, rediret header is sent
+     */    
+    public function serviceRedirectAfterAdd($aContentInfo)
+    {
+        bx_import('FormsEntryHelper', $this->_aModule);
+        $sClass = $this->_aModule['class_prefix'] . 'FormsEntryHelper';
+        $oFormsHelper = new $sClass($this);
+        $oFormsHelper->redirectAfterAdd($aContentInfo);
+    }
+
+    /**
+     * Get form object for add, edit, view or delete the content 
+     * @param $sType 'add', 'edit', 'view' or 'delete'
+     * @param $aParams optional array with parameters(display name, etc)
+     * @return form object or false on error
+     */
+    public function serviceGetObjectForm ($sType, $aParams = array())
+    {
+        if(!in_array($sType, array('add', 'edit', 'view', 'delete')))
+            return false;
+
+        $mixedContextId = isset($aParams['context_id']) ? $aParams['context_id'] : false;
+
+        $bContext = $mixedContextId !== false;
+        if($bContext && ($oContextProfile = BxDolProfile::getInstance(abs($mixedContextId))) !== false)
+            if($oContextProfile->checkAllowedPostInProfile() !== CHECK_ACTION_RESULT_ALLOWED)
+                return false;
+
+        $CNF = &$this->_oConfig->CNF;
+
+        bx_import('FormsEntryHelper', $this->_aModule);
+        $sClass = $this->_aModule['class_prefix'] . 'FormsEntryHelper';
+        $oFormsHelper = new $sClass($this);
+
+        $sParamsKey = 'ajax_mode';
+        if(isset($aParams[$sParamsKey]) && (bool)$aParams[$sParamsKey] === true)
+            $oFormsHelper->setAjaxMode((bool)$aParams[$sParamsKey]);
+
+        $sParamsKey = 'absolute_action_url';
+        if(isset($aParams[$sParamsKey]) && (bool)$aParams[$sParamsKey] === true)
+            $oFormsHelper->setAbsoluteActionUrl((bool)$aParams[$sParamsKey]);
+
+        $sDisplay = !empty($aParams['display']) ? $aParams['display'] : false;
+
+        $sFunc = 'getObjectForm' . ucfirst($sType);
+        $oForm = $oFormsHelper->$sFunc($sDisplay);
+
+        $sKey = 'FIELD_ALLOW_VIEW_TO';
+        if(!empty($CNF[$sKey]) && !empty($oForm->aInputs[$CNF[$sKey]]) && (!$bContext || $mixedContextId < 0)) {
+            $bContextOwner = $bContext && abs($mixedContextId) == (int)bx_get_logged_profile_id();
+
+            if(!$bContext || $bContextOwner) {
+                $iGc = 0;
+                $iKeyGh = false;
+                foreach($oForm->aInputs[$CNF[$sKey]]['values'] as $iKey => $aValue) {
+                    if(isset($aValue['type']) && in_array($aValue['type'], array('group_header', 'group_end'))) {
+                        if($iKeyGh !== false && $iGc == 0) {
+                            unset($oForm->aInputs[$CNF[$sKey]]['values'][$iKeyGh]);
+                            $iKeyGh = false;
+
+                            if($aValue['type'] == 'group_end')
+                                unset($oForm->aInputs[$CNF[$sKey]]['values'][$iKey]);
+                        }
+
+                        if($aValue['type'] == 'group_header') {
+                            $iGc = 0;
+                            $iKeyGh = $iKey;
+                        }
+
+                        continue;
+                    }
+
+                    //--- Show 'Public' privacy group only in Public post form. 
+                    if(!$bContext && $aValue['key'] == BX_DOL_PG_ALL) {
+                        $iGc += 1;
+                        continue;
+                    }
+
+                    //--- Show a default privacy groups in Profile (for Owner) post form.
+                    if($bContextOwner && (int)$aValue['key'] >= 0) {
+                        $iGc += 1;
+                        continue;
+                    }
+
+                    unset($oForm->aInputs[$CNF[$sKey]]['values'][$iKey]);
+                }
+            }
+            else {
+                $oForm->aInputs[$CNF[$sKey]]['value'] = $mixedContextId;
+                $oForm->aInputs[$CNF[$sKey]]['type'] = 'hidden';
+            }
+        }
+
+        bx_alert('system', 'get_object_form', 0, 0, array(
+            'module' => $this->_oConfig->getName(),
+            'type' => $sType,
+            'params' => $aParams,
+            'form' => &$oForm
+        ));
+
+        return $oForm;
+    }
+
+    /**
+     * Create entry form
+     * @return HTML string
+     */
+    public function serviceEntityCreate ($sParams = false)
+    {
+        $bParamsArray = is_array($sParams);
+
+        $sDisplay = is_string($sParams) ? $sParams : false;
+        if($bParamsArray && !empty($sParams['display']))
+            $sDisplay = $sParams['display'];
+
+        bx_import('FormsEntryHelper', $this->_aModule);
+        $sClass = $this->_aModule['class_prefix'] . 'FormsEntryHelper';
+        $oFormsHelper = new $sClass($this);
+        if($bParamsArray && isset($sParams['dynamic_mode']))
+            $oFormsHelper->setDynamicMode($sParams['dynamic_mode']);
+
+        $mixedResult = $oFormsHelper->addDataForm($sDisplay);
+        if(isset($mixedResult['_dt']) && $mixedResult['_dt'] == 'json') {
+            echoJson($mixedResult);
+            exit;
+        }
+
+        return $mixedResult;
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_base_general Base General
+     * @subsection bx_base_general-forms Forms
+     * @subsubsection bx_base_general-get_create_post_form get_create_post_form
+     * 
+     * @code bx_srv('bx_posts', 'get_create_post_form'); @endcode
+     * 
+     * Get content creation form
+     * @param $aParams additional parameters array: 
+     *                 context_id, ajax_mode, absolute_action_url, displays
+     * 
+     * @see BxBaseModGeneralModule::serviceGetCreatePostForm
+     */
+    /** 
+     * @ref bx_base_general-get_create_post_form "get_create_post_form"
+     */
+    public function serviceGetCreatePostForm($aParams = array())
+    {
+    	$aParams = array_merge($this->_aFormParams, $aParams);
+
+    	$oForm = $this->serviceGetObjectForm('add', $aParams);
+    	if(!$oForm)
+            return ''; 	
+
+    	return $this->serviceEntityCreate($aParams);
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_base_general Base General
+     * @subsection bx_base_general-forms Forms
+     * @subsubsection bx_base_general-entity_edit entity_edit
+     * 
+     * @code bx_srv('bx_posts', 'entity_edit', [...]); @endcode
+     * 
+     * Get content edit form
+     * @param $iContentId content ID
+     * @param $sDisplay optional form display name
+     * 
+     * @see BxBaseModGeneralModule::serviceEntityEdit
+     */
+    /** 
+     * @ref bx_base_general-entity_edit "entity_edit"
+     */
+    public function serviceEntityEdit ($iContentId = 0, $sDisplay = false)
+    {
+        return $this->_serviceEntityForm ('editDataForm', $iContentId, $sDisplay);
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_base_general Base General
+     * @subsection bx_base_general-forms Forms
+     * @subsubsection bx_base_general-entity_delete entity_delete
+     * 
+     * @code bx_srv('bx_posts', 'entity_delete', [...]); @endcode
+     * 
+     * Get content delete form
+     * @param $iContentId content ID
+     * 
+     * @see BxBaseModGeneralModule::serviceEntityDelete
+     */
+    /** 
+     * @ref bx_base_general-entity_delete "entity_delete"
+     */
+    public function serviceEntityDelete ($iContentId = 0)
+    {
+        return $this->_serviceEntityForm ('deleteDataForm', $iContentId);
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_base_general Base General
+     * @subsection bx_base_general-page_blocks Page Blocks
+     * @subsubsection bx_base_general-entity_text_block entity_text_block
+     * 
+     * @code bx_srv('bx_posts', 'entity_text_block', [...]); @endcode
+     * 
+     * Get content text/description
+     * @param $iContentId content ID
+     * 
+     * @see BxBaseModGeneralModule::serviceEntityTextBlock
+     */
+    /** 
+     * @ref bx_base_general-entity_text_block "entity_text_block"
+     */
+    public function serviceEntityTextBlock ($iContentId = 0)
+    {
+        return $this->_serviceEntityForm ('viewDataEntry', $iContentId);
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_base_general Base General
+     * @subsection bx_base_general-page_blocks Page Blocks
+     * @subsubsection bx_base_general-entity_info entity_info
+     * 
+     * @code bx_srv('bx_posts', 'entity_info', [...]); @endcode
+     * 
+     * Get content info
+     * @param $iContentId content ID
+     * 
+     * @see BxBaseModGeneralModule::serviceEntityInfo
+     */
+    /** 
+     * @ref bx_base_general-entity_info "entity_info"
+     */
+    public function serviceEntityInfo ($iContentId = 0, $sDisplay = false)
+    {
+        return $this->_serviceEntityForm ('viewDataForm', $iContentId, $sDisplay);
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_base_general Base General
+     * @subsection bx_base_general-page_blocks Page Blocks
+     * @subsubsection bx_base_general-entity_info_full entity_info_full
+     * 
+     * @code bx_srv('bx_posts', 'entity_info_full', [...]); @endcode
+     * 
+     * Get full content info
+     * @param $iContentId content ID
+     * 
+     * @see BxBaseModGeneralModule::serviceEntityInfoFull
+     */
+    /** 
+     * @ref bx_base_general-entity_info_full "entity_info_full"
+     */
+	public function serviceEntityInfoFull ($iContentId = 0)
+    {
+    	$CNF = &$this->_oConfig->CNF;
+
+    	$sDisplayName = 'OBJECT_FORM_ENTRY_DISPLAY_VIEW_FULL';
+        return $this->_serviceEntityForm ('viewDataForm', $iContentId, !empty($CNF[$sDisplayName]) ? $CNF[$sDisplayName] : false);
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_base_general Base General
+     * @subsection bx_base_general-page_blocks Page Blocks
+     * @subsubsection bx_base_general-entity_info_extended entity_info_extended
+     * 
+     * @code bx_srv('bx_posts', 'entity_info_extended', [...]); @endcode
+     * 
+     * Get extended content info
+     * @param $iContentId content ID
+     * 
+     * @see BxBaseModGeneralModule::serviceEntityInfoExtended
+     */
+    /** 
+     * @ref bx_base_general-entity_info_extended "entity_info_extended"
+     */
+	public function serviceEntityInfoExtended ($iContentId = 0)
+    {
+        return $this->_serviceTemplateFunc ('entryInfo', $iContentId);
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_base_general Base General
+     * @subsection bx_base_general-page_blocks Page Blocks
+     * @subsubsection bx_base_general-entity_location entity_location
+     * 
+     * @code bx_srv('bx_posts', 'entity_location', [...]); @endcode
+     * 
+     * Get content location
+     * @param $iContentId content ID
+     * 
+     * @see BxBaseModGeneralModule::serviceEntityLocation
+     */
+    /** 
+     * @ref bx_base_general-entity_location "entity_location"
+     */
+    public function serviceEntityLocation ($iContentId = 0)
+    {
+        $iContentId = $this->_getContent($iContentId, false);
+        if($iContentId === false)
+            return false;
+
+        return $this->_oTemplate->entryLocation ($iContentId);
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_base_general Base General
+     * @subsection bx_base_general-page_blocks Page Blocks
+     * @subsubsection bx_base_general-entity_comments entity_comments
+     * 
+     * @code bx_srv('bx_posts', 'entity_comments', [...]); @endcode
+     * 
+     * Get content comments
+     * @param $iContentId content ID
+     * 
+     * @see BxBaseModGeneralModule::serviceEntityComments
+     */
+    /** 
+     * @ref bx_base_general-entity_comments "entity_comments"
+     */
+    public function serviceEntityComments ($iContentId = 0)
+    {
+        $CNF = &$this->_oConfig->CNF;
+        if(empty($CNF['OBJECT_COMMENTS']))
+            return '';
+
+        return $this->_entityComments($CNF['OBJECT_COMMENTS'], $iContentId);
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_base_general Base General
+     * @subsection bx_base_general-page_blocks Page Blocks
+     * @subsubsection bx_base_general-entity_attachments entity_attachments
+     * 
+     * @code bx_srv('bx_posts', 'entity_attachments', [...]); @endcode
+     * 
+     * Get content attachments
+     * @param $iContentId content ID
+     * 
+     * @see BxBaseModGeneralModule::serviceEntityAttachments
+     */
+    /** 
+     * @ref bx_base_general-entity_attachments "entity_attachments"
+     */
+    public function serviceEntityAttachments ($iContentId = 0)
+    {
+        return $this->_serviceTemplateFunc ('entryAttachments', $iContentId);
+    }
+    
+    /**
+     * Delete content entry
+     * @param $iContentId content id 
+     * @return error message or empty string on success
+     */
+    public function serviceDeleteEntity ($iContentId, $sFuncDelete = 'deleteData')
+    {
+        bx_import('FormsEntryHelper', $this->_aModule);
+        $sClass = $this->_oConfig->getClassPrefix() . 'FormsEntryHelper';
+        $oFormsHelper = new $sClass($this);
+        return $oFormsHelper->$sFuncDelete($iContentId);
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_base_general Base General
+     * @subsection bx_base_general-menu Menu
+     * @subsubsection bx_base_general-my_entries_actions my_entries_actions
+     * 
+     * @code bx_srv('bx_posts', 'my_entries_actions', [...]); @endcode
+     * 
+     * My entries actions menu
+     * @param $iProfileId profiles ID, 
+     *        if omitted it try to get it from 'profile_id' GET param
+     * 
+     * @see BxBaseModGeneralModule::serviceMyEntriesActions
+     */
+    /** 
+     * @ref bx_base_general-my_entries_actions "my_entries_actions"
+     */
+    public function serviceMyEntriesActions ($iProfileId = 0)
+    {
+        $CNF = &$this->_oConfig->CNF;
+        if(empty($CNF['OBJECT_MENU_ACTIONS_MY_ENTRIES']))
+            return false;
+
+        if (!$iProfileId)
+            $iProfileId = bx_process_input(bx_get('profile_id'), BX_DATA_INT);
+        if (!$iProfileId || !($oProfile = BxDolProfile::getInstance($iProfileId)))
+            return false;
+
+        if ($iProfileId != $this->_iProfileId)
+            return false;
+
+        $oMenu = BxTemplMenu::getObjectInstance($CNF['OBJECT_MENU_ACTIONS_MY_ENTRIES']);
+        return $oMenu ? $oMenu->getCode() : false;
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_base_general Base General
+     * @subsection bx_base_general-menu Menu
+     * @subsubsection bx_base_general-entity_all_actions entity_all_actions
+     * 
+     * @code bx_srv('bx_posts', 'entity_all_actions', [...]); @endcode
+     * 
+     * Entry actions and social sharing actions
+     * @param $mixedContent content ID
+     * @param $aParams additional params
+     * 
+     * @see BxBaseModGeneralModule::serviceEntityAllActions
+     */
+    /** 
+     * @ref bx_base_general-entity_all_actions "entity_all_actions"
+     */
+    public function serviceEntityAllActions ($mixedContent = false, $aParams = array())
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(!empty($mixedContent)) {
+            if(!is_array($mixedContent))
+                $mixedContent = array((int)$mixedContent, (method_exists($this->_oDb, 'getContentInfoById')) ? $this->_oDb->getContentInfoById((int)$mixedContent) : array());
+        }
+        else {
+            $mixedContent = $this->_getContent();
+            if($mixedContent === false)
+                return false;
+        }
+
+        list($iContentId, $aContentInfo) = $mixedContent;
+
+        $sObjectMenu = !empty($aParams['object_menu']) ? $aParams['object_menu'] : '';
+        if(empty($sObjectMenu) && !empty($CNF['OBJECT_MENU_ACTIONS_VIEW_ENTRY_ALL']))
+            $sObjectMenu = $CNF['OBJECT_MENU_ACTIONS_VIEW_ENTRY_ALL'];
+
+        if(empty($sObjectMenu))
+            return false;
+
+        $sEntryTitle = !empty($aParams['entry_title']) ? $aParams['entry_title'] : '';
+        if(empty($sEntryTitle) && !empty($CNF['FIELD_TITLE']) && !empty($aContentInfo[$CNF['FIELD_TITLE']]))
+            $sEntryTitle = $aContentInfo[$CNF['FIELD_TITLE']];
+
+        $sEntryUrl = !empty($aParams['entry_url']) ? $aParams['entry_url'] : '';
+        if(empty($sEntryUrl) && !empty($CNF['URI_VIEW_ENTRY']))
+            $sEntryUrl = BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $iContentId);
+
+        $iEntryThumb = !empty($aParams['entry_thumb']) ? (int)$aParams['entry_thumb'] : 0;
+        if(empty($iEntryThumb) && !empty($CNF['FIELD_THUMB']) && !empty($aContentInfo[$CNF['FIELD_THUMB']]))
+            $iEntryThumb = (int)$aContentInfo[$CNF['FIELD_THUMB']];
+
+        $sObjectStorage = !empty($aParams['object_storage']) ? $aParams['object_storage'] : false;
+        if(empty($sObjectStorage) && !empty($CNF['OBJECT_STORAGE']))
+            $sObjectStorage = $CNF['OBJECT_STORAGE'];
+
+        $sObjectTranscoder = !empty($aParams['object_transcoder']) ? $aParams['object_transcoder'] : false;
+
+        $aMarkers = array(
+            'id' => $iContentId,
+            'module' => $this->_oConfig->getName(),
+            'title' => !empty($sEntryTitle) ? $sEntryTitle : '',
+            'url' => !empty($sEntryUrl) ? $sEntryUrl : '',
+            'img_url' => ''
+        );
+
+        if(!empty($iEntryThumb)) {
+            if(!empty($sObjectTranscoder))
+                $o = BxDolTranscoder::getObjectInstance($sObjectTranscoder);
+            else if(!empty($sObjectStorage))
+                $o = BxDolStorage::getObjectInstance($sObjectStorage);
+
+            $sImageUrl = $o ? $o->getFileUrlById($iEntryThumb) : '';
+            if(!empty($sImageUrl))
+                $aMarkers['img_url'] = $sImageUrl;
+        }
+
+        $oActions = BxDolMenu::getObjectInstance($sObjectMenu);
+        if(!$oActions)
+            return false;
+
+        $oActions->setContentId($iContentId);
+        $oActions->addMarkers($aMarkers);
+        return $this->_oTemplate->entryAllActions($oActions->getCode());
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_base_general Base General
+     * @subsection bx_base_general-menu Menu
+     * @subsubsection bx_base_general-entity_actions entity_actions
+     * 
+     * @code bx_srv('bx_posts', 'entity_actions', [...]); @endcode
+     * 
+     * Entry actions, without social sharing actions
+     * @param $iContentId content ID
+     * 
+     * @see BxBaseModGeneralModule::serviceEntityActions
+     */
+    /** 
+     * @ref bx_base_general-entity_actions "entity_actions"
+     */
+    public function serviceEntityActions ($iContentId = 0)
+    {
+        $iContentId = $this->_getContent($iContentId, false);
+        if($iContentId === false)
+            return false;
+
+        $oMenu = BxTemplMenu::getObjectInstance($this->_oConfig->CNF['OBJECT_MENU_ACTIONS_VIEW_ENTRY']);
+        return $oMenu ? $oMenu->getCode() : false;
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_base_general Base General
+     * @subsection bx_base_general-menu Menu
+     * @subsubsection bx_base_general-entity_social_sharing entity_social_sharing
+     * 
+     * @code bx_srv('bx_posts', 'entity_social_sharing', [...]); @endcode
+     * 
+     * Entry social sharing actions
+     * @param $mixedContent content ID or array with integer content ID and 
+     *                      array with content info, or false
+     * @param $aParams array with additional custom params 
+     *                 which may overwrite some default values
+     * 
+     * @see BxBaseModGeneralModule::serviceEntitySocialSharing
+     */
+    /** 
+     * @ref bx_base_general-entity_social_sharing "entity_social_sharing"
+     */
+    public function serviceEntitySocialSharing($mixedContent = false, $aParams = array())
+    {
+        if(!empty($mixedContent)) {
+            if(!is_array($mixedContent))
+               $mixedContent = array((int)$mixedContent, array());
+        }
+        else {
+            $mixedContent = $this->_getContent();
+            if($mixedContent === false)
+                return false;
+        }
+
+        list($iContentId, $aContentInfo) = $mixedContent;
+
+        $CNF = &$this->_oConfig->CNF;
+
+        $sUri = !empty($aParams['uri']) ? $aParams['uri'] : '';
+        if(empty($sUri) && !empty($CNF['URI_VIEW_ENTRY']))
+            $sUri = $CNF['URI_VIEW_ENTRY'];
+
+        $sUrl = !empty($sUri) ? BxDolPermalinks::getInstance()->permalink('page.php?i=' . $sUri . '&id=' . $iContentId) : '';
+
+        $sTitle = !empty($aParams['title']) ? $aParams['title'] : '';
+        if(empty($sTitle) && !empty($aContentInfo[$CNF['FIELD_TITLE']]))
+            $sTitle = $aContentInfo[$CNF['FIELD_TITLE']];
+
+        $aMarkers = array(
+            'id' => $iContentId,
+            'module' => $this->_aModule['name'],
+            'url' => BX_DOL_URL_ROOT . $sUrl,
+            'title' => $sTitle,
+        );
+
+        $iIdThumb = !empty($aParams['id_thumb']) ? (int)$aParams['id_thumb'] : 0;
+        if(empty($iIdThumb) && !empty($CNF['FIELD_THUMB']) && !empty($aContentInfo[$CNF['FIELD_THUMB']]))
+            $iIdThumb = (int)$aContentInfo[$CNF['FIELD_THUMB']];
+
+        if ($iIdThumb) {
+            $sTranscoder = !empty($aParams['object_transcoder']) ? $aParams['object_transcoder'] : '';
+            $sStorage = !empty($aParams['object_storage']) ? $aParams['object_storage'] : '';
+            if(empty($sStorage) && !empty($CNF['OBJECT_STORAGE']))
+                $sStorage = $CNF['OBJECT_STORAGE'];
+
+            if(!empty($sTranscoder))
+                $o = BxDolTranscoder::getObjectInstance($sTranscoder);
+            else if(!empty($sStorage))
+                $o = BxDolStorage::getObjectInstance($sStorage);
+
+            $sImgUrl = $o ? $o->getFileUrlById($iIdThumb) : '';
+            if($sImgUrl)
+                $aMarkers['img_url'] = $sImgUrl;
+        }
+
+        $oMenu = BxDolMenu::getObjectInstance('sys_social_sharing');
+        $oMenu->addMarkers($aMarkers);
+        $sMenu = $oMenu->getCode();
+
+        if(empty($sMenu))
+            return '';
+
+        return $this->_oTemplate->parseHtmlByName('entry-share.html', array(
+            'menu' => $sMenu
+        ));
+    }
+    
+    /**
+     * Entry context block
+     */
+    public function serviceEntityContext ($iContentId = 0)
+    {
+        return $this->_serviceTemplateFunc ('entryContext', $iContentId);
+    }
+
+    /**
+     * Blocks for lables tree browsing
+     */
+    public function serviceGetLablesTree()
+    {
+		$aBreadcrumbs = array();
+		$this->_getLablesBreadcrumbsArray(bx_get('label'), $aBreadcrumbs);
+		return $this->_getLablesTreeLevel($aBreadcrumbs);
+	}	
+    
+    public function serviceGetLablesBreadcrumbs()
+    {
+		if (bx_get('label')){
+            $aBreadcrumbs = array();
+            $this->_getLablesBreadcrumbsArray(bx_process_input(bx_get('label'), BX_DATA_INT), $aBreadcrumbs);
+            $aLabels = array();
+            foreach (array_reverse($aBreadcrumbs) as $aLabel){
+                $aLabels[] = array(
+                   'value' => $aLabel['value'], 
+                   'url' => $this->_getLablesBrowseUrl($aLabel['id'])
+               );
+            }
+            
+            return $this->_oTemplate->parseHtmlByName('labels_breadcrumbs.html', array('bx_repeat:items' => $aLabels));
+		}
+        return '';
+	}
+    
+    public function serviceBrowseByLabel()
+    {   
+        $CNF = &$this->_oConfig->CNF;
+        if (empty($CNF['OBJECT_METATAGS']))
+            return '';
+        
+		$sMode = 'recent';
+		$sClassSearchResult ='SearchResult';
+		
+		bx_import($sClassSearchResult, $this->_aModule['name']);
+        $sClass = $this->_aModule['class_prefix'] . $sClassSearchResult;
+		
+        $o = new $sClass($sMode, false);
+		$o->setDesignBoxTemplateId(BX_DB_PADDING_DEF);
+        $o->setDisplayEmptyMsg(true);
+        $o->setAjaxPaginate(false);
+        $o->setUnitParams(array('context' => $sMode));
+		
+		if (bx_get('label')){
+			$iLabelId = (int)bx_get('label');
+			$oLabel = BxDolLabel::getInstance();
+			$aLabel = $oLabel->getLabels(array('type' => 'id', 'id' => $iLabelId));
+			$oMetatags = BxDolMetatags::getObjectInstance($CNF['OBJECT_METATAGS']);
+			$sTmp =$oMetatags->keywordsSetSearchCondition($o, $aLabel['value']);
+		}
+		
+        if ($o->isError)
+            return '';
+
+        if ($s = $o->processing())
+            return $s;
+        else
+            return '';
+	}
+    
+    /**
+     * Data for Notifications module
+     */
+    public function serviceGetNotificationsData()
+    {
+    	$sModule = $this->_aModule['name'];
+
+    	$sEventPrivacy = $sModule . '_allow_view_event_to';
+        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'),
+
+                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'),
+
+                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'),
+
+                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'),
+
+                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'),
+
+                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),
+
+                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'),
+
+                array('unit' => $sModule, 'action' => 'commentPost'),
+                array('unit' => $sModule, 'action' => 'commentRemoved'),
+
+                array('unit' => $sModule, 'action' => 'replyPost'),
+                array('unit' => $sModule, 'action' => 'replyRemoved'),
+
+                array('unit' => $sModule, 'action' => 'doVote'),
+                array('unit' => $sModule, 'action' => 'undoVote'),
+
+                array('unit' => $sModule . '_reactions', 'action' => 'doVote'),
+                array('unit' => $sModule . '_reactions', 'action' => 'undoVote'),
+
+                array('unit' => $sModule, 'action' => 'doVoteUp'),
+                array('unit' => $sModule, 'action' => 'doVoteDown'),
+            )
+        );
+
+        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'),
+            ));
+        }
+
+        return $aResult;
+    }
+
+    /**
+     * Entry post for Notifications module
+     */
+    public function serviceGetNotificationsPost($aEvent)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aContentInfo = $this->_oDb->getContentInfoById($aEvent['object_id']);
+        if(empty($aContentInfo) || !is_array($aContentInfo))
+            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']],
+            'entry_privacy' => '', //may be empty or not specified. In this case Public privacy will be used.
+            'lang_key' => '', //may be empty or not specified, or a string, or an array('site' => '...', 'email' => '...', 'push' => '...'). In this case of empty/not specified the default one from Notification module will be used.
+            /*
+             * Custom settings for email and/or push notifications can be provided here. 
+             * Only necessary parts of 'settings' array can be used.
+             * 
+            'settings' => array(
+                'email' => array(
+                    'template' => '',   //--- custom email template
+                    'markers' => '',    //--- markers to parse email parts (subject, body) with
+                    'subject' => ''     //--- custom email subject
+                ),
+                'push' => array(
+                    'subject' => ''     //--- custom push notification subject
+                )
+            )
+             */
+        );
+    }
+
+    public function serviceGetNotificationsPostPublishFailed($aEvent)
+    {
+        return $this->serviceGetNotificationsPost($aEvent);
+    }
+
+    public function serviceGetNotificationsPostPublishSucceeded($aEvent)
+    {
+        return $this->serviceGetNotificationsPost($aEvent);
+    }
+
+    /**
+     * Entry post comment for Notifications module
+     */
+    public function serviceGetNotificationsComment($aEvent)
+    {
+    	$CNF = &$this->_oConfig->CNF;
+
+    	$iContentId = (int)$aEvent['object_id'];
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+        if(empty($aContentInfo) || !is_array($aContentInfo))
+            return array();
+
+		$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.
+		);
+    }
+
+	/**
+     * Entry post reply for Notifications module
+     */
+    public function serviceGetNotificationsReply($aEvent)
+    {
+    	$CNF = &$this->_oConfig->CNF;
+
+    	$oComment = BxDolCmts::getObjectInstance($CNF['OBJECT_COMMENTS'], 0, false);
+        if(!$oComment || !$oComment->isEnabled())
+            return array();
+
+    	$iParentId = (int)$aEvent['object_id'];
+        $aParentInfo = $oComment->getQueryObject()->getCommentsBy(array('type' => 'id', 'id' => $iParentId));
+        if(empty($aParentInfo) || !is_array($aParentInfo))
+            return array();
+
+        $iObjectId = (int)$aParentInfo['cmt_object_id'];
+		$oComment->init($iObjectId);
+
+		return array(
+		    'object_id' => $iObjectId,
+			'entry_sample' => '_cmt_txt_sample_comment_single',
+			'entry_url' => $oComment->getViewUrl($iParentId),
+			'entry_caption' => strmaxtextlen($aParentInfo['cmt_text'], 20, '...'),
+			'entry_author' => (int)$aParentInfo['cmt_author_id'],
+			'subentry_sample' => '_cmt_txt_sample_reply_to',
+			'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.
+		);
+    }
+
+	/**
+     * Entry post vote for Notifications module
+     */
+    public function serviceGetNotificationsVote($aEvent)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $iContentId = (int)$aEvent['object_id'];
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+        if(empty($aContentInfo) || !is_array($aContentInfo))
+            return array();
+
+        $oVote = BxDolVote::getObjectInstance($CNF['OBJECT_VOTES'], $iContentId);
+        if(!$oVote || !$oVote->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_vote_single'],
+            'lang_key' => '', //may be empty or not specified. In this case the default one from Notification module will be used.
+        );
+    }
+
+    /**
+     * Entry post vote for Notifications module
+     */
+    public function serviceGetNotificationsReaction($aEvent)
+    {
+    	$CNF = &$this->_oConfig->CNF;
+
+    	$iContentId = (int)$aEvent['object_id'];
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+        if(empty($aContentInfo) || !is_array($aContentInfo))
+            return array();
+
+        $oReaction = BxDolVote::getObjectInstance($CNF['OBJECT_REACTIONS'], $iContentId);
+        if(!$oReaction || !$oReaction->isEnabled())
+            return array();
+
+        $aSubentry = $oReaction->getTrackBy(array('type' => 'id', 'id' => (int)$aEvent['subobject_id']));
+        if(empty($aSubentry) || !is_array($aSubentry))
+            return array();
+
+        $aSubentrySampleParams = array();
+        $aReaction = $oReaction->getReaction($aSubentry['reaction']);
+        if(!empty($aReaction['title']))
+            $aSubentrySampleParams[] = $aReaction['title'];
+        else
+            $aSubentrySampleParams[] = '_undefined';
+
+        $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_reaction_single'],
+            'subentry_sample_params' => $aSubentrySampleParams,
+            'lang_key' => '', //may be empty or not specified. In this case the default one from Notification module will be used.
+        );
+    }
+
+    /**
+     * Entry post score -> vote up for Notifications module
+     */
+    public function serviceGetNotificationsScoreUp($aEvent)
+    {
+    	return $this->_serviceGetNotificationsScore('up', $aEvent);
+    }
+
+    /**
+     * Entry post score -> vote up for Notifications module
+     */
+    public function serviceGetNotificationsScoreDown($aEvent)
+    {
+    	return $this->_serviceGetNotificationsScore('down', $aEvent);
+    }
+
+    protected function _serviceGetNotificationsScore($sType, $aEvent)
+    {
+    	$CNF = &$this->_oConfig->CNF;
+
+    	$iContentId = (int)$aEvent['object_id'];
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+        if(empty($aContentInfo) || !is_array($aContentInfo))
+            return array();
+
+        $oScore = BxDolScore::getObjectInstance($CNF['OBJECT_SCORES'], $iContentId);
+        if(!$oScore || !$oScore->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_score_' . $sType . '_single'],
+            'lang_key' => '', //may be empty or not specified. In this case the default one from Notification module will be used.
+        );
+    }
+    
+    /**
+     * Data for Timeline module
+     */
+    public function serviceGetTimelineData()
+    {
+    	$sModule = $this->_aModule['name'];
+
+        return array(
+            'handlers' => array(
+                array('group' => $sModule . '_object', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'added', 'module_name' => $sModule, 'module_method' => 'get_timeline_post', 'module_class' => 'Module',  'groupable' => 0, 'group_by' => ''),
+                array('group' => $sModule . '_object', 'type' => 'update', 'alert_unit' => $sModule, 'alert_action' => 'edited'),
+                array('group' => $sModule . '_object', 'type' => 'delete', 'alert_unit' => $sModule, 'alert_action' => 'deleted')
+            ),
+            'alerts' => array(
+                array('unit' => $sModule, 'action' => 'added'),
+                array('unit' => $sModule, 'action' => 'edited'),
+                array('unit' => $sModule, 'action' => 'deleted'),
+            )
+        );
+    }
+
+    /**
+     * Entry post for Timeline module
+     */
+    public function serviceGetTimelinePost($aEvent, $aBrowseParams = array())
+    {
+        $aContentInfo = $this->_oDb->getContentInfoById($aEvent['object_id']);
+        if(empty($aContentInfo) || !is_array($aContentInfo))
+            return false;
+
+        $CNF = &$this->_oConfig->CNF;
+
+        if((!empty($CNF['FIELD_STATUS']) && $aContentInfo[$CNF['FIELD_STATUS']] != 'active') || (!empty($CNF['FIELD_STATUS_ADMIN']) && $aContentInfo[$CNF['FIELD_STATUS_ADMIN']] != 'active'))
+            return false;
+
+        $iUserId = $this->getUserId();
+        $iAuthorId = (int)$aContentInfo[$CNF['FIELD_AUTHOR']];
+        $iAuthorIdAbs = abs($iAuthorId);
+        if($iAuthorId < 0 && ((is_numeric($aEvent['owner_id']) && $iAuthorIdAbs == (int)$aEvent['owner_id']) || (is_array($aEvent['owner_id']) && in_array($iAuthorIdAbs, $aEvent['owner_id']))) && $iAuthorIdAbs != $iUserId)
+            return false;
+
+        //--- Views
+        $oViews = isset($CNF['OBJECT_VIEWS']) ? BxDolView::getObjectInstance($CNF['OBJECT_VIEWS'], $aContentInfo[$CNF['FIELD_ID']]) : null;
+
+        $aViews = array();
+        if ($oViews && $oViews->isEnabled())
+            $aViews = array(
+                'system' => $CNF['OBJECT_VIEWS'],
+                'object_id' => $aContentInfo[$CNF['FIELD_ID']],
+                'count' => $aContentInfo['views']
+            );
+
+        //--- Votes
+        $oVotes = isset($CNF['OBJECT_VOTES']) ? BxDolVote::getObjectInstance($CNF['OBJECT_VOTES'], $aContentInfo[$CNF['FIELD_ID']]) : null;
+
+        $aVotes = array();
+        if ($oVotes && $oVotes->isEnabled())
+            $aVotes = array(
+                'system' => $CNF['OBJECT_VOTES'],
+                'object_id' => $aContentInfo[$CNF['FIELD_ID']],
+                'count' => $aContentInfo['votes']
+            );
+        
+        //--- Reactions
+        $oReactions = isset($CNF['OBJECT_REACTIONS']) ? BxDolVote::getObjectInstance($CNF['OBJECT_REACTIONS'], $aContentInfo[$CNF['FIELD_ID']]) : null;
+
+        $aReactions = array();
+        if ($oReactions && $oReactions->isEnabled())
+            $aReactions = array(
+                'system' => $CNF['OBJECT_REACTIONS'],
+                'object_id' => $aContentInfo[$CNF['FIELD_ID']],
+                'count' => $aContentInfo['rvotes']
+            );
+
+        //--- Scores
+        $oScores = isset($CNF['OBJECT_SCORES']) ? BxDolScore::getObjectInstance($CNF['OBJECT_SCORES'], $aContentInfo[$CNF['FIELD_ID']]) : null;
+
+        $aScores = array();
+        if ($oScores && $oScores->isEnabled())
+            $aScores = array(
+                'system' => $CNF['OBJECT_SCORES'],
+                'object_id' => $aContentInfo[$CNF['FIELD_ID']],
+                'score' => $aContentInfo['score']
+            );
+
+        //--- Reports
+        $oReports = isset($CNF['OBJECT_REPORTS']) ? BxDolReport::getObjectInstance($CNF['OBJECT_REPORTS'], $aContentInfo[$CNF['FIELD_ID']]) : null;
+
+        $aReports = array();
+        if ($oReports && $oReports->isEnabled())
+            $aReports = array(
+                'system' => $CNF['OBJECT_REPORTS'],
+                'object_id' => $aContentInfo[$CNF['FIELD_ID']],
+                'count' => $aContentInfo['reports']
+            );
+
+        //--- Comments
+        $oCmts = isset($CNF['OBJECT_COMMENTS']) ? BxDolCmts::getObjectInstance($CNF['OBJECT_COMMENTS'], $aContentInfo[$CNF['FIELD_ID']]) : null;
+
+        $aComments = array();
+        if($oCmts && $oCmts->isEnabled())
+            $aComments = array(
+                'system' => $CNF['OBJECT_COMMENTS'],
+                'object_id' => $aContentInfo[$CNF['FIELD_ID']],
+                'count' => $aContentInfo['comments']
+            );
+
+        //--- Title & Description
+        $sTitle = !empty($aContentInfo[$CNF['FIELD_TITLE']]) ? $aContentInfo[$CNF['FIELD_TITLE']] : '';
+        if(empty($sTitle) && !empty($aContentInfo[$CNF['FIELD_TEXT']]))
+            $sTitle = $aContentInfo[$CNF['FIELD_TEXT']];
+
+        $iOwnerId = $iAuthorIdAbs;
+        if(isset($aEvent['object_privacy_view']) && (int)$aEvent['object_privacy_view'] < 0)
+            $iOwnerId = abs($aEvent['object_privacy_view']);
+
+        $bCache = true;
+        $aContent = $this->_getContentForTimelinePost($aEvent, $aContentInfo, $aBrowseParams);
+        if(isset($aContent['_cache'])) {
+            $bCache = (bool)$aContent['_cache'];
+
+            unset($aContent['_cache']);
+        }
+
+        return array(
+            '_cache' => $bCache,
+            'owner_id' => $iOwnerId,
+            'object_owner_id' => $iAuthorId,
+            'icon' => !empty($CNF['ICON']) ? $CNF['ICON'] : '',
+            'sample' => isset($CNF['T']['txt_sample_single_with_article']) ? $CNF['T']['txt_sample_single_with_article'] : $CNF['T']['txt_sample_single'],
+            'sample_wo_article' => $CNF['T']['txt_sample_single'],
+            'sample_action' => isset($CNF['T']['txt_sample_action']) ? $CNF['T']['txt_sample_action'] : '',
+            'url' => BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $aContentInfo[$CNF['FIELD_ID']]),
+            'content' => $aContent, //a string to display or array to parse default template before displaying.
+            'date' => $aContentInfo[$CNF['FIELD_ADDED']],
+            'views' => $aViews,
+            'votes' => $aVotes,
+            'reactions' => $aReactions,
+            'scores' => $aScores,
+            'reports' => $aReports,
+            'comments' => $aComments,
+            'title' => $sTitle, //may be empty.
+            'description' => '' //may be empty.
+        );
+    }
+
+    public function serviceGetTimelinePostAllowedView($aEvent)
+    {
+        $iContentId = (int)$aEvent['object_id'];
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+        if(empty($aContentInfo) || !is_array($aContentInfo))
+            return _t('_sys_txt_access_denied');
+
+        return $this->serviceCheckAllowedViewForProfile($aContentInfo);
+    }
+
+    /**
+     * Check particular action permission without content
+     * @param $sAction action to check, for example: Browse, Add
+     * @param $iContentId content ID
+     * @return message on error, or CHECK_ACTION_RESULT_ALLOWED when allowed
+     */ 
+    public function serviceCheckAllowed($sAction, $isPerformAction = false)
+    {
+        $sMethod = 'checkAllowed' . bx_gen_method_name($sAction);
+        if (!method_exists($this, $sMethod))
+            return _t('_sys_request_method_not_found_cpt');
+
+        return $this->$sMethod($isPerformAction);
+    }
+    
+    /**
+     * Check particular action permission with content
+     * @param $sAction action to check, for example: View, Edit
+     * @param $iContentId content ID
+     * @return message on error, or CHECK_ACTION_RESULT_ALLOWED when allowed
+     */ 
+    public function serviceCheckAllowedWithContent($sAction, $iContentId, $isPerformAction = false)
+    {
+        if (!$iContentId || !($aContentInfo = $this->_oDb->getContentInfoById($iContentId)))
+            return _t('_sys_request_page_not_found_cpt');
+
+        $sMethod = 'checkAllowed' . bx_gen_method_name($sAction);
+        if (!method_exists($this, $sMethod))
+            return _t('_sys_request_method_not_found_cpt');
+
+        return $this->$sMethod($aContentInfo, $isPerformAction);
+    }
+
+    /**
+     * Check particular action permission with content for specified profile
+     * @param $sAction action to check, for example: View, Edit
+     * @param $iContentId content ID
+     * @param $iProfileId profile ID which the permissions to be cheked for
+     * @return message on error, or CHECK_ACTION_RESULT_ALLOWED when allowed
+     */ 
+    public function serviceCheckAllowedWithContentForProfile($sAction, $iContentId, $iProfileId, $isPerformAction = false)
+    {
+        if (!$iContentId || !($aContentInfo = $this->_oDb->getContentInfoById($iContentId)))
+            return _t('_sys_request_page_not_found_cpt');
+
+        $sMethod = 'checkAllowed' . bx_gen_method_name($sAction) . 'ForProfile';
+        if (!method_exists($this, $sMethod))
+            return _t('_sys_request_method_not_found_cpt');
+
+        return $this->$sMethod($aContentInfo, $iProfileId, $isPerformAction);
+    }
+    
+    public function serviceCheckAllowedCommentsView($iContentId, $sObjectComments) 
+    {
+        return $this->serviceCheckAllowedWithContent('comments_view', $iContentId);
+    }
+    
+    public function serviceCheckAllowedCommentsPost($iContentId, $sObjectComments) 
+    {
+        return $this->serviceCheckAllowedWithContent('comments_post', $iContentId);
+    }
+
+    public function serviceGetContentOwnerProfileId ($iContentId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        // file owner must be author of the content or profile itself in case of profile based module
+        if ($iContentId) {
+            $sModule = $this->getName();
+            if ($this instanceof iBxDolProfileService && BxDolService::call($sModule, 'act_as_profile')) {
+                $oProfile = BxDolProfile::getInstanceByContentAndType($iContentId, $sModule);
+            }
+            else {
+                $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+                $oProfile = $aContentInfo ? BxDolProfile::getInstance($aContentInfo[$CNF['FIELD_AUTHOR']]) : null;
+            }
+            $iProfileId = $oProfile ? $oProfile->id() : bx_get_logged_profile_id();
+        }
+        else {
+            $iProfileId = bx_get_logged_profile_id();
+        }
+
+        return $iProfileId;
+    }
+	
+    public function serviceGetBadges($iContentId,  $bIsSingle = false, $bIsCompact  = false)
+    {
+        $sModule = $this->getName();
+
+        $oBadges = BxDolBadges::getInstance();
+        $aBadges = $oBadges->getData(array(
+            'type' => ($bIsSingle ? 'by_module&object2_single' : 'by_module&object2'),
+            'module' => $sModule,
+            'object_id' => $iContentId
+        ));
+
+        $sResult = false;
+        bx_alert('system', 'get_badges', 0, 0, array(
+            'module' => $sModule, 
+            'content_id' => $iContentId,
+            'is_single' => $bIsSingle,
+            'is_compact' => $bIsCompact,
+            'badges' => $aBadges, 
+            'override_result' => &$sResult
+        ));
+        if($sResult !== false)
+            return $sResult;
+
+        if($bIsSingle && count($aBadges) > 0)
+            return BxDolService::call('system', 'get_badge', array($aBadges[0], $bIsCompact), 'TemplServices');
+
+        $aBadgesOutput = array();
+        foreach($aBadges as $aBadge)
+            $aBadgesOutput[] =  array('badge' => BxDolService::call('system', 'get_badge', array($aBadge, $bIsCompact), 'TemplServices'));
+
+        return $this->_oTemplate->parseHtmlByName('badges.html', array(
+            'bx_repeat:items' => $aBadgesOutput,
+        ));
+    }
+
+    /**
+     * ======
+     * PERMISSION METHODS
+     * ======
+     */
+    public function serviceCheckAllowedViewForProfile ($aDataEntry, $isPerformAction = false, $iProfileId = false)
+    {
+        $mixedResult = $this->_serviceCheckAllowedViewForProfile ($aDataEntry, $isPerformAction, $iProfileId);
+
+        // check alert to allow custom checks        
+        bx_alert('system', 'check_allowed_view', 0, 0, array('module' => $this->getName(), 'content_info' => $aDataEntry, 'profile_id' => $iProfileId, 'override_result' => &$mixedResult));
+
+        return $mixedResult;
+    }
+
+    public function checkAllowedSetThumb ($iContentId = 0)
+    {
+        return CHECK_ACTION_RESULT_ALLOWED;
+    }
+    
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden. So make sure to make "true === " checking.
+     */
+    public function checkAllowedBrowse ()
+    {
+        // check alert to allow custom checks
+        $mixedResult = null;
+        bx_alert('system', 'check_allowed_browse', 0, 0, array('module' => $this->getName(), 'profile_id' => $this->_iProfileId, 'override_result' => &$mixedResult));
+        if($mixedResult !== null)
+            return $mixedResult;
+
+        return CHECK_ACTION_RESULT_ALLOWED;
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden. So make sure to make strict(===) checking.
+     */
+    public function checkAllowedView ($aDataEntry, $isPerformAction = false)
+    {
+        return $this->serviceCheckAllowedViewForProfile ($aDataEntry, $isPerformAction);
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden. So make sure to make strict(===) checking.
+     */
+    public function checkAllowedViewForProfile ($aDataEntry, $iProfileId, $isPerformAction = false)
+    {
+        return $this->serviceCheckAllowedViewForProfile ($aDataEntry, $isPerformAction, $iProfileId);
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden. So make sure to make strict(===) checking.
+     */
+    public function checkAllowedAdd ($isPerformAction = false)
+    {
+        // check ACL
+        $aCheck = checkActionModule($this->_iProfileId, 'create entry', $this->getName(), $isPerformAction);
+        if ($aCheck[CHECK_ACTION_RESULT] !== CHECK_ACTION_RESULT_ALLOWED)
+            return $aCheck[CHECK_ACTION_MESSAGE];
+        return CHECK_ACTION_RESULT_ALLOWED;
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden. So make sure to make strict(===) checking.
+     */
+    public function checkAllowedEdit ($aDataEntry, $isPerformAction = false)
+    {
+        // moderator and owner always have access
+        if ($aDataEntry[$this->_oConfig->CNF['FIELD_AUTHOR']] == $this->_iProfileId || -$aDataEntry[$this->_oConfig->CNF['FIELD_AUTHOR']] == $this->_iProfileId || $this->_isModerator($isPerformAction))
+            return CHECK_ACTION_RESULT_ALLOWED;
+        return _t('_sys_txt_access_denied');
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden. So make sure to make strict(===) checking.
+     */
+    public function checkAllowedDelete (&$aDataEntry, $isPerformAction = false)
+    {
+        // moderator always has access
+        if ($this->_isModerator($isPerformAction))
+            return CHECK_ACTION_RESULT_ALLOWED;
+
+        // check ACL
+        $aCheck = checkActionModule($this->_iProfileId, 'delete entry', $this->getName(), $isPerformAction);
+        if (($aDataEntry[$this->_oConfig->CNF['FIELD_AUTHOR']] == $this->_iProfileId || -$aDataEntry[$this->_oConfig->CNF['FIELD_AUTHOR']] == $this->_iProfileId) && $aCheck[CHECK_ACTION_RESULT] === CHECK_ACTION_RESULT_ALLOWED)
+            return CHECK_ACTION_RESULT_ALLOWED;
+
+        return _t('_sys_txt_access_denied');
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden. So make sure to make strict(===) checking.
+     */
+    public function checkAllowedSetMembership (&$aDataEntry, $isPerformAction = false)
+    {
+        // admin always has access
+        if (isAdmin())
+            return CHECK_ACTION_RESULT_ALLOWED;
+
+        // check ACL
+        $aCheck = checkActionModule($this->_iProfileId, 'set acl level', 'system', $isPerformAction);
+        if ($aCheck[CHECK_ACTION_RESULT] !== CHECK_ACTION_RESULT_ALLOWED)
+            return $aCheck[CHECK_ACTION_MESSAGE];
+
+        return CHECK_ACTION_RESULT_ALLOWED;
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden. So make sure to make strict(===) checking.
+     */
+    public function checkAllowedEditAnyEntry ($isPerformAction = false)
+    {
+    	return $this->checkAllowedEditAnyEntryForProfile($isPerformAction, $this->_iProfileId);
+    }
+    
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden. So make sure to make strict(===) checking.
+     */
+    public function checkAllowedEditAnyEntryForProfile ($isPerformAction = false, $iProfileId = false)
+    {
+        if(!$iProfileId)
+            $iProfileId = $this->_iProfileId;
+
+    	$aCheck = checkActionModule($iProfileId, 'edit any entry', $this->getName(), $isPerformAction);
+    	if($aCheck[CHECK_ACTION_RESULT] === CHECK_ACTION_RESULT_ALLOWED)
+    		return CHECK_ACTION_RESULT_ALLOWED;
+
+    	return _t('_sys_txt_access_denied');
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden. So make sure to make "true === " checking.
+     */
+    public function checkAllowedCommentsView ($aContentInfo, $isPerformAction = false)
+    {
+        return $this->checkAllowedView ($aContentInfo, $isPerformAction);
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden. So make sure to make "true === " checking.
+     */
+    public function checkAllowedCommentsPost ($aContentInfo, $isPerformAction = false)
+    {
+        return $this->checkAllowedView ($aContentInfo, $isPerformAction);
+    }
+
+    protected function _serviceCheckAllowedViewForProfile ($aDataEntry, $isPerformAction, $iProfileId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(!$iProfileId)
+            $iProfileId = $this->_iProfileId;
+
+        // moderator and owner always have access
+        if(!empty($iProfileId) && (abs($aDataEntry[$CNF['FIELD_AUTHOR']]) == (int)$iProfileId || $this->_isModeratorForProfile($isPerformAction, $iProfileId)))
+            return CHECK_ACTION_RESULT_ALLOWED;
+
+        // check ACL
+        $aCheck = checkActionModule($iProfileId, 'view entry', $this->getName(), $isPerformAction);
+        if($aCheck[CHECK_ACTION_RESULT] !== CHECK_ACTION_RESULT_ALLOWED)
+            return $aCheck[CHECK_ACTION_MESSAGE];
+
+        // check privacy
+        if(!empty($CNF['OBJECT_PRIVACY_VIEW'])) {
+            $oPrivacy = BxDolPrivacy::getObjectInstance($CNF['OBJECT_PRIVACY_VIEW']);
+            if ($oPrivacy && !$oPrivacy->check($aDataEntry[$CNF['FIELD_ID']], $iProfileId))
+                return _t('_sys_access_denied_to_private_content');
+        }
+
+        return CHECK_ACTION_RESULT_ALLOWED;
+    }
+
+    public function _serviceBrowse ($sMode, $aParams = false, $iDesignBox = BX_DB_PADDING_DEF, $bDisplayEmptyMsg = false, $bAjaxPaginate = true, $sClassSearchResult = 'SearchResult')
+    {
+        if (CHECK_ACTION_RESULT_ALLOWED !== ($sMsg = $this->checkAllowedBrowse()))
+            return MsgBox($sMsg);
+
+        bx_import($sClassSearchResult, $this->_aModule);
+        $sClass = $this->_aModule['class_prefix'] . $sClassSearchResult;
+        $o = new $sClass($sMode, $aParams);
+
+        $o->setDesignBoxTemplateId($iDesignBox);
+        $o->setDisplayEmptyMsg($bDisplayEmptyMsg);
+        $o->setAjaxPaginate($bAjaxPaginate);
+        $o->setUnitParams(array('context' => $sMode));
+
+        if ($o->isError)
+            return '';
+
+        if ($s = $o->processing())
+            return $s;
+        else
+            return '';
+    }
+
+    // ====== COMMON METHODS
+    public function alertAfterAdd($aContentInfo) {}
+    
+    public function alertAfterEdit($aContentInfo) {}
+
+    public function onPublished($iContentId) {}
+
+    public function onFailed($iContentId) {}
+
+    public function processMetasAdd($iContentId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(empty($CNF['OBJECT_METATAGS'])) 
+            return false;
+
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+
+        $bFldStatus = isset($CNF['FIELD_STATUS']);
+        $bFldStatusAdmin = isset($CNF['FIELD_STATUS_ADMIN']);
+        $bContentInfo = $aContentInfo && (!$bFldStatus || ($bFldStatus && $aContentInfo[$CNF['FIELD_STATUS']] == 'active')) && (!$bFldStatusAdmin || ($bFldStatusAdmin && $aContentInfo[$CNF['FIELD_STATUS_ADMIN']] == 'active'));
+        if(!$bContentInfo)
+            return false;
+
+        $oMetatags = BxDolMetatags::getObjectInstance($CNF['OBJECT_METATAGS']);
+        $oMetatags->metaAddAuto($iContentId, $aContentInfo, $CNF, $CNF['OBJECT_FORM_ENTRY_DISPLAY_ADD']);
+
+        $sKey = 'FIELD_LOCATION';
+        if($oMetatags->locationsIsEnabled() && !empty($CNF[$sKey]) && !empty($aContentInfo[$CNF[$sKey]])) {
+            $aLocation = unserialize($aContentInfo[$CNF[$sKey]]);
+            if(!empty($aLocation) && is_array($aLocation))
+                call_user_func_array(array($oMetatags, 'locationsAdd'), array_merge(array($iContentId), array_values($aLocation)));
+        }
+
+        $sKey = 'FIELD_LABELS';
+        if($oMetatags->keywordsIsEnabled() && !empty($CNF[$sKey]) && !empty($aContentInfo[$CNF[$sKey]])) {
+            $aLabels = unserialize($aContentInfo[$CNF[$sKey]]);
+            if(!empty($aLabels) && is_array($aLabels))
+                foreach ($aLabels as $sLabel)
+                    $oMetatags->keywordsAddOne($iContentId, $sLabel, false);
+        }
+        
+        return true;
+    }
+
+    public function processMetasEdit($iContentId, $oForm)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(empty($CNF['OBJECT_METATAGS']))
+            return false;
+
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+
+        $bFldStatus = isset($CNF['FIELD_STATUS']);
+        $bFldStatusAdmin = isset($CNF['FIELD_STATUS_ADMIN']);
+        $bContentInfo = $aContentInfo && (!$bFldStatus || ($bFldStatus && $aContentInfo[$CNF['FIELD_STATUS']] == 'active')) && (!$bFldStatusAdmin || ($bFldStatusAdmin && $aContentInfo[$CNF['FIELD_STATUS_ADMIN']] == 'active'));
+        if(!$bContentInfo)
+            return false;
+
+        $oMetatags = BxDolMetatags::getObjectInstance($CNF['OBJECT_METATAGS']);
+        $oMetatags->metaAddAuto($iContentId, $aContentInfo, $CNF, $CNF['OBJECT_FORM_ENTRY_DISPLAY_EDIT']);
+
+        $sKey = 'FIELD_LOCATION';
+        if($oMetatags->locationsIsEnabled() && !empty($CNF[$sKey]) && !empty($aContentInfo[$CNF[$sKey]])) {
+            $aLocation = unserialize($aContentInfo[$CNF[$sKey]]);
+            if(!empty($aLocation) && is_array($aLocation))
+                call_user_func_array(array($oMetatags, 'locationsAdd'), array_merge(array($iContentId), array_values($aLocation)));
+        }
+
+        $sKey = 'FIELD_LABELS';
+        if($oMetatags->keywordsIsEnabled() && !empty($CNF[$sKey]) && !empty($aContentInfo[$CNF[$sKey]])) {
+            $aLabels = unserialize($aContentInfo[$CNF[$sKey]]);
+            if(!empty($aLabels) && is_array($aLabels))
+                foreach ($aLabels as $sLabel)
+                    $oMetatags->keywordsAddOne($iContentId, $sLabel, false);
+        }
+
+        return true;
+    }
+
+    public function getEntryImageData($aContentInfo, $sField = 'FIELD_THUMB', $aTranscoders = array())
+    {
+        if(empty($aTranscoders))
+            $aTranscoders = array('OBJECT_TRANSCODER_COVER', 'OBJECT_IMAGES_TRANSCODER_COVER', 'OBJECT_IMAGES_TRANSCODER_GALLERY');
+
+        $CNF = &$this->_oConfig->CNF;
+        if(empty($CNF[$sField]) || empty($aContentInfo[$CNF[$sField]]) || empty($CNF['OBJECT_STORAGE']))
+            return false;
+
+        $iId = (int)$aContentInfo[$CNF[$sField]];
+        foreach($aTranscoders as $sTranscoder)
+            if(!empty($CNF[$sTranscoder]))
+                return array('id' => $iId, 'transcoder' => $CNF[$sTranscoder]);
+
+        return array('id' => $iId, 'object' => $CNF['OBJECT_STORAGE']);
+    }
+
+    public function getProfileId()
+    {
+    	return bx_get_logged_profile_id();
+    }
+
+	public function getProfileInfo($iUserId = 0)
+    {
+        $oProfile = $this->getObjectUser($iUserId);
+
+        $oAccount = null;
+        if($oProfile && !($oProfile instanceof BxDolProfileUndefined) && !($oProfile instanceof BxDolProfileAnonymous))
+            $oAccount = $oProfile->getAccountObject();
+        $bAccount = !empty($oAccount);
+
+        return array(
+        	'id' => $oProfile->id(),
+            'name' => $oProfile->getDisplayName(),
+        	'email' => $bAccount ? $oAccount->getEmail() : '',
+            'link' => $oProfile->getUrl(),
+            'icon' => $oProfile->getIcon(),
+        	'thumb' => $oProfile->getThumb(),
+        	'avatar' => $oProfile->getAvatar(),
+            'unit' => $oProfile->getUnit(),
+        	'active' => $oProfile->isActive(),
+        );
+    }
+    
+    public function getObjectUser($iUserId = 0)
+    {
+    	bx_import('BxDolProfile');
+        $oProfile = BxDolProfile::getInstanceMagic($iUserId);
+        return $oProfile;
+    }
+
+    public function getObjectFavorite($sSystem = '', $iId = 0)
+    {
+        $CNF = &$this->_oConfig->CNF;
+        if(empty($sSystem) && !empty($CNF['OBJECT_FAVORITES']))
+            $sSystem = $CNF['OBJECT_FAVORITES'];
+
+        if(empty($sSystem))
+            return false;
+
+        $oFavorite = BxDolFavorite::getObjectInstance($sSystem, $iId, true, $this->_oTemplate);
+        if(!$oFavorite || !$oFavorite->isEnabled())
+            return false;
+
+        return $oFavorite;
+    }
+
+	public function getUserId()
+    {
+        return isLogged() ? bx_get_logged_profile_id() : 0;
+    }
+
+    public function getUserIp()
+    {
+        return getVisitorIP();
+    }
+
+    public function getUserInfo($iUserId = 0)
+    {
+        $oProfile = BxDolProfile::getInstanceMagic($iUserId);
+
+        return array(
+            $oProfile->getDisplayName(),
+            $oProfile->getUrl(),
+            $oProfile->getThumb(),
+            $oProfile->getUnit(),
+            $oProfile->getUnit(0, array('template' => 'unit_wo_info'))
+        );
+    }
+    
+    public function getUserInfoWithBadges($iUserId = 0)
+    {
+        $oProfile = BxDolProfile::getInstanceMagic($iUserId);
+
+        return array(
+            $oProfile->getDisplayName(),
+            $oProfile->getUrl(),
+            $oProfile->getThumb(),
+            $oProfile->getUnit(),
+            $oProfile->getUnit(0, array('template' => 'unit_wo_info')),
+            $oProfile->getBadges()
+        );
+    }
+
+    public function isMenuItemVisible($sObject, &$aItem, &$aContentInfo)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        // default visible settings
+        if(!BxDolAcl::getInstance()->isMemberLevelInSet($aItem['visible_for_levels']))
+            return false;
+
+        if (!empty($aItem['visibility_custom'])) {
+            $oMenu = BxDolMenu::getObjectInstance($sObject);
+            if ($oMenu && !BxDolService::callSerialized($aItem['visibility_custom'], $oMenu->getMarkers()))
+                return false;
+        }
+        
+        // get custom function name to check menu item visibility
+        $sFuncCheckAccess = false;
+        if(isset($CNF['MENU_ITEM_TO_METHOD'][$sObject][$aItem['name']]))
+            $sFuncCheckAccess = $CNF['MENU_ITEM_TO_METHOD'][$sObject][$aItem['name']];
+
+        // check custom visibility settings defined in module config class
+        if(!isset($aContentInfo))
+            $aContentInfo = array();
+
+        if($sFuncCheckAccess && CHECK_ACTION_RESULT_ALLOWED !== call_user_func_array(array($this, $sFuncCheckAccess), array(&$aContentInfo)))
+            return false;
+
+        return true;
+    }
+
+    public function _isModerator ($isPerformAction = false)
+    {
+        return $this->_isModeratorForProfile($isPerformAction, $this->_iProfileId);
+    }
+
+    public function _isModeratorForProfile($isPerformAction = false, $iProfileId = false)
+    {
+        return CHECK_ACTION_RESULT_ALLOWED === $this->checkAllowedEditAnyEntryForProfile ($isPerformAction, $iProfileId);
+    }
+
+    public function _prepareAuditParams($aContentInfo, $bIsSaveData = true, $aOverrideAuditParams  = array())
+    {
+        $CNF = &$this->_oConfig->CNF;
+        
+        $iContextId = isset($CNF['FIELD_ALLOW_VIEW_TO']) && (!empty($aContentInfo[$CNF['FIELD_ALLOW_VIEW_TO']]) && (int)$aContentInfo[$CNF['FIELD_ALLOW_VIEW_TO']] < 0) ? - $aContentInfo[$CNF['FIELD_ALLOW_VIEW_TO']] : 0;
+        
+        $AuditParams = array(
+            'content_title' => (isset($CNF['FIELD_TITLE']) && isset($aContentInfo[$CNF['FIELD_TITLE']])) ? $aContentInfo[$CNF['FIELD_TITLE']] : '',
+            'context_profile_id' => $iContextId,
+            'content_info_object' =>  isset($CNF['OBJECT_CONTENT_INFO']) ? $CNF['OBJECT_CONTENT_INFO'] : '',
+            'data' => ($bIsSaveData ? $aContentInfo : array())
+        );
+        if ($iContextId > 0)
+            $AuditParams['context_profile_title'] = BxDolProfile::getInstance($iContextId)->getDisplayName();
+        
+        $AuditParams = array_merge($AuditParams, $aOverrideAuditParams);
+        
+        return $AuditParams;
+    }
+    
+    // ====== PROTECTED METHODS
+
+    protected function _serviceEntityForm ($sFormMethod, $iContentId = 0, $sDisplay = false, $sCheckFunction = false, $bErrorMsg = true)
+    {
+        $iContentId = $this->_getContent($iContentId, false);
+        if($iContentId === false)
+            return false;
+
+        bx_import('FormsEntryHelper', $this->_aModule);
+        $sClass = $this->_aModule['class_prefix'] . 'FormsEntryHelper';
+        $oFormsHelper = new $sClass($this);
+        return $oFormsHelper->$sFormMethod((int)$iContentId, $sDisplay, $sCheckFunction, $bErrorMsg);
+    }
+
+    protected function _serviceTemplateFunc ($sFunc, $iContentId, $sFuncGetContent = 'getContentInfoById')
+    {
+        $mixedContent = $this->_getContent($iContentId, $sFuncGetContent);
+        if($mixedContent === false)
+            return false;
+
+        list($iContentId, $aContentInfo) = $mixedContent;
+
+        return $this->_oTemplate->$sFunc($aContentInfo);
+    }
+
+    protected function _rss ($aArgs, $sClass = 'SearchResult')
+    {
+        $sMode = array_shift($aArgs);
+
+        if (CHECK_ACTION_RESULT_ALLOWED !== ($sMsg = $this->checkAllowedBrowse())) {
+            $this->_oTemplate->displayAccessDenied ($sMsg);
+            exit;
+        }
+
+        $aParams = $this->_buildRssParams($sMode, $aArgs);
+
+        bx_import ($sClass, $this->_aModule);
+        $sClass = $this->_aModule['class_prefix'] . $sClass;
+        $o = new $sClass($sMode, $aParams);
+
+        if ($o->isError)
+            $this->_oTemplate->displayPageNotFound ();
+        else
+            $o->outputRSS();
+
+        exit;
+    }
+
+    protected function _getContent($iContentId = 0, $sFuncGetContent = true)
+    {
+        if(!$iContentId)
+            $iContentId = bx_process_input(bx_get('id'), BX_DATA_INT);
+
+        if(!$iContentId)
+            return false;
+
+        if($sFuncGetContent === true)
+            $sFuncGetContent = 'getContentInfoById';
+
+        if(empty($sFuncGetContent) || !method_exists($this->_oDb, $sFuncGetContent))
+            return $iContentId;
+
+        $aContentInfo = $this->_oDb->$sFuncGetContent($iContentId);
+        if(!$aContentInfo)
+            return false;
+
+        return array($iContentId, $aContentInfo);
+    }
+
+    protected function _getContentForTimelinePost($aEvent, $aContentInfo, $aBrowseParams = array())
+    {
+    	$CNF = &$this->_oConfig->CNF;
+
+    	$sUrl = BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $aContentInfo[$CNF['FIELD_ID']]);
+
+    	//--- Image(s)
+        $aImages = $this->_getImagesForTimelinePost($aEvent, $aContentInfo, $sUrl, $aBrowseParams);
+        $aImagesAttach = $this->_getImagesForTimelinePostAttach($aEvent, $aContentInfo, $sUrl, $aBrowseParams);
+
+        //--- Video(s)
+        $aVideos = $this->_getVideosForTimelinePost($aEvent, $aContentInfo, $sUrl, $aBrowseParams);
+        $aVideosAttach = $this->_getVideosForTimelinePostAttach($aEvent, $aContentInfo, $sUrl, $aBrowseParams);
+
+        //--- Files(s)
+        $aFiles = $this->_getFilesForTimelinePost($aEvent, $aContentInfo, $sUrl, $aBrowseParams);
+        $aFilesAttach = $this->_getFilesForTimelinePostAttach($aEvent, $aContentInfo, $sUrl, $aBrowseParams);
+
+        //--- Title
+        $sTitle = '';
+        if(isset($CNF['FIELD_TITLE']) && isset($aContentInfo[$CNF['FIELD_TITLE']]))
+            $sTitle = $aContentInfo[$CNF['FIELD_TITLE']];
+        else if(isset($CNF['FIELD_TEXT']) && isset($aContentInfo[$CNF['FIELD_TEXT']]))
+            $sTitle = strmaxtextlen($aContentInfo[$CNF['FIELD_TEXT']], 20, '...');
+
+        //--- Text
+        $sText = isset($CNF['FIELD_TEXT']) && isset($aContentInfo[$CNF['FIELD_TEXT']]) ? $aContentInfo[$CNF['FIELD_TEXT']] : '';
+        if(!empty($CNF['OBJECT_METATAGS']) && is_string($sText)) {
+            $oMetatags = BxDolMetatags::getObjectInstance($CNF['OBJECT_METATAGS']);
+            $sText = $oMetatags->metaParse($aContentInfo[$CNF['FIELD_ID']], $sText);
+        }
+
+    	return array(
+            'sample' => isset($CNF['T']['txt_sample_single_with_article']) ? $CNF['T']['txt_sample_single_with_article'] : $CNF['T']['txt_sample_single'],
+            'sample_wo_article' => $CNF['T']['txt_sample_single'],
+            'sample_action' => isset($CNF['T']['txt_sample_action']) ? $CNF['T']['txt_sample_action'] : '',
+            'url' => $sUrl,
+            'title' => $sTitle,
+            'text' => $sText,
+            'images' => $aImages,
+            'images_attach' => $aImagesAttach,
+            'videos' => $aVideos,
+            'videos_attach' => $aVideosAttach,
+            'files' => $aFiles,
+            'files_attach' => $aFilesAttach
+        );
+    }
+
+    protected function _getImagesForTimelinePost($aEvent, $aContentInfo, $sUrl, $aBrowseParams = array())
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $sImage = $sImageOrig = '';
+        if(isset($CNF['FIELD_COVER']) && isset($aContentInfo[$CNF['FIELD_COVER']]) && $aContentInfo[$CNF['FIELD_COVER']]) {
+            $sImage = $this->_oConfig->getImageUrl($aContentInfo[$CNF['FIELD_COVER']], array('OBJECT_IMAGES_TRANSCODER_GALLERY', 'OBJECT_IMAGES_TRANSCODER_THUMB'));
+            $sImageOrig = $this->_oConfig->getImageUrl($aContentInfo[$CNF['FIELD_COVER']], array('OBJECT_IMAGES_TRANSCODER_COVER'));
+        }
+
+        $bImageThumb = isset($CNF['FIELD_THUMB']) && isset($aContentInfo[$CNF['FIELD_THUMB']]) && $aContentInfo[$CNF['FIELD_THUMB']];
+        if($sImage == '' && $bImageThumb)
+            $sImage = $this->_oConfig->getImageUrl($aContentInfo[$CNF['FIELD_THUMB']], array('OBJECT_IMAGES_TRANSCODER_GALLERY', 'OBJECT_IMAGES_TRANSCODER_THUMB'));
+
+        if($sImageOrig == '' && $bImageThumb)
+            $sImageOrig = $this->_oConfig->getImageUrl($aContentInfo[$CNF['FIELD_THUMB']], array('OBJECT_IMAGES_TRANSCODER_COVER'));
+
+        if(empty($sImage))
+            return array();
+
+        if($sImageOrig == '')
+            $sImageOrig = $sImage;
+
+        return array(
+            array('id' => $aContentInfo[$CNF['FIELD_THUMB']], 'url' => $sUrl, 'src' => $sImage, 'src_orig' => $sImageOrig),
+        );
+    }
+
+    protected function _getImagesForTimelinePostAttach($aEvent, $aContentInfo, $sUrl, $aBrowseParams = array())
+    {
+        return array();
+    }
+
+    protected function _getVideosForTimelinePost($aEvent, $aContentInfo, $sUrl, $aBrowseParams = array())
+    {
+        return array();
+    }
+
+    protected function _getVideosForTimelinePostAttach($aEvent, $aContentInfo, $sUrl, $aBrowseParams = array())
+    {
+        return array();
+    }
+
+    protected function _getFilesForTimelinePost($aEvent, $aContentInfo, $sUrl, $aBrowseParams = array())
+    {
+        return array();
+    }
+
+    protected function _getFilesForTimelinePostAttach($aEvent, $aContentInfo, $sUrl, $aBrowseParams = array())
+    {
+        return array();
+    }
+
+    protected function _entityComments ($sObject, $iId = 0)
+    {
+        if (!$iId)
+            $iId = bx_process_input(bx_get('id'), BX_DATA_INT);
+
+        if (!$iId)
+            return false;
+
+        $oCmts = BxDolCmts::getObjectInstance($sObject, $iId);
+        if (!$oCmts || !$oCmts->isEnabled())
+            return false;
+
+        return $oCmts->getCommentsBlock(array(), array('in_designbox' => false, 'show_empty' => true));
+    }
+
+    protected function _getFields($iContentId)
+    {
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+        if(empty($aContentInfo))
+            return array();
+
+        return BxDolContentInfo::formatFields($aContentInfo);
+    }
+
+    protected function _getFieldValue($sField, $iContentId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+        if(empty($CNF[$sField]))
+            return false;
+
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+        if(empty($aContentInfo) || empty($aContentInfo[$CNF[$sField]]))
+            return false;
+
+        return $aContentInfo[$CNF[$sField]];
+    }
+
+    protected function _getFieldValueThumb($sField, $iContentId, $sTranscoder = '') 
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(empty($sTranscoder) || empty($CNF[$sField]))
+            return false;
+
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+        if(empty($aContentInfo) || empty($aContentInfo[$CNF[$sField]]))
+            return false;
+
+        $oImagesTranscoder = BxDolTranscoderImage::getObjectInstance($sTranscoder);
+        if(!$oImagesTranscoder)
+            return false;
+
+        return $oImagesTranscoder->getFileUrl($aContentInfo[$CNF[$sField]]);
+    }
+
+	protected function _prepareResponse($aResponse, $bAsJson = false, $aAdditional = array())
+    {
+    	if(!$bAsJson)
+    		return $aResponse;
+
+		if(!empty($aAdditional) && is_array($aAdditional))
+			$aResponse = array_merge($aResponse, $aAdditional);
+
+		echoJson($aResponse);
+		exit;
+    }  
+    
+    /**
+     * Blocks for lables tree browsing private functions
+     */
+    
+    private function _getLablesBreadcrumbsArray($iLabelId, &$aBreadcrumbs)
+	{
+		$oLabel = BxDolLabel::getInstance();
+		$aLabel = $oLabel->getLabels(array('type' => 'id', 'id' => $iLabelId));
+		if ($aLabel){
+            $aBreadcrumbs[$aLabel['id']] = array('id' => $aLabel['id'], 'value' => $aLabel['value']);
+			$this->_getLablesBreadcrumbsArray($aLabel['parent'], $aBreadcrumbs);
+		}
+	}
+    
+    private function _getLablesBrowseUrl($iLabelId)
+	{
+        list($sPageLink, $aPageParams) = bx_get_base_url_inline();
+		return BxDolPermalinks::getInstance()->permalink(bx_append_url_params($sPageLink, array_merge($aPageParams, array('label' => $iLabelId))));
+	}
+    
+    private function _getLablesTreeLevel($aBreadcrumbs, $iParent = 0)
+	{
+		$oLabel = BxDolLabel::getInstance();
+		$aLabelsOutput = array();
+        $aLabels = $oLabel->getLabels(array('type' => 'parent', 'parent' => $iParent));
+        if (count($aLabels) > 0){
+			foreach($aLabels as $aLabel) {
+				$sChild = $this->_getLablesTreeLevel($aBreadcrumbs, $aLabel['id']);
+				$bChildPresent = $sChild != '' ? true : false;
+				$bIsOpen = array_key_exists($aLabel['id'], $aBreadcrumbs) ? true : false;
+				if (!$bIsOpen){
+					$sChild = '';
+				}
+				$aLabelsOutput[] = array(
+					'value' => $aLabel['value'], 
+					'url' => $this->_getLablesBrowseUrl($aLabel['id']),
+					'child' => $sChild,
+					
+					'selected' => bx_process_input(bx_get('label'), BX_DATA_INT) == $aLabel['id'] ? 'selected' : ''.$aLabel['id'], 
+					'bx_if:open' => array(
+						'condition' => $bChildPresent && $bIsOpen,
+						'content' => array()
+					),
+					'bx_if:can_open' => array(
+						'condition' => $bChildPresent && !$bIsOpen,
+						'content' => array()
+					)
+				);
+			}
+			return $this->_oTemplate->parseHtmlByName('labels_tree.html', 
+				array(
+					'bx_repeat:items' => $aLabelsOutput,
+					'parent' => $iParent,
+				)
+			);
+		}
+		return '';
+	}
+}
+
+/** @} */

+ 151 - 0
upgrade/files/11.0.0-11.0.1/files/modules/base/general/classes/BxBaseModGeneralPageEntry.php

@@ -0,0 +1,151 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    BaseGeneral Base classes for modules
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+bx_import('BxDolAcl');
+
+/**
+ * Entry create/edit pages
+ */
+class BxBaseModGeneralPageEntry extends BxTemplPage
+{
+    protected $MODULE;
+
+    protected $_oModule;
+    protected $_aContentInfo = false;
+
+    protected $_sCoverClass;
+
+    public function __construct($aObject, $oTemplate = false)
+    {
+        $this->_oModule = BxDolModule::getInstance($this->MODULE);
+
+        parent::__construct($aObject, $oTemplate ? $oTemplate : $this->_oModule->_oTemplate);
+
+        $this->_sCoverClass = $this->_oModule->getName() . '_cover';
+    }
+
+    public function isActive()
+    {
+        return true;
+    }
+
+    public function getContentInfo()
+    {
+        return $this->_aContentInfo;
+    }
+    
+    public function getCode ()
+    {
+        // check if content exists
+        if (!$this->_aContentInfo || !$this->isActive()) { // if entry is not found - display standard "404 page not found" page
+            $this->_oTemplate->displayPageNotFound();
+            exit;
+        }
+
+        $oCover = BxDolCover::getInstance($this->_oModule->_oTemplate);        
+        if ($oCover)
+            $oCover->setCoverClass($this->_sCoverClass);
+
+        // permissions check 
+        $this->_processPermissionsCheck ();
+
+        // count views
+        $CNF = &$this->_oModule->_oConfig->CNF;
+        if(!empty($CNF['OBJECT_VIEWS'])) {
+            $oView = BxDolView::getObjectInstance($CNF['OBJECT_VIEWS'], $this->_aContentInfo[$CNF['FIELD_ID']]);
+            if($oView && $oView->isEnabled())
+                $oView->doView();
+        }
+
+        // set cover image
+        $mixedCover = method_exists($this, '_getImageForPageCover') ? $this->_getImageForPageCover() : $this->_getThumbForMetaObject();
+        if($mixedCover !== false) {
+            $aCover = array(
+                'id' => $mixedCover['id']
+            );
+
+            if(!empty($mixedCover['transcoder']))
+                $aCover['transcoder'] = $mixedCover['transcoder'];
+            else if(!empty($CNF['OBJECT_IMAGES_TRANSCODER_COVER'])) 
+                $aCover['transcoder'] = $CNF['OBJECT_IMAGES_TRANSCODER_COVER'];
+            else if(!empty($mixedCover['object']))
+                $aCover['object'] = $mixedCover['object'];
+
+            $oCover->setCoverImageUrl($aCover);
+        }
+
+        // add content metatags
+        if(!empty($CNF['OBJECT_METATAGS']) && ($o = BxDolMetatags::getObjectInstance($CNF['OBJECT_METATAGS'])) !== false)
+            $o->addPageMetaInfo($this->_aContentInfo[$CNF['FIELD_ID']], $this->_getThumbForMetaObject());
+
+        $oMenuSubmenu = BxDolMenu::getObjectInstance('sys_site_submenu');
+        if($oMenuSubmenu) {
+            // add actions menu to submenu
+            if(isset($CNF['OBJECT_MENU_ACTIONS_VIEW_ENTRY']))
+                $oMenuSubmenu->setObjectActionsMenu($CNF['OBJECT_MENU_ACTIONS_VIEW_ENTRY']);
+
+            // add social sharing menu to submenu
+            $oMenuSubmenu->setServiceSocialSharing(array(
+                'module' => $this->MODULE,
+                'method' => 'entity_social_sharing',
+            ));
+        }
+
+        return parent::getCode ();
+    }
+
+    protected function _processPermissionsCheck ()
+    {
+        if (CHECK_ACTION_RESULT_ALLOWED !== ($sMsg = $this->_oModule->checkAllowedView($this->_aContentInfo))) {
+            $this->_oTemplate->displayAccessDenied($sMsg);
+            exit;
+        }
+        $this->_oModule->checkAllowedView($this->_aContentInfo, true);
+    }
+
+    protected function _getPageMetaDesc()
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $sResult = '';
+        if(isset($CNF['FIELD_TEXT'])) {
+            $sResult = strip_tags($this->_oModule->_oTemplate->getText($this->_aContentInfo, false));
+
+            $iLength = 0;
+            if(!empty($CNF['PARAM_CHARS_SUMMARY']))
+                $iLength = (int)getParam($CNF['PARAM_CHARS_SUMMARY']);
+            if(empty($iLength))
+                $iLength = 240;
+
+            if(mb_strlen($sResult) > $iLength)
+                $sResult = mb_substr($sResult, 0, $iLength);
+        }
+
+        if(empty($sResult))
+            $sResult = $this->_replaceMarkers(_t($this->_aObject['meta_description']));
+
+        return $sResult;
+    }
+
+    protected function _getThumbForMetaObject ()
+    {
+        return $this->_oModule->getEntryImageData($this->_aContentInfo);
+    }
+
+    protected function _getPageCacheParams ()
+    {
+        if (!$this->_aContentInfo)
+            return '';
+        return $this->_aContentInfo[$this->_oModule->_oConfig->CNF['FIELD_ID']]; // cache is different for every entry
+    }
+}
+
+/** @} */

+ 134 - 0
upgrade/files/11.0.0-11.0.1/files/modules/base/general/classes/BxBaseModGeneralSearchResult.php

@@ -0,0 +1,134 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    BaseGeneral Base classes for modules
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+define('BX_SYS_PER_PAGE_BROWSE_SHOWCASE', 32);
+
+class BxBaseModGeneralSearchResult extends BxTemplSearchResult
+{
+    protected $oModule;
+    protected $bShowcaseView = false;
+    protected $aUnitViews = array();
+    protected $sUnitViewDefault = 'gallery';
+
+    function __construct($sMode = '', $aParams = array())
+    {
+        $this->_sMode = $sMode;
+        parent::__construct();
+    }
+
+    function getMain()
+    {
+        return BxDolModule::getInstance($this->aCurrent['module_name']);
+    }
+
+    function getRssUnitLink (&$a)
+    {
+        $CNF = &$this->oModule->_oConfig->CNF;
+
+        return BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $a[$CNF['FIELD_ID']]);
+    }
+
+    function getRssPageUrl ()
+    {
+        if (false === parent::getRssPageUrl())
+            return false;
+
+        $oPermalinks = BxDolPermalinks::getInstance();
+        return BX_DOL_URL_ROOT . $oPermalinks->permalink($this->aCurrent['rss']['link']);
+    }
+
+    function rss ()
+    {
+        if (!isset($this->aCurrent['rss']))
+            return '';
+
+        $this->aCurrent['paginate']['perPage'] = empty($this->oModule->_oConfig->CNF['PARAM_NUM_RSS']) ? 10 : getParam($this->oModule->_oConfig->CNF['PARAM_NUM_RSS']);
+
+        return parent::rss();
+    }
+
+    /**
+     * Add conditions for private content
+     */
+    protected function addConditionsForPrivateContent($CNF, $oProfile, $aCustomGroup = array()) 
+    {
+        // default is bProcessPrivateContent = 1, 
+        // so private items are shown as empty boxes with "Private" title
+
+        // we can show public content when privacy object is available
+        
+        if(empty($CNF['OBJECT_PRIVACY_VIEW']))
+            return;
+
+        $oPrivacy = BxDolPrivacy::getObjectInstance($CNF['OBJECT_PRIVACY_VIEW']);
+        if(!$oPrivacy)
+            return;
+
+        // for posts in some context we need to show all items, not only public content
+        if (!empty($this->aCurrent['restriction']['context']['value']) || !empty($this->aCurrent['restriction']['author']['value'])) {
+            $this->setProcessPrivateContent(true);
+            return;
+        }
+
+        // build condition to show only public content
+        $aCondition = $oPrivacy->getContentPublicAsCondition($oProfile ? $oProfile->id() : 0, $aCustomGroup);
+        if(empty($aCondition) || !is_array($aCondition))
+            return;
+
+        if(isset($aCondition['restriction'])) {
+            $this->aCurrent['restriction'] = array_merge($this->aCurrent['restriction'], $aCondition['restriction']);
+            $this->aPrivateConditionsIndexes['restriction'] = array_keys($aCondition['restriction']);
+        }
+        if(isset($aCondition['join'])) {
+            $this->aCurrent['join'] = array_merge($this->aCurrent['join'], $aCondition['join']);
+            $this->aPrivateConditionsIndexes['join'] = array_keys($aCondition['join']);
+        }
+
+        $this->setProcessPrivateContent(false);
+    }
+    
+    function showPagination($bAdmin = false, $bChangePage = true, $bPageReload = true)
+    {
+        if ($this->bShowcaseView){
+            return '';
+        }
+        else{
+            $sTmp = parent::showPagination ($bAdmin, $bChangePage, $bPageReload);
+            if ($sTmp != '')
+                return '<div class="bx-def-margin-top">' . $sTmp . '</div>';
+            else
+                return '';
+        }
+    }
+    
+    protected function getItemPerPageInShowCase ()
+    {
+        $iPerPageInShowCase = BX_SYS_PER_PAGE_BROWSE_SHOWCASE;
+        $CNF = &$this->oModule->_oConfig->CNF;
+        if (isset($CNF['PARAM_PER_PAGE_BROWSE_SHOWCASE']))
+            $iPerPageInShowCase = getParam($CNF['PARAM_PER_PAGE_BROWSE_SHOWCASE']);
+        return $iPerPageInShowCase;
+    }
+    
+    function displayResultBlock()
+    {
+        if ($this->bShowcaseView) {
+            $this->addContainerClass(array('bx-base-unit-showcase-wrapper'));
+            $this->aCurrent['paginate']['perPage'] = $this->getItemPerPageInShowCase();
+            $this->oModule->_oTemplate->addCss(array(BX_DIRECTORY_PATH_PLUGINS_PUBLIC . 'flickity/|flickity.css'));
+            $this->oModule->_oTemplate->addJs(array('flickity/flickity.pkgd.min.js','modules/base/general/js/|showcase.js'));
+        }
+
+        return parent::displayResultBlock();
+    }
+}
+
+/** @} */

+ 29 - 0
upgrade/files/11.0.0-11.0.1/files/modules/base/groups/classes/BxBaseModGroupsConfig.php

@@ -0,0 +1,29 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Groups Groups
+ * @ingroup     UnaModules
+ * 
+ * @{
+ */
+
+class BxBaseModGroupsConfig extends BxBaseModProfileConfig
+{
+    function __construct($aModule)
+    {
+        parent::__construct($aModule);
+
+        $this->_aMenuItems2MethodsActions = array (
+            'profile-fan-add' => 'checkAllowedFanAdd',
+            'profile-fan-remove' => 'checkAllowedFanRemove',
+            'profile-subscribe-add' => 'checkAllowedSubscribeAdd',
+            'profile-subscribe-remove' => 'checkAllowedSubscribeRemove',
+            'profile-actions-more' => 'checkAllowedViewMoreMenu',
+            'convos-compose' => 'checkAllowedContact',
+        );
+    }
+}
+
+/** @} */

+ 832 - 0
upgrade/files/11.0.0-11.0.1/files/modules/base/groups/classes/BxBaseModGroupsModule.php

@@ -0,0 +1,832 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    BaseGroups Base classes for groups modules
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+/**
+ * Groups profiles module.
+ */
+class BxBaseModGroupsModule extends BxBaseModProfileModule
+{
+    function __construct(&$aModule)
+    {
+        parent::__construct($aModule);
+    }
+
+    /**
+     * Get possible recipients for start conversation form
+     */
+    public function actionAjaxGetInitialMembers ()
+    {
+        $sTerm = bx_get('term');
+
+        $a = BxDolService::call('system', 'profiles_search', array($sTerm), 'TemplServiceProfiles');
+
+        header('Content-Type:text/javascript; charset=utf-8');
+        echo(json_encode($a));
+    }
+    
+    /**
+     * Process Process Invitation
+     */
+    public function actionProcessInvite ($sKey, $iGroupProfileId, $bAccept)
+    {
+        $aData = $this->_oDb->getInviteByKey($sKey, $iGroupProfileId);
+        if (isset($aData['invited_profile_id'])){
+            $CNF = &$this->_oConfig->CNF;
+            if (!isset($CNF['OBJECT_CONNECTIONS']) || !($oConnection = BxDolConnection::getObjectInstance($CNF['OBJECT_CONNECTIONS'])))
+                return '';
+            $iInvitedProfileId = $aData['invited_profile_id'];
+            if ($iInvitedProfileId != bx_get_logged_profile_id())
+                return '';
+            if ($bAccept){
+                if($oConnection && !$oConnection->isConnected($iInvitedProfileId, $iGroupProfileId)){
+                    $oConnection->addConnection($iInvitedProfileId, $iGroupProfileId);
+                    $oConnection->addConnection($iGroupProfileId, $iInvitedProfileId);
+                }
+            }
+            $this->_oDb->deleteInviteByKey($sKey, $iGroupProfileId);
+        }   
+    }
+ 
+    public function serviceGetSearchResultUnit ($iContentId, $sUnitTemplate = '')
+    {
+        if(empty($sUnitTemplate))
+            $sUnitTemplate = 'unit.html';
+
+        return parent::serviceGetSearchResultUnit($iContentId, $sUnitTemplate);
+    }
+
+    /**
+     * @see BxBaseModProfileModule::serviceGetSpaceTitle
+     */ 
+    public function serviceGetSpaceTitle()
+    {
+        return _t($this->_oConfig->CNF['T']['txt_sample_single']);
+    }
+    
+    /**
+     * @see iBxDolProfileService::serviceGetParticipatingProfiles
+     */ 
+    public function serviceGetParticipatingProfiles($iProfileId, $aConnectionObjects = false)
+    {
+        if (isset($this->_oConfig->CNF['OBJECT_CONNECTIONS'])){
+            $aConnectionObjects = array($this->_oConfig->CNF['OBJECT_CONNECTIONS'], 'sys_profiles_subscriptions');
+            return parent::serviceGetParticipatingProfiles($iProfileId, $aConnectionObjects);
+        }
+        return parent::serviceGetParticipatingProfiles($iProfileId);
+    }
+    
+    /**
+     * Check if this module entry can be used as profile
+     */
+    public function serviceActAsProfile ()
+    {
+        return false;
+    }
+
+    /**
+     * Check if this module is group profile
+     */
+    public function serviceIsGroupProfile ()
+    {
+        return true;
+    }
+
+    /**
+     * check if provided profile is member of the group 
+     */ 
+    public function serviceIsFan ($iGroupProfileId, $iProfileId = false) 
+    {
+        $oGroupProfile = BxDolProfile::getInstance($iGroupProfileId);
+        return $this->isFan($oGroupProfile->getContentId(), $iProfileId);
+    }
+
+    /**
+     * check if provided profile is admin of the group 
+     */ 
+    public function serviceIsAdmin ($iGroupProfileId, $iProfileId = false) 
+    {
+        if(!$iProfileId)
+            $iProfileId = bx_get_logged_profile_id();
+
+        $oGroupProfile = BxDolProfile::getInstance($iGroupProfileId);
+
+        $iGroupContentId = $oGroupProfile->getContentId();
+        if(!$this->isFan($iGroupContentId, $iProfileId))
+            return false;
+
+        $aGroupContentInfo = $this->_oDb->getContentInfoById($iGroupContentId);
+        return $this->_oDb->isAdmin($iGroupProfileId, $iProfileId, $aGroupContentInfo);
+    }
+    
+    /**
+     * Delete profile from fans and admins tables
+     * @param $iProfileId profile id 
+     */
+    public function serviceDeleteProfileFromFansAndAdmins ($iProfileId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $this->_oDb->deleteAdminsByProfileId($iProfileId);
+
+        if (isset($CNF['OBJECT_CONNECTIONS']) && ($oConnection = BxDolConnection::getObjectInstance($CNF['OBJECT_CONNECTIONS'])))
+            $oConnection->onDeleteInitiatorAndContent($iProfileId);
+    }
+
+    /**
+     * Reset group's author for particular group
+     * @param $iContentId group id 
+     * @return false of error, or number of updated records on success
+     */
+    public function serviceReassignEntityAuthor ($iContentId)
+    {
+        $aContentInfo = $this->_oDb->getContentInfoById((int)$iContentId);
+        if (!$aContentInfo)
+            return false;
+
+        if (!($oGroupProfile = BxDolProfile::getInstanceByContentAndType($iContentId, $this->getName())))
+            return false;
+
+        $aAdmins = $this->_oDb->getAdmins($oGroupProfile->id());
+
+        return $this->_oDb->updateAuthorById($iContentId, $aAdmins ? array_pop($aAdmins) : 0);
+    }
+
+    /**
+     * Entry actions and social sharing block
+     */
+    public function serviceEntityAllActions ($mixedContent = false, $aParams = array())
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(!empty($mixedContent)) {
+            if(!is_array($mixedContent))
+                $mixedContent = array((int)$mixedContent, (method_exists($this->_oDb, 'getContentInfoById')) ? $this->_oDb->getContentInfoById((int)$mixedContent) : array());
+        }
+        else {
+            $mixedContent = $this->_getContent();
+            if($mixedContent === false)
+                return false;
+        }
+
+        list($iContentId, $aContentInfo) = $mixedContent;
+
+        if(!empty($CNF['FIELD_PICTURE']) && !empty($aContentInfo[$CNF['FIELD_PICTURE']]))
+            $aParams = array_merge(array(
+                'entry_thumb' => (int)$aContentInfo[$CNF['FIELD_PICTURE']]
+            ), $aParams); 
+
+        return parent::serviceEntityAllActions ($mixedContent, $aParams);
+    }
+    
+    /**
+     * Reset group's author when author profile is deleted
+     * @param $iProfileId profile id 
+     * @return number of changed items
+     */
+    public function serviceReassignEntitiesByAuthor ($iProfileId)
+    {
+        $a = $this->_oDb->getEntriesByAuthor((int)$iProfileId);
+        if (!$a)
+            return 0;
+
+        $iCount = 0;
+        foreach ($a as $aContentInfo)
+            $iCount += ('' == $this->serviceReassignEntityAuthor($aContentInfo[$this->_oConfig->CNF['FIELD_ID']]) ? 1 : 0);
+
+        return $iCount;
+    }
+
+    public function servicePrepareFields ($aFieldsProfile)
+    {
+        $CNF = &$this->_oConfig->CNF;
+        
+        $aFieldsProfile[$CNF['FIELD_NAME']] = $aFieldsProfile['name'];
+        $aFieldsProfile[$CNF['FIELD_TEXT']] = isset($aFieldsProfile['description']) ? $aFieldsProfile['description'] : '';
+        unset($aFieldsProfile['name']);
+        unset($aFieldsProfile['description']);
+        return $aFieldsProfile;
+    }
+
+    public function serviceOnRemoveConnection ($iGroupProfileId, $iInitiatorId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        list ($iProfileId, $iGroupProfileId, $oGroupProfile) = $this->_prepareProfileAndGroupProfile($iGroupProfileId, $iInitiatorId);
+        if (!$oGroupProfile)
+            return false;
+
+        $this->_oDb->fromAdmins($iGroupProfileId, $iProfileId);
+
+        if ($oConn = BxDolConnection::getObjectInstance('sys_profiles_subscriptions'))
+            return $oConn->removeConnection($iProfileId, $iGroupProfileId);
+
+        return false;
+    }
+
+    public function serviceAddMutualConnection ($iGroupProfileId, $iInitiatorId, $bSendInviteOnly = false)
+    {        
+        $CNF = &$this->_oConfig->CNF;
+
+        list ($iProfileId, $iGroupProfileId, $oGroupProfile) = $this->_prepareProfileAndGroupProfile($iGroupProfileId, $iInitiatorId);
+        if (!$oGroupProfile)
+            return false;
+
+        if (!($aContentInfo = $this->_oDb->getContentInfoById((int)BxDolProfile::getInstance($iGroupProfileId)->getContentId())))
+            return false;
+
+        if (!isset($CNF['OBJECT_CONNECTIONS']) || !($oConnection = BxDolConnection::getObjectInstance($CNF['OBJECT_CONNECTIONS'])))
+            return false;
+
+        $sEntryTitle = $aContentInfo[$CNF['FIELD_NAME']];
+        $sEntryUrl = BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $aContentInfo[$CNF['FIELD_ID']]);
+
+        // send invitation to the group 
+        if ($bSendInviteOnly && !$oConnection->isConnected((int)$iInitiatorId, $oGroupProfile->id()) && !$oConnection->isConnected($oGroupProfile->id(), (int)$iInitiatorId) && bx_get_logged_profile_id() != $iProfileId) {
+
+            bx_alert($this->getName(), 'join_invitation', $aContentInfo[$CNF['FIELD_ID']], $iGroupProfileId, array('content' => $aContentInfo, 'entry_title' => $sEntryTitle, 'entry_url' => $sEntryUrl, 'group_profile' => $iGroupProfileId, 'profile' => $iProfileId, 'notification_subobject_id' => $iProfileId, 'object_author_id' => $iGroupProfileId));
+
+        }
+        // send notification to group's admins that new connection is pending confirmation 
+        elseif (!$bSendInviteOnly && $oConnection->isConnected((int)$iInitiatorId, $oGroupProfile->id()) && !$oConnection->isConnected($oGroupProfile->id(), (int)$iInitiatorId) && $aContentInfo['join_confirmation']) {
+
+            bx_alert($this->getName(), 'join_request', $aContentInfo[$CNF['FIELD_ID']], $iGroupProfileId, array(
+            	'object_author_id' => $iGroupProfileId,
+            	'performer_id' => $iProfileId, 
+
+            	'content' => $aContentInfo, 
+            	'entry_title' => $sEntryTitle, 
+            	'entry_url' => $sEntryUrl, 
+
+            	'group_profile' => $iGroupProfileId, 
+            	'profile' => $iProfileId
+            ));
+        }
+        // send notification that join request was accepted 
+        else if (!$bSendInviteOnly && $oConnection->isConnected((int)$iInitiatorId, $oGroupProfile->id(), true) && $oGroupProfile->getModule() != $this->getName() && bx_get_logged_profile_id() != $iProfileId) {
+            bx_alert($this->getName(), 'join_request_accepted', $aContentInfo[$CNF['FIELD_ID']], $iGroupProfileId, array(
+            	'object_author_id' => $iGroupProfileId,
+            	'performer_id' => $iProfileId,
+
+            	'content' => $aContentInfo, 
+            	'entry_title' => $sEntryTitle, 
+            	'entry_url' => $sEntryUrl, 
+
+            	'group_profile' => $iGroupProfileId, 
+            	'profile' => $iProfileId
+            ));
+        }
+
+        // new fan was added
+        if ($oConnection->isConnected($oGroupProfile->id(), (int)$iInitiatorId, true)) {
+            // follow group on join
+            if (BxDolService::call($oGroupProfile->getModule(), 'act_as_profile')){
+                 $this->addFollower($oGroupProfile->id(), (int)$iInitiatorId);
+            }
+            else{
+                 $this->addFollower((int)$iInitiatorId, $oGroupProfile->id()); 
+            }
+            
+            bx_alert($this->getName(), 'fan_added', $aContentInfo[$CNF['FIELD_ID']], $iGroupProfileId, array(
+            	'object_author_id' => $iGroupProfileId,
+            	'performer_id' => $iProfileId,
+
+            	'content' => $aContentInfo,
+            	'entry_title' => $sEntryTitle, 
+            	'entry_url' => $sEntryUrl,
+
+            	'group_profile' => $iGroupProfileId, 
+            	'profile' => $iProfileId,
+            ));
+            
+            $this->doAudit($iGroupProfileId, $iInitiatorId, '_sys_audit_action_group_join_request_accepted');
+            
+            return false;
+        }
+
+        // don't automatically add connection (mutual) if group requires manual join confirmation
+        if ($bSendInviteOnly || $aContentInfo['join_confirmation'])
+            return false;
+
+        // check if connection already exists
+        if ($oConnection->isConnected($oGroupProfile->id(), (int)$iInitiatorId, true) || $oConnection->isConnected($oGroupProfile->id(), (int)$iInitiatorId))
+            return false;
+
+        if (!$oConnection->addConnection($oGroupProfile->id(), (int)$iInitiatorId))
+            return false;
+
+        return true;
+    }
+
+    public function serviceFansTable ()
+    {
+        $oGrid = BxDolGrid::getObjectInstance($this->_oConfig->CNF['OBJECT_GRID_CONNECTIONS']);
+        if (!$oGrid)
+            return false;
+
+        return $oGrid->getCode();
+    }
+    
+	public function serviceInvitesTable ()
+    {
+		if (!isset($this->_oConfig->CNF['OBJECT_GRID_INVITES']))
+			return false;
+		
+        $oGrid = BxDolGrid::getObjectInstance($this->_oConfig->CNF['OBJECT_GRID_INVITES']);
+        if (!$oGrid)
+            return false;
+
+        return $oGrid->getCode();
+    }
+	
+    public function serviceFans ($iContentId = 0, $bAsArray = false)
+    {
+        if (!$iContentId)
+            $iContentId = bx_process_input(bx_get('id'), BX_DATA_INT);
+        if (!$iContentId)
+            return false;
+
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+        if (!$aContentInfo)
+            return false;
+
+        if (!($oGroupProfile = BxDolProfile::getInstanceByContentAndType($iContentId, $this->getName())))
+            return false;
+
+        if(!$bAsArray) {
+            bx_import('BxDolConnection');
+            $mixedResult = $this->serviceBrowseConnectionsQuick ($oGroupProfile->id(), $this->_oConfig->CNF['OBJECT_CONNECTIONS'], BX_CONNECTIONS_CONTENT_TYPE_CONTENT, true);
+            if (!$mixedResult)
+                return MsgBox(_t('_sys_txt_empty'));
+        }
+        else
+            $mixedResult = BxDolConnection::getObjectInstance($this->_oConfig->CNF['OBJECT_CONNECTIONS'])->getConnectedContent($oGroupProfile->id(), true);
+
+        return $mixedResult;
+    }
+
+    public function serviceAdmins ($iContentId = 0)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(!$iContentId)
+            $iContentId = bx_process_input(bx_get('id'), BX_DATA_INT);
+        if(!$iContentId)
+            return false;
+
+        $oGroupProfile = BxDolProfile::getInstanceByContentAndType($iContentId, $this->getName());
+        if(!$oGroupProfile)
+            return false;
+
+        $iStart = (int)bx_get('start');
+        $iLimit = !empty($CNF['PARAM_NUM_CONNECTIONS_QUICK']) ? getParam($CNF['PARAM_NUM_CONNECTIONS_QUICK']) : 4;
+        if(!$iLimit)
+            $iLimit = 4;
+        
+        $aProfiles = $this->_oDb->getAdmins($oGroupProfile->id(), $iStart,  $iLimit+1);
+        if(empty($aProfiles) || !is_array($aProfiles))
+            return false;
+
+        return $this->_serviceBrowseQuick($aProfiles, $iStart, $iLimit);
+    }
+
+    public function serviceBrowseJoinedEntries ($iProfileId = 0, $bDisplayEmptyMsg = false)
+    {
+        if (!$iProfileId)
+            $iProfileId = bx_process_input(bx_get('profile_id'), BX_DATA_INT);
+        if (!$iProfileId)
+            return '';
+
+        return $this->_serviceBrowse ('joined_entries', array('joined_profile' => $iProfileId), BX_DB_PADDING_DEF, $bDisplayEmptyMsg);
+    }
+
+    public function serviceBrowseCreatedEntries ($iProfileId = 0, $bDisplayEmptyMsg = false)
+    {
+        if (!$iProfileId)
+            $iProfileId = bx_process_input(bx_get('profile_id'), BX_DATA_INT);
+        if (!$iProfileId)
+            return '';
+
+        return $this->_serviceBrowse ('created_entries', array('author' => $iProfileId), BX_DB_PADDING_DEF, $bDisplayEmptyMsg);
+    }
+    
+    public function serviceEntityInvite ($iContentId = 0, $bErrorMsg = true)
+    {
+        if (isset($this->_oConfig->CNF['OBJECT_FORM_ENTRY_DISPLAY_INVITE']))
+            return $this->_serviceEntityForm ('editDataForm', $iContentId, $this->_oConfig->CNF['OBJECT_FORM_ENTRY_DISPLAY_INVITE'], false, $bErrorMsg);
+        return false;
+    }
+    
+    /**
+     * Entry social sharing block
+     */
+    public function serviceEntitySocialSharing ($mixedContent = false, $aParams = array())
+    {
+        if(!empty($mixedContent)) {
+            if(!is_array($mixedContent))
+               $mixedContent = array((int)$mixedContent, array());
+        }
+        else {
+            $mixedContent = $this->_getContent();
+            if($mixedContent === false)
+                return false;
+        }
+
+        list($iContentId, $aContentInfo) = $mixedContent;
+
+        $oGroupProfile = BxDolProfile::getInstanceByContentAndType((int)$iContentId, $this->getName());
+        if(!$oGroupProfile)
+            return false;
+
+        return parent::serviceEntitySocialSharing(array($iContentId, $aContentInfo), array(
+            'title' => $oGroupProfile->getDisplayName()
+        ));
+    }
+
+	/**
+     * Data for Notifications module
+     */
+    public function serviceGetNotificationsData()
+    {
+    	$sModule = $this->_aModule['name'];
+
+        $aSettingsTypes = array('follow_member', 'follow_context');
+        if($this->serviceActAsProfile())
+            $aSettingsTypes = array('personal', 'follow_member');
+
+        return array(
+            'handlers' => array(
+                array('group' => $sModule . '_vote', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'doVote', 'module_name' => $sModule, 'module_method' => 'get_notifications_vote', 'module_class' => 'Module'),
+                array('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'),
+
+                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'),
+
+                array('group' => $sModule . '_fan_added', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'fan_added', 'module_name' => $sModule, 'module_method' => 'get_notifications_fan_added', 'module_class' => 'Module'),
+
+                array('group' => $sModule . '_join_request', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'join_request', 'module_name' => $sModule, 'module_method' => 'get_notifications_join_request', 'module_class' => 'Module', 'module_event_privacy' => $this->_oConfig->CNF['OBJECT_PRIVACY_VIEW_NOTIFICATION_EVENT']),
+                
+                array('group' => $sModule . '_timeline_post_common', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'timeline_post_common', 'module_name' => $sModule, 'module_method' => 'get_notifications_timeline_post_common', 'module_class' => 'Module'),
+            ),
+            'settings' => array(
+                array('group' => 'vote', 'unit' => $sModule, 'action' => 'doVote', 'types' => $aSettingsTypes),
+
+                array('group' => 'score_up', 'unit' => $sModule, 'action' => 'doVoteUp', 'types' => $aSettingsTypes),
+
+                array('group' => 'score_down', 'unit' => $sModule, 'action' => 'doVoteDown', 'types' => $aSettingsTypes),
+                
+                array('group' => 'fan', 'unit' => $sModule, 'action' => 'fan_added', 'types' => $aSettingsTypes),
+
+                array('group' => 'join', 'unit' => $sModule, 'action' => 'join_request', 'types' => $aSettingsTypes),
+
+                array('group' => 'timeline_post', 'unit' => $sModule, 'action' => 'timeline_post_common', 'types' => $aSettingsTypes)
+            ),
+            'alerts' => array(
+                array('unit' => $sModule, 'action' => 'doVote'),
+                array('unit' => $sModule, 'action' => 'undoVote'),
+
+                array('unit' => $sModule, 'action' => 'doVoteUp'),
+                array('unit' => $sModule, 'action' => 'doVoteDown'),
+
+                array('unit' => $sModule, 'action' => 'fan_added'),
+
+                array('unit' => $sModule, 'action' => 'join_request'),
+
+                array('unit' => $sModule, 'action' => 'timeline_post_common'),
+            )
+        );
+    }
+
+    /**
+     * Notification about new member requst in the group
+     */
+    public function serviceGetNotificationsJoinRequest($aEvent)
+    {
+        return $this->_serviceGetNotification($aEvent, $this->_oConfig->CNF['T']['txt_ntfs_join_request']);
+    }
+
+	/**
+     * Notification about new member in the group
+     */
+    public function serviceGetNotificationsFanAdded($aEvent)
+    {
+        return $this->_serviceGetNotification($aEvent, $this->_oConfig->CNF['T']['txt_ntfs_fan_added']);
+    }
+
+    protected function _serviceGetNotification($aEvent, $sLangKey)
+    {
+    	$CNF = &$this->_oConfig->CNF;
+
+        $iContentId = (int)$aEvent['object_id'];
+        $oGroupProfile = BxDolProfile::getInstanceByContentAndType((int)$iContentId, $this->getName());
+        if(!$oGroupProfile)
+            return array();
+
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+        if(empty($aContentInfo) || !is_array($aContentInfo))
+            return array();
+
+        $oProfile = BxDolProfile::getInstance((int)$aEvent['subobject_id']);
+        if(!$oProfile)
+            return array();
+
+        /*
+         * Note. Group Profile URL is used for both Entry and Subentry URLs, 
+         * because Subentry URL has higher display priority and notification
+         * should be linked to Group Profile (Group Profile -> Members tab) 
+         * instead of Personal Profile of a member, who performed an action.
+         */
+        $sEntryUrl = $oGroupProfile->getUrl();
+        if(!empty($CNF['URL_ENTRY_FANS']))
+            $sEntryUrl = BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink($CNF['URL_ENTRY_FANS'], array(
+                'profile_id' => $oGroupProfile->id()
+            ));
+
+        return array(
+            'entry_sample' => $CNF['T']['txt_sample_single'],
+            'entry_url' => $sEntryUrl,
+            'entry_caption' => $oGroupProfile->getDisplayName(),
+            'entry_author' => $oGroupProfile->id(),
+            'subentry_sample' => $oProfile->getDisplayName(),
+            'subentry_url' => $sEntryUrl,
+            'lang_key' => $sLangKey
+        );
+    }
+
+    /**
+     * Data for Timeline module
+     */
+    public function serviceGetTimelineData()
+    {
+        return BxBaseModGeneralModule::serviceGetTimelineData();
+    }
+
+    /**
+     * Entry post for Timeline module
+     */
+    public function serviceGetTimelinePost($aEvent, $aBrowseParams = array())
+    {
+        $a = parent::serviceGetTimelinePost($aEvent, $aBrowseParams);
+        if($a === false)
+            return false;
+
+        $oGroupProfile = BxDolProfile::getInstanceByContentAndType($aEvent['object_id'], $this->getName());
+
+        $a['content']['url'] = $oGroupProfile->getUrl();
+        $a['content']['title'] = $oGroupProfile->getDisplayName();
+
+        return $a;
+    }
+
+
+    // ====== PERMISSION METHODS
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden.
+     */
+    public function checkAllowedView ($aDataEntry, $isPerformAction = false)
+    {
+        return $this->serviceCheckAllowedViewForProfile ($aDataEntry, $isPerformAction);
+    }
+
+    public function serviceCheckAllowedViewForProfile ($aDataEntry, $isPerformAction = false, $iProfileId = false)
+    {
+        $bInvited = false;
+        if (bx_get('key')){
+            $oGroupProfile = BxDolProfile::getInstanceByContentAndType($aDataEntry[$this->_oConfig->CNF['FIELD_ID']], $this->getName());
+            $mixedInvited = $this->isInvited(bx_get('key'), $oGroupProfile->id());
+            if ($mixedInvited === true)
+                $bInvited = true;
+        }
+        
+        if ($this->isFan($aDataEntry[$this->_oConfig->CNF['FIELD_ID']], $iProfileId) || $bInvited)
+            return CHECK_ACTION_RESULT_ALLOWED;
+
+        return parent::serviceCheckAllowedViewForProfile ($aDataEntry, $isPerformAction, $iProfileId);
+    }
+
+    /**
+     * @deprecated since version 11.0.1 use BxBaseModGroupsModule::checkAllowedCompose instead
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden.
+     */
+    public function checkAllowedCompose(&$aDataEntry, $isPerformAction = false)
+    {
+        return $this->checkAllowedContact($aDataEntry, $isPerformAction);
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden.
+     */
+    public function checkAllowedContact($aDataEntry, $isPerformAction = false)
+    {
+        if(!$this->isFan($aDataEntry[$this->_oConfig->CNF['FIELD_ID']]))
+            return _t('_sys_txt_access_denied');
+
+        return parent::checkAllowedContact($aDataEntry, $isPerformAction);
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden.
+     */
+    public function checkAllowedFanAdd (&$aDataEntry, $isPerformAction = false)
+    {
+        if ($this->isFan($aDataEntry[$this->_oConfig->CNF['FIELD_ID']]) || !isLogged())
+            return _t('_sys_txt_access_denied');
+
+        return $this->_checkAllowedConnect ($aDataEntry, $isPerformAction, $this->_oConfig->CNF['OBJECT_CONNECTIONS'], true, false);
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden.
+     */
+    public function checkAllowedFanRemove (&$aDataEntry, $isPerformAction = false)
+    {
+        if (CHECK_ACTION_RESULT_ALLOWED === $this->_checkAllowedConnect ($aDataEntry, $isPerformAction, $this->_oConfig->CNF['OBJECT_CONNECTIONS'], false, true, true))
+            return CHECK_ACTION_RESULT_ALLOWED;
+        return $this->_checkAllowedConnect ($aDataEntry, $isPerformAction, $this->_oConfig->CNF['OBJECT_CONNECTIONS'], false, true, false);
+    }
+
+    public function checkAllowedManageAdmins ($mixedDataEntry, $isPerformAction = false)
+    {
+        if (is_array($mixedDataEntry)) {
+            $aDataEntry = $mixedDataEntry;
+        }
+        else {
+            $oGroupProfile = BxDolProfile::getInstance((int)$mixedDataEntry);
+            $aDataEntry = $oGroupProfile && $this->getName() == $oGroupProfile->getModule() ? $this->_oDb->getContentInfoById($oGroupProfile->getContentId()) : array();
+        }
+
+        return parent::checkAllowedEdit ($aDataEntry, $isPerformAction);
+    }
+
+    public function checkAllowedEdit ($aDataEntry, $isPerformAction = false)
+    {
+        $oGroupProfile = BxDolProfile::getInstanceByContentAndType($aDataEntry[$this->_oConfig->CNF['FIELD_ID']], $this->getName());
+        if ($this->_oDb->isAdmin($oGroupProfile->id(), bx_get_logged_profile_id(), $aDataEntry))
+            return CHECK_ACTION_RESULT_ALLOWED;
+        return parent::checkAllowedEdit ($aDataEntry, $isPerformAction);
+    }
+
+    public function checkAllowedInvite ($aDataEntry, $isPerformAction = false)
+    {
+        return $this->checkAllowedEdit ($aDataEntry, $isPerformAction);
+    }
+    
+    public function checkAllowedChangeCover ($aDataEntry, $isPerformAction = false)
+    {
+        $oGroupProfile = BxDolProfile::getInstanceByContentAndType($aDataEntry[$this->_oConfig->CNF['FIELD_ID']], $this->getName());
+        if ($this->_oDb->isAdmin($oGroupProfile->id(), bx_get_logged_profile_id(), $aDataEntry))
+            return CHECK_ACTION_RESULT_ALLOWED;
+        return parent::checkAllowedChangeCover ($aDataEntry, $isPerformAction);
+    }
+
+    public function checkAllowedDelete (&$aDataEntry, $isPerformAction = false)
+    {
+        $oGroupProfile = BxDolProfile::getInstanceByContentAndType($aDataEntry[$this->_oConfig->CNF['FIELD_ID']], $this->getName());
+        if ($oGroupProfile && $this->_oDb->isAdmin($oGroupProfile->id(), bx_get_logged_profile_id(), $aDataEntry))
+            return CHECK_ACTION_RESULT_ALLOWED;
+        return parent::checkAllowedDelete ($aDataEntry, $isPerformAction);
+    }
+    
+    public function checkAllowedJoin(&$aDataEntry, $isPerformAction = false)
+    {
+        if (bx_get('key')){
+            $sKey = bx_get('key');
+            $oGroupProfile = BxDolProfile::getInstanceByContentAndType($aDataEntry[$this->_oConfig->CNF['FIELD_ID']], $this->getName());
+            $aData = $this->_oDb->getInviteByKey($sKey, $oGroupProfile->id());
+            if (isset($aData['invited_profile_id']) && $aData['invited_profile_id'] == bx_get_logged_profile_id()){
+                return CHECK_ACTION_RESULT_ALLOWED;
+            }
+        }   
+        return _t('_sys_txt_access_denied');
+    }   
+
+    public function checkAllowedSubscribeAdd (&$aDataEntry, $isPerformAction = false)
+    {
+        if (!$this->isFan($aDataEntry[$this->_oConfig->CNF['FIELD_ID']]))
+            return _t('_sys_txt_access_denied');
+
+        return $this->_checkAllowedSubscribeAdd ($aDataEntry, $isPerformAction);
+    }
+    
+    public function _checkAllowedSubscribeAdd (&$aDataEntry, $isPerformAction = false)
+    {
+        return parent::checkAllowedSubscribeAdd ($aDataEntry, $isPerformAction);
+    }
+    
+    public function doAudit($iGroupProfileId, $iFanId, $sAction)
+    {
+        $oProfile = BxDolProfile::getInstance($iFanId);
+        
+        $iContentId = $oProfile->getContentId();
+        $sModule = $oProfile->getModule();
+        $oModule = BxDolModule::getInstance($sModule);
+        if (BxDolRequest::serviceExists($sModule, 'act_as_profile') && BxDolService::call($sModule, 'act_as_profile') && $oModule->_oConfig){
+            $CNF = $oModule->_oConfig->CNF;
+
+            $aContentInfo = BxDolRequest::serviceExists($sModule, 'get_all') ? BxDolService::call($sModule, 'get_all', array(array('type' => 'id', 'id' => $iContentId))) : array();
+        
+            $AuditParams = array(
+                'content_title' => (isset($CNF['FIELD_TITLE']) && isset($aContentInfo[$CNF['FIELD_TITLE']])) ? $aContentInfo[$CNF['FIELD_TITLE']] : '',
+                'context_profile_id' => $iGroupProfileId,
+                'context_profile_title' => BxDolProfile::getInstance($iGroupProfileId)->getDisplayName()
+            );
+        
+            bx_audit(
+                $iContentId, 
+                $sModule, 
+                $sAction,  
+                $AuditParams
+            );
+        }
+    }
+    
+    protected function _checkAllowedConnect (&$aDataEntry, $isPerformAction, $sObjConnection, $isMutual, $isInvertResult, $isSwap = false)
+    {
+        $sResult = $this->checkAllowedView($aDataEntry);
+
+        $oPrivacy = BxDolPrivacy::getObjectInstance($this->_oConfig->CNF['OBJECT_PRIVACY_VIEW']);
+
+        // if profile view isn't allowed but visibility is in partially visible groups 
+        // then display buttons to connect (befriend, join) to profile, 
+        // if other conditions (in parent::_checkAllowedConnect) are met as well
+        if (CHECK_ACTION_RESULT_ALLOWED !== $sResult && !in_array($aDataEntry[$this->_oConfig->CNF['FIELD_ALLOW_VIEW_TO']], array_merge($oPrivacy->getPartiallyVisiblePrivacyGroups(), array('s'))))
+            return $sResult;
+
+        return parent::_checkAllowedConnect ($aDataEntry, $isPerformAction, $sObjConnection, $isMutual, $isInvertResult, $isSwap);
+    }
+
+    public function addFollower ($iProfileId1, $iProfileId2)
+    {
+        $oConnectionFollow = BxDolConnection::getObjectInstance('sys_profiles_subscriptions');
+        if($oConnectionFollow && !$oConnectionFollow->isConnected($iProfileId1, $iProfileId2)){
+            $oConnectionFollow->addConnection($iProfileId1, $iProfileId2);
+            return true;
+        }
+        return false;
+    }
+    
+    public function isFan ($iContentId, $iProfileId = false) 
+    {
+        $oGroupProfile = BxDolProfile::getInstanceByContentAndType($iContentId, $this->getName());
+        if (isset($this->_oConfig->CNF['OBJECT_CONNECTIONS']))
+            return $oGroupProfile && ($oConnection = BxDolConnection::getObjectInstance($this->_oConfig->CNF['OBJECT_CONNECTIONS'])) && $oConnection->isConnected($iProfileId ? $iProfileId : bx_get_logged_profile_id(), $oGroupProfile->id(), true);
+        return false;
+    }
+    
+    public function isInvited ($sKey, $iProfileId) 
+    {
+        $CNF = &$this->_oConfig->CNF;
+        $aData = $this->_oDb->getInviteByKey($sKey,  $iProfileId);
+        if (!isset($aData['invited_profile_id']))
+            return _t($CNF['T']['txt_invitation_popup_error_invitation_absent']);
+        
+        if ($aData['invited_profile_id'] != bx_get_logged_profile_id())
+            return  _t($CNF['T']['txt_invitation_popup_error_wrong_user']);
+        
+        return true;
+    }
+
+    protected function _getImagesForTimelinePost($aEvent, $aContentInfo, $sUrl, $aBrowseParams = array())
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $oGroupProfile = BxDolProfile::getInstanceByContentAndType($aEvent['object_id'], $this->getName());
+
+        $sSrc = '';
+        if(isset($CNF['FIELD_COVER']) && !empty($aContentInfo[$CNF['FIELD_COVER']]))
+            $sSrc = $oGroupProfile->getCover();
+
+        if(empty($sSrc) && isset($CNF['FIELD_PICTURE']) && !empty($aContentInfo[$CNF['FIELD_PICTURE']]))
+            $sSrc = $oGroupProfile->getPicture();
+
+        return empty($sSrc) ? array() : array(
+            array('id' => $aContentInfo[$CNF['FIELD_PICTURE']], 'url' => $sUrl, 'src' => $sSrc, 'src_orig' => $sSrc),
+        );
+    }
+
+    protected function _prepareProfileAndGroupProfile($iGroupProfileId, $iInitiatorId)
+    {
+        if (!($oGroupProfile = BxDolProfile::getInstance($iGroupProfileId)))
+            return array(0, 0, null);
+
+        if ($oGroupProfile->getModule() == $this->getName()) {
+            $iProfileId = $iInitiatorId;
+            $iGroupProfileId = $oGroupProfile->id();
+        } else {
+            $iProfileId = $oGroupProfile->id();
+            $iGroupProfileId = $iInitiatorId;
+        }
+
+        return array($iProfileId, $iGroupProfileId, $oGroupProfile);
+    }
+}
+
+/** @} */

+ 79 - 0
upgrade/files/11.0.0-11.0.1/files/modules/base/groups/classes/BxBaseModGroupsPageEntry.php

@@ -0,0 +1,79 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    BaseGroups Base classes for groups modules
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+/**
+ * Profile create/edit/delete pages.
+ */
+class BxBaseModGroupsPageEntry extends BxBaseModProfilePageEntry
+{
+    public function __construct($aObject, $oTemplate = false)
+    {
+        parent::__construct($aObject, $oTemplate);
+
+        $this->_sCoverClass = 'bx-base-group-cover-wrapper ' . $this->_oModule->getName() . '_cover';
+    }
+
+    protected function _processPermissionsCheck ()
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $oPrivacy = BxDolPrivacy::getObjectInstance($CNF['OBJECT_PRIVACY_VIEW']);
+        
+        $mixedAllowView = $this->_oModule->checkAllowedView($this->_aContentInfo);
+        if (!$oPrivacy->isPartiallyVisible($this->_aContentInfo[$CNF['FIELD_ALLOW_VIEW_TO']]) && CHECK_ACTION_RESULT_ALLOWED !== ($sMsg = $mixedAllowView)) {
+            $this->_oTemplate->displayAccessDenied($sMsg);
+            exit;
+        }
+        elseif ($oPrivacy->isPartiallyVisible($this->_aContentInfo[$CNF['FIELD_ALLOW_VIEW_TO']]) && $CNF['OBJECT_PAGE_VIEW_ENTRY'] == $this->_sObject && CHECK_ACTION_RESULT_ALLOWED !== $mixedAllowView) {
+            // replace current page with different set of blocks
+            $aObject = BxDolPageQuery::getPageObject($CNF['OBJECT_PAGE_VIEW_ENTRY_CLOSED']);
+            $this->_sObject = $aObject['object'];
+            $this->_aObject = $aObject;
+            $this->_oQuery = new BxDolPageQuery($this->_aObject);
+        }
+
+        $this->_oModule->checkAllowedView($this->_aContentInfo, true);
+    }
+    
+    public function getCode ()
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+        $sRv = '';
+        if (bx_get('key')){
+            $sKey = bx_get('key');
+            $sId = $this->_oModule->getName() . '_popup_invite';
+            
+            $mixedInvited = $this->_oModule->isInvited($sKey, $this->_oProfile->id());
+            if ($mixedInvited === true){
+                $sRv = $this->_oModule->_oTemplate->parseHtmlByName('popup_invite.html', array(
+                    'popup_id' => $sId,
+					'text' => _t($CNF['T']['txt_invitation_popup_text']),
+					'button_accept' => _t($CNF['T']['txt_invitation_popup_accept_button']),
+					'button_decline' => _t($CNF['T']['txt_invitation_popup_decline_button']),
+						
+                ));
+            }
+            else{
+                $sRv = $mixedInvited;
+            }
+        }
+        $this->_oTemplate->addJs(array('invite_popup.js'));
+        return ($sRv != '' ? $this->_oModule->_oTemplate->getJsCode('invite_popup', array(
+            'sPopupId' => $sId,
+            'sKey' => $sKey,
+            'sAcceptUrl' =>  BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $this->_aProfileInfo['content_id']),
+            'sDeclineUrl' => BX_DOL_URL_ROOT,
+            'iGroupProfileId' => $this->_oProfile->id(),
+        )) .  BxTemplFunctions::getInstance()->popupBox($sId, _t($CNF['T']['txt_invitation_popup_title']), $sRv, true) : '') . parent::getCode();
+    }
+}
+
+/** @} */

+ 95 - 0
upgrade/files/11.0.0-11.0.1/files/modules/base/groups/classes/BxBaseModGroupsTemplate.php

@@ -0,0 +1,95 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    BaseGroups Base classes for groups modules
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+/*
+ * Groups module representation.
+ */
+class BxBaseModGroupsTemplate extends BxBaseModProfileTemplate
+{
+    protected $_iUnitCharsSummary;
+
+    protected $_sUnitClassWoCover;
+
+    function __construct(&$oConfig, &$oDb)
+    {
+        parent::__construct($oConfig, $oDb);
+
+        $this->_bLetterAvatar = false;
+        $this->_iUnitCharsSummary = 50;
+
+        $this->_sUnitClassWoCover = $this->_sUnitClass; //--- Save default 'Unit' class (from BxBaseModProfileTemplate) as 'Unit W\O Cover' class here.
+        $this->_sUnitClassWithCover .= ' bx-base-groups-unit-with-cover';
+        $this->_sUnitClass = $this->_sUnitClassWithCover;
+        $this->_sUnitClassWoInfo .= ' bx-base-groups-unit-wo-info'; 
+        $this->_sUnitClassWoInfoShowCase .= ' bx-base-groups-unit-wo-info bx-base-groups-unit-wo-info-showcase';
+        $this->_sUnitClassShowCase .= ' bx-base-groups-unit-with-cover bx-base-groups-unit-showcase';
+    }
+
+    function unitVars ($aData, $isCheckPrivateContent = true, $sTemplateName = 'unit.html')
+    {
+        $aVars = parent::unitVars ($aData, $isCheckPrivateContent, $sTemplateName);
+        
+        $CNF = &$this->_oConfig->CNF;
+
+        $oProfile = BxDolProfile::getInstance($aData[$CNF['FIELD_AUTHOR']]);
+        if (!$oProfile) 
+            $oProfile = BxDolProfileUndefined::getInstance();
+
+        $aVars['title'] = (boolean)$aVars['public'] ? bx_process_output($aData[$CNF['FIELD_NAME']]) : _t($CNF['T']['txt_private_group']);
+        $aVars['description'] = '';
+        if(!empty($CNF['FIELD_TEXT']) && !empty($aData[$CNF['FIELD_TEXT']]) && (boolean)$aVars['public'])
+        	$aVars['description'] = strmaxtextlen(strip_tags($aData[$CNF['FIELD_TEXT']]), $this->_iUnitCharsSummary);
+
+        $aVars['author'] = $oProfile->getDisplayName();
+        $aVars['author_url'] = $oProfile->getUrl();
+        $aVars['author_icon'] = $oProfile->getIcon();
+        $aVars['author_thumb'] = $oProfile->getThumb();
+        $aVars['author_avatar'] = $oProfile->getAvatar();
+
+        return $aVars;
+    }
+
+    protected function _getUnitClass($aData, $sTemplateName = 'unit.html')
+    {
+        $sResult = '';
+        
+        switch($sTemplateName) {
+            case 'unit_wo_cover.html':
+                $sResult = $this->_sUnitClassWoCover;
+                break;
+
+            default:
+                $sResult = parent::_getUnitClass($aData, $sTemplateName);
+        }
+
+        return $sResult;
+    }
+
+    protected function _getUnitSize($aData, $sTemplateName = 'unit.html')
+    {
+        $sResult = '';
+
+        switch($sTemplateName) {
+            case 'unit.html':
+            case 'unit_with_cover.html':
+                $sResult = 'ava';
+                break;
+
+            default:
+                $sResult = $this->_sUnitSizeDefault;
+                break;
+        }
+
+        return $sResult;
+    }
+}
+
+/** @} */

+ 278 - 0
upgrade/files/11.0.0-11.0.1/files/modules/base/payment/classes/BxBaseModPaymentCart.php

@@ -0,0 +1,278 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    BasePayment Base classes for Payment like modules
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+class BxBaseModPaymentCart extends BxDol
+{
+	protected $MODULE;
+	protected $_oModule;
+
+	function __construct()
+    {
+        parent::__construct();
+
+        $this->_oModule = BxDolModule::getInstance($this->MODULE);
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_base_payment Base Payment
+     * @subsection bx_base_payment-integration Integration
+     * @subsubsection bx_base_payment-get_cart_url get_cart_url
+     * 
+     * @code bx_srv('bx_payment', 'get_cart_url', [...], 'Cart'); @endcode
+     * 
+     * Get shopping cart URL.
+     *
+     * @param $iVendor (optional) integer value with vendor ID.
+     * @return string with shopping cart URL.
+     * 
+     * @see BxBaseModPaymentCart::serviceGetCartUrl
+     */
+    /** 
+     * @ref bx_base_payment-get_cart_url "get_cart_url"
+     */
+	public function serviceGetCartUrl($iVendor = 0)
+    {
+    	if(!$this->_oModule->isLogged())
+            return '';
+
+		if($iVendor == 0)
+    		return $this->_oModule->_oConfig->getUrl('URL_CARTS');
+
+    	return  bx_append_url_params($this->_oModule->_oConfig->getUrl('URL_CART'), array('seller_id' => $iVendor));
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_base_payment Base Payment
+     * @subsection bx_base_payment-integration Integration
+     * @subsubsection bx_base_payment-get_cart_url get_cart_url
+     * 
+     * @code bx_srv('bx_payment', 'get_cart_url', [...], 'Cart'); @endcode
+     * 
+     * Get shopping cart URL.
+     *
+     * @param $iVendor (optional) integer value with vendor ID.
+     * @return string with shopping cart URL.
+     * 
+     * @see BxBaseModPaymentCart::serviceGetCartUrl
+     */
+    /** 
+     * @ref bx_base_payment-get_cart_url "get_cart_url"
+     */
+    public function serviceGetCartJs($sType = '', $iVendorId = 0)
+    {
+        return $this->_oModule->_oTemplate->displayCartJs($sType, $iVendorId);
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_base_payment Base Payment
+     * @subsection bx_base_payment-integration Integration
+     * @subsubsection bx_base_payment-get_add_to_cart_js get_add_to_cart_js
+     * 
+     * @code bx_srv('bx_payment', 'get_add_to_cart_js', [...], 'Cart'); @endcode
+     * 
+     * Get JavaScript code to use in OnClick attributes. 
+     *
+     * @param $iVendor integer value with vendor ID.
+     * @param $mixedModuleId mixed value (ID, Name or URI) determining a module from which the action was initiated.
+     * @param $iItemId $iItemId integer value with item ID. 
+     * @param $iItemCount integer value with a number of items for purchasing. 
+     * @param $bNeedRedirect (optional) boolean value determining whether redirect is needed after add action or not.
+     * @param $aCustom (optional) array with custom data to attach to an item added into a cart.
+     * @return string with JavaScript code to use in OnClick attributes of HTML elements.
+     * 
+     * @see BxBaseModPaymentCart::serviceGetAddToCartJs
+     */
+    /** 
+     * @ref bx_base_payment-get_add_to_cart_js "get_add_to_cart_js"
+     */
+    public function serviceGetAddToCartJs($iVendorId, $mixedModuleId, $iItemId, $iItemCount, $bNeedRedirect = false, $aCustom = array())
+    {
+		$iModuleId = $this->_oModule->_oConfig->getModuleId($mixedModuleId);
+        if(empty($iModuleId))
+            return '';
+
+        return $this->_oModule->_oTemplate->displayAddToCartJs($iVendorId, $iModuleId, $iItemId, $iItemCount, $bNeedRedirect, $aCustom);
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_base_payment Base Payment
+     * @subsection bx_base_payment-integration Integration
+     * @subsubsection bx_base_payment-get_add_to_cart_link get_add_to_cart_link
+     * 
+     * @code bx_srv('bx_payment', 'get_add_to_cart_link', [...], 'Cart'); @endcode
+     * 
+     * Get HTML code for "Add to Cart" link. 
+     *
+     * @param $iVendorId integer value with vendor ID.
+     * @param $mixedModuleId mixed value (ID, Name or URI) determining a module from which the action was initiated.
+     * @param $iItemId $iItemId integer value with item ID. 
+     * @param $iItemCount integer value with a number of items for purchasing. 
+     * @param $bNeedRedirect (optional) boolean value determining whether redirect is needed after add action or not.
+     * @param $aCustom (optional) array with custom data to attach to an item added into a cart.
+     * @return HTML string with link to display on the site.
+     * 
+     * @see BxBaseModPaymentCart::serviceGetAddToCartLink
+     */
+    /** 
+     * @ref bx_base_payment-get_add_to_cart_link "get_add_to_cart_link"
+     */
+	public function serviceGetAddToCartLink($iVendorId, $mixedModuleId, $iItemId, $iItemCount, $bNeedRedirect = false, $aCustom = array())
+    {
+        $iModuleId = $this->_oModule->_oConfig->getModuleId($mixedModuleId);
+        if(empty($iModuleId))
+            return '';
+
+		return $this->_oModule->_oTemplate->displayAddToCartLink($iVendorId, $iModuleId, $iItemId, $iItemCount, $bNeedRedirect, $aCustom);
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_base_payment Base Payment
+     * @subsection bx_base_payment-integration Integration
+     * @subsubsection bx_base_payment-get_cart_item_descriptor get_cart_item_descriptor
+     * 
+     * @code bx_srv('bx_payment', 'get_cart_item_descriptor', [...], 'Cart'); @endcode
+     * 
+     * Get cart item descriptor: 1-2-3-1. 
+     *
+     * @param $iVendorId integer value with vendor ID.
+     * @param $iModuleId integer value with module ID determining a module from which the action was initiated.
+     * @param $iItemId $iItemId integer value with item ID. 
+     * @param $iItemCount integer value with a number of items for purchasing. 
+     * @return string with item descriptor.
+     * 
+     * @see BxBaseModPaymentCart::serviceGetCartItemDescriptor
+     */
+    /** 
+     * @ref bx_base_payment-get_cart_item_descriptor "get_cart_item_descriptor"
+     */
+	public function serviceGetCartItemDescriptor($iVendorId, $iModuleId, $iItemId, $iItemCount)
+	{
+		return $this->_oModule->_oConfig->descriptorA2S(array($iVendorId, $iModuleId, $iItemId, $iItemCount));
+	}
+
+	/**
+     * @page service Service Calls
+     * @section bx_base_payment Base Payment
+     * @subsection bx_base_payment-integration Integration
+     * @subsubsection bx_base_payment-get_cart_items_count get_cart_items_count
+     * 
+     * @code bx_srv('bx_payment', 'get_cart_items_count', [...], 'Cart'); @endcode
+     * 
+     * Get items count in member's shopping cart. 
+     * 
+     * @param $iUserId (optional) integer value with user ID. If empty value is provided then currently logged in user will be used.
+     * @return integer value with items count.
+     * 
+     * @see BxBaseModPaymentCart::serviceGetCartItemsCount
+     */
+    /** 
+     * @ref bx_base_payment-get_cart_items_count "get_cart_items_count"
+     */
+    public function serviceGetCartItemsCount($iUserId = 0)
+    {
+    	$iUserId = !empty($iUserId) ? $iUserId : $this->_oModule->getProfileId();
+        if(empty($iUserId))
+            return 0;
+
+        $aInfo = $this->getInfo(BX_PAYMENT_TYPE_SINGLE, $iUserId);
+
+        $iCount = 0;
+        foreach($aInfo as $iVendorId => $aVendorCart)
+            $iCount += $aVendorCart['items_count'];
+
+        return $iCount;
+    }
+    
+    /**
+     * @page service Service Calls
+     * @section bx_base_payment Base Payment
+     * @subsection bx_base_payment-integration Integration
+     * @subsubsection bx_base_payment-get_cart_items get_cart_items
+     * 
+     * @code bx_srv('bx_payment', 'get_cart_items', [...], 'Cart'); @endcode
+     * 
+     * Get array with items shopping cart. 
+     * 
+     * @param $iSellerId integer value with seller ID.
+     * @param $iModuleId integer value with module ID determining a module from which the action was initiated.
+     * @param $iUserId (optional) integer value with user ID. If empty value is provided then currently logged in user will be used.
+     * @return integer value with items count.
+     * 
+     * @see BxBaseModPaymentCart::serviceGetCartItems
+     */
+    /** 
+     * @ref bx_base_payment-get_cart_items "get_cart_items"
+     */
+    public function serviceGetCartItems($iSellerId, $iModuleId, $iUserId = 0)
+    {
+    	$iUserId = !empty($iUserId) ? $iUserId : $this->_oModule->getProfileId();
+        if(empty($iUserId))
+            return 0;
+
+        $aContent = $this->_parseByVendor($iUserId);
+        return $aContent[$iSellerId];
+    }  
+
+    protected function _parseByVendor($iUserId)
+    {                            
+        $aCart = $this->_oModule->_oDb->getCartContent($iUserId);
+        if(empty($aCart['items']))
+            return array();
+
+        $aResult = $this->_reparseBy($this->_oModule->_oConfig->descriptorsM2A($aCart['items']), 'vendor_id');
+        if(empty($aCart['customs']))
+            return $aResult;
+
+        $aCartCustoms = unserialize($aCart['customs']);
+
+        foreach($aResult as $iVendor => $aItems)
+            foreach($aItems as $iIndex => $aItem)
+                $aResult[$iVendor][$iIndex]['custom'] = $this->_oModule->_oConfig->pullCustom($aItem, $aCartCustoms);
+
+        return $aResult;
+    }
+
+    protected function _parseByModule($iUserId)
+    {
+        $aCart = $this->_oModule->_oDb->getCartContent($iUserId);
+        if(empty($aCart['items']))
+            return array();
+
+        $aResult = $this->_reparseBy($this->_oModule->_oConfig->descriptorsM2A($aCart['items']), 'module_id');
+        if(empty($aCart['customs']))
+            return $aResult;
+
+        $aCartCustoms = unserialize($aCart['customs']);
+
+        foreach($aResult as $iModule => $aItems)
+            foreach($aItems as $iIndex => $aItem)
+                $aResult[$iModule][$iIndex]['custom'] = $this->_oModule->_oConfig->pullCustom($aItem, $aCartCustoms);
+
+        return $aResult;
+    }
+
+    protected function _reparseBy($aItems, $sKey)
+    {
+        $aResult = array();
+        foreach($aItems as $aItem)
+            if(isset($aItem[$sKey]))
+                $aResult[$aItem[$sKey]][] = $aItem;
+
+        return $aResult;
+    }
+}
+
+/** @} */

+ 36 - 0
upgrade/files/11.0.0-11.0.1/files/modules/base/profile/classes/BxBaseModProfileConfig.php

@@ -0,0 +1,36 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    BaseProfile Base classes for profile modules
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+class BxBaseModProfileConfig extends BxBaseModGeneralConfig
+{
+    protected $_aMenuItems2MethodsActions = array();
+    protected $_aMenuItems2MethodsSubmenu = array();
+    
+    function __construct($aModule)
+    {
+        parent::__construct($aModule);
+
+        $this->_aMenuItems2MethodsActions = array (
+            'profile-friend-add' => 'checkAllowedFriendAdd',
+            'profile-friend-remove' => 'checkAllowedFriendRemove',
+            'profile-relation-add' => 'checkAllowedRelationAdd',
+            'profile-relation-remove' => 'checkAllowedRelationRemove',
+            'profile-subscribe-add' => 'checkAllowedSubscribeAdd',
+            'profile-subscribe-remove' => 'checkAllowedSubscribeRemove',
+            'profile-actions-more' => 'checkAllowedViewMoreMenu',
+            'profile-set-acl-level' => 'checkAllowedSetMembership',
+            'convos-compose' => 'checkAllowedContact',
+            'messenger' => 'checkAllowedContact',
+        );        
+    }
+}
+
+/** @} */

+ 113 - 0
upgrade/files/11.0.0-11.0.1/files/modules/base/profile/classes/BxBaseModProfileDb.php

@@ -0,0 +1,113 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    BaseProfile Base classes for profile modules
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+/*
+ * Profile based module database queries
+ */
+class BxBaseModProfileDb extends BxBaseModGeneralDb
+{
+    public function __construct(&$oConfig)
+    {
+        parent::__construct($oConfig);
+    }
+
+    public function getContentInfoById ($iContentId)
+    {
+        $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 `c`.`id` = ?", $this->_oConfig->getName(), $iContentId);
+        $aInfo = $this->getRow($sQuery);
+        bx_alert('profile', 'content_info_by_id', $iContentId, 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", [
+    		'file' => $iFileId,
+        ]);
+    }
+
+    public function updateContentPictureById($iContentId, $iProfileId, $iPictureId, $sFieldPicture)
+    {
+    	$aBindings = array(
+    		'pic' => $iPictureId,
+    		'id' => $iContentId
+    	);
+
+        $sWhere = '';
+        if ($iProfileId) {
+        	$aBindings['author'] = $iProfileId;
+
+            $sWhere = " AND `author` = :author ";
+        }
+
+        $sQuery = "UPDATE `" . $this->_oConfig->CNF['TABLE_ENTRIES'] . "` SET `" . $sFieldPicture . "` = :pic WHERE `id` = :id" . $sWhere;
+        return $this->query($sQuery, $aBindings);
+    }
+
+    public function searchByTerm($sTerm, $iLimit)
+    {
+        if (!$this->_oConfig->CNF['FIELDS_QUICK_SEARCH'])
+            return array();
+
+		$aBindings = array(
+			'type' => $this->_oConfig->getName(),
+			'status' => BX_PROFILE_STATUS_ACTIVE
+		);
+
+        $sWhere = '';
+        foreach ($this->_oConfig->CNF['FIELDS_QUICK_SEARCH'] as $sField) {
+        	$aBindings[$sField] = $sTerm . '%';
+
+            $sWhere .= " OR `c`.`$sField` LIKE :" . $sField;
+        }
+
+        $sOrderBy = $this->prepareAsString(" ORDER BY `a`.`logged` DESC LIMIT ?", (int)$iLimit);
+
+        $sQuery = "SELECT `c`.`id` AS `content_id`, `p`.`account_id`, `p`.`id` AS `profile_id`, `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` = :type) 
+            INNER JOIN `sys_accounts` AS `a` ON (`a`.`id` =  `p`.`account_id`)
+            WHERE `p`.`status` = :status AND (0 $sWhere)" . $sOrderBy;
+        return $this->getAll($sQuery, $aBindings);
+    }
+
+    protected function _getEntriesBySearchIds($aParams, &$aMethod, &$sSelectClause, &$sJoinClause, &$sWhereClause, &$sOrderClause, &$sLimitClause)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+       
+        $aMethod['params'][1] = array_merge($aMethod['params'][1], array(
+            'profile_type' => $this->_oConfig->getName()
+        ));  
+        
+        $sJoinClause .= " LEFT JOIN `sys_profiles` AS `tp` ON `" . $CNF['TABLE_ENTRIES'] . "`.`" . $CNF['FIELD_ID'] . "`=`tp`.`content_id` AND `tp`.`type`=:profile_type ";
+        
+        $sWhereClause .= " AND `tp`.`status`='active'";
+        
+        if(isset($aParams['search_params']['online'])) {
+            $aMethod['params'][1] = array_merge($aMethod['params'][1], array(
+                'online_time' => (int)getParam('sys_account_online_time')
+            ));
+
+            $sJoinClause .= "
+            	INNER JOIN `sys_accounts` AS `ta` ON `tp`.`account_id`=`ta`.`id` 
+            	INNER JOIN `sys_sessions` AS `ts` ON `tp`.`account_id`=`ts`.`user_id` 
+                ";
+
+            $sWhereClause .= " AND `ta`.`profile_id`=`tp`.`id` AND `ts`.`date` > (UNIX_TIMESTAMP() - 60 * :online_time)";
+
+            unset($aParams['search_params']['online']);
+        }
+
+        parent::_getEntriesBySearchIds($aParams, $aMethod, $sSelectClause, $sJoinClause, $sWhereClause, $sOrderClause, $sLimitClause);        
+    }
+}
+
+/** @} */

+ 239 - 0
upgrade/files/11.0.0-11.0.1/files/modules/base/profile/classes/BxBaseModProfileFormEntry.php

@@ -0,0 +1,239 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    BaseProfile Base classes for profile modules
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+/**
+ * Create/edit profile form.
+ */
+class BxBaseModProfileFormEntry extends BxBaseModGeneralFormEntry
+{
+    protected $_iAccountProfileId = 0;
+    protected $_aImageFields = array ();
+
+    public function __construct($aInfo, $oTemplate = false)
+    {
+        parent::__construct($aInfo, $oTemplate);
+
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if (!empty($CNF['FIELD_PICTURE']) && isset($this->aInputs[$CNF['FIELD_PICTURE']])) {
+            $this->_aImageFields[$CNF['FIELD_PICTURE']] = array (
+                'storage_object' => $CNF['OBJECT_STORAGE'],
+                'images_transcoder' => $CNF['OBJECT_IMAGES_TRANSCODER_THUMB'],
+                'uploaders' => $CNF['OBJECT_UPLOADERS_PICTURE'],
+            );
+        }
+
+        if (!empty($CNF['FIELD_COVER']) && isset($this->aInputs[$CNF['FIELD_COVER']])) {
+            $this->_aImageFields[$CNF['FIELD_COVER']] = array (
+                'storage_object' => $CNF['OBJECT_STORAGE_COVER'],
+                'images_transcoder' => $CNF['OBJECT_IMAGES_TRANSCODER_COVER_THUMB'],
+                'uploaders' => $CNF['OBJECT_UPLOADERS_COVER'],
+            );
+        }
+
+        foreach ($this->_aImageFields as $sField => $aParams) {
+            $this->aInputs[$sField]['storage_object'] = $aParams['storage_object'];
+            $this->aInputs[$sField]['uploaders'] = !empty($this->aInputs[$sField]['value']) ? unserialize($this->aInputs[$sField]['value']) : $aParams['uploaders'];
+            $this->aInputs[$sField]['images_transcoder'] = $aParams['images_transcoder'];
+            $this->aInputs[$sField]['storage_private'] = 0;
+            $this->aInputs[$sField]['multiple'] = false;
+            $this->aInputs[$sField]['content_id'] = 0;
+            $this->aInputs[$sField]['ghost_template'] = '';
+        }
+
+        $oAccountProfile = BxDolProfile::getInstanceAccountProfile();
+        if ($oAccountProfile)
+            $this->_iAccountProfileId = $oAccountProfile->id();
+    }
+
+    function initChecker ($aValues = array (), $aSpecificValues = array())
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $aContentInfo = isset($CNF['FIELD_ID']) && isset($aValues[$CNF['FIELD_ID']]) ? $this->_oModule->_oDb->getContentInfoById ($aValues[$CNF['FIELD_ID']]) : array();
+        
+        foreach ($this->_aImageFields as $sField => $aParams) {
+
+            if ($aValues && !empty($aValues[$CNF['FIELD_ID']]))
+                $this->aInputs[$sField]['content_id'] = $aValues[$CNF['FIELD_ID']];
+
+            $this->aInputs[$sField]['ghost_template'] = $this->_oModule->_oTemplate->parseHtmlByName('form_ghost_template.html', $this->_getProfilePhotoGhostTmplVars($sField, $aContentInfo));
+        }
+
+        parent::initChecker($aValues, $aSpecificValues);
+    }
+
+    function delete ($iContentId, $aContentInfo = array())
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $iProfileId = $this->getContentOwnerProfileId($iContentId);        
+
+        foreach ($this->_aImageFields as $sField => $aParams) {
+            $oStorage = BxDolStorage::getObjectInstance($aParams['storage_object']);
+            $aFiles = $oStorage->getGhosts($iProfileId, $iContentId);
+
+            foreach ($aFiles as $aFile) {
+                if (!$oStorage->getFile($aFile['id']))
+                    continue;
+                $bRet = $oStorage->deleteFile($aFile['id'], $this->_iAccountProfileId);
+            }
+        }
+
+        return parent::delete($iContentId, $aContentInfo);
+    }
+
+    protected function genCustomViewRowValueProfileEmail($aInput)
+    {
+        return $this->genCustomViewRowValueProfileEmailOrIp($aInput);
+    }
+    
+    protected function genCustomViewRowValueProfileIp($aInput)
+    {
+        return $this->genCustomViewRowValueProfileEmailOrIp($aInput);
+    }
+    
+    protected function genCustomViewRowValueFriendsCount($aInput)
+    {
+        if (isset($this->_oModule->_oConfig->CNF['URI_VIEW_FRIENDS'])){
+            $oProfile = $this->_oModule->getProfileByCurrentUrl();
+            if ($oProfile){
+                $oConnection = BxDolConnection::getObjectInstance('sys_profiles_friends');
+                $iCount = $oConnection->getConnectedContentCount($oProfile->id(), true);
+                return $this->_oModule->_oTemplate->parseHtmlByName('name_link.html', array(
+                    'href' => BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=' . $this->_oModule->_oConfig->CNF['URI_VIEW_FRIENDS'] . '&profile_id=' . $oProfile->id()),
+                    'title' => '',
+                    'content' => $iCount
+                ));
+            }
+        }
+        return '';
+    }
+    
+    protected function genCustomViewRowValueFollowersCount($aInput)
+    {
+        if (isset($this->_oModule->_oConfig->CNF['URI_VIEW_SUBSCRIPTIONS'])){
+            $oProfile = $this->_oModule->getProfileByCurrentUrl();
+            if ($oProfile){
+                $oConnectionFollow = BxDolConnection::getObjectInstance('sys_profiles_subscriptions');
+                $iCount = $oConnectionFollow->getConnectedInitiatorsCount($oProfile->id());
+                return $this->_oModule->_oTemplate->parseHtmlByName('name_link.html', array(
+                    'href' => BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=' . $this->_oModule->_oConfig->CNF['URI_VIEW_SUBSCRIPTIONS'] . '&profile_id=' . $oProfile->id()),
+                    'title' => '',
+                    'content' => $iCount
+                ));
+            }
+        }
+        return '';
+    }
+    
+    private function genCustomViewRowValueProfileEmailOrIp($aInput)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+        if(empty($aInput['value']))
+            return '';
+
+        $sValue = $aInput['value'];
+
+        $sModuleAccounts = 'bx_accounts';
+    	if(!BxDolModuleQuery::getInstance()->isEnabledByName($sModuleAccounts))
+    		return $sValue;
+
+		$oModuleAccounts = BxDolModule::getInstance($sModuleAccounts);
+		if(!$oModuleAccounts || empty($oModuleAccounts->_oConfig->CNF['URL_MANAGE_ADMINISTRATION']))
+			return $sValue;
+
+        return $this->_oModule->_oTemplate->parseHtmlByName('name_link.html', array(
+            'href' => BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink($oModuleAccounts->_oConfig->CNF['URL_MANAGE_ADMINISTRATION'], array(
+            	'filter' => urlencode($sValue)
+            )),
+            'title' => '',
+            'content' => $sValue
+        ));
+    }
+    
+    protected function genCustomViewRowValueProfileStatus($aInput)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+        if(empty($aInput['value']))
+            return '';
+
+        $sStatus = _t('_sys_profile_status_' . $aInput['value']);
+        if(empty($CNF['URL_MANAGE_ADMINISTRATION']) || empty($CNF['FIELD_TITLE']) && !empty($this->aInputs[$CNF['FIELD_TITLE']]['value']))
+            return $sStatus;
+
+        return $this->_oModule->_oTemplate->parseHtmlByName('name_link.html', array(
+            'href' => BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink($CNF['URL_MANAGE_ADMINISTRATION'], array(
+            	'filter' => urlencode($this->aInputs[$CNF['FIELD_TITLE']]['value'])
+            )),
+            'title' => '',
+            'content' => $sStatus
+        ));
+    }
+
+    protected function _associalFileWithContent($oStorage, $iFileId, $iProfileId, $iContentId, $sPictureField = '')
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $oStorage->updateGhostsContentId ($iFileId, $iProfileId, $iContentId, $this->_isAdmin($iContentId));
+
+        $bResult = (int)$this->_oModule->_oDb->updateContentPictureById($iContentId, 0/*$iProfileId*/, $iFileId, $sPictureField) > 0;
+        if($bResult) {
+            $sModule = $this->_oModule->getName();
+            switch($sPictureField) {
+                case $CNF['FIELD_PICTURE']:
+                    bx_alert($sModule, 'profile_picture_changed', $iFileId, $iProfileId, array('content' => $iContentId, 'field' => $CNF['FIELD_PICTURE']));
+                    break;
+
+                case $CNF['FIELD_COVER']:
+                    bx_alert($sModule, 'profile_cover_changed', $iFileId, $iProfileId, array('content' => $iContentId, 'field' => $CNF['FIELD_COVER']));
+                    break;
+            }
+        }
+    }
+
+    protected function _getProfilePhotoGhostTmplVars($sField, $aContentInfo = array())
+    {
+    	$CNF = &$this->_oModule->_oConfig->CNF;
+
+    	return array (
+			'name' => $this->aInputs[$sField]['name'],
+            'content_id' => $this->aInputs[$sField]['content_id'],
+			'bx_if:set_thumb' => array (
+				'condition' => false,
+				'content' => array (),
+			),
+		);
+    }
+
+    protected function _isAdmin ($iContentId = 0)
+    {
+        if (parent::_isAdmin ($iContentId))
+            return true;
+        if (!$iContentId || !($aDataEntry = $this->_oModule->_oDb->getContentInfoById((int)$iContentId)))
+            return false;
+        return CHECK_ACTION_RESULT_ALLOWED == $this->_oModule->checkAllowedEdit ($aDataEntry);        
+    }
+
+    protected function _getPrivacyFields($aKeysF2O = array())
+    {
+        if(empty($aKeysF2O))
+            $aKeysF2O = array(
+                'FIELD_ALLOW_VIEW_TO' => 'OBJECT_PRIVACY_VIEW',
+                'FIELD_ALLOW_POST_TO' => 'OBJECT_PRIVACY_POST',
+                'FIELD_ALLOW_CONTACT_TO' => 'OBJECT_PRIVACY_CONTACT'
+            );
+
+        return parent::_getPrivacyFields($aKeysF2O);
+    }
+}
+
+/** @} */

+ 1836 - 0
upgrade/files/11.0.0-11.0.1/files/modules/base/profile/classes/BxBaseModProfileModule.php

@@ -0,0 +1,1836 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    BaseProfile Base classes for profile modules
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+define('BX_DOL_PROFILE_REDIRECT_PROFILE', 'profile');
+define('BX_DOL_PROFILE_REDIRECT_LAST', 'last');
+define('BX_DOL_PROFILE_REDIRECT_CUSTOM', 'custom');
+
+/**
+ * Base class for profile modules.
+ */
+class BxBaseModProfileModule extends BxBaseModGeneralModule implements iBxDolContentInfoService, iBxDolProfileService
+{
+    protected $_iAccountId;
+
+    function __construct(&$aModule)
+    {
+        parent::__construct($aModule);
+
+        $CNF = &$this->_oConfig->CNF;
+
+        if(!empty($CNF['FIELD_ALLOW_POST_TO']))
+            $this->_aSearchableNamesExcept[] = $CNF['FIELD_ALLOW_POST_TO'];
+
+        if(!empty($CNF['FIELD_ALLOW_CONTACT_TO']))
+            $this->_aSearchableNamesExcept[] = $CNF['FIELD_ALLOW_CONTACT_TO'];
+
+        $this->_iAccountId = getLoggedId();
+    }
+
+    public function actionDeleteProfileImg($iFileId, $iContentId, $sFieldPicture) 
+    {
+        $aResult = array();
+        $CNF = &$this->_oConfig->CNF;
+
+        $oSrorage = BxDolStorage::getObjectInstance($CNF['OBJECT_STORAGE']);
+        if (!($aFile = $oSrorage->getFile((int)$iFileId)) || !($aContentInfo = $this->_oDb->getContentInfoById($iContentId)) || $aContentInfo[$sFieldPicture] != (int)$iFileId)
+            $aResult = array('error' => 1, 'msg' => _t('_sys_storage_err_file_not_found'));
+
+        $oAccountProfile = BxDolProfile::getInstanceAccountProfile();
+        if ($oAccountProfile)
+            $iAccountProfileId = $oAccountProfile->id();
+
+        if ((!$aResult && !isLogged()) || (!$aResult && $aFile['profile_id'] != $iAccountProfileId && !$this->_isModerator()))           
+            $aResult = array('error' => 2, 'msg' => _t('_Access denied'));
+
+        $oForm = BxDolForm::getObjectInstance($CNF['OBJECT_FORM_ENTRY'], $CNF['OBJECT_FORM_ENTRY_DISPLAY_ADD'], $this->_oTemplate);
+
+        if (!$aResult && !$oForm->_deleteFile($iContentId, $sFieldPicture, (int)$iFileId, true))
+            $aResult = array('error' => 3, 'msg' => _t('_Failed'));
+        elseif (!$aResult)            
+            $aResult = array('error' => 0, 'msg' => '');
+
+        header('Content-Type: application/json; charset=utf-8');
+        echo json_encode($aResult);
+    }
+
+    // ====== SERVICE METHODS
+
+    public function serviceGetSafeServices()
+    {
+        $a = parent::serviceGetSafeServices();
+        return array_merge($a, array (
+            'ProfileUnitSafe' => '',
+            'ProfileUrl' => '',
+            'EntityCreate' => '',
+            'BrowseRecommended' => '',
+            'BrowseRecentProfiles' => '',
+            'BrowseActiveProfiles' => '',
+            'BrowseTopProfiles' => '',
+            'BrowseOnlineProfiles' => '',
+            'BrowseByAcl' => '',
+        ));
+    }
+
+    public function serviceGetOptionsRedirectAfterAdd()
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aResult = array();
+        $aChoices = array(BX_DOL_PROFILE_REDIRECT_PROFILE, BX_DOL_PROFILE_REDIRECT_LAST, BX_DOL_PROFILE_REDIRECT_CUSTOM);
+        foreach($aChoices as $sChoice) 
+            $aResult[] = array('key' => $sChoice, 'value' => _t($CNF['T']['option_redirect_aadd_' . $sChoice]));
+
+        return $aResult;
+    }
+
+    public function serviceGetThumb ($iContentId, $sTranscoder = '') 
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(empty($sTranscoder) && !empty($CNF['OBJECT_IMAGES_TRANSCODER_THUMB']))
+            $sTranscoder = $CNF['OBJECT_IMAGES_TRANSCODER_THUMB'];
+
+        $mixedResult = $this->_getFieldValueThumb('FIELD_PICTURE', $iContentId, $sTranscoder);
+        return $mixedResult !== false ? $mixedResult : '';
+    }
+
+    public function serviceGetSearchResultUnit ($iContentId, $sUnitTemplate = '')
+    {
+        if(empty($sUnitTemplate))
+            $sUnitTemplate = 'unit_with_cover.html';
+
+        return parent::serviceGetSearchResultUnit($iContentId, $sUnitTemplate);
+    }
+
+    public function serviceGetSearchableFieldsExtended($aInputsAdd = array())
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aResult = parent::serviceGetSearchableFieldsExtended($aInputsAdd);
+        if(!$this->serviceActAsProfile())
+            return $aResult;
+
+        if(!in_array('online', $this->_aSearchableNamesExcept))
+            $aResult['online'] = array(
+                'type' => 'checkbox', 
+                'caption' => $CNF['T']['form_field_online'],
+                'info' => '',
+            	'value' => '1',
+                'values' => '',
+                'pass' => ''
+            );
+
+        if(!empty($CNF['FIELD_PICTURE']) && !in_array($CNF['FIELD_PICTURE'], $this->_aSearchableNamesExcept))
+            $aResult[$CNF['FIELD_PICTURE']] = array(
+                'type' => 'checkbox', 
+                'caption' => $CNF['T']['form_field_picture'],
+                'info' => '',
+            	'value' => '1',
+                'values' => '',
+                'pass' => '',
+                'search_operator' => '>=' 
+            );
+
+        return $aResult;
+    }
+
+	public function servicePrivateProfileMsg()
+    {
+        $mixedContent = $this->_getContent();
+        if ($mixedContent) {
+            list($iContentId, $aContentInfo) = $mixedContent;
+            return $this->checkAllowedView($aContentInfo);
+        }
+        return MsgBox(_t('_sys_access_denied_to_private_content'));
+    }
+    
+	public function serviceGetContentInfoById($iContentId)
+    {
+        return $this->_oDb->getContentInfoById((int)$iContentId);
+    }
+
+	public function serviceGetMenuAddonManageTools()
+	{
+		bx_import('SearchResult', $this->_aModule);
+        $sClass = $this->_aModule['class_prefix'] . 'SearchResult';
+        $o = new $sClass();
+        $o->unsetPaginate();
+        $iNumTotal = $o->getNum();
+        
+        $o->fillFilters(array(
+			'perofileStatus' => BX_PROFILE_STATUS_PENDING
+        ));
+        $iNum1 = $o->getNum();
+        
+        $iNum2 = 0;
+        $CNF = &$this->_oConfig->CNF;
+        if (isset($CNF['OBJECT_REPORTS'])){
+            $o->fillFilters(array('perofileStatus' => ''));
+            $o->fillFiltersByObjects(array('reported' => array('value' => '0', 'field' => 'reports', 'operator' => '>', 'table' => $CNF['TABLE_ENTRIES'])));
+            $iNum2 = $o->getNum();
+        }
+        return array('counter1_value' => $iNum1, 'counter2_value' => $iNum2, 'counter3_value' => $iNumTotal, 'counter1_caption' => _t('_sys_menu_dashboard_manage_tools_addon_counter1_caption_profile_default'));
+	}
+
+	public function serviceGetMenuAddonManageToolsProfileStats()
+	{
+		bx_import('SearchResult', $this->_aModule);
+        $sClass = $this->_aModule['class_prefix'] . 'SearchResult';
+        $o = new $sClass();
+        $o->fillFilters(array(
+			'account_id' => getLoggedId(),
+        	'perofileStatus' => ''
+        ));
+        $o->unsetPaginate();
+
+        return $o->getNum();
+	}
+
+    public function serviceGetMenuAddonFavoritesProfileStats()
+    {
+    	bx_import('SearchResult', $this->_aModule);
+        $sClass = $this->_aModule['class_prefix'] . 'SearchResult';
+        $o = new $sClass('favorite', array('user' => bx_get_logged_profile_id()));
+        $o->unsetPaginate();
+
+        return $o->getNum();
+    }
+
+    public function serviceGetSubmenuObject ()
+    {
+        return $this->_oConfig->CNF['OBJECT_MENU_SUBMENU_VIEW_ENTRY'];
+    }
+
+    public function serviceGetMenuSetNameForMenuTrigger ($sMenuTriggerName)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if (isset($CNF['TRIGGER_MENU_PROFILE_VIEW_SUBMENU']) && $CNF['TRIGGER_MENU_PROFILE_VIEW_SUBMENU'] == $sMenuTriggerName)
+            return $CNF['OBJECT_MENU_SUBMENU_VIEW_ENTRY'];
+        elseif (isset($CNF['TRIGGER_MENU_PROFILE_SNIPPET_META']) && $CNF['TRIGGER_MENU_PROFILE_SNIPPET_META'] == $sMenuTriggerName)
+            return $CNF['OBJECT_MENU_SNIPPET_META'];
+        else if (isset($CNF['TRIGGER_MENU_PROFILE_VIEW_ACTIONS']) && $CNF['TRIGGER_MENU_PROFILE_VIEW_ACTIONS'] == $sMenuTriggerName) {
+            if(empty($CNF['OBJECT_MENU_ACTIONS_VIEW_ENTRY_ALL']))
+                return $CNF['OBJECT_MENU_ACTIONS_VIEW_ENTRY'];
+            else
+                return array($CNF['OBJECT_MENU_ACTIONS_VIEW_ENTRY'], $CNF['OBJECT_MENU_ACTIONS_VIEW_ENTRY_ALL']);
+        }
+
+        return '';
+    }
+
+    public function serviceGetSnippetMenuVars($iProfileId, $bPublic = null)
+    {
+        return $this->_oTemplate->getSnippetMenuVars($iProfileId, $bPublic);
+    }
+        
+	public function serviceGetPageObjectForPageTrigger ($sPageTriggerName)
+    {
+        if (isset($this->_oConfig->CNF['TRIGGER_PAGE_VIEW_ENTRY']) && $this->_oConfig->CNF['TRIGGER_PAGE_VIEW_ENTRY'] == $sPageTriggerName)
+        	return $this->_oConfig->CNF['OBJECT_PAGE_VIEW_ENTRY'];
+
+        return '';
+    }
+
+    public function serviceProfilesSearch ($sTerm, $iLimit)
+    {
+		$aRet = array();
+
+        $a = $this->_oDb->searchByTerm($sTerm, $iLimit);
+        foreach ($a as $r) {
+            $oProfile = BxDolProfile::getInstance($r['profile_id']);
+
+            $aRet[] = array (
+            	'label' => $this->serviceProfileName($r['content_id']), 
+                'value' => $r['profile_id'], 
+                'url' => $oProfile->getUrl(),
+            	'thumb' => $oProfile->getThumb(),
+                'unit' => $oProfile->getUnit(0, array('template' => 'unit_wo_info'))
+            );
+        }
+
+        return $aRet;
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_base_profile Base Profile
+     * @subsection bx_base_profile-general General
+     * @subsubsection bx_base_profile-profile_unit_safe profile_unit_safe
+     * 
+     * @code bx_srv('bx_persons', 'profile_unit_safe', [...]); @endcode
+     * 
+     * Get profile unit
+     * @param $iContentId content ID
+     * 
+     * @see BxBaseModProfileModule::serviceProfileUnitSafe
+     */
+    /** 
+     * @ref bx_base_profile-profile_unit_safe "profile_unit_safe"
+     */
+    public function serviceProfileUnitSafe ($iContentId, $aParams = array())
+    {
+        unset($aParams['check_private_content']);
+        return $this->serviceProfileUnit ($iContentId, $aParams);
+    }
+
+    public function serviceProfileUnit ($iContentId, $aParams = array())
+    {
+        $mixedContent = $this->_getContent($iContentId);
+        if($mixedContent === false)
+            return false;
+
+        list($iContentId, $aContentInfo) = $mixedContent;
+
+        $bCheckPrivateContent = isset($aParams['check_private_content']) ? (bool)$aParams['check_private_content'] : true;
+
+        $sTemplate = 'unit.html';
+        $sTemplateSize = false;
+        $aTemplateVars = array();
+        if(!empty($aParams['template'])) {
+            if(is_string($aParams['template']))
+                $sTemplate = $aParams['template'] . '.html';
+            else if(is_array($aParams['template'])) {
+                if(!empty($aParams['template']['name']))
+                    $sTemplate = $aParams['template']['name'] . '.html';
+
+                if(!empty($aParams['template']['size']))
+                    $sTemplateSize = $aParams['template']['size'];
+
+                if(!empty($aParams['template']['vars']))
+                    $aTemplateVars = $aParams['template']['vars'];
+            }
+        }
+
+        return $this->_oTemplate->unit($aContentInfo, $bCheckPrivateContent, array($sTemplate, $sTemplateSize, $aTemplateVars));
+    }
+
+    public function serviceHasImage ($iContentId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $mixedContent = $this->_getContent($iContentId, 'getContentInfoById');
+        if($mixedContent === false)
+            return false;
+
+        list($iContentId, $aContentInfo) = $mixedContent;
+
+        return !empty($aContentInfo[$CNF['FIELD_PICTURE']]);
+    }
+
+    public function serviceProfilePicture ($iContentId)
+    {
+        return $this->_serviceTemplateFunc('urlPicture', $iContentId);
+    }
+
+    public function serviceProfileAvatar ($iContentId)
+    {
+        return $this->_serviceTemplateFunc('urlAvatar', $iContentId);
+    }
+
+    public function serviceProfileAvatarBig ($iContentId)
+    {
+        return $this->_serviceTemplateFunc('urlAvatarBig', $iContentId);
+    }
+
+    public function serviceProfileCover ($iContentId)
+    {
+        return $this->_serviceTemplateFunc('urlCover', $iContentId);
+    }
+
+    public function serviceProfileUnitCover ($iContentId)
+    {
+        return $this->_serviceTemplateFunc('urlCoverUnit', $iContentId);
+    }
+
+    public function serviceProfileEditUrl ($iContentId)
+    {
+        return BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=' . $this->_oConfig->CNF['URI_EDIT_ENTRY'] . '&id=' . $iContentId);
+    }
+
+    public function serviceProfileThumb ($iContentId)
+    {
+        return $this->_serviceTemplateFunc('urlThumb', $iContentId);
+    }
+
+    public function serviceProfileIcon ($iContentId)
+    {
+        return $this->_serviceTemplateFunc('urlIcon', $iContentId);
+    }
+
+    public function serviceProfileName ($iContentId)
+    {
+        if (!$iContentId)
+            return false;
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+        if (!$aContentInfo)
+            return false;
+        return bx_process_output($aContentInfo[$this->_oConfig->CNF['FIELD_NAME']]);
+    }
+
+    public function serviceProfileCreateUrl ($bAbsolute = true)
+    {
+    	$CNF = $this->_oConfig->CNF;
+    	if(empty($CNF['URL_CREATE']))
+    		return false;
+
+    	return $bAbsolute ? BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink($CNF['URL_CREATE']) : $CNF['URL_CREATE'];
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_base_profile Base Profile
+     * @subsection bx_base_profile-general General
+     * @subsubsection bx_base_profile-profile_url profile_url
+     * 
+     * @code bx_srv('bx_persons', 'profile_url', [...]); @endcode
+     * 
+     * Get profile URL
+     * @param $iContentId content ID
+     * 
+     * @see BxBaseModProfileModule::serviceProfileUrl
+     */
+    /** 
+     * @ref bx_base_profile-profile_url "profile_url"
+     */
+    public function serviceProfileUrl ($iContentId)
+    {
+        if (!$iContentId)
+            return false;
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+        if (!$aContentInfo)
+            return false;
+        $CNF = $this->_oConfig->CNF;
+        return BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $aContentInfo[$CNF['FIELD_ID']]);
+    }
+
+    /**
+     * @see iBxDolProfileService::serviceGetSpaceTitle
+     */ 
+    public function serviceGetSpaceTitle()
+    {
+        return _t('_sys_ps_space_title_friend');
+    }
+
+    /**
+     * @see iBxDolProfileService::serviceGetParticipatingProfiles
+     */ 
+    public function serviceGetParticipatingProfiles($iProfileId, $aConnectionObjects = false)
+    {
+        if (false === $aConnectionObjects)
+            $aConnectionObjects = array('sys_profiles_friends');
+
+        $a = array();
+        foreach ($aConnectionObjects as $sConnectionObject) {
+            if (!($o = BxDolConnection::getObjectInstance($sConnectionObject)))
+                continue;
+
+            if (BX_CONNECTIONS_TYPE_MUTUAL == $o->getType())
+                $a = array_merge($a, $o->getConnectedContent($iProfileId, true));
+            else
+                $a = array_merge($a, $o->getConnectedContent($iProfileId));
+        }
+
+        $a = array_unique($a);
+        $aRet = array();
+        foreach ($a as $iConnectedProfileId) {
+            if (!($oConnectedProfile = BxDolProfile::getInstance($iConnectedProfileId)))
+                continue;
+            if ($oConnectedProfile->getModule() != $this->getName())
+                continue;
+
+            if (CHECK_ACTION_RESULT_ALLOWED === bx_srv($oConnectedProfile->getModule(), 'check_allowed_post_in_profile', array($oConnectedProfile->getContentId(), $iProfileId)))
+                $aRet[] = $iConnectedProfileId;
+        }
+
+        bx_alert('system', 'get_participating_profiles', $iProfileId, false, array(
+            'module' => $this->_oConfig->getName(),
+            'profiles' => &$aRet
+        ));
+
+        return $aRet;
+    }
+
+    /**
+     * Prepare fields from some universal set of fields to fields in particular profile module. 
+     * By default only 'name' and 'description' fields are supported.
+     * After fields convertion it can be used in @see BxBaseModGeneralModule::serviceEntityAdd
+     * @param $aFieldsProfile fields in soem universal format.
+     * @return array which is ready to use for particular module
+     */ 
+    public function servicePrepareFields ($aFieldsProfile)
+    {        
+        bx_alert($this->getName(), 'prepare_fields', 0, 0, array('fields_orig' => $aFieldsProfile, 'fields_result' => &$aFieldsProfile));
+
+        return $aFieldsProfile;
+    }
+
+    protected function _servicePrepareFields ($aFieldsProfile, $aFieldsDefault, $aMap)
+    {
+        $aFieldsOrig = $aFieldsProfile;
+
+        bx_import('BxDolPrivacy');
+        $aFieldsDefault2 = array(
+            'allow_view_to' => BX_DOL_PG_ALL,
+            'allow_contact_to' => BX_DOL_PG_ALL, 
+            'allow_post_to' => BX_DOL_PG_FRIENDS,
+        );
+        $aFieldsProfile = array_merge($aFieldsDefault2, $aFieldsDefault, $aFieldsProfile);
+
+        foreach ($aMap as $k => $v) {
+            if (isset($aFieldsProfile[$v]))
+                $aFieldsProfile[$k] = $aFieldsProfile[$v];
+            if ($k != $v && !isset($aMap[$v]))
+                unset($aFieldsProfile[$v]);
+        }
+        
+        bx_alert($this->getName(), 'prepare_fields', 0, 0, array('fields_orig' => $aFieldsOrig, 'fields_result' => &$aFieldsProfile));
+
+        return $aFieldsProfile;
+    }
+
+    public function serviceFormsHelper ()
+    {
+        return parent::serviceFormsHelper ();
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_base_profile Base Profile
+     * @subsection bx_base_profile-forms Forms
+     * @subsubsection bx_base_profile-entity_create entity_create
+     * 
+     * @code bx_srv('bx_persons', 'entity_create', [...]); @endcode
+     * 
+     * Get profile creation form
+     * @param $sDisplay optional form display name
+     * 
+     * @see BxBaseModProfileModule::serviceEntityCreate
+     */
+    /** 
+     * @ref bx_base_profile-entity_create "entity_create"
+     */
+    public function serviceEntityCreate ($sDisplay = false)
+    {
+	    BxDolInformer::getInstance($this->_oTemplate)->setEnabled(false);
+
+	    return parent::serviceEntityCreate ($sDisplay);
+    }
+
+    public function serviceActAsProfile ()
+    {
+        return true;
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_base_profile Base Profile
+     * @subsection bx_base_profile-browse Browse
+     * @subsubsection bx_base_profile-browse_recommended browse_recommended
+     * 
+     * @code bx_srv('bx_persons', 'browse_recommended', [...]); @endcode
+     * 
+     * Browse recommended profiles
+     * @param $sUnitView optional, unit view, for example - showcase
+     * @param $bEmptyMessage optional, display or not "empty" message when there is no content
+     * @param $bAjaxPaginate optional, use AJAX paginate or not
+     * 
+     * @see BxBaseModProfileModule::serviceBrowseRecommended
+     */
+    /** 
+     * @ref bx_base_profile-browse_recommended "browse_recommended"
+     */
+    public function serviceBrowseRecommended ($sUnitView = false, $bEmptyMessage = false, $bAjaxPaginate = true)
+    {
+        if (!isLogged())
+            return '';
+        return $this->_serviceBrowse ('recommended', $sUnitView ? array('unit_view' => $sUnitView) : false, BX_DB_PADDING_DEF, $bEmptyMessage, $bAjaxPaginate);
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_base_profile Base Profile
+     * @subsection bx_base_profile-browse Browse
+     * @subsubsection bx_base_profile-browse_recent_profiles browse_recent_profiles
+     * 
+     * @code bx_srv('bx_persons', 'browse_recent_profiles', [...]); @endcode
+     * 
+     * Browse recently joined profiles
+     * @param $bEmptyMessage optional, display or not "empty" message when there is no content
+     * @param $bAjaxPaginate optional, use AJAX paginate or not
+     * 
+     * @see BxBaseModProfileModule::serviceBrowseRecentProfiles
+     */
+    /** 
+     * @ref bx_base_profile-browse_recent_profiles "browse_recent_profiles"
+     */
+    public function serviceBrowseRecentProfiles ($bDisplayEmptyMsg = false, $bAjaxPaginate = true)
+    {
+        return $this->_serviceBrowse ('recent', false, BX_DB_PADDING_DEF, $bDisplayEmptyMsg, $bAjaxPaginate);
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_base_profile Base Profile
+     * @subsection bx_base_profile-browse Browse
+     * @subsubsection bx_base_profile-browse_active_profiles browse_active_profiles
+     * 
+     * @code bx_srv('bx_persons', 'browse_active_profiles', [...]); @endcode
+     * 
+     * Browse recently active profiles
+     * @param $bEmptyMessage optional, display or not "empty" message when there is no content
+     * @param $bAjaxPaginate optional, use AJAX paginate or not
+     * 
+     * @see BxBaseModProfileModule::serviceBrowseActiveProfiles
+     */
+    /** 
+     * @ref bx_base_profile-browse_active_profiles "browse_active_profiles"
+     */
+    public function serviceBrowseActiveProfiles ($sUnitView = false, $bEmptyMessage = false, $bAjaxPaginate = false)
+    {
+        return $this->_serviceBrowse ('active', $sUnitView ? array('unit_view' => $sUnitView) : false, BX_DB_PADDING_DEF, $bEmptyMessage, $bAjaxPaginate);
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_base_profile Base Profile
+     * @subsection bx_base_profile-browse Browse
+     * @subsubsection bx_base_profile-browse_top_profiles browse_top_profiles
+     * 
+     * @code bx_srv('bx_persons', 'browse_top_profiles', [...]); @endcode
+     * 
+     * Browse top profiles
+     * @param $bEmptyMessage optional, display or not "empty" message when there is no content
+     * @param $bAjaxPaginate optional, use AJAX paginate or not
+     * 
+     * @see BxBaseModProfileModule::serviceBrowseTopProfiles
+     */
+    /** 
+     * @ref bx_base_profile-browse_top_profiles "browse_top_profiles"
+     */
+    public function serviceBrowseTopProfiles ($bDisplayEmptyMsg = false, $bAjaxPaginate = false)
+    {
+        return $this->_serviceBrowse ('top', false, BX_DB_PADDING_DEF, $bDisplayEmptyMsg, $bAjaxPaginate);
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_base_profile Base Profile
+     * @subsection bx_base_profile-browse Browse
+     * @subsubsection bx_base_profile-browse_online_profiles browse_online_profiles
+     * 
+     * @code bx_srv('bx_persons', 'browse_online_profiles', [...]); @endcode
+     * 
+     * Browse online profiles
+     * @param $bEmptyMessage optional, display or not "empty" message when there is no content
+     * @param $bAjaxPaginate optional, use AJAX paginate or not
+     * 
+     * @see BxBaseModProfileModule::serviceBrowseOnlineProfiles
+     */
+    /** 
+     * @ref bx_base_profile-browse_online_profiles "browse_online_profiles"
+     */
+    public function serviceBrowseOnlineProfiles ($bDisplayEmptyMsg = false, $bAjaxPaginate = false)
+    {
+        return $this->_serviceBrowse ('online', false, BX_DB_PADDING_DEF, $bDisplayEmptyMsg, $bAjaxPaginate);
+    }
+
+    public function serviceBrowseConnections ($iProfileId, $sObjectConnections = 'sys_profiles_friends', $sConnectionsType = 'content', $iMutual = false, $iDesignBox = BX_DB_PADDING_DEF, $iProfileId2 = 0)
+    {
+        return $this->_serviceBrowse (
+            'connections',
+            array(
+                'object' => $sObjectConnections,
+                'type' => $sConnectionsType,
+                'mutual' => $iMutual,
+                'profile' => (int)$iProfileId,
+                'profile2' => (int)$iProfileId2),
+            $iDesignBox
+        );
+    }
+
+    public function serviceBrowseRelationsQuick ($iProfileId, $sObjectConnections = 'sys_profiles_friends', $sConnectionsType = 'content', $iMutual = false, $iProfileId2 = 0)
+    {
+        // get connections object
+        $oConnection = BxDolConnection::getObjectInstance($sObjectConnections);
+        if (!$oConnection)
+            return '';       
+
+        // set some vars
+        $iStart = (int)bx_get('start');
+        $iLimit = empty($this->_oConfig->CNF['PARAM_NUM_CONNECTIONS_QUICK']) ? 4 : getParam($this->_oConfig->CNF['PARAM_NUM_CONNECTIONS_QUICK']);
+        if (!$iLimit)
+            $iLimit = 4;
+
+        // get connections array
+        bx_import('BxDolConnection');
+        $aConnections = $oConnection->getConnectionsAsArrayExt($sConnectionsType, $iProfileId, $iProfileId2, $iMutual, $iStart, $iLimit + 1, BX_CONNECTIONS_ORDER_ADDED_DESC);
+        if(empty($aConnections) || !is_array($aConnections))
+            return '';
+
+        $aResult = array();
+        foreach($aConnections as $iProfile => $aConnection)
+            $aResult[] = array(
+                'id' => $iProfile,
+                'info' => array(
+                    'addon' => $oConnection->getRelationTranslation($aConnection['relation'])
+                )
+            );
+
+        return $this->_serviceBrowseQuick($aResult, $iStart, $iLimit);
+    }
+
+    public function serviceBrowseConnectionsQuick ($iProfileId, $sObjectConnections = 'sys_profiles_friends', $sConnectionsType = 'content', $iMutual = false, $iProfileId2 = 0)
+    {
+        // get connections object
+        $oConnection = BxDolConnection::getObjectInstance($sObjectConnections);
+        if (!$oConnection)
+            return '';
+
+        // set some vars
+        $iLimit = empty($this->_oConfig->CNF['PARAM_NUM_CONNECTIONS_QUICK']) ? 4 : getParam($this->_oConfig->CNF['PARAM_NUM_CONNECTIONS_QUICK']);
+        if (!$iLimit)
+            $iLimit = 4;
+        $iStart = (int)bx_get('start');
+
+        // get connections array
+        bx_import('BxDolConnection');
+        $a = $oConnection->getConnectionsAsArray ($sConnectionsType, $iProfileId, $iProfileId2, $iMutual, (int)bx_get('start'), $iLimit + 1, BX_CONNECTIONS_ORDER_ADDED_DESC);
+        if (!$a)
+            return '';
+
+        return $this->_serviceBrowseQuick($a, $iStart, $iLimit);
+    }
+
+    public function serviceBrowseConnectionsByType ($aParamsCnn, $aParamsBrs = array())
+    {
+        $sAll = 'all';
+
+        $aParamsCnn['object'] = !empty($aParamsCnn['object']) ? $aParamsCnn['object'] : 'sys_profiles_friends';
+        $aParamsCnn['type'] = !empty($aParamsCnn['type']) ? $aParamsCnn['type'] : 'content';
+        $aParamsCnn['profile_id2'] = !empty($aParamsCnn['profile_id2']) ? (int)$aParamsCnn['profile_id2'] : 0;
+        $aParamsCnn['mutual'] = isset($aParamsCnn['mutual']) ? $aParamsCnn['mutual'] : false;
+
+        $oConnection = BxDolConnection::getObjectInstance($aParamsCnn['object']);
+        if(!$oConnection)
+            return '';
+
+        $aSQLParts = $oConnection->getConnectionsAsSQLParts ($aParamsCnn['type'], 'sys_profiles', 'id', $aParamsCnn['profile_id'], $aParamsCnn['profile_id2'], $aParamsCnn['mutual']);
+
+        $sType = !empty($aParamsBrs['type']) ? $aParamsBrs['type'] : $sAll;
+        if(bx_get('type') !== false)
+            $sType = bx_process_input(bx_get('type'));
+
+        $iStart = (int)bx_get('start');
+
+        $iLimitDefault = 4;
+        $iLimit = !empty($this->_oConfig->CNF['PARAM_NUM_CONNECTIONS_QUICK']) ? getParam($this->_oConfig->CNF['PARAM_NUM_CONNECTIONS_QUICK']) : $iLimitDefault;
+        if(!empty($aParamsBrs['per_page']))
+            $iLimit = (int)$aParamsBrs['per_page'];
+        if(!$iLimit)
+            $iLimit = $iLimitDefault;
+
+        $aProfiles = BxDolProfileQuery::getInstance()->getConnectedProfilesByType($aSQLParts, ($sType != $sAll ? $sType : ''), $iStart, $iLimit + 1);
+
+        $mixedMenu = '';
+        if(!isset($aParamsBrs['filter_menu']) || $aParamsBrs['filter_menu'] === true) {
+            $aModules = array(
+                array('name' => $sAll)
+            );
+            $aModules = array_merge($aModules, BxDolModuleQuery::getInstance()->getModulesBy(array('type' => 'modules', 'active' => 1)));
+
+            $aMenuItems = array();
+            foreach($aModules as $aModule)
+                if($aModule['name'] == $sAll || BxDolRequest::serviceExists($aModule['name'], 'act_as_profile'))
+                    $aMenuItems[] = array(
+                    	'id' => $aModule['name'], 
+                    	'name' => $aModule['name'], 
+                    	'class' => '', 
+                    	'link' => 'javascript:void(0)', 
+                    	'onclick' => "return !loadDynamicBlockAutoPaginate(this, " . $iStart . ", " . $iLimit . ", " . bx_js_string(json_encode(array('type' => $aModule['name']))) . ");", 
+                    	'target' => '_self', 
+                    	'title' => _t('_' . $aModule['name']), 
+                    	'active' => 1
+                    );
+    
+            if(!empty($aMenuItems)) {
+                $mixedMenu = new BxTemplMenu(array('template' => 'menu_vertical.html', 'menu_id'=> $this->_oConfig->getName() . '-connections-by-type', 'menu_items' => $aMenuItems));
+                $mixedMenu->setSelected('', $sType);
+            }
+        }
+
+        return array(
+            'menu' => $mixedMenu,
+            'content' => $this->_serviceBrowseQuick(array_keys($aProfiles), $iStart, $iLimit, array('type' => $sType))
+        );
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_base_profile Base Profile
+     * @subsection bx_base_profile-browse Browse
+     * @subsubsection bx_base_profile-browse_by_acl browse_by_acl
+     * 
+     * @code bx_srv('bx_persons', 'browse_by_acl', [...]); @endcode
+     * 
+     * Browse profiles by membership(ACL) level
+     * @param $mixedLevelId membership level, or array of membership levels
+     * @param $iDesignBox optional, design box identifier
+     * 
+     * @see BxBaseModProfileModule::serviceBrowseByAcl
+     */
+    /** 
+     * @ref bx_base_profile-browse_by_acl "browse_by_acl"
+     */
+	public function serviceBrowseByAcl ($mixedLevelId, $iDesignBox = BX_DB_PADDING_DEF)
+    {
+        return $this->_serviceBrowse (
+            'acl',
+            array(
+                'level' => $mixedLevelId,
+			),
+            $iDesignBox,
+            true
+        );
+    }
+
+    public function serviceEntityEditCover ($iContentId = 0)
+    {
+        return $this->_serviceEntityForm ('editDataForm', $iContentId, $this->_oConfig->CNF['OBJECT_FORM_ENTRY_DISPLAY_EDIT_COVER']);
+    }
+
+	/**
+     * Entry comments
+     */
+    public function serviceEntityCommentsByProfile ($iProfileId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+        if(empty($CNF['OBJECT_COMMENTS']))
+            return '';
+
+        $oProfile = BxDolProfile::getInstance($iProfileId);
+        if(!$oProfile)
+            return '';
+
+        return $this->_entityComments($CNF['OBJECT_COMMENTS'], $oProfile->getContentId());
+    }
+
+    /**
+     * Entry social sharing block
+     */
+    public function serviceEntitySocialSharing ($mixedContent = false, $aParams = array())
+    {
+        if(!empty($mixedContent)) {
+            if(!is_array($mixedContent))
+               $mixedContent = array((int)$mixedContent, array());
+        }
+        else {
+            $mixedContent = $this->_getContent();
+            if($mixedContent === false)
+                return false;
+        }
+
+        list($iContentId, $aContentInfo) = $mixedContent;    
+
+        return parent::serviceEntitySocialSharing(array($iContentId, $aContentInfo), array(
+            'id_thumb' => !empty($CNF['FIELD_PICTURE']) && !empty($aContentInfo[$CNF['FIELD_PICTURE']]) ? $aContentInfo[$CNF['FIELD_PICTURE']] : 0, 
+        ));
+    }
+
+    public function serviceProfileMembership ($iContentId = 0)
+    {
+    	$mixedContent = $this->_getContent($iContentId);
+        if($mixedContent === false)
+            return false;
+
+        list($iContentId, $aContentInfo) = $mixedContent;
+
+		return BxDolAcl::getInstance()->getProfileMembership($aContentInfo['profile_id']);
+    }
+
+    public function serviceProfileFriends ($iContentId = 0)
+    {
+        $mixedContent = $this->_getContent($iContentId);
+        if($mixedContent === false)
+            return false;
+
+        list($iContentId, $aContentInfo) = $mixedContent;
+
+        bx_import('BxDolConnection');
+        $s = $this->serviceBrowseConnectionsQuick ($aContentInfo['profile_id'], 'sys_profiles_friends', BX_CONNECTIONS_CONTENT_TYPE_CONTENT, true);
+        if (!$s)
+            return MsgBox(_t('_sys_txt_empty'));
+        return $s;
+    }
+
+    public function serviceProfileSubscriptions ($iContentId = 0, $aParams = array())
+    {
+        $mixedContent = $this->_getContent($iContentId);
+        if($mixedContent === false)
+            return false;
+
+        list($iContentId, $aContentInfo) = $mixedContent;
+
+        bx_import('BxDolConnection');
+        $aResult = $this->serviceBrowseConnectionsByType(array(
+        	'profile_id' => $aContentInfo['profile_id'],  
+        	'object' => 'sys_profiles_subscriptions', 
+            'type' => BX_CONNECTIONS_CONTENT_TYPE_CONTENT
+        ), array(
+            'type' => isset($aParams['type']) ? $aParams['type'] : '',
+            'filter_menu' => isset($aParams['filter_menu']) ? $aParams['filter_menu'] : true,
+        ));
+        if(empty($aResult['content']))
+            $aResult['content'] = MsgBox(_t('_sys_txt_empty'));
+
+        return $aResult;
+    }
+
+    public function serviceProfileSubscribedMe ($iContentId = 0)
+    {
+        $mixedContent = $this->_getContent($iContentId);
+        if($mixedContent === false)
+            return false;
+
+        list($iContentId, $aContentInfo) = $mixedContent;
+
+        bx_import('BxDolConnection');
+        $s = $this->serviceBrowseConnectionsQuick ($aContentInfo['profile_id'], 'sys_profiles_subscriptions', BX_CONNECTIONS_CONTENT_TYPE_INITIATORS);
+        if (!$s)
+            return MsgBox(_t('_sys_txt_empty'));
+
+        return $s;
+    }
+
+    public function serviceProfileRelations ($iContentId = 0, $aParams = array())
+    {
+        $mixedContent = $this->_getContent($iContentId);
+        if($mixedContent === false)
+            return false;
+
+        list($iContentId, $aContentInfo) = $mixedContent;
+
+        bx_import('BxDolConnection');
+        $s = $this->serviceBrowseRelationsQuick ($aContentInfo['profile_id'], 'sys_profiles_relations', BX_CONNECTIONS_CONTENT_TYPE_CONTENT, 1);
+        if (!$s)
+            return MsgBox(_t('_sys_txt_empty'));
+
+        return $s;
+    }
+
+    public function serviceProfileRelatedMe ($iContentId = 0)
+    {
+        $mixedContent = $this->_getContent($iContentId);
+        if($mixedContent === false)
+            return false;
+
+        list($iContentId, $aContentInfo) = $mixedContent;
+
+        bx_import('BxDolConnection');
+        $s = $this->serviceBrowseRelationsQuick ($aContentInfo['profile_id'], 'sys_profiles_relations', BX_CONNECTIONS_CONTENT_TYPE_INITIATORS, 1);
+        if (!$s)
+            return MsgBox(_t('_sys_txt_empty'));
+
+        return $s;
+    }
+
+    /**
+     * check enabled profile activation letter
+     */
+    public function  serviceIsEnableProfileActivationLetter()
+    {
+        $CNF = &$this->_oConfig->CNF;
+        return isset($CNF['PARAM_ENABLE_ACTIVATION_LETTER']) ? (bool)getParam($CNF['PARAM_ENABLE_ACTIVATION_LETTER']) : true;
+    }
+
+    public function serviceIsEnableRelations()
+    {
+        $sModule = $this->_oConfig->getName();
+        $oRelations = BxDolConnection::getObjectInstance('sys_profiles_relations');
+        return $oRelations->isRelationAvailableWithProfile($sModule) || $oRelations->isRelationAvailableFromProfile($sModule);
+    }
+
+    /**
+     * For internal usage only.
+     */
+    public function serviceDeleteEntityService ($iContentId, $bDeleteWithContent = false)
+    {
+        return parent::serviceDeleteEntity ($iContentId, 'deleteDataService');
+    }
+
+	/**
+     * Data for Notifications module
+     */
+    public function serviceGetNotificationsData()
+    {
+        $a = parent::serviceGetNotificationsData();
+
+        $sModule = $this->_aModule['name'];
+        
+        $a['handlers'][] = array('group' => $sModule . '_timeline_post_common', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'timeline_post_common', 'module_name' => $sModule, 'module_method' => 'get_notifications_timeline_post_common', 'module_class' => 'Module');
+
+        $a['settings'][] = array('group' => 'timeline_post', 'unit' => $sModule, 'action' => 'timeline_post_common', 'types' => array('follow_member'));
+
+        $a['alerts'][] = array('unit' => $sModule, 'action' => 'timeline_post_common');
+
+        return $a;
+    }
+
+    public function serviceGetNotificationsPost($aEvent)
+    {
+        $aResult = parent::serviceGetNotificationsVote($aEvent);
+        if(empty($aResult) || !is_array($aResult) || !$this->serviceActAsProfile())
+            return $aResult;
+
+        $oProfile = BxDolProfile::getInstanceByContentAndType((int)$aEvent['object_id'], $this->_oConfig->getName());
+        if($oProfile !== false)
+            $aResult['entry_author'] = $oProfile->id();
+
+        return $aResult;
+    }
+
+    public function serviceGetNotificationsVote($aEvent)
+    {
+        $aResult = parent::serviceGetNotificationsVote($aEvent);
+        if(empty($aResult) || !is_array($aResult) || !$this->serviceActAsProfile())
+            return $aResult;
+
+        $oProfile = BxDolProfile::getInstanceByContentAndType((int)$aEvent['object_id'], $this->_oConfig->getName());
+        if($oProfile !== false)
+            $aResult['entry_author'] = $oProfile->id();
+
+        return $aResult;
+    }
+
+    /**
+     * Notification about new member requst in the group
+     */
+    public function serviceGetNotificationsTimelinePostCommon($aEvent)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $iContentId = (int)$aEvent['object_id'];
+        $oGroupProfile = BxDolProfile::getInstanceByContentAndType((int)$iContentId, $this->getName());
+        if(!$oGroupProfile)
+            return array();
+
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+        if(empty($aContentInfo) || !is_array($aContentInfo))
+            return array();
+            
+        $aSubcontentInfo = BxDolService::call('bx_timeline', 'get_info', array((int)$aEvent['subobject_id'], false));
+        if(empty($aSubcontentInfo) || !is_array($aSubcontentInfo))
+            return array();
+
+        $sSubentryUrl = BxDolService::call('bx_timeline', 'get_link', array((int)$aEvent['subobject_id']));
+        $sSubentrySample = $aSubcontentInfo['title'];
+        if(empty($sSubentrySample))
+            $sSubentrySample = strmaxtextlen($aSubcontentInfo['description'], 20, '...');
+
+        return array(
+            'entry_sample' => $CNF['T']['txt_sample_single'],
+            'entry_url' => $oGroupProfile->getUrl(),
+            'entry_caption' => $oGroupProfile->getDisplayName(),
+            'entry_author' => $oGroupProfile->id(),
+            'subentry_sample' => $sSubentrySample,
+            'subentry_url' => $sSubentryUrl,
+            'lang_key' => $CNF['T']['txt_ntfs_timeline_post_common'],
+        );
+    }
+
+	/**
+     * Data for Timeline module
+     */
+    public function serviceGetTimelineData()
+    {
+        $CNF = &$this->_oConfig->CNF;
+        $sModule = $this->_aModule['name'];
+
+        $aAlerts = array();
+        $aHandlers = array();
+        if(!empty($CNF['FIELD_PICTURE'])) {
+            $aAlerts = array_merge($aAlerts, array(
+                array('unit' => $sModule, 'action' => 'profile_picture_changed'),
+                array('unit' => $sModule, 'action' => 'profile_picture_deleted')
+            ));
+            $aHandlers = array_merge($aHandlers, array(
+                array('group' => $sModule . '_profile_picture', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'profile_picture_changed', 'module_name' => $sModule, 'module_method' => 'get_timeline_profile_picture', 'module_class' => 'Module',  'groupable' => 0, 'group_by' => ''),
+                array('group' => $sModule . '_profile_picture', 'type' => 'delete', 'alert_unit' => $sModule, 'alert_action' => 'profile_picture_deleted')
+            ));
+        }
+
+        if(!empty($CNF['FIELD_COVER'])) {
+            $aAlerts = array_merge($aAlerts, array(
+                array('unit' => $sModule, 'action' => 'profile_cover_changed'),
+                array('unit' => $sModule, 'action' => 'profile_cover_deleted')
+            ));
+            $aHandlers = array_merge($aHandlers, array(
+                array('group' => $sModule . '_profile_cover', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'profile_cover_changed', 'module_name' => $sModule, 'module_method' => 'get_timeline_profile_cover', 'module_class' => 'Module',  'groupable' => 0, 'group_by' => ''),
+                array('group' => $sModule . '_profile_cover', 'type' => 'delete', 'alert_unit' => $sModule, 'alert_action' => 'profile_cover_deleted')
+            ));
+        }
+
+    	$aResult = array();
+        if(!empty($aAlerts) && !empty($aHandlers))
+            $aResult = array(
+            	'handlers' => $aHandlers,
+            	'alerts' => $aAlerts
+            );
+
+        return $aResult;
+    }
+
+    /**
+     * Entry post for Timeline module
+     */
+    public function serviceGetTimelineProfilePicture($aEvent, $aBrowseParams = array())
+    {
+        $aResult = $this->_serviceGetTimelineProfileImage($aEvent, $aBrowseParams, array(
+            'stg' => 'OBJECT_STORAGE',
+            'trans' => array('OBJECT_IMAGES_TRANSCODER_GALLERY', 'OBJECT_IMAGES_TRANSCODER_AVATAR'),
+            'trans_orig' => array('OBJECT_IMAGES_TRANSCODER_PICTURE', 'OBJECT_IMAGES_TRANSCODER_GALLERY'),
+            'txt_ss' => 'txt_sample_pp_single',
+            'txt_sswa' => 'txt_sample_pp_single_with_article',
+            'txt_sa' => 'txt_sample_pi_action',
+            'txt_sau' => 'txt_sample_pi_action_user'
+        ));
+        $aResult['allowed_view'] = array('module' => $this->_oConfig->getName(), 'method' => 'get_timeline_profile_picture_allowed_view');
+
+        return $aResult;
+    }
+
+    public function serviceGetTimelineProfilePictureAllowedView($aEvent)
+    {
+        return $this->_serviceGetTimelineProfileImageAllowedView($aEvent);
+    }
+
+    public function serviceGetTimelineProfileCover($aEvent, $aBrowseParams = array())
+    {
+        $aResult = $this->_serviceGetTimelineProfileImage($aEvent, $aBrowseParams, array(
+            'stg' => 'OBJECT_STORAGE_COVER',
+            'trans' => array('OBJECT_IMAGES_TRANSCODER_GALLERY', 'OBJECT_IMAGES_TRANSCODER_COVER_THUMB'),
+            'trans_orig' => array('OBJECT_IMAGES_TRANSCODER_COVER', 'OBJECT_IMAGES_TRANSCODER_GALLERY'),
+            'txt_ss' => 'txt_sample_pc_single',
+            'txt_sswa' => 'txt_sample_pc_single_with_article',
+            'txt_sa' => 'txt_sample_pi_action',
+            'txt_sau' => 'txt_sample_pi_action_user'
+        ));
+        $aResult['allowed_view'] = array('module' => $this->_oConfig->getName(), 'method' => 'get_timeline_profile_cover_allowed_view');
+
+        return $aResult;
+    }
+    
+    public function serviceGetTimelineProfileCoverAllowedView($aEvent)
+    {
+        return $this->_serviceGetTimelineProfileImageAllowedView($aEvent);
+    }
+
+    public function serviceGetConnectionButtonsTitles($iProfileId, $sConnectionsObject = 'sys_profiles_friends')
+    {
+        if (!isLogged())
+            return array();
+
+        if (!($oConn = BxDolConnection::getObjectInstance($sConnectionsObject)))
+            return array();
+
+        $CNF = $this->_oConfig->CNF;
+
+        if ($oConn->isConnectedNotMutual(bx_get_logged_profile_id(), $iProfileId)) {
+            return array(
+                'add' => _t($CNF['T']['menu_item_title_befriend_sent']),
+                'remove' => _t($CNF['T']['menu_item_title_unfriend_cancel_request']),
+            );
+        } elseif ($oConn->isConnectedNotMutual($iProfileId, bx_get_logged_profile_id())) {
+            return array(
+                'add' => _t($CNF['T']['menu_item_title_befriend_confirm']),
+                'remove' => _t($CNF['T']['menu_item_title_unfriend_reject_request']),
+            );
+        } elseif ($oConn->isConnected($iProfileId, bx_get_logged_profile_id(), true)) {
+            return array(
+                'add' => '',
+                'remove' => _t($CNF['T']['menu_item_title_unfriend']),
+            );
+        } else {
+            return array(
+                'add' => _t($CNF['T']['menu_item_title_befriend']),
+                'remove' => '',
+            );
+        }
+    }
+
+
+    // ====== PERMISSION METHODS
+    /**
+     * @see iBxDolProfileService::serviceCheckAllowedProfileView
+     */ 
+    public function serviceCheckAllowedProfileView($iContentId)
+    {        
+        return $this->serviceCheckAllowedWithContent('View', $iContentId);
+    }
+
+    /**
+     * @see iBxDolProfileService::serviceCheckAllowedProfileContact
+     */ 
+    public function serviceCheckAllowedProfileContact($iContentId)
+    {        
+        return $this->serviceCheckAllowedWithContent('Contact', $iContentId);
+    }
+
+    /**
+     * @see iBxDolProfileService::serviceCheckAllowedPostInProfile
+     */
+    public function serviceCheckAllowedPostInProfile($iContentId)
+    {
+        return $this->serviceCheckAllowedWithContent('Post', $iContentId);
+    }
+
+    /**
+     * @see iBxDolProfileService::serviceCheckSpacePrivacy
+     */ 
+    public function serviceCheckSpacePrivacy($iContentId)
+    {
+        return $this->serviceCheckAllowedProfileView($iContentId);
+    }
+
+    /**
+     * Check if the profile can be viewed.
+     * 
+     * NOTE. This service should be used if it's needed to pass some specific values in 
+     * $isPerformAction and $iProfileId parameters, otherwise it's recommended to use 
+     * BxBaseModProfileModule::serviceCheckAllowedProfileView service method or 
+     * BxDolProfile::checkAllowedProfileView method.
+     * 
+     * @param type $aDataEntry - entry which the action will be performed for
+     * @param type $isPerformAction - perform or just check the action
+     * @param type $iProfileId - performer's profile ID
+     * @return integer - one of CHECK_ACTION_RESULT_XXX constants.
+     */
+    public function serviceCheckAllowedViewForProfile ($aDataEntry, $isPerformAction = false, $iProfileId = false)
+    {
+        if (!$iProfileId)
+            $iProfileId = $this->_iProfileId;
+
+        $oProfile = BxDolProfile::getInstanceByContentAndType($aDataEntry[$this->_oConfig->CNF['FIELD_ID']], $this->getName());
+        if ($oProfile && $oProfile->id() == $iProfileId)
+            return CHECK_ACTION_RESULT_ALLOWED;
+
+        return parent::serviceCheckAllowedViewForProfile ($aDataEntry, $isPerformAction, $iProfileId);
+    }
+
+    /**
+     * Check if posting (comment, post in Timeline) is available.
+     * 
+     * NOTE. This service should be used if it's needed to pass some specific values in 
+     * $isPerformAction and $iProfileId parameters, otherwise it's recommended to use 
+     * BxBaseModProfileModule::serviceCheckAllowedPostInProfile service method or 
+     * BxDolProfile::checkAllowedPostInProfile method.
+     * 
+     * @param type $aDataEntry - entry which the action will be performed for
+     * @param type $isPerformAction - perform or just check the action
+     * @param type $iProfileId - performer's profile ID
+     * @return integer - one of CHECK_ACTION_RESULT_XXX constants.
+     */
+    public function serviceCheckAllowedPostForProfile ($aDataEntry, $isPerformAction = false, $iProfileId = false)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(!$iProfileId)
+            $iProfileId = $this->_iProfileId;
+
+        // check is view allowed
+        if(($mixedResult = $this->serviceCheckAllowedViewForProfile($aDataEntry, $isPerformAction, $iProfileId)) !== CHECK_ACTION_RESULT_ALLOWED)
+            return $mixedResult;
+
+        // moderator and owner always have access
+        $oProfile = BxDolProfile::getInstanceByContentAndType($aDataEntry[$CNF['FIELD_ID']], $this->getName());
+        if(($oProfile && $oProfile->id() == $iProfileId) || $this->_isModerator($isPerformAction))
+            return CHECK_ACTION_RESULT_ALLOWED;
+
+        // check privacy
+        if(!empty($CNF['OBJECT_PRIVACY_POST'])) {
+            $oPrivacy = BxDolPrivacy::getObjectInstance($CNF['OBJECT_PRIVACY_POST']);
+            if($oPrivacy && !$oPrivacy->check($aDataEntry[$CNF['FIELD_ID']], $iProfileId))
+                return _t('_sys_access_denied_to_private_content');
+        }
+
+        // check alert to allow custom checks
+        $mixedResult = null;
+        bx_alert('system', 'check_allowed_post', 0, 0, array('module' => $this->getName(), 'content_info' => $aDataEntry, 'profile_id' => $iProfileId, 'override_result' => &$mixedResult));
+        if($mixedResult !== null)
+            return $mixedResult;
+
+        return CHECK_ACTION_RESULT_ALLOWED;
+    }
+
+    /**
+     * Check if profile can be contacted.
+     * 
+     * NOTE. This service should be used if it's needed to pass some specific values in 
+     * $isPerformAction and $iProfileId parameters, otherwise it's recommended to use 
+     * BxBaseModProfileModule::serviceCheckAllowedProfileContact service method or 
+     * BxDolProfile::checkAllowedProfileContact method.
+     * 
+     * @param type $aDataEntry - entry which the action will be performed for
+     * @param type $isPerformAction - perform or just check the action
+     * @param type $iProfileId - performer's profile ID
+     * @return integer - one of CHECK_ACTION_RESULT_XXX constants.
+     */
+    public function serviceCheckAllowedContactForProfile ($aDataEntry, $isPerformAction = false, $iProfileId = false)
+    {
+        $mixedResult = $this->_serviceCheckAllowedContactForProfile($aDataEntry, $isPerformAction, $iProfileId);
+
+        // call alert to allow custom checks
+        bx_alert('system', 'check_allowed_contact', 0, 0, array(
+            'module' => $this->getName(), 
+            'content_info' => $aDataEntry, 
+            'profile_id' => $iProfileId, 
+            'override_result' => &$mixedResult
+        ));
+
+        return $mixedResult;
+    }
+
+    public function serviceSetViewProfileCover($oPage, $aProfileInfo)
+    {
+        $this->_oTemplate->setCover($oPage,$aProfileInfo);
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden. So make sure to make strict(===) checking.
+     */
+    public function checkAllowedAdd ($isPerformAction = false)
+    {
+        $oAccount = BxDolAccount::getInstance();
+        if (!$oAccount || ($this->serviceActAsProfile() && $oAccount->isProfilesLimitReached()))
+            return _t('_sys_txt_access_denied');
+        return parent::checkAllowedAdd ($isPerformAction);
+    }   
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden. So make sure to make strict(===) checking.
+     */
+    public function checkAllowedView ($aDataEntry, $isPerformAction = false)
+    {
+        return $this->serviceCheckAllowedViewForProfile ($aDataEntry, $isPerformAction);
+    }
+    
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden.
+     */
+    public function checkAllowedViewProfileImage ($aDataEntry, $isPerformAction = false)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        // check privacy
+        if (empty($CNF['OBJECT_PRIVACY_VIEW'])) 
+            return CHECK_ACTION_RESULT_ALLOWED;
+
+        $oPrivacy = BxDolPrivacy::getObjectInstance($CNF['OBJECT_PRIVACY_VIEW']);
+        if ($oPrivacy && !$oPrivacy->check($aDataEntry[$CNF['FIELD_ID']]) && !$oPrivacy->isPartiallyVisible($aDataEntry[$CNF['FIELD_ALLOW_VIEW_TO']]))
+            return _t('_sys_access_denied_to_private_content');
+
+        return CHECK_ACTION_RESULT_ALLOWED;
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden.
+     */
+    public function checkAllowedViewCoverImage ($aDataEntry, $isPerformAction = false)
+    {
+        return $this->checkAllowedViewProfileImage($aDataEntry);
+    }
+    
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden.
+     */
+    public function checkAllowedPost ($aDataEntry, $isPerformAction = false)
+    {
+        return $this->serviceCheckAllowedPostForProfile ($aDataEntry, $isPerformAction);
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden.
+     */
+    public function checkAllowedContact ($aDataEntry, $isPerformAction = false)
+    {
+        return $this->serviceCheckAllowedContactForProfile ($aDataEntry, $isPerformAction);
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden.
+     */
+    public function checkAllowedEdit ($aDataEntry, $isPerformAction = false)
+    {
+        // moderator always has access
+        if ($this->_isModerator($isPerformAction))
+            return CHECK_ACTION_RESULT_ALLOWED;
+
+        // owner (checked by account! not as profile as ususal) always have access
+        $oProfile = BxDolProfile::getInstanceByContentAndType($aDataEntry[$this->_oConfig->CNF['FIELD_ID']], $this->_aModule['name']);
+        if (!$oProfile)
+            return _t('_sys_txt_error_occured');
+
+        if ($oProfile->getAccountId() == $this->_iAccountId)
+            return CHECK_ACTION_RESULT_ALLOWED;
+
+        return _t('_sys_txt_access_denied');
+    }
+
+    /**
+     * Check if user can change cover image
+     */
+    public function checkAllowedChangeCover ($aDataEntry, $isPerformAction = false)
+    {
+        // moderator always has access
+        if ($this->_isModerator($isPerformAction))
+            return CHECK_ACTION_RESULT_ALLOWED;
+
+        // owner (checked by account! not as profile as ususal) always have access
+        $oProfile = BxDolProfile::getInstanceByContentAndType($aDataEntry[$this->_oConfig->CNF['FIELD_ID']], $this->_aModule['name']);
+        if (!$oProfile)
+            return _t('_sys_txt_error_occured');
+
+        if ($oProfile->getAccountId() == $this->_iAccountId)
+            return CHECK_ACTION_RESULT_ALLOWED;
+
+        return _t('_sys_txt_access_denied');
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden.
+     */
+    public function checkAllowedDelete (&$aDataEntry, $isPerformAction = false)
+    {
+        // moderator always has access
+        if ($this->_isModerator($isPerformAction))
+            return CHECK_ACTION_RESULT_ALLOWED;
+
+        // check ACL and owner (checked by account! not as profile as ususal)
+        $aCheck = checkActionModule($this->_iProfileId, 'delete entry', $this->getName(), $isPerformAction);
+
+        $oProfile = BxDolProfile::getInstanceByContentAndType($aDataEntry[$this->_oConfig->CNF['FIELD_ID']], $this->_aModule['name'], $isPerformAction);
+        if (!$oProfile)
+            return _t('_sys_txt_error_occured');
+
+        if ($oProfile->getAccountId() == $this->_iAccountId && $aCheck[CHECK_ACTION_RESULT] === CHECK_ACTION_RESULT_ALLOWED)
+            return CHECK_ACTION_RESULT_ALLOWED;
+
+        return _t('_sys_txt_access_denied');
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden.
+     */
+    public function checkAllowedViewMoreMenu (&$aDataEntry, $isPerformAction = false)
+    {
+        $oMenu = BxTemplMenu::getObjectInstance($this->_oConfig->CNF['OBJECT_MENU_ACTIONS_VIEW_ENTRY_MORE']);
+        if (!$oMenu || !$oMenu->getCode())
+            return _t('_sys_txt_access_denied');
+        return CHECK_ACTION_RESULT_ALLOWED;
+    }
+
+    /**
+     * @deprecated since version 11.0.1 use BxBaseModProfileModule::checkAllowedCompose instead
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden.
+     */
+    public function checkAllowedCompose (&$aDataEntry, $isPerformAction = false)
+    {
+        return $this->checkAllowedContact($aDataEntry, $isPerformAction);
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden.
+     */
+    public function checkAllowedFriendAdd (&$aDataEntry, $isPerformAction = false)
+    {
+        return $this->_checkAllowedConnect ($aDataEntry, $isPerformAction, 'sys_profiles_friends', true, false);
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden.
+     */
+    public function checkAllowedFriendRemove (&$aDataEntry, $isPerformAction = false)
+    {
+        if (CHECK_ACTION_RESULT_ALLOWED === $this->_checkAllowedConnect ($aDataEntry, $isPerformAction, 'sys_profiles_friends', false, true, true))
+            return CHECK_ACTION_RESULT_ALLOWED;
+        return $this->_checkAllowedConnect ($aDataEntry, $isPerformAction, 'sys_profiles_friends', false, true, false);
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden.
+     */
+    public function checkAllowedRelationAdd (&$aDataEntry, $isPerformAction = false)
+    {
+        if (CHECK_ACTION_RESULT_ALLOWED !== ($sMsg = $this->checkAllowedView($aDataEntry)))
+            return $sMsg;
+
+        return $this->_checkAllowedConnect ($aDataEntry, $isPerformAction, 'sys_profiles_relations', false, false);
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden.
+     */
+    public function checkAllowedRelationRemove (&$aDataEntry, $isPerformAction = false)
+    {
+        return $this->_checkAllowedConnect ($aDataEntry, $isPerformAction, 'sys_profiles_relations', false, true);
+    }
+
+    public function checkAllowedRelationsView (&$aDataEntry, $isPerformAction = false)
+    {
+        $sResult = _t('_sys_txt_access_denied');
+
+        $sModule = $this->_oConfig->getName();
+        $oRelations = BxDolConnection::getObjectInstance('sys_profiles_relations');
+        if(!$oRelations->isRelationAvailableWithProfile($sModule) && !$oRelations->isRelationAvailableFromProfile($sModule))
+            return $sResult;
+
+        if(empty($aDataEntry) || !is_array($aDataEntry))
+            return $sResult;
+
+        return CHECK_ACTION_RESULT_ALLOWED;
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden.
+     */
+    public function checkAllowedSubscribeAdd (&$aDataEntry, $isPerformAction = false)
+    {
+        if (CHECK_ACTION_RESULT_ALLOWED !== ($sMsg = $this->checkAllowedView($aDataEntry)))
+            return $sMsg;
+        return $this->_checkAllowedConnect ($aDataEntry, $isPerformAction, 'sys_profiles_subscriptions', false, false);
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden.
+     */
+    public function checkAllowedSubscribeRemove (&$aDataEntry, $isPerformAction = false)
+    {
+        return $this->_checkAllowedConnect ($aDataEntry, $isPerformAction, 'sys_profiles_subscriptions', false, true);
+    }
+
+    public function checkAllowedSubscriptionsView (&$aDataEntry, $isPerformAction = false)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $sResult = _t('_sys_txt_access_denied');
+        if(empty($aDataEntry) || !is_array($aDataEntry))
+            return $sResult;
+
+        $oProfile = BxDolProfile::getInstanceByContentAndType($aDataEntry[$CNF['FIELD_ID']], $this->_aModule['name']);
+        if(!$oProfile || ($oProfile->id() != $this->_iProfileId && $this->_oDb->getParam($CNF['PARAM_PUBLIC_SBSN']) != 'on' && $this->_oDb->getParam($CNF['PARAM_PUBLIC_SBSD']) != 'on'))
+            return $sResult;
+
+        return CHECK_ACTION_RESULT_ALLOWED;
+    }
+
+    public function checkMyself($iContentId)
+    {
+		$iLogged = (int)bx_get_logged_profile_id();
+    	if(empty($iLogged))
+    		return false;
+
+    	$oProfile = BxDolProfile::getInstanceByContentAndType((int)$iContentId, $this->_oConfig->getName());
+    	if(!$oProfile)
+    		return false;
+
+		return $oProfile->id() == $iLogged;
+    }
+
+    protected function _serviceCheckAllowedContactForProfile($aDataEntry, $isPerformAction = false, $iProfileId = false)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(!$iProfileId)
+            $iProfileId = $this->_iProfileId;
+
+        // cannot contact myself
+        $oProfile = BxDolProfile::getInstanceByContentAndType($aDataEntry[$CNF['FIELD_ID']], $this->getName());
+        if($oProfile && $oProfile->id() == $iProfileId)
+            return _t('_sys_txt_access_denied');
+
+        // moderator always has access
+        if($this->_isModeratorForProfile($isPerformAction, $iProfileId))
+            return CHECK_ACTION_RESULT_ALLOWED;
+
+        // check privacy
+        if(!empty($CNF['OBJECT_PRIVACY_CONTACT'])) {
+            $oPrivacy = BxDolPrivacy::getObjectInstance($CNF['OBJECT_PRIVACY_CONTACT']);
+            if($oPrivacy && !$oPrivacy->check($aDataEntry[$CNF['FIELD_ID']], $iProfileId))
+                return _t('_sys_access_denied_to_private_content');
+        }
+
+        return CHECK_ACTION_RESULT_ALLOWED;
+    }
+
+    // ====== COMMON METHODS
+    public function alertAfterAdd($aContentInfo)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $sModule = $this->getName();
+        $iContentId = (int)$aContentInfo[$CNF['FIELD_ID']];
+
+        $aParams = array();        
+        if(isset($aContentInfo[$CNF['FIELD_ALLOW_VIEW_TO']]))
+            $aParams['privacy_view'] = $aContentInfo[$CNF['FIELD_ALLOW_VIEW_TO']];
+
+        bx_alert($sModule, 'added', $iContentId, false, $aParams);
+    }
+
+    public function alertAfterEdit($aContentInfo)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $sModule = $this->getName();
+        $iContentId = (int)$aContentInfo[$CNF['FIELD_ID']];
+
+        $oProfile = BxDolProfile::getInstanceByContentAndType($iContentId, $sModule);
+
+        bx_alert($sModule, 'edited', $iContentId);
+        bx_alert('profile', 'edit', $oProfile->id(), 0, array('content' => $iContentId, 'module' => $sModule));
+    }
+
+    public function getProfileByCurrentUrl ()
+    {
+        $iProfileId = bx_process_input(bx_get('profile_id'), BX_DATA_INT);
+        
+        if ($iProfileId)
+            return  BxDolProfile::getInstance($iProfileId);
+
+        $iContentId = bx_process_input(bx_get('id'), BX_DATA_INT);
+        if ($iContentId)
+            return BxDolProfile::getInstanceByContentAndType($iContentId, $this->getName());
+        
+        return false;
+    }
+    
+    public function getEntryImageData($aContentInfo, $sField = 'FIELD_PICTURE', $aTranscoders = array())
+    {
+        return parent::getEntryImageData($aContentInfo, $sField, $aTranscoders);
+    }
+
+
+    // ====== PROTECTED METHODS
+    protected function _checkAllowedConnect (&$aDataEntry, $isPerformAction, $sObjConnection, $isMutual, $isInvertResult, $isSwap = false)
+    {
+        if (!$this->_iProfileId)
+            return _t('_sys_txt_access_denied');
+
+        $CNF = &$this->_oConfig->CNF;
+
+        $oProfile = BxDolProfile::getInstanceByContentAndType($aDataEntry[$CNF['FIELD_ID']], $this->_aModule['name']);
+        if (!$oProfile || $oProfile->id() == $this->_iProfileId)
+            return _t('_sys_txt_access_denied');
+
+        return BxDolConnection::getObjectInstance($sObjConnection)->checkAllowedConnect ($this->_iProfileId, $oProfile->id(), $isPerformAction, $isMutual, $isInvertResult, $isSwap);
+    }
+
+    protected function _buildRssParams($sMode, $aArgs)
+    {
+        $aParams = array ();
+        $sMode = bx_process_input($sMode);
+        switch ($sMode) {
+            case 'connections':
+                $aParams = array(
+                    'object' => isset($aArgs[0]) ? $aArgs[0] : '',
+                    'type' => isset($aArgs[1]) ? $aArgs[1] : '',
+                    'profile' => isset($aArgs[2]) ? (int)$aArgs[2] : 0,
+                    'mutual' => isset($aArgs[3]) ? (int)$aArgs[3] : 0,
+                    'profile2' => isset($aArgs[4]) ? (int)$aArgs[4] : 0,
+                );
+                break;
+        }
+
+        return $aParams;
+    }
+
+    protected function _serviceBrowseQuick($aProfiles, $iStart = 0, $iLimit = 4, $aAdditionalParams = array())
+    {
+        // get paginate object
+        $oPaginate = new BxTemplPaginate(array(
+            'on_change_page' => "return !loadDynamicBlockAutoPaginate(this, '{start}', '{per_page}', " . bx_js_string(json_encode($aAdditionalParams)) . ");",
+            'num' => count($aProfiles),
+            'per_page' => $iLimit,
+            'start' => $iStart,
+        ));
+
+        // remove last item from connection array, because we've got one more item for pagination calculations only
+        if (count($aProfiles) > $iLimit)
+            array_pop($aProfiles);
+
+        // get profiles HTML
+        $s = '';
+        foreach ($aProfiles as $mixedProfile) {
+            $bProfile = is_array($mixedProfile);
+
+            $oProfile = BxDolProfile::getInstance($bProfile ? (int)$mixedProfile['id'] : (int)$mixedProfile);
+            if(!$oProfile)
+                continue;
+
+            $aUnitParams = array('template' => array('name' => 'unit', 'size' => 'thumb'));
+            if(BxDolModule::getInstance($oProfile->getModule()) instanceof BxBaseModGroupsModule)
+                $aUnitParams['template']['name'] = 'unit_wo_cover';
+
+            if($bProfile && is_array($mixedProfile['info']))
+                $aUnitParams['template']['vars'] = $mixedProfile['info'];
+
+            $s .= $oProfile->getUnit(0, $aUnitParams);
+        }
+
+        // return profiles + paginate
+        return $s . (!$iStart && $oPaginate->getNum() <= $iLimit ?  '' : $oPaginate->getSimplePaginate());
+    }
+
+    protected function _serviceGetTimelineProfileImage($aEvent, $aBrowseParams, $aBuildParams)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aFileInfo = BxDolStorage::getObjectInstance($CNF[$aBuildParams['stg']])->getFile((int)$aEvent['object_id']);
+        if(empty($aFileInfo) || !is_array($aFileInfo))
+            return false;
+            
+        $aEventContent = unserialize($aEvent['content']);
+        if(!is_array($aEventContent) || empty($aEventContent['content']))
+            return false;
+
+        $aContentInfo = $this->_oDb->getContentInfoById($aEventContent['content']);
+        if(empty($aContentInfo) || !is_array($aContentInfo))
+            return false;
+
+        $oProfile = BxDolProfile::getInstanceMagic($aContentInfo['profile_id']);
+        if (!$oProfile->isActive())
+            return false;
+            
+        $sUserName = $oProfile->getDisplayName();
+
+        $sSample = isset($CNF['T'][$aBuildParams['txt_sswa']]) ? $CNF['T'][$aBuildParams['txt_sswa']] : $CNF['T'][$aBuildParams['txt_ss']];
+
+        //--- Title & Description
+        $sTitle = !empty($aContentInfo[$CNF['FIELD_TITLE']]) ? $aContentInfo[$CNF['FIELD_TITLE']] : '';
+        if(empty($sTitle) && !empty($aContentInfo[$CNF['FIELD_TEXT']]))
+            $sTitle = $aContentInfo[$CNF['FIELD_TEXT']];
+
+        $sDescription = _t($CNF['T'][$aBuildParams['txt_sau']], $sUserName, _t($sSample));
+
+        return array(
+            'owner_id' => $aEvent['owner_id'],
+            'object_owner_id' => $aContentInfo['profile_id'],
+            'icon' => !empty($CNF['ICON']) ? $CNF['ICON'] : '',
+            'sample' => $sSample,
+            'sample_wo_article' => $CNF['T'][$aBuildParams['txt_ss']],
+            'sample_action' => isset($CNF['T'][$aBuildParams['txt_sa']]) ? $CNF['T'][$aBuildParams['txt_sa']] : '',
+            'url' => BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $aContentInfo[$CNF['FIELD_ID']]),
+            'content' => $this->_getContentForTimelineProfileImage($aEvent, $aBrowseParams, $aBuildParams, $aContentInfo, $aFileInfo), //a string to display or array to parse default template before displaying.
+            'date' => $aContentInfo[$CNF['FIELD_ADDED']],
+            'views' => '',
+            'votes' => '',
+            'scores' => '',
+            'reports' => '',
+            'comments' => '',
+            'title' => $sTitle, //may be empty.
+            'description' => $sDescription //may be empty.
+        );
+    }
+
+    protected function _serviceGetTimelineProfileImageAllowedView($aEvent)
+    {
+        $sError = _t('_sys_access_denied_to_private_content');
+
+        if(empty($aEvent['content']) || !is_array($aEvent['content']) || empty($aEvent['content']['id']))
+            return $sError;
+
+        $aContentInfo = $this->_oDb->getContentInfoById($aEvent['content']['id']);
+        if(empty($aContentInfo) || !is_array($aContentInfo))
+            return $sError;
+
+        return $this->serviceCheckAllowedViewForProfile($aContentInfo);
+    }
+
+    protected function _getContentForTimelineProfileImage($aEvent, $aBrowseParams, $aBuildParams, $aContentInfo, $aFileInfo)
+    {
+    	$CNF = &$this->_oConfig->CNF;
+
+    	$sUrl = BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $aContentInfo[$CNF['FIELD_ID']]);
+
+    	//--- Image(s)
+        $sImage = $this->_oConfig->getImageUrl($aFileInfo['id'], $aBuildParams['trans']);
+        $sImageOrig = $this->_oConfig->getImageUrl($aFileInfo['id'], $aBuildParams['trans_orig']);
+        if(!empty($sImage)) {
+            if(empty($sImageOrig))
+                $sImageOrig = $sImage;
+
+            $a = array('id' => $aFileInfo['id'], 'url' => $sUrl, 'src' => $sImage);
+            if (CHECK_ACTION_RESULT_ALLOWED === $this->checkAllowedView($aContentInfo))
+                $a['src_orig'] = $sImageOrig;
+            $aImages = array($a);
+        }
+
+    	return array(
+            'sample' => isset($CNF['T'][$aBuildParams['txt_sswa']]) ? $CNF['T'][$aBuildParams['txt_sswa']] : $CNF['T'][$aBuildParams['txt_ss']],
+            'sample_wo_article' => $CNF['T'][$aBuildParams['txt_ss']],
+            'sample_action' => isset($CNF['T'][$aBuildParams['txt_sa']]) ? $CNF['T'][$aBuildParams['txt_sa']] : '',
+            'id' => $aContentInfo[$CNF['FIELD_ID']],
+            'url' => $sUrl,
+            'title' =>  '',
+            'text' => '',
+            'images' => $aImages,
+            'videos' => array()
+        );
+    }
+
+    protected function _entityComments($sObject, $iId = 0)
+    {
+        if(!$iId)
+            $iId = bx_process_input(bx_get('id'), BX_DATA_INT);
+
+        if(!$iId)
+            $iId = bx_process_input(bx_get('profile_id'), BX_DATA_INT);
+
+        if(!$iId)
+            return false;
+
+        $oCmts = BxDolCmts::getObjectInstance($sObject, $iId);
+        if(!$oCmts || !$oCmts->isEnabled())
+            return false;
+
+        return $oCmts->getCommentsBlock(array(), array('in_designbox' => false));
+    }
+
+    protected function _getContent($iContentId = 0, $sFuncGetContent = 'getContentInfoById')
+    {
+        if(!$iContentId && bx_get('id') === false && bx_get('profile_id') !== false) {
+            $oProfile = BxDolProfile::getInstance((int)bx_get('profile_id'));
+            if($oProfile)
+                $iContentId = $oProfile->getContentId();
+        }
+
+        return parent::_getContent($iContentId, $sFuncGetContent);
+    }
+
+    /** 
+     * Returns list of members by mode with limited number of records for React Jot
+     * @param string $sMode
+     * @param int $iStart
+     * @param int $iPerPage
+     * @return mixed
+     */
+    public function serviceGetMembers($sMode = 'active', $iStart = 0, $iPerPage = 10){
+        bx_import('SearchResult', $this->_aModule);
+        $sClass = $this->_aModule['class_prefix'] . 'SearchResult';
+        $o = new $sClass($sMode);
+
+        $o -> aCurrent['paginate'] = array('perPage' => $iPerPage, 'forceStart' => $iStart);
+        return $o -> getSearchData();
+    }
+}
+
+/** @} */

+ 501 - 0
upgrade/files/11.0.0-11.0.1/files/modules/base/profile/classes/BxBaseModProfileTemplate.php

@@ -0,0 +1,501 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    BaseProfile Base classes for profile modules
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+/*
+ * Profile based modules representation.
+ */
+class BxBaseModProfileTemplate extends BxBaseModGeneralTemplate
+{
+    protected $_sUnitDefault;    
+    protected $_sUnitSizeDefault;
+
+    protected $_sUnitClass;
+    protected $_sUnitClassWithCover;
+    protected $_sUnitClassWoInfo;
+    protected $_sUnitClassWoInfoShowCase;
+    protected $_sUnitClassShowCase;
+
+    /*
+     * Enable/Disable Letter based thumbnail in "Unit with Cover" only.
+     */
+    protected $_bLetterAvatar;
+    
+    function __construct(&$oConfig, &$oDb)
+    {
+        parent::__construct($oConfig, $oDb);
+
+        $this->_sUnitDefault = 'unit.html';
+        $this->_sUnitSizeDefault = 'thumb';
+                
+        $this->_sUnitClass = 'bx-base-pofile-unit';
+        $this->_sUnitClassWithCover = 'bx-base-pofile-unit-with-cover';
+        $this->_sUnitClassWoInfo = 'bx-base-pofile-unit-wo-info';
+        $this->_sUnitClassWoInfoShowCase = 'bx-base-pofile-unit-wo-info bx-base-unit-showcase bx-base-pofile-unit-wo-info-showcase';
+        $this->_sUnitClassShowCase = 'bx-base-pofile-unit-with-cover bx-base-unit-showcase bx-base-pofile-unit-showcase';
+
+        $this->_bLetterAvatar = true;
+    }
+
+    /**
+     * Get profile unit
+     */
+    function unit ($aData, $isCheckPrivateContent = true, $mixedTemplate = false)
+    {
+        list($sTemplate) = is_array($mixedTemplate) ? $mixedTemplate : array($mixedTemplate);
+        if(empty($sTemplate))
+            $sTemplate = $this->_sUnitDefault;
+
+        $aVars = $this->unitVars($aData, $isCheckPrivateContent, $mixedTemplate);
+ 
+        return $this->parseHtmlByName($sTemplate, $aVars);
+    }
+
+    function unitVars ($aData, $isCheckPrivateContent = true, $mixedTemplate = false)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        list($sTemplate, $sTemplateSize, $aTemplateVars) = is_array($mixedTemplate) ? $mixedTemplate : array($mixedTemplate, false, array());
+
+        if(empty($sTemplate))
+            $sTemplate = $this->_sUnitDefault;
+        if(empty($sTemplateSize))
+            $sTemplateSize = $this->_getUnitSize($aData, $sTemplate);
+        if(empty($aTemplateVars) || !is_array($aTemplateVars))
+            $aTemplateVars = array();
+
+        $oModule = $this->getModule();
+        $iContentId = (int)$aData[$CNF['FIELD_ID']];
+
+        $bPublic = $isCheckPrivateContent && !empty($CNF['OBJECT_PRIVACY_VIEW']) ? $this->isProfilePublic($aData) : true;
+
+        $bPublicThumb = true;
+        if($isCheckPrivateContent && $oModule->checkAllowedViewProfileImage($aData) !== CHECK_ACTION_RESULT_ALLOWED)
+            $bPublicThumb = false;
+
+        $bPublicCover = true;
+        if($isCheckPrivateContent && $oModule->checkAllowedViewCoverImage($aData) !== CHECK_ACTION_RESULT_ALLOWED)
+            $bPublicCover = false;
+
+        $oProfile = BxDolProfile::getInstanceByContentAndType($iContentId, $this->MODULE);
+        $iProfile = $oProfile->id();
+
+        // get profile's title
+        $sTitle = bx_process_output($aData[$CNF['FIELD_NAME']]);
+
+        // get profile's url
+        $sUrl = BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $iContentId);
+
+        $sThumbUrl = $bPublicThumb ? $this->_getUnitThumbUrl($sTemplateSize, $aData, false) : '';
+        $bThumbUrl = !empty($sThumbUrl);
+		
+        $aTmplVarsThumbnail = array(
+            'bx_if:show_thumb_image' => array(
+                'condition' => $bThumbUrl,
+                'content' => array(
+                    'size' => $sTemplateSize,
+                    'thumb_url' => $sThumbUrl
+                )
+            ),
+            'bx_if:show_thumb_letter' => array(
+                'condition' => !$bThumbUrl,
+                'content' => array(
+                    'size' => $sTemplateSize,
+                    'color' => implode(', ', BxDolTemplate::getColorCode($iProfile, 1.0)),
+                    'letter' => mb_strtoupper(mb_substr($sTitle, 0, 1))
+                )
+            ),
+            'bx_if:show_online' => array(
+                'condition' => $oProfile->isOnline(),
+                'content' => array()
+            ),
+            'size' => $sTemplateSize,
+            'badges' => $oModule->serviceGetBadges($iContentId, false, true),
+            'thumb_url' => $bThumbUrl ? $sThumbUrl : $this->getImageUrl('no-picture-thumb.png'),
+        );
+
+        $sCoverUrl = $bPublicCover ? $this->urlCoverUnit($aData, false) : '';
+        if(empty($sCoverUrl) && ($iCoverId = (int)getParam('sys_unit_cover_profile')) != 0)
+            $sCoverUrl = BxDolTranscoder::getObjectInstance(BX_DOL_TRANSCODER_OBJ_COVER_UNIT_PROFILE)->getFileUrlById($iCoverId);
+        if(empty($sCoverUrl))
+            $sCoverUrl = $this->getImageUrl('cover.svg');
+        
+        $aTmplVarsMeta = $this->getSnippetMenuVars ($iProfile, $bPublic);
+
+        return array_merge(array (
+            'class' => $this->_getUnitClass($aData, $sTemplate),
+            'id' => $iContentId,
+            'public' => $bPublic,
+            'bx_if:show_thumbnail' => array(
+                'condition' => $bThumbUrl || $this->_bLetterAvatar,
+                'content' => $aTmplVarsThumbnail
+            ),
+            'cover_url' => $sCoverUrl,
+            'content_url' => $bPublic ? $sUrl : 'javascript:void(0);',
+            'content_click' => !$bPublic ? 'javascript:bx_alert(' . bx_js_string('"' . _t('_sys_access_denied_to_private_content') . '"') . ');' : '',
+            'title' => $sTitle,
+            'title_attr' => bx_html_attribute($sTitle),
+            'addon' => !empty($aData['addon']) ? $aData['addon'] : '',
+            'module_name' => _t($CNF['T']['txt_sample_single']),
+            'ts' => $aData[$CNF['FIELD_ADDED']],
+            'bx_if:meta' => array(
+                'condition' => !empty($aTmplVarsMeta),
+                'content' => $aTmplVarsMeta
+            )
+        ), $aTmplVarsThumbnail, $aTemplateVars);
+    }
+
+    function isProfilePublic($aData)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $oPrivacy = BxDolPrivacy::getObjectInstance($CNF['OBJECT_PRIVACY_VIEW']);
+        if ($oPrivacy && !$oPrivacy->check($aData[$CNF['FIELD_ID']]) && !$oPrivacy->isPartiallyVisible($aData[$CNF['FIELD_ALLOW_VIEW_TO']]))
+            return false;
+        return true;
+    }
+
+    function getSnippetMenuVars ($iProfileId, $bPublic = null)
+    {
+        if (!($oProfile = BxDolProfile::getInstance($iProfileId)))
+            return array();
+        
+        $CNF = &$this->_oConfig->CNF;
+        
+        if (null === $bPublic) {
+            $aData = $this->getModule()->serviceGetContentInfoById($oProfile->getContentId());
+            $bPublic = $this->isProfilePublic($aData);
+        }
+        
+        $aTmplVarsMeta = array();
+        if (!empty($CNF['OBJECT_MENU_SNIPPET_META'])) {
+            $oMenuMeta = BxDolMenu::getObjectInstance($CNF['OBJECT_MENU_SNIPPET_META'], $this);
+            if($oMenuMeta) {
+                $oMenuMeta->setContentId($oProfile->getContentId());
+                $oMenuMeta->setContentPublic($bPublic);
+                $aTmplVarsMeta = array(
+                    'meta' => $oMenuMeta->getCode()
+                );
+            }
+        }
+
+        return $aTmplVarsMeta;
+    }
+
+    /**
+     * Get profile cover
+     */
+    function setCover ($oPage, $aData, $sTemplateName = 'cover.html')
+    {
+        $CNF = &$this->_oConfig->CNF;
+        $oModule = $this->getModule();
+
+        BxDolTemplate::getInstance()->addInjection('injection_main_class', 'text', 'bx-base-profile-view');
+
+        $bProfileViewAllowed = CHECK_ACTION_RESULT_ALLOWED === $oModule->checkAllowedView($aData);
+
+        if (CHECK_ACTION_RESULT_ALLOWED !== $oModule->checkAllowedViewProfileImage($aData))
+            $aData[$CNF['FIELD_PICTURE']] = 0;
+
+        if (CHECK_ACTION_RESULT_ALLOWED !== $oModule->checkAllowedViewCoverImage($aData))
+            $aData[$CNF['FIELD_COVER']] = 0;
+
+        $oProfile = BxDolProfile::getInstanceByContentAndType($aData[$CNF['FIELD_ID']], $this->MODULE);
+        $iProfile = $oProfile->id();
+
+        $sUrl = BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $aData[$CNF['FIELD_ID']]);
+        $sTitle = bx_process_output($oProfile->getDisplayName());
+
+        $sUrlAvatar = $this->urlAvatarBig($aData, false);
+        $bUrlAvatar = !empty($sUrlAvatar);
+
+        $sUrlPicture = $this->urlPicture ($aData);
+        $sUrlPictureChange = BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_EDIT_ENTRY'] . '&id=' . $aData[$CNF['FIELD_ID']]);
+
+        $sUrlCover = $this->urlCover ($aData, false);
+        if(!$sUrlCover && $oPage !== false && $oPage->isPageCover()) {
+            $aCover = $oPage->getPageCoverImage();
+            if(!empty($aCover))
+                $sUrlCover = BxDolCover::getCoverImageUrl($aCover);
+        }
+
+        if(!$sUrlCover)
+            $sUrlCover = $this->getImageUrl('cover.svg');
+        
+        $sUrlCoverChange = BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_EDIT_COVER'] . '&id=' . $aData[$CNF['FIELD_ID']]);
+
+        $sCoverPopup = '';
+        $sCoverPopupId = $this->MODULE . '-popup-cover';
+        if($bProfileViewAllowed && $aData[$CNF['FIELD_COVER']]) {
+            $sCoverPopup = BxTemplFunctions::getInstance()->transBox($sCoverPopupId, $this->parseHtmlByName('image_popup.html', array (
+                'image_url' => $sUrlCover,
+                'bx_if:owner' => array (
+                    'condition' => CHECK_ACTION_RESULT_ALLOWED === $oModule->checkAllowedChangeCover($aData),
+                    'content' => array (
+                        'change_image_url' => $sUrlCoverChange,
+                    ),
+                ),
+            )), true, true);
+        }
+
+        $bShowAvatar = $bUrlAvatar || $this->_bLetterAvatar;
+        $aShowAvatar = array();
+        $sPicturePopup = '';
+        if($bShowAvatar) {
+            $sPicturePopupId = $this->MODULE . '-popup-picture';
+
+            $aShowAvatar = array(
+                'bx_if:show_ava_image' => array(
+                    'condition' => $bUrlAvatar,
+                    'content' => array(
+                        'ava_url' => $sUrlAvatar
+                    )
+                ),
+                'bx_if:show_ava_letter' => array(
+                    'condition' => !$bUrlAvatar,
+                    'content' => array(
+                        'color' => implode(', ', BxDolTemplate::getColorCode($iProfile, 1.0)),
+                        'letter' => mb_substr($sTitle, 0, 1)
+                    )
+                ),
+                'bx_if:show_online' => array(
+                    'condition' => $oProfile->isOnline(),
+                    'content' => array()
+                ),
+                'picture_avatar_url' => $bUrlAvatar ? $sUrlAvatar : $this->getImageUrl('no-picture-preview.png'),
+                'picture_popup_id' => $sPicturePopupId,
+                'picture_url' => $sUrlPicture,
+                'picture_href' => !$aData[$CNF['FIELD_PICTURE']] && CHECK_ACTION_RESULT_ALLOWED === $oModule->checkAllowedEdit($aData) ? $sUrlPictureChange : 'javascript:void(0);',
+            );
+
+            if($bProfileViewAllowed && $aData[$CNF['FIELD_PICTURE']]) {
+                $sPicturePopup = BxTemplFunctions::getInstance()->transBox($sPicturePopupId, $this->parseHtmlByName('image_popup.html', array (
+                    'image_url' => $sUrlPicture,
+                    'bx_if:owner' => array (
+                        'condition' => CHECK_ACTION_RESULT_ALLOWED === $oModule->checkAllowedEdit($aData),
+                        'content' => array (
+                            'change_image_url' => $sUrlPictureChange,
+                        ),
+                    ),
+                )), true, true);
+            }
+        }
+
+        $sActionsMenu = '';
+        if(empty($CNF['OBJECT_MENU_ACTIONS_VIEW_ENTRY_ALL'])) {
+            $oMenu = BxTemplMenu::getObjectInstance($CNF['OBJECT_MENU_ACTIONS_VIEW_ENTRY']);
+            if($oMenu)
+                $sActionsMenu = $oMenu->getCode();
+        }
+        else 
+            $sActionsMenu = $oModule->serviceEntityAllActions();
+        
+        // generate html
+        $aVars = array (
+            'id' => $aData[$CNF['FIELD_ID']],
+            'content_url' => $sUrl,
+            'title' => $sTitle,
+            //'menu' => BxDolMenu::getObjectInstance($CNF['OBJECT_MENU_SUBMENU_VIEW_ENTRY_COVER'])->getCode(), // TODO: check if menu is used somewhere
+
+            'action_menu' => $sActionsMenu,
+
+            'bx_if:show_avatar' => array(
+                'condition' => $bShowAvatar,
+                'content' => $aShowAvatar
+            ),
+            'picture_popup' => $sPicturePopup,
+
+            'cover_popup' => $sCoverPopup,
+            'cover_popup_id' => $sCoverPopupId,
+            'cover_url' => $sUrlCover,
+            'cover_href' => !$aData[$CNF['FIELD_COVER']] && CHECK_ACTION_RESULT_ALLOWED === $oModule->checkAllowedChangeCover($aData) ? $sUrlCoverChange : 'javascript:void(0);',
+            'badges' => $oModule->serviceGetBadges($aData[$CNF['FIELD_ID']]),
+        );
+
+        BxDolCover::getInstance($this)->set($aVars, $sTemplateName);
+    }
+
+    /**
+     * Get profile picture thumb url
+     * @deprecated since 11.0.0 use BxBaseModProfileTemplate::urlAvatar instead.
+     */
+    function avatar ($aData, $bSubstituteNoImage = true)
+    {
+        return $this->urlAvatar($aData, $bSubstituteNoImage);
+    }
+
+    /**
+     * Get profile picture thumb url
+     * @deprecated since 11.0.0 use BxBaseModProfileTemplate::urlThumb instead.
+     */
+    function thumb ($aData, $bSubstituteNoImage = true)
+    {
+        return $this->urlThumb($aData, $bSubstituteNoImage);
+    }
+
+    /**
+     * Get profile picture thumb url
+     * @deprecated since 11.0.0 use BxBaseModProfileTemplate::urlIcon instead.
+     */
+    function icon ($aData, $bSubstituteNoImage = true)
+    {
+        return $this->urlIcon($aData, $bSubstituteNoImage);
+    }
+
+    /**
+     * Get profile picture icon url
+     */
+    function urlIcon ($aData, $bSubstituteNoImage = true)
+    {
+        $CNF = &$this->_oConfig->CNF;
+        return $this->_image ($CNF['FIELD_PICTURE'], $CNF['OBJECT_IMAGES_TRANSCODER_ICON'], 'no-picture-icon.png', $aData, $bSubstituteNoImage);
+    }
+
+    /**
+     * Get profile thumb url
+     */
+    function urlThumb ($aData, $bSubstituteNoImage = true)
+    {
+        $CNF = &$this->_oConfig->CNF;
+        return $this->_image ($CNF['FIELD_PICTURE'], $CNF['OBJECT_IMAGES_TRANSCODER_THUMB'], 'no-picture-thumb.png', $aData, $bSubstituteNoImage);
+    }
+
+    /**
+     * Get profile avatar url
+     */
+    function urlAvatar ($aData, $bSubstituteNoImage = true)
+    {
+        $CNF = &$this->_oConfig->CNF;
+        return $this->_image ($CNF['FIELD_PICTURE'], $CNF['OBJECT_IMAGES_TRANSCODER_AVATAR'], 'no-picture-preview.png', $aData, $bSubstituteNoImage);
+    }
+
+    /**
+     * Get profile avatar big url
+     */
+    function urlAvatarBig ($aData, $bSubstituteNoImage = true)
+    {
+        $CNF = &$this->_oConfig->CNF;
+        return $this->_image ($CNF['FIELD_PICTURE'], $CNF['OBJECT_IMAGES_TRANSCODER_AVATAR_BIG'], 'no-picture-preview.png', $aData, $bSubstituteNoImage);
+    }
+
+    /**
+     * Get profile picture url
+     */
+    function urlPicture ($aData, $bSubstituteNoImage = true)
+    {
+        $CNF = &$this->_oConfig->CNF;
+        return $this->_image ($CNF['FIELD_PICTURE'], $CNF['OBJECT_IMAGES_TRANSCODER_PICTURE'], 'no-picture-preview.png', $aData, $bSubstituteNoImage);
+    }
+
+    /**
+     * Get profile cover image url
+     */
+    function urlCover ($aData, $bSubstituteNoImage = false)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $sImageUrl = $this->_image($CNF['FIELD_COVER'], $CNF['OBJECT_IMAGES_TRANSCODER_COVER'], '', $aData, $bSubstituteNoImage);
+
+        return $sImageUrl;
+    }
+
+    /**
+     * Get profile cover image url for browse unit
+     */
+    function urlCoverUnit ($aData, $bSubstituteNoImage = true)
+    {
+        $CNF = &$this->_oConfig->CNF;
+        $sImageUrl = $this->_image ($CNF['FIELD_COVER'], $CNF['OBJECT_IMAGES_TRANSCODER_GALLERY'], '', $aData, false);
+        if($sImageUrl === false) {
+        	$iImageId = (int)getParam('sys_unit_cover_profile');
+        	$oImageTranscoder = BxDolTranscoderImage::getObjectInstance(BX_DOL_TRANSCODER_OBJ_COVER_UNIT_PROFILE);
+        	if($oImageTranscoder && $iImageId != 0)
+        		$sImageUrl = $oImageTranscoder->getFileUrl($iImageId);
+        }
+
+        if($bSubstituteNoImage && !$sImageUrl)
+        	$sImageUrl = $this->getImageUrl('cover.svg');
+
+		return $sImageUrl;
+    }
+
+    /**
+     * Get profile picture icon url
+     */
+    function _image ($sField, $sTranscodeObject, $sNoImage, $aData, $bSubstituteNoImage = true)
+    {
+        $sImageUrl = false;
+        if ($aData[$sField]) {
+            $oImagesTranscoder = BxDolTranscoderImage::getObjectInstance($sTranscodeObject);
+            if ($oImagesTranscoder)
+                $sImageUrl = $oImagesTranscoder->getFileUrl($aData[$sField]);
+        }
+        return $bSubstituteNoImage && !$sImageUrl ? $this->getImageUrl($sNoImage) : $sImageUrl;
+    }
+
+    protected function _getUnitClass($aData, $sTemplateName = 'unit.html')
+    {
+        $sResult = '';
+
+        switch($sTemplateName) {
+            case 'unit.html':
+                $sResult = $this->_sUnitClass;
+                break;
+
+            case 'unit_with_cover.html':
+                $sResult = $this->_sUnitClassWithCover;
+                break;
+
+            case 'unit_wo_info.html':
+            case 'unit_wo_info_links.html':
+                $sResult = $this->_sUnitClassWoInfo;
+                break;
+                
+            case 'unit_with_cover_showcase.html':
+                $sResult = $this->_sUnitClassShowCase;
+                break;
+
+            case 'unit_wo_info_showcase.html':
+                $sResult = $this->_sUnitClassWoInfoShowCase;
+                break;
+        }
+
+        return $sResult;
+    }
+
+    protected function _getUnitSize($aData, $sTemplateName = 'unit.html')
+    {
+        $sResult = '';
+
+        switch($sTemplateName) {
+            case 'unit_with_cover.html':
+                $sResult = 'ava';
+                break;
+
+            default:
+                $sResult = $this->_sUnitSizeDefault;
+                break;
+        }
+
+        return $sResult;
+    }
+
+    protected function _getUnitThumbUrl($sSize, $aData, $bSubstituteNoImage = true)
+    {
+        $sMethod = 'url' . bx_gen_method_name(str_replace('ava', 'avatar', $sSize), array('_', '-'));
+
+        if(method_exists($this, $sMethod))
+            return $this->$sMethod($aData, $bSubstituteNoImage);
+        else
+            return $this->_getUnitThumbUrl($this->_sUnitSizeDefault, $aData, $bSubstituteNoImage);
+    }
+}
+
+/** @} */

+ 528 - 0
upgrade/files/11.0.0-11.0.1/files/modules/base/profile/template/css/main.css

@@ -0,0 +1,528 @@
+@import url(../../../general/template/css/main.css);
+
+/* browse unit */
+
+.bx-base-pofile-unit {
+    width:184px;
+    width:11.5rem;
+    float:left;
+}
+
+.bx-base-pofile-unit-wo-info {
+    position: relative;
+    display: block;
+}
+
+.bx-search-result-block {
+    text-align:center;
+}
+
+.bx-base-pofile-unit .bx-def-unit-img,
+.bx-base-pofile-unit-wo-info .bx-def-unit-img,
+.bx-base-pofile-unit-with-cover .bx-def-unit-img {
+    position: relative;
+    overflow: visible;
+}
+
+.bx-def-unit-img .bx-base-pofile-unit-thumb {
+    background: rgba(255, 255, 255, 1.0);
+    background-clip: padding-box;
+    border-width: 0px;
+    border-color: rgba(255, 255, 255, 1.0);
+    box-shadow: inset 0px 0px 4px rgba(0, 0, 0, 0.2);
+}
+
+/* Badges in unit */
+.bx-base-pofile-unit div.bx-badges-list {
+    -webkit-justify-content: flex-start;
+    justify-content: flex-start;
+
+    padding-top: 0.25rem;
+}
+.bx-base-pofile-unit div.bx-badges-list div.bx-badge:first-child {
+    margin-left: 0;
+}
+
+/* browse unit with cover */
+.bx-base-pofile-unit-cover {
+    height: 10.0rem;
+
+    background-repeat:no-repeat;
+    background-position:center center;
+    -webkit-background-size:cover;
+    -moz-background-size:cover;
+    -o-background-size:cover;
+    background-size:cover;
+}
+.bx-base-pofile-unit-cover:not(.bx-def-align-center) {
+    display:block;
+
+    line-height:10.0rem;
+    text-align:center;
+}
+
+.bx-base-pofile-unit-with-cover .sys-author ul.bx-menu-custom.bx-menu-hor,
+.bx-base-pofile-unit-with-cover .bx-base-general-unit-meta ul.bx-menu-custom.bx-menu-hor {
+    -webkit-justify-content: center;
+    justify-content: center;
+}
+
+img.bx-base-pofile-unit-thumb {
+    display: block;
+
+    background-color: #ffffff;
+}
+
+p.bx-base-pofile-unit-thumb {
+    display: -webkit-flex;
+    display: flex;
+
+    -webkit-justify-content: center;
+    justify-content: center;
+
+    -webkit-align-items: center;
+    align-items: center;
+
+    margin: 0px;
+
+    font-size: 30px;
+    font-size: 1.875rem;
+    font-weight: 700;
+    color: #ffffff;
+}
+p.bx-base-pofile-unit-thumb.bx-def-icon-size {
+    font-size: 20px;
+    font-size: 1.25rem;
+}
+p.bx-base-pofile-unit-thumb.bx-def-ava-size {
+    font-size: 48px;
+    font-size: 3.0rem;
+    font-weight: 700;
+}
+.bx-base-pofile-unit-online {
+    position: absolute;
+    right: 2px;
+    bottom: 2px;
+    width: 10px;
+    height: 10px;
+    box-shadow: inset 0px 0px 0px 1px #ffffff;
+    border-radius: 10px;
+    background-color: #7ed321;
+}
+.bx-base-pofile-unit-with-cover {
+    display:inline-block;
+    overflow:hidden;
+}
+.bx-base-pofile-unit-with-cover .bx-base-pofile-unit-cnt {
+    height: 100%;
+}
+.bx-base-pofile-unit-with-cover div.bx-def-unit-img {
+    display: inline-block;
+    vertical-align: middle;
+}
+.bx-base-pofile-unit-with-cover .bx-base-pofile-unit-online {
+    right: 9px;
+    bottom: 9px;
+}
+
+.bx-layout-column-thin .bx-base-pofile-unit-with-cover {
+    margin-left:0;
+    margin-right:0;
+}
+
+.bx-base-pofile-unit-title {
+    display: -webkit-flex;
+    display: flex; 
+    -webkit-justify-content:center;
+    justify-content:center;
+    
+    text-align:center;
+    white-space:nowrap;
+    text-overflow:ellipsis;
+    overflow:hidden;
+}
+
+.bx-base-pofile-unit-title > a {
+    text-overflow: ellipsis;
+    overflow: hidden;
+}
+
+.bx-base-pofile-unit-info-members {
+    float:left;
+    line-height:1.5rem;
+    vertical-align:middle;
+}
+
+.bx-base-pofile-unit-info-btn {
+    float:right;
+}
+
+
+/* --- Profile Cover --- */
+.bx-media-phone .bx-base-pofile-cover-wrapper {
+    padding-left: 0;
+    padding-right: 0;
+}
+.bx-base-pofile-cover {
+    position: relative;
+    min-height: 10rem;
+}
+.bx-media-phone .bx-base-pofile-cover {
+    border-left-width: 0;
+    border-right-width: 0;
+}
+div.bx-base-pofile-cover-image {
+    position: relative;
+    max-width: 100%;
+    height: 31rem;
+}
+a.bx-base-pofile-cover-image,
+a.bx-base-pofile-cover-image:hover {
+    position:absolute;
+    width:100%;
+    height:100%;
+    text-decoration:none;
+
+    background-color: #eee;
+    background-repeat: no-repeat;
+    background-position: center center;
+    -webkit-background-size: cover;
+    -moz-background-size: cover;
+    -o-background-size: cover;
+    background-size: cover;
+}
+a.bx-base-pofile-cover-image img {
+    position: relative;
+    display: block;
+    width: 100%;
+    height: 100%;
+
+    background-repeat: no-repeat;
+    background-position: center center;
+    -webkit-background-size: cover;
+    -moz-background-size: cover;
+    -o-background-size: cover;
+    background-size: cover;
+}
+.bx-base-pofile-cover-info {
+    position: relative;
+    width: 100%;
+}
+.bx-base-pofile-ci-cnt {
+    display: -webkit-flex;
+    display: flex;
+    -webkit-align-items: flex-start;
+    align-items: flex-start;
+    -webkit-justify-content: space-between;
+    justify-content: space-between;
+}
+.bx-media-phone .bx-base-pofile-ci-cnt {
+    -webkit-flex-direction: column;
+    flex-direction: column;
+    -webkit-align-items: center;
+    align-items: center;
+}
+
+div.bx-base-pofile-cover-thumb {
+    position: relative;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-align-items: flex-end;
+    align-items: flex-end;
+    height: 7.0rem;
+    margin-right: 1rem;
+    margin-bottom: 1rem;
+}
+.bx-media-phone div.bx-base-pofile-cover-thumb {
+    margin-right: 0;
+    margin-bottom: 0;
+}
+div.bx-base-pofile-cover-thumb .bx-base-pofile-unit-online {
+    width: 16px;
+    height: 16px;
+    right: 25px;
+    bottom: 25px;
+
+    border-radius: 16px;
+}
+a.bx-base-pofile-cover-thumb,
+a.bx-base-pofile-cover-thumb img {
+    position: relative;
+    display: block;
+}
+a.bx-base-pofile-cover-thumb img {
+    background-color: #fff;
+}
+
+a.bx-base-pofile-cover-thumb p.bx-base-pofile-unit-thumb,
+a.bx-base-pofile-cover-thumb img.bx-base-pofile-unit-thumb {
+    background-color: rgba(255, 255, 255, 1.0);
+    background-clip: padding-box;
+    border: 5px solid rgba(255, 255, 255, 1.0);
+    box-shadow: inset 0px 0px 4px rgba(0, 0, 0, 0.2);
+}
+a.bx-base-pofile-cover-thumb p.bx-base-pofile-unit-thumb.bx-def-ava-big-size {
+    font-size: 5.0rem;
+}
+
+.bx-base-pofile-cover-user {
+    -webkit-flex: 2 1 auto; 
+    -ms-flex:  2 1 auto; 
+    flex: 2 1 auto;
+
+    position: relative;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-flex-direction: column;
+    flex-direction: column;
+    -webkit-align-items: flex-start;
+    align-items: flex-start;
+    -webkit-align-self: center;
+    align-self: center;
+
+    height: 100%;
+}
+
+.bx-base-pofile-cover-user h1.bx-def-unit-info {
+    position: relative;
+    display: block;
+
+    margin: 0;
+    padding: 0;
+
+    line-height:normal;
+    text-align: left;
+    text-shadow: none;
+    font-size: 1.5rem;
+    font-weight: 800;
+}
+
+.bx-base-pofile-cover-actions {
+    position: relative;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-align-items: center;
+    align-items: center;
+
+    min-width: 3rem;
+    width: 100%;
+    height: 3.625rem;
+}
+
+.bx-base-pofile-cover-actions div.bx-base-general-entity-actions {
+    position: absolute;
+    width: 100%;
+}
+
+.bx-base-profile-view .bx-menu-main-bar {
+    overflow: visible;
+}
+
+.bx-menu-main-bar ul.bx-menu-main-submenu.bx-menu-more-auto,
+.bx-base-pofile-cover-actions ul.bx-menu-hor.bx-menu-more-auto {
+    display: -webkit-flex;
+    display: flex;
+    -webkit-flex-wrap: nowrap;
+    flex-wrap: nowrap;
+    -webkit-justify-content: flex-start;
+    justify-content: flex-start;
+}
+.bx-menu-main-bar ul.bx-menu-main-submenu.bx-menu-more-auto {
+    -webkit-justify-content: center;
+    justify-content: center;
+}
+.bx-base-pofile-cover-actions .bx-menu-hor li.bx-menu-item {
+    white-space: nowrap;
+}
+
+.bx-base-pofile-cover-actions .cmt,
+.bx-base-pofile-cover-actions .bx-view,
+.bx-base-pofile-cover-actions .bx-vote,
+.bx-base-pofile-cover-actions .bx-score,
+.bx-base-pofile-cover-actions .bx-favorite,
+.bx-base-pofile-cover-actions .bx-feature,
+.bx-base-pofile-cover-actions .bx-report,
+.bx-base-pofile-cover-actions .bx-tl-repost,
+.bx-base-pofile-cover-actions .bx-menu-btns-hor.bx-menu-object-sys_social_sharing,
+.bx-base-pofile-cover-actions .bx-base-general-entity-action {
+    position: relative;
+    float: none;
+    display: inline-block;
+    vertical-align: top;
+
+    margin: 0;
+    padding: 0;
+}
+
+.bx-base-pofile-cover-actions .bx-menu-hor li.bx-menu-item .cmt-element-holder,
+.bx-base-pofile-cover-actions .bx-menu-hor li.bx-menu-item .cmt-counter-holder,
+.bx-base-pofile-cover-actions .bx-menu-hor li.bx-menu-item .bx-view-element-holder,
+.bx-base-pofile-cover-actions .bx-menu-hor li.bx-menu-item .bx-view-counter-holder,
+.bx-base-pofile-cover-actions .bx-menu-hor li.bx-menu-item .bx-vote-element-holder, 
+.bx-base-pofile-cover-actions .bx-menu-hor li.bx-menu-item .bx-vote-counter-holder, 
+.bx-base-pofile-cover-actions .bx-menu-hor li.bx-menu-item .bx-score-element-holder, 
+.bx-base-pofile-cover-actions .bx-menu-hor li.bx-menu-item .bx-score-counter-holder, 
+.bx-base-pofile-cover-actions .bx-menu-hor li.bx-menu-item .bx-favorite-element-holder,
+.bx-base-pofile-cover-actions .bx-menu-hor li.bx-menu-item .bx-favorite-counter-holder,
+.bx-base-pofile-cover-actions .bx-menu-hor li.bx-menu-item .bx-report-element-holder,
+.bx-base-pofile-cover-actions .bx-menu-hor li.bx-menu-item .bx-report-counter-holder,
+.bx-base-pofile-cover-actions .bx-menu-hor li.bx-menu-item .bx-tl-repost-element-holder,
+.bx-base-pofile-cover-actions .bx-menu-hor li.bx-menu-item .bx-tl-repost-counter-holder {
+    float: none;
+    display: inline-block;
+    vertical-align: top;
+}
+
+.bx-base-pofile-popup-wrapper {
+    position:relative;
+}
+
+.bx-base-pofile-popup-btn {
+    position:absolute;
+    top:0;
+    right:0;
+}
+
+/* flexbox */
+.bx-base-pofile-units-wrapper {
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: -ms-flexbox;
+    display: flex;
+
+    /* makes all items on a row equal height */
+    -webkit-box-align: stretch;
+    -webkit-align-items: stretch;
+    -ms-flex-align: stretch;
+    align-items: stretch;
+
+    /* wrap onto the next line rather than stretching or shrinking */
+    -webkit-flex-wrap: wrap;
+    -ms-flex-wrap: wrap;
+    flex-wrap: wrap;
+
+    /* tells flexbox to start items from the left */
+    -webkit-box-pack: start;
+    -ms-flex-pack: start;
+    plex-pack: start;
+    -webkit-justify-content: flex-start;
+    justify-content: flex-start;
+}
+
+.bx-base-pofile-units-wrapper.bx-base-puw-gallery-wo-info {
+    -webkit-justify-content: space-between;
+    justify-content: space-between;
+}
+.bx-base-pofile-units-wrapper.bx-base-puw-gallery-wo-info:before,
+.bx-base-pofile-units-wrapper.bx-base-puw-gallery-wo-info:after {
+    display: none;
+}
+
+.bx-base-pofile-units-wrapper .bx-base-pofile-unit-with-cover {
+    -webkit-box-flex: 0;
+
+    -webkit-flex: 0 0 100%;
+    -ms-flex: 0 0 100%;
+    flex: 0 0 100%;
+
+    overflow:hidden;
+}
+
+/* showcase view */
+.bx-base-unit-showcase-wrapper {
+    display: block;
+    white-space: nowrap;
+    overflow: hidden;
+}
+.bx-base-unit-showcase-wrapper .bx-base-unit-showcase {
+	white-space: normal;
+}
+.bx-base-unit-showcase-wrapper .bx-base-pofile-unit-showcase {
+    width:calc(100%);
+}
+.bx-base-unit-showcase-wrapper .bx-base-pofile-unit-wo-info-showcase {
+	display: inline-block;
+	vertical-align: top;
+
+    width: calc(16.6%);
+}
+
+/* MEDIA: tablet2 */
+
+.bx-media-tablet2 .bx-layout-column-wide .bx-base-pofile-units-wrapper .bx-base-pofile-unit-with-cover,
+.bx-media-tablet2 #sys_search_results .bx-base-pofile-units-wrapper .bx-base-pofile-unit-with-cover,
+.bx-media-tablet2 .bx-layout-column-fullwidth .bx-base-pofile-units-wrapper .bx-base-pofile-unit-with-cover {
+    -webkit-flex: 0 0 49.8%;
+    -ms-flex: 0 0 49.8%;
+    flex: 0 0 49.8%;
+}
+
+.bx-media-tablet2 .bx-base-unit-showcase-wrapper .bx-base-pofile-unit-showcase {
+    width:calc(50%);
+}
+.bx-media-tablet2 .bx-layout-column-thin .bx-base-unit-showcase-wrapper .bx-base-pofile-unit-showcase {
+    width:calc(100%);
+}
+
+.bx-media-tablet2 .bx-base-unit-showcase-wrapper .bx-base-pofile-unit-wo-info-showcase {
+    width:calc(10.0%);
+}
+.bx-media-tablet2 .bx-layout-column-thin .bx-base-unit-showcase-wrapper .bx-base-pofile-unit-wo-info-showcase {
+    width:calc(16.6%);
+}
+
+/* MEDIA: desktop */
+
+.bx-media-desktop .bx-layout-column-wide .bx-base-pofile-units-wrapper .bx-base-pofile-unit-with-cover {
+    -webkit-flex: 0 0 49.8%;
+    -ms-flex: 0 0 49.8%;
+    flex: 0 0 49.8%;
+}
+.bx-media-desktop #sys_search_results .bx-base-pofile-units-wrapper .bx-base-pofile-unit-with-cover,
+.bx-media-desktop .bx-layout-column-fullwidth .bx-base-pofile-units-wrapper .bx-base-pofile-unit-with-cover {
+    -webkit-flex: 0 0 33.25%;
+    -ms-flex: 0 0 33.25%;
+    flex: 0 0 33.25%;
+}
+
+.bx-media-desktop .bx-base-unit-showcase-wrapper .bx-base-pofile-unit-showcase {
+    width:calc(33.3%);
+}
+.bx-media-desktop .bx-layout-column-thin .bx-base-unit-showcase-wrapper .bx-base-pofile-unit-showcase {
+    width:calc(100%);
+}
+.bx-media-desktop .bx-layout-column-half .bx-base-unit-showcase-wrapper .bx-base-pofile-unit-showcase {
+    width:calc(50%);
+}
+
+.bx-media-desktop .bx-base-unit-showcase-wrapper .bx-base-pofile-unit-wo-info-showcase {
+    width:calc(6.25%);
+}
+.bx-media-desktop .bx-layout-column-thin .bx-base-unit-showcase-wrapper .bx-base-pofile-unit-wo-info-showcase {
+    width:calc(25.0%);
+}
+.bx-media-desktop .bx-layout-column-half .bx-base-unit-showcase-wrapper .bx-base-pofile-unit-wo-info-showcase {
+    width:calc(10.0%);
+}
+
+
+
+/* view page */
+.bx-base-profile-entity-content,
+.bx-base-profile-entity-content p {
+    overflow: hidden;
+    text-overflow: ellipsis;
+}
+
+.bx-base-pofile-unit-bages {
+    position: absolute;
+    right: -30%;
+    top: 0px;
+}
+.bx-base-profile-cover-title-bages{
+    display: flex; 
+    align-items: center;
+    justify-content: center; 
+    overflow: hidden;
+    align-items: center;
+}

+ 65 - 0
upgrade/files/11.0.0-11.0.1/files/modules/base/text/classes/BxBaseModTextMenuAttachments.php

@@ -0,0 +1,65 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    BaseText Base classes for text modules
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+/**
+ * Create\edit entry attachments menu
+ */
+class BxBaseModTextMenuAttachments extends BxTemplMenu
+{
+    protected $_sModule;
+    protected $_oModule;
+
+    public function __construct($aObject, $oTemplate = false)
+    {
+        parent::__construct($aObject, $oTemplate);
+ 
+        $this->_oModule = BxDolModule::getInstance($this->_sModule);
+
+        $this->addMarkers(array(
+            'js_object' => $this->_oModule->_oConfig->getJsObject('poll')
+        ));
+    }
+
+    protected function _isVisible ($a)
+    {
+        if(!parent::_isVisible($a))
+            return false;
+
+        $sUploader = 'js_object_uploader_';
+        switch ($a['name']) {
+            case 'photo_simple':
+                $sUploader .= 'photos_simple';
+                break;
+            case 'photo_html5':
+                $sUploader .= 'photos_html5';
+                break;
+            case 'video_simple':
+                $sUploader .= 'videos_simple';
+                break;
+            case 'video_html5':
+                $sUploader .= 'videos_html5';
+                break;
+            case 'file_simple':
+                $sUploader .= 'files_simple';
+                break;
+            case 'file_html5':
+                $sUploader .= 'files_html5';
+                break;
+        }
+
+        if(!isset($this->_aMarkers[$sUploader]))
+            return false;
+
+        return true;
+    }
+}
+
+/** @} */

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
upgrade/files/11.0.0-11.0.1/files/plugins_public/croppie/croppie.min.js


+ 42 - 0
upgrade/files/11.0.0-11.0.1/files/searchKeyword.php

@@ -0,0 +1,42 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaCore UNA Core
+ * @{
+ */
+
+require_once('./inc/header.inc.php');
+require_once(BX_DIRECTORY_PATH_INC . "design.inc.php");
+
+$sClass = 'BxTemplSearch';
+
+$sElsName = 'bx_elasticsearch';
+$sElsMethod = 'is_configured';
+if(BxDolRequest::serviceExists($sElsName, $sElsMethod) && BxDolService::call($sElsName, $sElsMethod) && !bx_get('cat') && !bx_get('type')) {
+     $oModule = BxDolModule::getInstance($sElsName);
+     bx_import('Search', $oModule->_aModule);
+     $sClass = 'BxElsSearch';
+}
+bx_alert('system', 'search_keyword', 0, 0, array('class' => &$sClass));
+
+$oSearch = new $sClass(bx_get('section'));
+$oSearch->setLiveSearch(bx_get('live_search') ? 1 : 0);
+$oSearch->setMetaType(bx_process_input(bx_get('type')));
+$oSearch->setCategoryObject(bx_process_input(bx_get('cat')));
+
+$sCode = '';
+if (bx_get('keyword') !== false) {
+    $sCode = $oSearch->response();
+    if (!$sCode)
+        $sCode = $oSearch->getEmptyResult();
+}
+
+$oTemplate = BxDolTemplate::getInstance();
+$oTemplate->setPageNameIndex (BX_PAGE_DEFAULT);
+$oTemplate->setPageHeader (_t("_Search"));
+$oTemplate->setPageContent ('page_main_code', $oSearch->getForm() . ($sCode ? $oSearch->getResultsContainer($sCode) : ''));
+$oTemplate->getPageCode();
+
+/** @} */

+ 522 - 0
upgrade/files/11.0.0-11.0.1/files/studio/classes/BxDolStudioBuilderPageQuery.php

@@ -0,0 +1,522 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaStudio UNA Studio
+ * @{
+ */
+
+class BxDolStudioBuilderPageQuery extends BxDolStudioPageQuery
+{
+    function __construct()
+    {
+        parent::__construct();
+    }
+
+    public function insertPage ($sObj, $sModule, $sUri, $sUrl, $sTitleLangKey, $iType = 1, $iLayoutId = 5, $iVisibleForLevels = 2147483647, $sClass = '', $sClassFile = '')
+    {
+        $b = $this->query('INSERT INTO `sys_objects_page` SET
+            `author` = :author,
+            `added` = :added,
+            `object` = :obj,
+            `uri` = :uri,
+            `title` = :title,
+            `module` = :module,
+            `cover` = :cover,
+            `type_id` = :type,
+            `layout_id` = :layout,
+            `visible_for_levels` = :levels, 
+            `visible_for_levels_editable` = 1,
+            `url` = :url,
+            `cache_lifetime` = 0,
+            `cache_editable` = 1,
+            `deletable` = 1,
+            `override_class_name` = :class,
+            `override_class_file` = :file
+        ', array(
+            'author' => bx_get_logged_profile_id(),
+            'added' => time(),
+            'obj' => $sObj,
+            'uri' => $sUri,
+            'title' => $sTitleLangKey,
+            'module' => $sModule,
+            'cover' => 0,
+            'type' => $iType,
+            'layout' => $iLayoutId,
+            'levels' => $iVisibleForLevels,
+            'url' => $sUrl,
+            'class' => $sClass,
+            'file' => $sClassFile,
+        ));
+        if (!$b)
+            return false;
+
+        return $this->lastId();
+    }
+
+    function getPages($aParams, &$aItems, $bReturnCount = true)
+    {
+        $aMethod = array('name' => 'getAll', 'params' => array(0 => 'query'));
+        $sSelectClause = $sJoinClause = $sWhereClause = $sGroupClause = $sOrderClause = $sLimitClause = "";
+
+        if(!isset($aParams['order']) || empty($aParams['order']))
+           $sOrderClause = "ORDER BY `tp`.`object` ASC";
+
+        switch($aParams['type']) {
+            case 'by_id':
+                $aMethod['name'] = 'getRow';
+                $aMethod['params'][1] = array(
+                	'id' => $aParams['value']
+                );
+
+                $sWhereClause = " AND `tp`.`id`=:id ";
+                break;
+            case 'by_object':
+                $aMethod['name'] = 'getRow';
+                $aMethod['params'][1] = array(
+                	'object' => $aParams['value']
+                );
+
+                $sWhereClause = " AND `tp`.`object`=:object ";
+                break;
+
+			case 'by_uri':
+                $aMethod['name'] = 'getRow';
+                $aMethod['params'][1] = array(
+                	'uri' => $aParams['value']
+                );
+
+                $sWhereClause = " AND `tp`.`uri`=:uri ";
+                break;
+
+            case 'by_object_full':
+                $aMethod['name'] = 'getRow';
+                $aMethod['params'][1] = array(
+                	'object' => $aParams['value']
+                );
+
+                $sSelectClause = ", `tpl`.`name` AS `layout_name`, `tpl`.`icon` AS `layout_icon`, `tpl`.`title` AS `layout_title`, `tpl`.`template` AS `layout_template`, `tpl`.`cells_number` AS `layout_cells_number`";
+                $sJoinClause = "LEFT JOIN `sys_pages_layouts` AS `tpl` ON `tp`.`layout_id`=`tpl`.`id`";
+                $sWhereClause = " AND `tp`.`object`=:object ";
+                break;
+
+            case 'by_module':
+            	$aMethod['params'][1] = array(
+                	'module' => $aParams['value']
+                );
+
+                $sWhereClause = " AND `tp`.`module`=:module ";
+                break;
+
+            case 'all':
+                break;
+        }
+
+        $aMethod['params'][0] = "SELECT " . ($bReturnCount ? "SQL_CALC_FOUND_ROWS" : "") . "
+                `tp`.`id` AS `id`,
+                `tp`.`object` AS `object`,
+                `tp`.`uri` AS `uri`,
+                `tp`.`title_system` AS `title_system`,
+                `tp`.`title` AS `title`,
+                `tp`.`module` AS `module`,
+                `tp`.`cover` AS `cover`,
+                `tp`.`cover_image` AS `cover_image`,
+                `tp`.`type_id` AS `type_id`,
+                `tp`.`layout_id` AS `layout_id`,
+                `tp`.`submenu` AS `submenu`,
+                `tp`.`visible_for_levels` AS `visible_for_levels`,
+                `tp`.`visible_for_levels_editable` AS `visible_for_levels_editable`,
+                `tp`.`url` AS `url`,
+                `tp`.`meta_description` AS `meta_description`,
+                `tp`.`meta_keywords` AS `meta_keywords`,
+                `tp`.`meta_robots` AS `meta_robots`,
+                `tp`.`cache_lifetime` AS `cache_lifetime`,
+                `tp`.`cache_editable` AS `cache_editable`,
+                `tp`.`deletable` AS `deletable`,
+                `tp`.`override_class_name` AS `override_class_name`,
+                `tp`.`override_class_file` AS `override_class_file`" . $sSelectClause . "
+            FROM `sys_objects_page` AS `tp` " . $sJoinClause . "
+            WHERE 1 " . $sWhereClause . " " . $sGroupClause . " " . $sOrderClause . " " . $sLimitClause;
+        $aItems = call_user_func_array(array($this, $aMethod['name']), $aMethod['params']);
+
+        if(!$bReturnCount)
+            return !empty($aItems);
+
+        return (int)$this->getOne("SELECT FOUND_ROWS()");
+    }
+
+    function updatePage($iId, $aFields)
+    {
+        $sSql = "UPDATE `sys_objects_page` SET `" . implode("`=?, `", array_keys($aFields)) . "`=?  WHERE `id`=?";
+        $oStml = call_user_func_array(array($this, 'prepare'), array_merge(array($sSql), array_values($aFields), array($iId)));
+        return $this->res($oStml);
+    }
+
+    function deletePages($aParams)
+    {
+    	$aBindings = array();
+        $sWhereClause = "";
+
+        switch($aParams['type']) {
+            case 'by_id':
+            	$aBindings = array(
+                	'id' => $aParams['value']
+                );
+
+                $sWhereClause = "AND `tp`.`id`=:id ";
+                break;
+
+            case 'by_object':
+            	$aBindings = array(
+                	'object' => $aParams['value']
+                );
+
+                $sWhereClause = "AND `tp`.`object`=:object ";
+                break;
+
+            case 'all':
+                break;
+        }
+
+        $sSql = "DELETE FROM `tp` USING `sys_objects_page` AS `tp` WHERE 1 " . $sWhereClause;
+        return (int)$this->query($sSql, $aBindings) > 0;
+    }
+
+    function isUniqUri($sUri)
+    {
+        $sSql = $this->prepare("SELECT `id` FROM `sys_objects_page` WHERE `uri`=? LIMIT 1", $sUri);
+        return (int)$this->getOne($sSql) <= 0;
+    }
+
+	function getTypes($aParams, &$aItems, $bReturnCount = true)
+    {
+        $aMethod = array('name' => 'getAll', 'params' => array(0 => 'query'));
+        $sSelectClause = $sJoinClause = $sWhereClause = $sGroupClause = $sOrderClause = $sLimitClause = "";
+
+        if(!isset($aParams['order']) || empty($aParams['order']))
+           $sOrderClause = "ORDER BY `tpt`.`id` ASC";
+
+        switch($aParams['type']) {
+            case 'by_id':
+                $aMethod['name'] = 'getRow';
+                $aMethod['params'][1] = array(
+                	'id' => $aParams['value']
+                );
+
+                $sWhereClause = "AND `tpt`.`id`=:id ";
+                break;
+
+            case 'all':
+                break;
+        }
+
+        $aMethod['params'][0] = "SELECT " . ($bReturnCount ? "SQL_CALC_FOUND_ROWS" : "") . "
+                `tpt`.`id` AS `id`,
+                `tpt`.`title` AS `title`,
+                `tpt`.`template` AS `template`,
+                `tpt`.`order` AS `order`" . $sSelectClause . "
+            FROM `sys_pages_types` AS `tpt` " . $sJoinClause . "
+            WHERE 1 " . $sWhereClause . " " . $sGroupClause . " " . $sOrderClause . " " . $sLimitClause;
+        $aItems = call_user_func_array(array($this, $aMethod['name']), $aMethod['params']);
+
+        if(!$bReturnCount)
+            return !empty($aItems);
+
+        return (int)$this->getOne("SELECT FOUND_ROWS()");
+    }
+
+    function getSubmenus()
+    {
+        return BxDolDb::getInstance()->getPairs('SELECT `object`, `title` FROM `sys_objects_menu` WHERE `template_id` = 8', 'object', 'title');
+    }
+
+    function getLayouts($aParams, &$aItems, $bReturnCount = true)
+    {
+        $aMethod = array('name' => 'getAll', 'params' => array(0 => 'query'));
+        $sSelectClause = $sJoinClause = $sWhereClause = $sGroupClause = $sOrderClause = $sLimitClause = "";
+
+        if(!isset($aParams['order']) || empty($aParams['order']))
+           $sOrderClause = "ORDER BY `tpl`.`id` ASC";
+
+        switch($aParams['type']) {
+            case 'by_id':
+                $aMethod['name'] = 'getRow';
+                $aMethod['params'][1] = array(
+                	'id' => $aParams['value']
+                );
+
+                $sWhereClause = "AND `tpl`.`id`=:id ";
+                break;
+
+            case 'all':
+                break;
+        }
+
+        $aMethod['params'][0] = "SELECT " . ($bReturnCount ? "SQL_CALC_FOUND_ROWS" : "") . "
+                `tpl`.`id` AS `id`,
+                `tpl`.`name` AS `name`,
+                `tpl`.`icon` AS `icon`,
+                `tpl`.`title` AS `title`,
+                `tpl`.`template` AS `template`,
+                `tpl`.`cells_number` AS `cells_number`" . $sSelectClause . "
+            FROM `sys_pages_layouts` AS `tpl` " . $sJoinClause . "
+            WHERE 1 " . $sWhereClause . " " . $sGroupClause . " " . $sOrderClause . " " . $sLimitClause;
+        $aItems = call_user_func_array(array($this, $aMethod['name']), $aMethod['params']);
+
+        if(!$bReturnCount)
+            return !empty($aItems);
+
+        return (int)$this->getOne("SELECT FOUND_ROWS()");
+    }
+
+    function getDesignBoxes($aParams, &$aItems, $bReturnCount = true)
+    {
+        $aMethod = array('name' => 'getAll', 'params' => array(0 => 'query'));
+        $sSelectClause = $sJoinClause = $sWhereClause = $sGroupClause = $sOrderClause = $sLimitClause = "";
+
+        if(!isset($aParams['order']) || empty($aParams['order']))
+           $sOrderClause = "ORDER BY `tpd`.`order` ASC";
+
+        switch($aParams['type']) {
+            case 'by_id':
+                $aMethod['name'] = 'getRow';
+                $aMethod['params'][1] = array(
+                	'id' => $aParams['value']
+                );
+
+                $sWhereClause = "AND `tpd`.`id`=:id ";
+                break;
+
+            case 'ordered':
+                $sWhereClause = "AND `tpd`.`order`<>0 ";
+                break;
+
+            case 'all':
+                break;
+        }
+
+        $aMethod['params'][0] = "SELECT " . ($bReturnCount ? "SQL_CALC_FOUND_ROWS" : "") . "
+                `tpd`.`id` AS `id`,
+                `tpd`.`title` AS `title`,
+                `tpd`.`template` AS `template`" . $sSelectClause . "
+            FROM `sys_pages_design_boxes` AS `tpd` " . $sJoinClause . "
+            WHERE 1 " . $sWhereClause . " " . $sGroupClause . " " . $sOrderClause . " " . $sLimitClause;
+        $aItems = call_user_func_array(array($this, $aMethod['name']), $aMethod['params']);
+
+        if(!$bReturnCount)
+            return !empty($aItems);
+
+        return (int)$this->getOne("SELECT FOUND_ROWS()");
+    }
+
+    function getModulesWithCopyableBlocks()
+    {
+    	$sSql = $this->prepare("SELECT
+				`tm`.`name` AS `module`
+			FROM `sys_modules` AS `tm`
+			LEFT JOIN `sys_pages_blocks` AS `tpb` ON `tm`.`name`=`tpb`.`module`
+			WHERE `tm`.`type`=? AND `tpb`.`copyable`=?
+			GROUP BY `tm`.`name`", BX_DOL_MODULE_TYPE_MODULE, 1);
+    	return $this->getColumn($sSql);
+    }
+
+    function getBlocks($aParams, &$aItems, $bReturnCount = true)
+    {
+        $aMethod = array('name' => 'getAll', 'params' => array(0 => 'query'));
+        $sSelectClause = $sJoinClause = $sWhereClause = $sGroupClause = $sOrderClause = $sLimitClause = "";
+
+        if(!isset($aParams['order']) || empty($aParams['order']))
+           $sOrderClause = "ORDER BY `tpb`.`order` ASC";
+
+        switch($aParams['type']) {
+            case 'by_id':
+                $aMethod['name'] = 'getRow';
+                $aMethod['params'][1] = array(
+                	'id' => $aParams['value']
+                );
+
+                $sWhereClause = "AND `tpb`.`id`=:id ";
+                break;
+
+            case 'skeleton_by_type':
+                $aMethod['name'] = 'getRow';
+                $aMethod['params'][1] = array(
+                	'module' => BX_DOL_STUDIO_BP_SKELETONS,
+                	'type' => $aParams['value']
+                );
+
+                $sWhereClause = "AND `tpb`.`object`='' AND `tpb`.`module`=:module AND `tpb`.`type`=:type ";
+                break;
+
+            case 'by_ids':
+                $sWhereClause = "AND `tpb`.`id` IN (" . $this->implode_escape($aParams['value']) . ")";
+                break;
+
+            case 'by_object':
+            	$aMethod['params'][1] = array(
+                	'object' => $aParams['value']
+                );
+
+                $sWhereClause = "AND `tpb`.`object`=:object";
+                break;
+
+            case 'by_object_cell':
+            	$aMethod['params'][1] = array(
+                	'object' => $aParams['object'],
+            		'cell_id' => $aParams['cell']
+                );
+
+                $sWhereClause = "AND `tpb`.`object`=:object AND `tpb`.`cell_id`=:cell_id";
+                break;
+
+            case 'by_module_to_copy':
+            	$aMethod['params'][1] = array(
+                	'module' => $aParams['value']
+                );
+
+                $sWhereClause = "AND `tpb`.`module`=:module AND `tpb`.`copyable`=1";
+
+                if($aParams['value'] == BX_DOL_STUDIO_BP_SKELETONS)
+                    $sWhereClause .= " AND `tpb`.`object`=''";
+                break;
+
+            case 'counter_by_pages':
+                $aMethod['name'] = 'getPairs';
+                $aMethod['params'][1] = 'object';
+                $aMethod['params'][2] = 'counter';
+                $sSelectClause = ", COUNT(*) AS `counter`";
+                $sGroupClause = "GROUP BY `tpb`.`object`";
+                break;
+
+            case 'all':
+                break;
+        }
+
+        $aMethod['params'][0] = "SELECT " . ($bReturnCount ? "SQL_CALC_FOUND_ROWS" : "") . "
+                `tpb`.`id` AS `id`,
+                `tpb`.`object` AS `object`,
+                `tpb`.`cell_id` AS `cell_id`,
+                `tpb`.`module` AS `module`,
+                `tpb`.`title_system` AS `title_system`,
+                `tpb`.`title` AS `title`,
+                `tpb`.`designbox_id` AS `designbox_id`,
+                `tpb`.`hidden_on` AS `hidden_on`,
+                `tpb`.`visible_for_levels` AS `visible_for_levels`,
+                `tpb`.`type` AS `type`,
+                `tpb`.`content` AS `content`,
+                `tpb`.`deletable` AS `deletable`,
+                `tpb`.`copyable` AS `copyable`,
+                `tpb`.`active` AS `active`,
+                `tpb`.`order` AS `order`" . $sSelectClause . "
+            FROM `sys_pages_blocks` AS `tpb` " . $sJoinClause . "
+            WHERE 1 " . $sWhereClause . " " . $sGroupClause . " " . $sOrderClause . " " . $sLimitClause;
+        $aItems = call_user_func_array(array($this, $aMethod['name']), $aMethod['params']);
+
+        if(!$bReturnCount)
+            return !empty($aItems);
+
+        return (int)$this->getOne("SELECT FOUND_ROWS()");
+    }
+
+    function insertBlock($aFields)
+    {
+        $aFields['order'] = $this->getBlockOrderMax($aFields['object']) + 1;
+
+        $sSql = "INSERT INTO `sys_pages_blocks` SET " . $this->arrayToSQL($aFields);
+        if (!$this->query($sSql))
+            return false;
+        return $this->lastId();
+    }
+
+    function updateBlock($iId, $aFields)
+    {
+        $oStmt = $this->prepare("UPDATE `sys_pages_blocks` SET " . $this->arrayToSQL($aFields) . " WHERE `id`=:id");
+        return $this->res($oStmt, array('id' => $iId)) ? true : false;
+    }
+
+    function deleteBlocks($aParams)
+    {
+    	$aBindings = array();
+        $sWhereClause = "";
+        $aBlockIds = array();
+        switch($aParams['type']) {
+            case 'by_id':
+            	$aBindings = array(
+                	'id' => $aParams['value']
+                );
+
+                $sWhereClause = "AND `tpb`.`id`=:id ";
+                $aBlockIds[] = $aParams['value'];
+                break;
+
+            case 'by_object':
+            	$aBindings = array(
+                	'object' => $aParams['value']
+                );
+
+                $sWhereClause = "AND `tpb`.`object`=:object ";
+                $aBlockIds = $this->getColumn("SELECT `id` FROM `sys_pages_blocks` AS `tpb` WHERE 1 " . $sWhereClause, $aBindings);
+                break;
+
+            default:
+                return false;
+        }
+
+        $sSql = "DELETE FROM `tpb` USING `sys_pages_blocks` AS `tpb` WHERE 1 " . $sWhereClause;
+        $b = ((int)$this->query($sSql, $aBindings) > 0);
+        if ($b && $aBlockIds)
+            BxDolWikiQuery::deleteAllRevisions($aBlockIds);
+        return $b;
+    }
+
+    function resetBlocksByPage($sObject, $iCellId, $bDeactivate = false)
+    {
+        $aSetClause = array('cell_id' => $iCellId);
+        if($bDeactivate)
+            $aSetClause['active'] = 0;
+
+        return $this->query("UPDATE `sys_pages_blocks` SET " . $this->arrayToSQL($aSetClause) . " WHERE `object`=:object AND `cell_id`>:cell_id", array(
+            'object' => $sObject,
+            'cell_id' => $iCellId
+        ));
+    }
+
+    function getBlockOrderMax($sObject, $iCellId = 1)
+    {
+        $sSql = $this->prepare("SELECT MAX(`order`) FROM `sys_pages_blocks` WHERE `object`=? AND `cell_id`=? LIMIT 1", $sObject, $iCellId);
+        return (int)$this->getOne($sSql);
+    }
+
+    function deleteImage($aParams)
+    {
+        $sWhereClause = "";
+
+        switch($aParams['type']) {
+            case 'by_id':
+            	$aMethod['params'][1] = array(
+                	'id' => $aParams['value']
+                );
+
+                $sWhereClause = "AND `tpb`.`id`=:id ";
+                break;
+
+            case 'all':
+                break;
+        }
+
+        $sSql = "DELETE FROM `tpb` USING `sys_pages_blocks` AS `tpb` WHERE 1 " . $sWhereClause;
+        return (int)$this->query($sSql) > 0;
+    }
+
+    function getMenus($bFull = false)
+    {
+        $sSql = "SELECT * FROM `sys_objects_menu` WHERE 1 ORDER BY `module`, `object`";
+        if($bFull)
+            return $this->getAllWithKey($sSql, 'object');
+        else
+            return $this->getPairs($sSql, 'object', 'title');
+    }
+}
+
+/** @} */

+ 295 - 0
upgrade/files/11.0.0-11.0.1/files/studio/classes/BxDolStudioForm.php

@@ -0,0 +1,295 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaStudio UNA Studio
+ * @{
+ */
+
+define('BX_DOL_STUDIO_METHOD_DEFAULT', 'post');
+
+class BxDolStudioForm extends BxBaseFormView
+{
+    function __construct($aInfo, $oTemplate)
+    {
+        parent::__construct($aInfo, $oTemplate !== false ? $oTemplate : BxDolStudioTemplate::getInstance());
+
+        $this->_sCheckerHelper = isset($this->aParams['checker_helper']) ? $this->aParams['checker_helper'] : 'BxDolStudioFormCheckerHelper';
+    }
+
+    function initChecker($aValues = array (), $aSpecificValues = array())
+    {
+        parent::initChecker($aValues, $aSpecificValues);
+
+        if($this->isSubmitted() && !$this->_isValid)
+            $this->processTranslationsValues();
+    }
+
+    function insert($aValsToAdd = array(), $isIgnore = false)
+    {
+        $sAction = 'insert';
+        $this->processTranslationsKey($sAction);
+
+        $mixedResult = parent::insert($aValsToAdd, $isIgnore);
+        if($mixedResult !== false)
+            $this->processTranslations($sAction);
+
+        return $mixedResult;
+    }
+
+    function update($val, $aValsToAdd = array(), &$aTrackTextFieldsChanges = null)
+    {
+        $sAction = 'update';
+        $this->processTranslationsKey($sAction);
+
+        $mixedResult = parent::update($val, $aValsToAdd, $aTrackTextFieldsChanges);
+        if($mixedResult === false) 
+            return $mixedResult;
+
+        $mixedResult += (int)$this->processTranslations($sAction);
+        return $mixedResult;
+    }
+
+    function updateWithVisibility($iId)
+    {
+        $iVisibleFor = BxDolStudioUtils::getVisibilityValue($this->getCleanValue('visible_for'), $this->getCleanValue('visible_for_levels'));
+        BxDolForm::setSubmittedValue('visible_for_levels', $iVisibleFor, $this->aFormAttrs['method']);
+        unset($this->aInputs['visible_for']);
+
+        return $this->update($iId);
+    }
+
+    function processImageUploaderSave($sName, $iId = 0)
+    {
+        if($this->aInputs[$sName]['type'] != 'image_uploader')
+            return $iId;
+
+        $aInput = $this->aInputs[$sName];
+        if(!empty($_FILES[$sName]['tmp_name'])) {
+            $iProfileId = getLoggedId();
+
+            $sStorage = isset($aInput['storage_object']) && $aInput['storage_object'] != '' ? $aInput['storage_object'] : BX_DOL_STORAGE_OBJ_IMAGES;
+            $oStorage = BxDolStorage::getObjectInstance($sStorage);
+
+            if((int)$iId != 0 && !$oStorage->deleteFile($iId))
+                return _t('_adm_err_form_view_iu_delete');
+
+            $iId = $oStorage->storeFileFromForm($_FILES[$aInput['name']], false, $iProfileId);
+            if($iId === false)
+                return _t('_adm_err_form_view_iu_save') . $oStorage->getErrorString();
+
+            $oStorage->afterUploadCleanup($iId, $iProfileId);
+        }
+
+        return (int)$iId;
+    }
+
+    function getCleanValue ($sName)
+    {
+        $aResult = parent::getCleanValue($sName);
+
+        $a = isset($this->aInputs[$sName]) ? $this->aInputs[$sName] : false;
+        if($a && !empty($a['reverse']) && !empty($a['values']))
+            $aResult = array_diff(array_keys($a['values']), (is_array($aResult) ? $aResult : array()));
+
+        return $aResult;
+    }
+
+    protected function processTranslations($sType = 'insert')
+    {
+        $bResult = false;
+        foreach($this->aInputs as $sName => $aInput) {
+            if(!in_array($aInput['type'], array('text_translatable', 'textarea_translatable', 'list_translatable')))
+                continue; 
+
+            if($aInput['type'] == 'list_translatable') {
+                $iIndex = (int)$this->getCleanValue($sName . '_ind');
+                for($i = 0; $i < $iIndex; $i++) 
+                    if($this->_processTranslationsByName($sType, $sName . '_' . $i) !== false)
+                        $bResult = true;
+
+                continue;
+            }
+
+            if($this->_processTranslationsByName($sType, $sName) !== false)
+                $bResult = true;
+        }
+
+        return $bResult;
+    }
+
+    protected function _processTranslationsByName($sType, $sName)
+    {
+        $oLanguage = BxDolStudioLanguagesUtils::getInstance();
+
+        $aType2Method = array(
+            'insert' => 'addLanguageString',
+            'update' => 'updateLanguageString'
+        );
+
+        $bResult = false;
+
+        $sKey = $this->getCleanValue($sName);
+        if(empty($sKey))
+            return $bResult;
+
+        $aValues = $this->getTranslationsValues($sType, $sName);
+        foreach($aValues as $iLanguageId => $sString)
+            if($oLanguage->{$aType2Method[$sType]}($sKey, $sString, $iLanguageId))
+                $bResult = true;
+
+        return $bResult;
+    }
+
+    protected function processTranslationsKey($sType = 'insert')
+    {
+        foreach($this->aInputs as $sName => $aInput) {
+            if(!in_array($aInput['type'], array('text_translatable', 'textarea_translatable', 'list_translatable'))) 
+                continue;
+
+            if($aInput['type'] == 'list_translatable') {
+                $iIndex = (int)$this->getCleanValue($sName . '_ind');
+                for($i = 0; $i < $iIndex; $i++)
+                    $this->_processTranslationsKeyByName($sType, $sName . '_' . $i);
+
+                continue;
+            }
+
+            $this->_processTranslationsKeyByName($sType, $sName);
+        }
+    }
+
+    protected function _processTranslationsKeyByName($sType, $sName)
+    {
+        $oLanguage = BxDolStudioLanguagesUtils::getInstance();
+
+        //--- Fill in the Key if some values are available only. 
+        $sKey = '';
+        $sKeyDefault = $this->getCleanValue($sName);
+
+        $aValues = $this->getTranslationsValues($sType, $sName, true);
+        if(!empty($aValues) && is_array($aValues))
+            $sKey = $this->getTranslationsKey($sType, $sName, $sKeyDefault);
+
+        //--- Remove Key if it exists but no values for him
+        if(empty($sKey) && !empty($sKeyDefault)) 
+            $oLanguage->deleteLanguageString($sKeyDefault);
+
+        BxDolForm::setSubmittedValue($sName, $sKey, $this->aFormAttrs['method']);
+    }
+            
+    protected function processTranslationsValues()
+    {
+        foreach($this->aInputs as $sName => $aInput) {
+            if(!in_array($aInput['type'], array('text_translatable', 'textarea_translatable', 'list_translatable')))
+                continue;
+
+            if($aInput['type'] == 'list_translatable') {
+                $iIndex = (int)$this->getCleanValue($sName . '_ind');
+                for($i = 0; $i < $iIndex; $i++)
+                    $this->_processTranslationsValuesByName($sName . '_' . $i);
+
+                continue;
+            }
+
+            $this->_processTranslationsValuesByName($sName);
+        }
+    }
+
+    protected function _processTranslationsValuesByName($sName)
+    {
+        $aLanguages = BxDolStudioLanguagesUtils::getInstance()->getLanguages();
+
+        foreach($aLanguages as $sLangName => $sLangTitle)
+            $this->aInputs[$sName]['values'][$sLangName] = BxDolForm::getSubmittedValue($sName . '-' . $sLangName, $this->aFormAttrs['method']);
+    }
+
+    protected function getTranslationsKey($sType, $sName, $sValue)
+    {
+        $iRand = time();
+        $sPrefixDefault = "_sys_form_input";
+
+        $sName = BxDolStudioUtils::getSystemName(trim($sName));
+
+        $sResult = '';
+        switch($sType) {
+            case 'insert':
+                $sValue = !empty($sValue) ? BxDolStudioUtils::getSystemName($sValue) : $sPrefixDefault;
+                $sResult = $sValue . '_' . $sName . '_' . $iRand;
+            break;
+
+            case 'update':
+                $sResult = !empty($sValue) ? $sValue : $sPrefixDefault . '_' . $sName . '_' . $iRand;
+                break;
+        }
+
+        return $sResult;
+    }
+
+    protected function getTranslationsValues($sType, $sName, $bCheckEmpty = false)
+    {
+        $aLanguages = BxDolStudioLanguagesUtils::getInstance()->getLanguagesInfo();
+
+        $aResults = array();
+        foreach($aLanguages as $aLanguage) {
+            $sString = BxDolForm::getSubmittedValue($sName . '-' . $aLanguage['name'], $this->aFormAttrs['method']);
+            if(($bCheckEmpty && empty($sString)) || (!$bCheckEmpty &&  $sString === false))
+                continue;
+
+            $aResults[$aLanguage['id']] = $sString;
+        }
+
+        return $aResults;
+    }
+}
+
+class BxDolStudioFormCheckerHelper extends BxDolFormCheckerHelper
+{
+    function checkAvailTranslatable($sVal, $aName, $sMethod = BX_DOL_STUDIO_METHOD_DEFAULT, $bAll = true)
+    {
+        if(empty($sMethod) || empty($aName))
+            return false;
+
+        $aLanguages = BxDolStudioLanguagesUtils::getInstance()->getLanguages();
+
+        foreach($aLanguages as $sLangName => $sLangTitle) {
+            $sValue = BxDolForm::getSubmittedValue($aName . '-' . $sLangName, $sMethod);
+            $bValue = parent::checkAvail($sValue);
+
+            if($bAll && !$bValue)
+                return $sLangName;
+            if(!$bAll && $bValue)
+                return true;
+        }
+
+        return $bAll ? true : false;
+    }
+
+    function checkLengthTranslatable($sVal, $iLenMin, $iLenMax, $aName, $sMethod = BX_DOL_STUDIO_METHOD_DEFAULT, $bAll = true)
+    {
+        if(empty($sMethod) || empty($aName))
+            return false;
+
+        $aLanguages = BxDolStudioLanguagesUtils::getInstance()->getLanguages();
+
+        foreach($aLanguages as $sLangName => $sLangTitle) {
+            $sValue = BxDolForm::getSubmittedValue($aName . '-' . $sLangName, $sMethod);
+            $bValue = parent::checkLength($sValue, $iLenMin, $iLenMax);
+
+            if($bAll && !$bValue)
+                return $sLangName;
+            if(!$bAll && $bValue)
+                return true;
+        }
+
+        return $bAll ? true : false;
+    }
+
+    function checkTemplate($sVal)
+    {
+        return strlen($sVal) > 0 && BxDolModuleQuery::getInstance()->isEnabled($sVal);
+    }
+}
+
+/** @} */

+ 749 - 0
upgrade/files/11.0.0-11.0.1/files/studio/classes/BxDolStudioInstallerUtils.php

@@ -0,0 +1,749 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaStudio UNA Studio
+ * @{
+ */
+
+bx_import('BxDolLanguages');
+
+define('BX_DOL_MARKET_URL_ROOT', 'https://una.io/');
+define('BX_DOL_MARKET_URL_INTEGRATION', BX_DOL_MARKET_URL_ROOT . 'm/market_api/');
+
+define('BX_DOL_STUDIO_IU_RC_SUCCESS', 0);
+define('BX_DOL_STUDIO_IU_RC_FAILED', 1);
+define('BX_DOL_STUDIO_IU_RC_SCHEDULED', 2);
+
+/*
+ * Errors list.
+ * It can be broaden later.
+ */
+define('BX_DOL_STUDIO_IU_RCE_NOT_FOUND', 100);
+define('BX_DOL_STUDIO_IU_RCE_NOT_AVAILABLE', 101);
+define('BX_DOL_STUDIO_IU_RCE_NOT_AUTHORIZED', 102);
+define('BX_DOL_STUDIO_IU_RCE_ALREADY_PERFORMED', 103);
+define('BX_DOL_STUDIO_IU_RCE_UNIQUE_PARAMS_USED', 104);
+define('BX_DOL_STUDIO_IU_RCE_WSV_MI', 105); //--- Wrong script version during module install
+define('BX_DOL_STUDIO_IU_RCE_WSV_MU', 106); //--- Wrong script version during module update
+define('BX_DOL_STUDIO_IU_RCE_WMV', 107); //--- Wrong module version during module update
+define('BX_DOL_STUDIO_IU_RCE_MODIFIED', 108);
+define('BX_DOL_STUDIO_IU_RCE_CHECKSUM_FAILED', 109);
+
+define('BX_DOL_STUDIO_IU_RCE_SUBACTION_FAILED', 200); //--- Error appeared in some subaction
+
+
+class BxDolStudioInstallerUtils extends BxDolInstallerUtils implements iBxDolSingleton
+{
+    protected $bUseFtp;
+
+    protected $sAuthorizedAccessClass;
+    protected $sStoreDataUrlPublic;
+
+    protected $_oLog;
+
+    public function __construct()
+    {
+        if (isset($GLOBALS['bxDolClasses'][get_class($this)]))
+            trigger_error ('Multiple instances are not allowed for the class: ' . get_class($this), E_USER_ERROR);
+
+        parent::__construct();
+
+        $this->bUseFtp = BX_FORCE_USE_FTP_FILE_TRANSFER;
+
+        $this->sAuthorizedAccessClass = 'BxDolStudioOAuthOAuth2';
+        $this->sStoreDataUrlPublic = BX_DOL_MARKET_URL_INTEGRATION;
+
+        $this->_oLog = BxDolLog::getInstance();
+        $this->_oLog->setName('upgrade_modules');
+    }
+
+    /**
+     * Prevent cloning the instance
+     */
+    public function __clone()
+    {
+        if (isset($GLOBALS['bxDolClasses'][get_class($this)]))
+            trigger_error('Clone is not allowed for the class: ' . get_class($this), E_USER_ERROR);
+    }
+
+    /**
+     * Get singleton instance of the class
+     */
+    public static function getInstance()
+    {
+        if (!isset($GLOBALS['bxDolClasses'][__CLASS__]))
+            $GLOBALS['bxDolClasses'][__CLASS__] = new BxDolStudioInstallerUtils();
+
+        return $GLOBALS['bxDolClasses'][__CLASS__];
+    }
+
+    public static function getNamePerformAction($sParam)
+    {
+    	return 'sys_perform_action_' . md5($sParam);
+    }
+
+    public static function getNameDownloadFile($sParam)
+    {
+    	return 'sys_download_file_complete_' . $sParam;
+    }
+
+    public static function getNamePerformModulesUpgrade()
+    {
+    	return 'sys_upgrade_modules_transient';
+    }
+
+    /*
+     * Is used to complete Downloading inside Transient Cron Job.
+     */
+    public function serviceDownloadFileComplete($sFilePath, $aParams = array())
+    {
+    	return $this->downloadFileComplete($sFilePath, array_merge($aParams, array('transient' => true)));
+    }
+
+    /*
+     * Is used to perform action inside Transient Cron Job.
+     */
+    public function servicePerformAction($sDirectory, $sOperation, $aParams)
+    {
+    	return $this->perform($sDirectory, $sOperation, array_merge($aParams, array('transient' => true)));
+    }
+
+    /*
+     * Is used to perform modules upgrade inside Transient Cron Job.
+     */
+    public function servicePerformModulesUpgrade($aParams = array())
+    {
+        return $this->performModulesUpgrade(array_merge($aParams, array('directly' => true, 'transient' => true)));
+    }
+
+    public function getStoreDataUrl($sType = 'public')
+    {
+    	return $sType == 'public' ? $this->sStoreDataUrlPublic : '';
+    }
+
+    public function getAccessObject($bAuthorizedAccess)
+    {
+        $sClass = $bAuthorizedAccess ? $this->sAuthorizedAccessClass : 'BxDolStudioJson';
+
+        bx_import($sClass);
+        return $sClass::getInstance();
+    }
+
+    public function getModules($bTitleAsKey = true)
+    {
+        $aModules = array();
+        $oTemplate = BxDolStudioTemplate::getInstance();
+
+        $aInstalledInfo = $this->getInstalledInfo();
+
+        $sPath = BX_DIRECTORY_PATH_MODULES;
+        if(($rHandleVendor = opendir($sPath)) !== false) {
+            while(($sVendor = readdir($rHandleVendor)) !== false) {
+                if(substr($sVendor, 0, 1) == '.' || !is_dir($sPath . $sVendor))
+                    continue;
+
+                if(($rHandleModule = opendir($sPath . $sVendor)) !== false) {
+                    while(($sModule = readdir($rHandleModule)) !== false) {
+                        if(!is_dir($sPath . $sVendor . '/' . $sModule) || substr($sModule, 0, 1) == '.')
+                            continue;
+
+                        $sModulePath = $sVendor . '/' . $sModule . '/';
+                        $aModule = $this->getConfigModule($sPath . $sModulePath . 'install/config.php', !empty($aInstalledInfo[$sModulePath]) ? $aInstalledInfo[$sModulePath] : array());
+                        if(empty($aModule))
+                            continue;
+
+                        $aModules[$bTitleAsKey ? $aModule['title'] : $aModule['name']] = $aModule;
+                    }
+                    closedir($rHandleModule);
+                }
+            }
+            closedir($rHandleVendor);
+        }
+
+        ksort($aModules);
+        return $aModules;
+    }
+
+    public function getUpdates()
+    {
+        $aUpdates = array();
+
+        $oTemplate = BxDolStudioTemplate::getInstance();
+
+        $aInstalledInfo = $this->getInstalledInfo();
+
+        $sPath = BX_DIRECTORY_PATH_MODULES;
+        if(($rHandleVendor = opendir($sPath)) !== false) {
+            while(($sVendor = readdir($rHandleVendor)) !== false) {
+                if(substr($sVendor, 0, 1) == '.' || !is_dir($sPath . $sVendor))
+                    continue;
+
+                if(($rHandleModule = opendir($sPath . $sVendor . '/')) !== false) {
+                    while(($sModule = readdir($rHandleModule)) !== false) {
+                        if(!is_dir($sPath . $sVendor . '/' . $sModule) || substr($sModule, 0, 1) == '.')
+                            continue;
+
+                        $sModulePath = $sVendor . '/' . $sModule . '/';
+                        if(empty($aInstalledInfo[$sModulePath]))
+                            continue;
+
+                        $aUpdate = $this->getUpdate($aInstalledInfo[$sModulePath]);
+                        if(!empty($aUpdate))
+                            $aUpdates[$aUpdate['title']] = $aUpdate;
+                    }
+                    closedir($rHandleModule);
+                }
+            }
+            closedir($rHandleVendor);
+        }
+
+        ksort($aUpdates);
+        return $aUpdates;
+    }
+
+    public function getUpdate($mixedModule)
+    {
+        $aResult = array();
+
+        if(!is_array($mixedModule))
+            $mixedModule = BxDolModuleQuery::getInstance()->{is_numeric($mixedModule) ? 'getModuleById' : 'getModuleByName'}($mixedModule); 
+
+        if(empty($mixedModule) || !is_array($mixedModule))
+            return $aResult;
+
+        $sPathModule = BX_DIRECTORY_PATH_MODULES . $mixedModule['path'];
+        $sPathUpdates = $sPathModule . 'updates/';
+
+        $rHandleUpdate = @opendir($sPathUpdates);
+        if($rHandleUpdate === false)
+            return $aResult; 
+
+        while(($sUpdate = readdir($rHandleUpdate)) !== false) {
+            $sPathUpdate = $sPathUpdates . $sUpdate . '/';
+            if(substr($sUpdate, 0, 1) == '.' || !is_dir($sPathUpdate))
+                continue;
+
+            $aUpdate = $this->getConfigUpdate($sPathModule . 'install/config.php', $sPathUpdate . 'install/config.php', $mixedModule);
+            if(empty($aUpdate) || !is_array($aUpdate) || version_compare(strtolower($aUpdate['module_version']), strtolower($aUpdate['version_from'])) != 0)
+                continue;
+
+            $aResult = $aUpdate;
+            break;
+        }
+
+        closedir($rHandleUpdate);
+
+        return $aResult;
+    }
+
+    public function perform($sDirectory, $sOperation, $aParams = array())
+    {
+        $bTransient = false;
+        if(isset($aParams['transient'])) {
+            $bTransient = (bool)$aParams['transient'];
+            unset($aParams['transient']);
+        }
+
+        if(!defined('BX_DOL_CRON_EXECUTE') && !self::isRealOwner() && !in_array($sOperation, array('install', 'uninstall', 'enable', 'disable'))) {
+            if($this->addTransientJob(self::getNamePerformAction($sDirectory), 'perform_action', array($sDirectory, $sOperation, $aParams)))
+                return array('code' => BX_DOL_STUDIO_IU_RC_SCHEDULED, 'message' => _t('_adm_mod_msg_process_operation_scheduled'));
+            else 
+                return array('code' => BX_DOL_STUDIO_IU_RC_FAILED, 'message' => _t('_adm_mod_err_process_operation_failed', $sOperation, $sDirectory));
+        }
+
+        $sConfigFile = 'install/config.php';
+        $sInstallerFile = 'install/installer.php';
+        $sInstallerClass = $sOperation == 'update' ? 'Updater' : 'Installer';
+
+        $aConfig = self::getModuleConfig(BX_DIRECTORY_PATH_MODULES . $sDirectory . $sConfigFile);
+        if(empty($aParams['module_name']) && !empty($aConfig['name']))
+            $aParams['module_name'] = $aConfig['name'];
+
+        $sPathInstaller = BX_DIRECTORY_PATH_MODULES . $sDirectory . $sInstallerFile;
+        if(empty($aConfig) || !file_exists($sPathInstaller)) {
+            $sMessage = _t('_adm_mod_err_process_operation_failed', $sOperation, $sDirectory);
+
+            $this->writeLog($sMessage, $aParams);
+            if($bTransient)
+                $this->emailNotify($sMessage, $aParams);
+
+            return array('code' => BX_DOL_STUDIO_IU_RC_FAILED, 'message' => $sMessage);
+        } 
+
+        require_once($sPathInstaller);
+
+        $sClassName = $aConfig['class_prefix'] . $sInstallerClass;
+        $oInstaller = new $sClassName($aConfig);
+        $aResult = $oInstaller->$sOperation($aParams);
+
+        if(!$aResult['result']) {
+            $this->writeLog($aResult['message'], $aParams);
+
+            if($bTransient)
+                $this->emailNotify($aResult['message'], $aParams);
+        }
+
+        return array(
+            'code' => isset($aResult['code']) ? $aResult['code'] : ($aResult['result'] ? BX_DOL_STUDIO_IU_RC_SUCCESS : BX_DOL_STUDIO_IU_RC_FAILED), 
+            'message' => $aResult['message']
+        );
+    }
+
+    /**
+     * Update modules using cron based auto updates.
+     * @see BxDolCronUpgradeModulesCheck
+     */
+    public function performModulesUpgrade($aParams = array())
+    {
+        $bDirectly = true;
+        if(isset($aParams['directly'])) {
+            $bDirectly = (bool)$aParams['directly'];
+            unset($aParams['directly']);
+        }
+
+        $bTransient = true;
+        if(isset($aParams['transient'])) {
+            $bTransient = (bool)$aParams['transient'];
+            unset($aParams['transient']);
+        }
+
+        if(!defined('BX_DOL_CRON_EXECUTE') && !$bDirectly)
+            return $this->addTransientJob(self::getNamePerformModulesUpgrade(), 'perform_modules_upgrade', array($aParams));
+
+        $aFailed = array();
+        $aUpdates = $this->checkUpdates();
+        if(empty($aUpdates) || !is_array($aUpdates)) {
+            $this->_oLog->write('Cannot get a list of modules which require to be updated.');
+            return true;
+        }
+
+        foreach($aUpdates as $aUpdate) {
+            $mixedResult = $this->downloadUpdatePublic($aUpdate['name']);
+            if($mixedResult !== true) {
+                $aFailed[$aUpdate['name']] = $mixedResult;
+                continue;
+            }
+        }
+
+        $aSuccess = array();
+        $aUpdates = $this->getUpdates();
+        if(empty($aUpdates) || !is_array($aUpdates)) {
+            $this->_oLog->write('Cannot find update scripts for modules. They are damaged or were not downloaded.');
+            return true;
+        }
+
+        foreach($aUpdates as $aUpdate) {
+            $aParams['module_name'] = $aUpdate['module_name'];
+
+            $aResult = $this->perform($aUpdate['dir'], 'update', $aParams);
+            switch((int)$aResult['code']) {
+                case BX_DOL_STUDIO_IU_RC_SUCCESS:
+                    $aSuccess[$aUpdate['module_name']] = $aUpdate['version_to'];
+                    break;
+
+                case BX_DOL_STUDIO_IU_RCE_WSV_MU:
+                    if(isset($aParams['autoupdate']) && (bool)$aParams['autoupdate'] === true)
+                        break;
+
+                default:
+                    $aFailed[$aUpdate['module_name']] = $aResult['message'];
+            }
+        }
+
+        if(!empty($aFailed)) {
+            $this->_oLog->write('Failed to update modules:', $aFailed);
+
+            if($bTransient)
+                $this->emailNotifyModulesUpgrade('failed', $aFailed);
+        }
+
+        if(!empty($aSuccess)) {
+            $this->_oLog->write('Successfully updated modules:', $aSuccess);
+
+            if($bTransient)
+                $this->emailNotifyModulesUpgrade('success', $aSuccess);
+        }
+
+        return empty($aFailed);
+    }
+
+    public function checkModules($bAuthorizedAccess = false)
+    {
+        $iPerPage = 9999; //--- Note. It's essential to load all purchased products at the same time.
+
+        if($bAuthorizedAccess)
+            $aProducts = $this->getAccessObject(true)->loadItems(array(
+                'method' => 'browse_purchased', 
+                'domain' => BX_DOL_URL_ROOT, 
+                'products' => $this->getInstalledInfoShort(),
+                'per_page' => $iPerPage
+            ));
+        else
+            $aProducts = $this->getAccessObject(false)->load($this->sStoreDataUrlPublic . 'json_browse_purchased', array('key' => getParam('sys_oauth_key'), 'per_page' => $iPerPage));
+
+        if(empty($aProducts) || !is_array($aProducts))
+            return $aProducts;
+
+        $oModuleDb = BxDolModuleQuery::getInstance();
+
+        $oModuleDb->updateModule(array('hash' => ''));
+        if(!empty($aProducts) && is_array($aProducts))
+            foreach ($aProducts as $aProduct)
+                if(!empty($aProduct['hash']))
+                    $oModuleDb->updateModule(array('hash' => $aProduct['hash']), array('name' => $aProduct['name']));
+
+        return $aProducts;
+    }
+
+    public function checkUpdates($bAuthorizedAccess = false)
+    {
+        $sVersion = bx_get_ver();
+        $sProducts = $this->getInstalledInfoShort();
+
+        if($bAuthorizedAccess)
+            return $this->getAccessObject(true)->loadItems(array(
+                'method' => 'browse_updates', 
+                'version' => $sVersion,
+                'products' => $sProducts
+            ));
+
+        return $this->getAccessObject(false)->load($this->sStoreDataUrlPublic . 'json_browse_updates', array(
+            'key' => getParam('sys_oauth_key'),
+            'version' => $sVersion,
+            'products' => $sProducts
+        ));
+    }
+
+    public function downloadFileAuthorized($iFileId)
+    {
+        $aItem = $this->getAccessObject(true)->loadItems(array(
+            'method' => 'download_file', 
+            'file_id' => $iFileId
+        ));
+
+        if(empty($aItem) || !is_array($aItem))
+            return $aItem;
+
+        return $this->downloadFileInit($aItem, array('module_name' => $aItem['module_name']));
+    }
+
+    public function downloadUpdatePublic($sModuleName, $bApplyUpdate = false)
+    {
+        $aModule = BxDolModuleQuery::getInstance()->getModuleByName($sModuleName);
+
+        $aItem = $this->getAccessObject(false)->load($this->sStoreDataUrlPublic . 'json_download_update', array(
+            'key' => getParam('sys_oauth_key'),
+            'version' => bx_get_ver(),
+            'product' => base64_encode(serialize(array(
+                'name' => $aModule['name'],
+                'version' => $aModule['version'],
+                'hash' => $aModule['hash'],
+            ))),
+        ));
+
+        return $this->downloadFileInit($aItem, array('module_name' => $sModuleName, 'auto_action' => $bApplyUpdate ? 'update' : ''));
+    }
+
+    protected function downloadFileInit($aItem, $aParams = array())
+    {
+        if(empty($aItem) || !is_array($aItem))
+            return $aItem;
+
+        //--- write ZIP archive.
+        $sFilePath = '';
+        $mixedResult = $this->performWrite($aItem, $sFilePath);
+        if($mixedResult !== true)
+            return $mixedResult;
+
+        return $this->downloadFileComplete($sFilePath, $aParams);
+    }
+
+    protected function downloadFileComplete($sFilePath, $aParams = array())
+    {
+        $bTransient = false;
+    	if(isset($aParams['transient'])) {
+            $bTransient = (bool)$aParams['transient'];
+            unset($aParams['transient']);
+    	}
+
+        $sAutoAction = '';
+        $bAutoAction = false;
+        if(isset($aParams['auto_action'])) {
+            $sAutoAction = $aParams['auto_action'];
+            $bAutoAction = !empty($sAutoAction);
+            unset($aParams['auto_action']);
+        }
+
+        if(!defined('BX_DOL_CRON_EXECUTE') && !self::isRealOwner()) {
+            if(!$this->addTransientJob(self::getNameDownloadFile($aParams['module_name']), 'download_file_complete', array($sFilePath, array_merge($aParams, array('auto_action' => $sAutoAction)))))
+                return array('code' => BX_DOL_STUDIO_IU_RC_FAILED, 'message' => _t('_adm_str_err_download_failed'));
+
+            return array('code' => BX_DOL_STUDIO_IU_RC_SCHEDULED, 'message' => _t('_adm_str_msg_download' . ($bAutoAction ? '_and_install' : '') . '_scheduled'));
+        }
+
+        //--- Unarchive package.
+        $sPackagePath = '';
+        $mixedResult = $this->performUnarchive($sFilePath, $sPackagePath);
+
+        @unlink($sFilePath);
+        if($mixedResult !== true) {
+            $this->writeLog($mixedResult, $aParams);
+            if($bTransient)
+                $this->emailNotify($mixedResult, $aParams);
+
+            return array('code' => BX_DOL_STUDIO_IU_RC_FAILED, 'message' => $mixedResult);
+        }
+
+        //--- Copy unarchived package.
+        $sHomePath = '';
+        $mixedResult = $this->performCopy($sPackagePath, $sHomePath);
+
+        @bx_rrmdir($sPackagePath);
+        if($mixedResult !== true) {
+            $this->writeLog($mixedResult, $aParams);
+            if($bTransient)
+                $this->emailNotify($mixedResult, $aParams);
+
+            return array('code' => BX_DOL_STUDIO_IU_RC_FAILED, 'message' => $mixedResult);
+        }
+
+        if(!$bAutoAction)
+            return true;
+
+        //--- Autoinstall the downloaded package if it's needed.
+        $aResult = $this->perform($sHomePath, $sAutoAction, $aParams);
+        if((int)$aResult['code'] == 0) 
+            return true;
+
+        if($bTransient)
+            $this->emailNotify($aResult['message'], $aParams);
+
+        return array('code' => BX_DOL_STUDIO_IU_RC_FAILED, 'message' => $aResult['message']);
+    }
+
+    protected function performWrite($aItem, &$sFilePath)
+    {
+        $sFilePath = BX_DIRECTORY_PATH_TMP . $aItem['name'];
+        if(file_exists($sFilePath))
+            @unlink($sFilePath);
+
+        $iUmaskSave = umask(0);
+
+        if(!$rHandler = fopen($sFilePath, 'w')) {
+            umask($iUmaskSave);
+            return _t('_adm_str_err_cannot_write');
+        }
+
+        $sContent = urldecode($aItem['content']);
+        if(!fwrite($rHandler, $sContent, strlen($sContent))) {
+            umask($iUmaskSave);
+            return _t('_adm_str_err_cannot_write');
+        }
+
+        fclose($rHandler);
+
+        umask($iUmaskSave);
+        return true;
+    }
+
+    protected function performUnarchive($sFilePath, &$sPackagePath)
+    {
+        if(!class_exists('ZipArchive'))
+            return _t('_adm_str_err_zip_not_available');
+
+        $iUmaskSave = umask(0);
+
+        $oZip = new ZipArchive();
+        if($oZip->open($sFilePath) !== true) {
+            umask($iUmaskSave);
+            return _t('_adm_str_err_cannot_unzip_package');
+        }
+
+        $sPackageFolder = '';
+        if($oZip->numFiles > 0)
+            $sPackageFolder = $oZip->getNameIndex(0);
+
+        if(empty($sPackageFolder)) {
+            umask($iUmaskSave);
+            return _t('_adm_str_err_cannot_unzip_package');
+        }
+
+        $sPackagePath = BX_DIRECTORY_PATH_TMP . $sPackageFolder;
+        if(file_exists($sPackagePath)) // remove existing tmp folder with the same name
+            @bx_rrmdir($sPackagePath);
+
+        if(!$oZip->extractTo(BX_DIRECTORY_PATH_TMP)) {
+            umask($iUmaskSave);
+            return _t('_adm_str_err_cannot_unzip_package');
+        }
+
+        $oZip->close();
+
+        umask($iUmaskSave);
+        return true;
+    }
+
+    protected function performCopy($sPackagePath, &$sHomePath)
+    {
+        if($this->bUseFtp) {
+            $sLogin = getParam('sys_ftp_login');
+            $sPassword = getParam('sys_ftp_password');
+            $sPath = getParam('sys_ftp_dir');
+            if(empty($sLogin) || empty($sPassword) || empty($sPath))
+                return _t('_adm_str_err_no_ftp_info');
+
+            $oFile = new BxDolFtp($_SERVER['HTTP_HOST'], $sLogin, $sPassword, $sPath);
+
+            if(!$oFile->connect())
+                return _t('_adm_str_err_cannot_connect_to_ftp');
+
+            if(!$oFile->isUna())
+                return _t('_adm_str_err_destination_not_valid');
+        }
+        else
+            $oFile = BxDolFile::getInstance();
+
+        $aConfig = self::getModuleConfig($sPackagePath . 'install/config.php');
+        if(empty($aConfig) || !is_array($aConfig) || empty($aConfig['home_dir']))
+            return _t('_adm_str_err_wrong_package_format');
+
+        $sHomePath = $aConfig['home_dir'];
+        if(!$oFile->copy($sPackagePath, 'modules/' . $sHomePath))
+            return _t('_adm_str_err_files_copy_failed');
+
+        return true;
+    }
+
+    protected function getConfigModule($sConfigPath, $aInstalled = array())
+    {
+    	$aConfig = self::getModuleConfig($sConfigPath);
+        if(empty($aConfig) || !is_array($aConfig))
+            return array();
+
+        $bInstalled = !empty($aInstalled);
+        $bEnabled = $bInstalled && (int)$aInstalled['enabled'] == 1;
+        $sVersion = $bInstalled ? $aInstalled['version'] : $aConfig['version'];
+
+        return array(
+            'type' => $aConfig['type'],
+            'name' => isset($aConfig['name']) ? $aConfig['name'] : $aConfig['home_uri'],
+            'title' => bx_process_output($aConfig['title']),
+            'vendor' => $aConfig['vendor'],
+            'version' => $sVersion,
+            'uri' => $aConfig['home_uri'],
+            'dir' => $aConfig['home_dir'],
+            'note' => isset($aConfig['note']) ? bx_process_output($aConfig['note']) : '',
+            'installed' => $bInstalled,
+            'enabled' => $bEnabled
+        );
+    }
+
+    protected function getConfigUpdate($sConfigPathModule, $sConfigPathUpdate, $aInstalledModule = array())
+    {
+        $aModule = $this->getConfigModule($sConfigPathModule, $aInstalledModule);
+        if(empty($aModule) || !$aModule['installed'])
+            return array();
+
+		$aConfig = self::getModuleConfig($sConfigPathUpdate);
+        if(empty($aConfig) || !is_array($aConfig))
+            return array();
+
+        return array(
+            'title' => bx_process_output($aConfig['title']),
+            'vendor' => $aConfig['vendor'],
+            'version_from' => $aConfig['version_from'],
+            'version_to' => $aConfig['version_to'],
+            'dir' => $aConfig['home_dir'],
+        	'module_type' => $aModule['type'],
+            'module_name' => $aModule['name'],
+            'module_dir' => $aModule['dir'],
+        	'module_version' => $aModule['version']
+        );
+    }
+
+    private function getInstalledInfo()
+    {
+        $aModules = BxDolModuleQuery::getInstance()->getModules();
+
+        $aInstalledInfo = array();
+        foreach($aModules as $aModule)
+            $aInstalledInfo[$aModule['path']] = $aModule;
+
+        return $aInstalledInfo;
+    }
+
+    private function getInstalledInfoShort()
+    {
+    	$aModules = BxDolModuleQuery::getInstance()->getModules();
+
+        $aProducts = array();
+        foreach($aModules as $aModule) {
+            if(empty($aModule['name']))
+                continue;
+
+            $aProducts[$aModule['name']] = array(
+                'name' => $aModule['name'],
+                'version' => $aModule['version'],
+            	'hash' => $aModule['hash'],
+            );
+        }
+
+        return base64_encode(serialize($aProducts));
+    }
+
+    private function addTransientJob($sName, $sAction, $aParams)
+    {
+        if(BxDolCronQuery::getInstance()->addTransientJobService($sName, array('system', $sAction, $aParams, 'DolStudioInstallerUtils')))
+            return true;
+
+        return false;
+    }
+
+    private function writeLog($sMessage, $aParams = array())
+    {
+        $this->_oLog->write('Operation failed:', $this->getModuleTitle($aParams) . $sMessage);
+    }
+
+    private function emailNotify($sMessage, $aParams = array())
+    {
+    	sendMailTemplateSystem('t_BgOperationFailed', array (
+            'conclusion' => strip_tags($this->getModuleTitle($aParams) . $sMessage),
+        ));
+    }
+
+    private function getModuleTitle($aParams = array())
+    {
+        if(empty($aParams['module_name'])) 
+            return '';
+
+        $sTitleKey = '_' . $aParams['module_name'];
+        $sTitleValue = _t($sTitleKey);
+        if(strcmp($sTitleKey, $sTitleValue) == 0) {
+            $aModule = BxDolModuleQuery::getInstance()->getModuleByName($aParams['module_name']);
+            $sTitleValue = is_array($aModule) && !empty($aModule['title']) ? $aModule['title'] : $aParams['module_name'];
+        }
+
+        return $sTitleValue . ': ';
+    }
+
+    private function emailNotifyModulesUpgrade($sResult, $aData)
+    {
+        $oModuleQuery = BxDolModuleQuery::getInstance();
+
+    	$sConclusion = '';
+    	if(!empty($aData))
+            foreach($aData as $sModule => $sMessage) {
+                $aModule = $oModuleQuery->getModuleByName($sModule);
+
+                $sConclusion .= _t('_sys_et_txt_body_modules_upgrade_' . $sResult, $aModule['title'], $sMessage);
+            }
+
+        sendMailTemplateSystem('t_UpgradeModules' . ucfirst($sResult), array (
+            'conclusion' => $sConclusion,
+        ));
+    }
+}
+
+/** @} */

+ 650 - 0
upgrade/files/11.0.0-11.0.1/files/studio/classes/BxDolStudioToolsAudit.php

@@ -0,0 +1,650 @@
+<?php defined('BX_DOL') or defined('BX_DOL_INSTALL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaStudio UNA Studio
+ * @{
+ */
+
+define('BX_DOL_AUDIT_FAIL', 'fail');
+define('BX_DOL_AUDIT_WARN', 'warn');
+define('BX_DOL_AUDIT_UNDEF', 'undef');
+define('BX_DOL_AUDIT_OK', 'ok');
+
+class BxDolStudioToolsAudit extends BxDol
+{
+    protected $aType2ClassCSS;
+    protected $aType2Title;
+
+    protected $sMinPhpVer;
+    protected $aPhpSettings;
+    protected $iPhpErrorReporting;
+
+    protected $sMinMysqlVer;
+    protected $aMysqlOptimizationSettings;
+
+    protected $aOptimizationSettings;
+
+    protected $aRequiredApacheModules;
+
+    function __construct()
+    {
+        parent::__construct();
+
+        $this->aType2ClassCSS = array (
+            BX_DOL_AUDIT_FAIL => 'fail',
+            BX_DOL_AUDIT_WARN => 'warn',
+            BX_DOL_AUDIT_UNDEF => 'undef',
+            BX_DOL_AUDIT_OK => 'ok',
+        );
+
+        $this->aType2Title = array (
+            BX_DOL_AUDIT_FAIL => _t('_sys_audit_title_fail'),
+            BX_DOL_AUDIT_WARN => _t('_sys_audit_title_warn'),
+            BX_DOL_AUDIT_UNDEF => _t('_sys_audit_title_undef'),
+            BX_DOL_AUDIT_OK => _t('_sys_audit_title_ok'),
+        );
+
+        $this->sMinPhpVer = '5.6.0';
+        $this->aPhpSettings = array (
+            'allow_url_fopen' => array('op' => '=', 'val' => true, 'type' => 'bool'),
+            'allow_url_include' => array('op' => '=', 'val' => false, 'type' => 'bool'),
+            'magic_quotes_gpc' => array('op' => '=', 'val' => false, 'type' => 'bool', 'warn' => 1),
+            'memory_limit' => array('op' => '>=', 'val' => 128*1024*1024, 'type' => 'bytes', 'unlimited' => -1),
+            'post_max_size' => array('op' => '>=', 'val' => 2*1024*1024, 'type' => 'bytes', 'warn' => 1),
+            'upload_max_filesize' => array('op' => '>=', 'val' => 2*1024*1024, 'type' => 'bytes', 'warn' => 1),
+            'register_globals' => array('op' => '=', 'val' => false, 'type' => 'bool'),
+            'safe_mode' => array('op' => '=', 'val' => false, 'type' => 'bool'),
+            'short_open_tag' => array('op' => '=', 'val' => true, 'type' => 'bool'),
+            'disable_functions' => array('op' => 'without', 'val' => 'shell_exec,eval,assert,phpinfo,getenv,ini_set,mail,fsockopen,chmod,parse_ini_file,readfile,escapeshellcmd'),
+            'php module: curl' => array('op' => 'module', 'val' => 'curl'),
+            'php module: gd' => array('op' => 'module', 'val' => 'gd'),
+            'php module: mbstring' => array('op' => 'module', 'val' => 'mbstring'),
+            'php module: json' => array('op' => 'module', 'val' => 'json'),
+            'php module: fileinfo' => array('op' => 'module', 'val' => 'fileinfo'),
+            'php module: zip' => array('op' => 'module', 'val' => 'zip'),
+            'php module: openssl' => array('op' => 'module', 'val' => 'openssl'),
+            'php module: exif' => array('op' => 'module', 'val' => 'exif'),
+        );
+
+        $this->sMinMysqlVer = '5.5.3';
+        $this->aMysqlOptimizationSettings = array (
+            'key_buffer_size' => array('op' => '>=', 'val' => 128*1024, 'type' => 'bytes'),
+            'max_heap_table_size' => array('op' => '>=', 'val' => 16*1024*1024, 'type' => 'bytes'),
+            'tmp_table_size' => array('op' => '>=', 'val' => 16*1024*1024, 'type' => 'bytes'),
+            'thread_cache_size ' => array('op' => '>', 'val' => 0),
+        );
+
+        $this->aOptimizationSettings = array (
+            'DB cache' => array('enabled' => 'sys_db_cache_enable', 'cache_engine' => 'sys_db_cache_engine', 'check_accel' => true),
+            'Pages cache' => array('enabled' => 'sys_page_cache_enable', 'cache_engine' => 'sys_page_cache_engine', 'check_accel' => true),
+            'Page blocks cache' => array('enabled' => 'sys_pb_cache_enable', 'cache_engine' => 'sys_pb_cache_engine', 'check_accel' => true),
+            'Templates Cache' => array('enabled' => 'sys_template_cache_enable', 'cache_engine' => 'sys_template_cache_engine', 'check_accel' => true),
+            'CSS files cache' => array('enabled' => 'sys_template_cache_css_enable', 'cache_engine' => '', 'check_accel' => false),
+            'JS files cache' => array('enabled' => 'sys_template_cache_js_enable', 'cache_engine' => '', 'check_accel' => false),
+            'Compression for CSS/JS cache' => array('enabled' => 'sys_template_cache_compress_enable', 'cache_engine' => '', 'check_accel' => false),
+        );
+
+        $this->aRequiredApacheModules = array (
+            'rewrite_module' => 'mod_rewrite',
+        );
+
+        if (isset($_GET['action'])) {
+            $sOutput = null;
+            switch ($_GET['action']) {
+                case 'audit_send_test_email':
+                    $sOutput = $this->sendTestEmail();
+                    break;
+                case 'phpinfo':
+                    ob_start();
+                    phpinfo();
+                    $sOutput = ob_get_clean();
+                    break;
+                case 'phpinfo_popup':
+                    $sUrlSelf = bx_js_string($_SERVER['PHP_SELF'], BX_ESCAPE_STR_APOS);
+                    $sUrlSelf = bx_append_url_params($sUrlSelf, array('action' => 'phpinfo'));
+                    $sOutput = '<iframe width="640" height="480" src="' . $sUrlSelf . '"></iframe>';
+                    break;
+            }
+            if ($sOutput) {
+                header('Content-type: text/html; charset=utf-8');
+                echo $sOutput;
+                exit;
+            }
+        }
+    }
+
+    public function generate()
+    {
+        ob_start();
+
+        $this->setErrorReporting();
+
+        $this->generateStyles();
+        $this->generateJs();
+
+        $this->requirements();
+
+        if (!defined('BX_DOL_INSTALL'))
+            $this->siteSetup();
+
+        $this->optimization();
+
+        $this->manualCheck();
+
+        $this->restoreErrorReporting();
+
+        return ob_get_clean();
+    }
+
+    public function generateStyles($bReturn = false)
+    {
+    	ob_start();
+        ?>
+<style>
+    .ok {
+        color:green;
+    }
+    .fail {
+        color:red;
+    }
+    .warn {
+        color:orange;
+    }
+    .undef {
+        color:gray;
+    }
+</style>
+        <?php
+        $sStyles = ob_get_clean();
+
+        if($bReturn)
+			return $sStyles;
+
+		echo $sStyles;
+    }
+
+    public function generateJs()
+    {
+        $sUrlSelf = bx_js_string($_SERVER['PHP_SELF'], BX_ESCAPE_STR_APOS);
+        ?>
+        <script language="javascript">
+            function bx_sys_adm_audit_test_email()
+            {
+            	bx_prompt('<?php echo _t('_Email'); ?>', '<?php echo class_exists('BxDolDb') && BxDolDb::getInstance() ? BxDolDb::getInstance()->getParam('site_email') : ''; ?>', function(oPopup) {
+        			var sEmail = oPopup.getValue();
+                    if (null == sEmail || ('string' == (typeof sEmail) && !sEmail.length))
+                        return;
+
+                    $('#bx-sys-adm-audit-test-email').html('Sending...');
+                    $.post('<?php echo bx_append_url_params($sUrlSelf, array('action' => 'audit_send_test_email')); ?>&email=' + sEmail, function(data) {
+                        $('#bx-sys-adm-audit-test-email').html(data);
+                    });
+				});
+
+            	return false;
+            }
+
+            function bx_sys_adm_audit_phpinfo()
+            {
+                $(window).dolPopupAjax({url: '<?php echo bx_append_url_params($sUrlSelf, array('action' => 'phpinfo_popup')); ?>'});
+            }
+        </script>
+        <?php
+    }
+
+    /**
+     * Check minimal requirements
+     * @param $sType - BX_DOL_AUDIT_FAIL, BX_DOL_AUDIT_WARN, BX_DOL_AUDIT_UNDEF or BX_DOL_AUDIT_OK
+     * @param $sFunc - requirementsPHP, requirementsMySQL or requirementsWebServer
+     * @return array of FAIL, WARN, UNDEF or OK items, if array is empty then no specified items are found
+     */
+    public function checkRequirements($sType = BX_DOL_AUDIT_FAIL, $sFunc = 'requirementsPHP')
+    {
+        $this->setErrorReporting();
+
+        $aRet = array ();        
+        $aMessages = array ();  
+
+        $this->$sFunc(false, $aMessages);
+
+        foreach ($aMessages as $sName => $r)
+            if ($sType == $r['type'])
+                $aRet[] = "$sName = " . $this->format_output($r['params']['real_val'], isset($this->aPhpSettings[$sName]) ? $this->aPhpSettings[$sName] : '') . " - " . $this->getMsgHTML($sName, $r);        
+
+        $this->restoreErrorReporting();
+
+        return $aRet;
+    }
+
+    public function typeToTitle ($sType) 
+    {
+        return $this->aType2Title[$sType];
+    }
+
+    protected function requirements()
+    {
+        echo '<h1>' . _t('_sys_audit_header_requirements') . '</h1>';
+        $this->requirementsPHP();
+        if (!defined('BX_DOL_INSTALL'))
+            $this->requirementsMySQL();
+        $this->requirementsWebServer();
+        $this->requirementsOS();
+        $this->requirementsHardware();
+    }
+
+    protected function requirementsPHP($bEcho = true, &$aOutputMessages = null)
+    {
+        $sHttpCode = null; 
+        $s = bx_file_get_contents('http://php.net/releases/index.php?serialize=1', array(), 'get', array(), $sHttpCode, array(), 20, array(CURLOPT_IPRESOLVE => CURL_IPRESOLVE_V4));
+        $sLatestPhpVersion = $s && ($a = unserialize($s)) ? $a[5]['version'] : '';
+
+        if (version_compare(phpversion(), "5.4", ">=") == 1)
+            unset($this->aPhpSettings['short_open_tag']);
+
+        $aMessages = array ();
+
+        $sPhpVer = PHP_VERSION;
+        if (empty($sLatestPhpVersion))
+            $aVer = array('type' => BX_DOL_AUDIT_UNDEF, 'msg' => _t('_sys_audit_msg_value_checking_failed'), 'params' => array ('real_val' => $sPhpVer));
+        elseif (version_compare($sPhpVer, $this->sMinPhpVer, '<'))
+            $aVer = array('type' => BX_DOL_AUDIT_FAIL, 'msg' => _t('_sys_audit_msg_version_is_incompatible', $this->sMinPhpVer), 'params' => array ('real_val' => $sPhpVer));
+        elseif (version_compare($sPhpVer, '5.3.0', '>=') && version_compare($sPhpVer, '5.4.0', '<'))
+            $aVer = array('type' => BX_DOL_AUDIT_WARN, 'msg' => _t('_sys_audit_msg_version_is_outdated', $sLatestPhpVersion), 'params' => array ('real_val' => $sPhpVer));
+        else
+            $aVer = array('type' => BX_DOL_AUDIT_OK, 'params' => array ('real_val' => $sPhpVer));
+
+        $aMessages[_t('_sys_audit_version')] = $aVer;
+
+        foreach ($this->aPhpSettings as $sName => $r) {
+            $a = $this->checkPhpSetting($sName, $r);
+            if ($a['res'])
+                $aMessages[$sName] = array('type' => BX_DOL_AUDIT_OK, 'params' => $a);
+            elseif (isset($r['warn']) && $r['warn'])
+                $aMessages[$sName] = array('type' => BX_DOL_AUDIT_WARN, 'msg' => _t('_sys_audit_msg_should_be', $r['op'], $this->format_output($r['val'], $r)), 'params' => $a);
+            else
+                $aMessages[$sName] = array('type' => BX_DOL_AUDIT_FAIL, 'msg' => _t('_sys_audit_msg_must_be', $r['op'], $this->format_output($r['val'], $r)), 'params' => $a);
+        }
+
+        if (null !== $aOutputMessages)
+            $aOutputMessages = $aMessages;
+
+        if ($bEcho) {
+            $s = '';
+            foreach ($aMessages as $sName => $r) {
+                $s .= $this->getBlock($sName, $this->format_output($r['params']['real_val'], isset($this->aPhpSettings[$sName]) ? $this->aPhpSettings[$sName] : ''), $this->getMsgHTML($sName, $r));
+            }
+            echo $this->getSection('PHP', '', $s);
+        }
+    }
+
+    protected function requirementsMySQL($bEcho = true, &$aOutputMessages = null)
+    {
+        $sMysqlVer = BxDolDb::getInstance()->getServerInfo();
+        if (preg_match ('/^(\d+)\.(\d+)\.(\d+)/', $sMysqlVer, $m)) {
+            $sMysqlVer = "{$m[1]}.{$m[2]}.{$m[3]}";
+            if (version_compare($sMysqlVer, $this->sMinMysqlVer, '<'))
+                $aMessage = array('type' => BX_DOL_AUDIT_FAIL, 'msg' => _t('_sys_audit_msg_version_is_incompatible', $this->sMinMysqlVer));
+            else
+                $aMessage = array('type' => BX_DOL_AUDIT_OK);
+        } else {
+            $aMessage = array('type' => BX_DOL_AUDIT_UNDEF, 'msg' => _t('_sys_audit_msg_value_checking_failed'));
+        }
+
+        if (null !== $aOutputMessages)
+            $aOutputMessages['mysql'] = array($aMessage);
+
+        if ($bEcho) {
+            $s = $this->getBlock(_t('_sys_audit_version'), $sMysqlVer, $this->getMsgHTML(_t('_sys_audit_version'), $aMessage));
+            echo $this->getSection('MySQL', '', $s);
+        }
+    }
+
+    protected function requirementsWebServer($bEcho = true, &$aOutputMessages = null)
+    {
+        $aMessages = array();
+        foreach ($this->aRequiredApacheModules as $sName => $sNameCompiledName)
+            $aMessages[$sName] = $this->checkApacheModule($sName, $sNameCompiledName);
+
+        if (null !== $aOutputMessages)
+            $aOutputMessages = $aMessages;
+
+        if ($bEcho) {
+            $s = '';
+            foreach ($aMessages as $sName => $r) {
+                $s .= $this->getBlock($sName, '', $this->getBlock('', '', $this->getMsgHTML($sName, $r), false));
+            }
+            echo $this->getSection(_t('_sys_audit_section_webserver'), $_SERVER['SERVER_SOFTWARE'], $s);
+        }
+    }
+
+    protected function requirementsOS()
+    {
+        $s = $this->getBlock(php_uname());
+        echo $this->getSection(_t('_sys_audit_section_os'), '', $s);
+    }
+
+    protected function requirementsHardware()
+    {
+        $s = $this->getBlock(_t('_sys_audit_msg_hardware_requirements'));
+        echo $this->getSection(_t('_sys_audit_section_hardware'), '', $s);
+    }
+
+    protected function siteSetup()
+    {
+        $sFfmpegPath = defined('BX_SYSTEM_FFMPEG') ? BX_SYSTEM_FFMPEG : BX_DIRECTORY_PATH_PLUGINS . 'ffmpeg/ffmpeg.exe';
+
+        $sEmailToCkeckMailSending = class_exists('BxDolDb') && BxDolDb::getInstance() ? BxDolDb::getInstance()->getParam('site_email') : '';
+
+        $oUpgrader = new BxDolUpgrader();
+        if (!($sLatestVer = $oUpgrader->getLatestVersionNumber()))
+            $sLatestVer = 'undefined';
+
+        $sVer = bx_get_ver();
+        $aMessage = array('type' => BX_DOL_AUDIT_OK);
+        if (!version_compare($sVer, $sLatestVer, '>='))
+            $aMessage = array('type' => BX_DOL_AUDIT_WARN, 'msg' => _t('_sys_audit_msg_version_is_outdated', $sLatestVer));
+
+        $s = $this->getBlock(_t('_sys_audit_version_script'), $sVer, $this->getMsgHTML(_t('_sys_audit_version_script'), $aMessage));
+
+        $s .= $this->getBlock(_t('_sys_audit_permissions'), '', _t('_sys_audit_msg_permissions'));
+
+        $s .= $this->getBlock('ffmpeg', '', _t('_sys_audit_msg_ffmpeg', `{$sFfmpegPath} 2>&1`));
+
+        $s .= $this->getBlock(_t('_sys_audit_mail_sending'), '', _t('_sys_audit_msg_mail_sending'));
+
+        $s .= $this->getBlock(_t('_sys_audit_cron_jobs'), '', _t('_sys_audit_msg_cron_jobs', `crontab -l 2>&1`));
+
+        $iCronTime = getParam('sys_cron_time');
+        $s .= $this->getBlock(_t('_sys_audit_cron_jobs_exec_time'), '', !empty($iCronTime) ? bx_time_js($iCronTime, BX_FORMAT_DATE_TIME, true) : _t('_None'));
+
+        echo '<h1>' . _t('_sys_audit_header_site_setup') . '</h1>';
+        echo "<ul>$s</ul>";
+    }
+
+    protected function optimization()
+    {
+        echo '<h1>' . _t('_sys_audit_header_site_optimization') . '</h1>';
+
+        $this->optimizationPhp();
+
+        if (!defined('BX_DOL_INSTALL'))
+            $this->optimizationMySQL();
+
+        $this->optimizationWebServer();
+
+        if (!defined('BX_DOL_INSTALL'))
+            $this->optimizationScript();
+    }
+
+    protected function optimizationPhp()
+    {
+        $s = '';
+        $aMessage = array();
+
+        $sAccel = $this->getPhpAccelerator();
+        if (!$sAccel)
+            $aMessage = array('type' => BX_DOL_AUDIT_WARN, 'msg' => _t('_sys_audit_msg_php_accelerator_missing'));
+        else
+            $aMessage = array('type' => BX_DOL_AUDIT_OK);
+        $s .= $this->getBlock(_t('_sys_audit_php_accelerator'), $sAccel, $this->getMsgHTML(_t('_sys_audit_php_accelerator'), $aMessage));
+
+        $sSapi = php_sapi_name();
+        if (0 === strcasecmp('cgi', $sSapi))
+            $aMessage = array('type' => BX_DOL_AUDIT_WARN, 'msg' => _t('_sys_audit_msg_php_setup_inefficient'));
+        else
+            $aMessage = array('type' => BX_DOL_AUDIT_OK);
+        $s .= $this->getBlock(_t('_sys_audit_php_setup'), $sSapi, $this->getMsgHTML(_t('_sys_audit_php_setup'), $aMessage));
+
+        echo $this->getSection('PHP', '', $s);
+    }
+
+    protected function optimizationMySQL()
+    {
+        $s = '';
+        $oDb = BxDolDb::getInstance();
+
+        foreach ($this->aMysqlOptimizationSettings as $sName => $r) {
+            $a = $this->checkMysqlSetting($sName, $r, $oDb);
+            $aMessage = array('type' => BX_DOL_AUDIT_OK);
+            if (!$a['res'])
+                $aMessage = array('type' => BX_DOL_AUDIT_FAIL, 'msg' => _t('_sys_audit_msg_must_be', $r['op'], $this->format_output($r['val'], $r)));
+            $s .= $this->getBlock($sName, $this->format_output($a['real_val'], $r), $this->getMsgHTML($sName, $aMessage));
+        }
+
+        echo $this->getSection('MySQL', '', $s);
+    }
+
+    protected function optimizationWebServer()
+    {
+        $s = '';
+
+        $sName = 'expires_module';
+        $sApacheModuleChack = _t('_sys_audit_msg_optimization_apache_module', $sName, $this->checkApacheModule($sName));
+        $s .= $this->getBlock(_t('_sys_audit_userside_caching'), '', _t('_sys_audit_msg_userside_caching', $this->getUrlForGooglePageSpeed('LeverageBrowserCaching'), $sApacheModuleChack));
+
+        $sName = 'deflate_module';
+        $sApacheModuleChack = _t('_sys_audit_msg_optimization_apache_module', $sName, $this->checkApacheModule($sName));
+        $s .= $this->getBlock(_t('_sys_audit_serverside_compression'), '', _t('_sys_audit_msg_serverside_compression', $sApacheModuleChack));
+
+        echo $this->getSection(_t('_sys_audit_section_webserver'), '', $s);
+    }
+
+    protected function optimizationScript()
+    {
+        $s = '';
+        foreach ($this->aOptimizationSettings as $sName => $a) {
+
+            $sVal = ('always_on' == $a['enabled'] || getParam($a['enabled'])) ? 'On' : 'Off';
+            if ($a['cache_engine'])
+                $sVal .= _t('_sys_audit_msg_x_based_cache_engine', getParam($a['cache_engine']));
+
+            if ('always_on' != $a['enabled'] && !getParam($a['enabled']))
+                $aMessage = array('type' => BX_DOL_AUDIT_FAIL, 'msg' => _t('_sys_audit_msg_optimization_fail'));
+            elseif ($a['check_accel'] && !$this->getPhpAccelerator() && 'File' == getParam($a['cache_engine']))
+                $aMessage = array('type' => BX_DOL_AUDIT_WARN, 'msg' => _t('_sys_audit_msg_optimization_warn'));
+            else
+                $aMessage = array('type' => BX_DOL_AUDIT_OK);
+
+            $s .= $this->getBlock($sName, $sVal, $this->getMsgHTML($sName, $aMessage));
+        }
+
+        echo $this->getSection('UNA', '', $s);
+    }
+
+    protected function manualCheck()
+    {
+        echo '<a name="manual_audit"></a>';
+        echo '<h1>' . _t('_sys_audit_header_manual_audit') . '</h1>';
+        echo _t('_sys_audit_msg_manual_audit');
+    }
+
+    protected function checkPhpSetting($sName, $a)
+    {
+        $mixedVal = ini_get($sName);
+        $mixedVal = $this->format_input ($mixedVal, $a);
+
+        switch ($a['op']) {
+            case 'without':
+                $aFuncsDisabled = explode(',', $mixedVal);
+                $aFuncsMustBeEnabled = explode(',', $a['val']);
+                $a = array_intersect($aFuncsDisabled, $aFuncsMustBeEnabled);
+                $bResult = !$a;
+                break;
+            case 'module':
+                $bResult = extension_loaded($a['val']);
+                $mixedVal = $bResult ? $a['val'] : '';
+                break;
+            case '>':
+                $bResult = (isset($a['unlimited']) && $mixedVal == $a['unlimited']) ? true : ($mixedVal > $a['val']);
+                break;
+            case '>=':
+                $bResult = (isset($a['unlimited']) && $mixedVal == $a['unlimited']) ? true : ($mixedVal >= $a['val']);
+                break;
+            case '=':
+            default:
+                $bResult = ($mixedVal == $a['val']);
+        }
+        return array ('res' => $bResult, 'real_val' => $mixedVal);
+    }
+
+    protected function checkMysqlSetting($sName, $a, $oDb)
+    {
+        $mixedVal = $oDb->getOption($sName);
+        $mixedVal = $this->format_input ($mixedVal, $a);
+
+        switch ($a['op']) {
+            case '>':
+                $bResult = ($mixedVal > $a['val']);
+                break;
+            case '>=':
+                $bResult = ($mixedVal >= $a['val']);
+                break;
+            case 'strcasecmp':
+                $bResult = 0 === strcasecmp($mixedVal, $a['val']);
+                break;
+            case '=':
+            default:
+                $bResult = ($mixedVal == $a['val']);
+        }
+        return array ('res' => $bResult, 'real_val' => $mixedVal);
+    }
+
+    protected function format_output ($mixedVal, $a)
+    {
+        if (isset($a['type']) && 'bool' == $a['type'])
+            return $mixedVal ? 'On' : 'Off';
+        else
+            return $mixedVal;
+    }
+
+    protected function format_input ($mixedVal, $a)
+    {
+        if (isset($a['type']) && 'bytes' == $a['type'])
+            return $this->format_bytes ($mixedVal);
+        else
+            return $mixedVal;
+    }
+
+    protected function format_bytes($val)
+    {
+        return return_bytes($val);
+    }
+
+    protected function checkApacheModule ($sModule, $sNameCompiledName = '')
+    {
+        $a = array (
+            'deflate_module' => 'mod_deflate',
+            'expires_module' => 'mod_expires',
+        );
+        if (!$sNameCompiledName && isset($a[$sModule]))
+            $sNameCompiledName = $a[$sModule];
+
+        if (function_exists('apache_get_modules')) {
+
+            $aModules = apache_get_modules();
+            $ret = in_array($sNameCompiledName, $aModules);
+
+        } else {
+
+            $sApachectlPath = trim(`which apachectl`);
+            if (!$sApachectlPath)
+                $sApachectlPath = trim(`which apache2ctl`);
+            if (!$sApachectlPath)
+                $sApachectlPath = trim(`which /usr/local/apache/bin/apachectl`);
+            if (!$sApachectlPath)
+                $sApachectlPath = trim(`which /usr/local/apache/bin/apache2ctl`);
+            if (!$sApachectlPath) {
+                return array('type' => BX_DOL_AUDIT_UNDEF, 'msg' => _t('_sys_audit_msg_apache_module_undef', $sModule));
+            }
+            $ret = (boolean)`$sApachectlPath -M 2>&1 | grep $sModule`;
+            if (!$ret)
+                $ret = (boolean)`$sApachectlPath -l 2>&1 | grep $sNameCompiledName`;
+        }
+
+        $aMessage = array('type' => BX_DOL_AUDIT_OK);
+        if (!$ret)
+            $aMessage = array('type' => BX_DOL_AUDIT_FAIL, 'msg' => _t('_sys_audit_msg_apache_module_fail', $sModule));
+        
+        return $aMessage;
+    }
+
+    protected function getPhpAccelerator ()
+    {
+        $aAccelerators = array (
+            'APC' => array('op' => 'module', 'val' => 'apc'),
+            'XCache' => array('op' => 'module', 'val' => 'xcache'),
+        	'ZendOPcache' => array('op' => 'module', 'val' => 'Zend OPcache'),
+        );
+        foreach ($aAccelerators as $sName => $r) {
+            $a = $this->checkPhpSetting($sName, $r);
+            if ($a['res'])
+                return $sName;
+        }
+        return false;
+    }
+
+    protected function getUrlForGooglePageSpeed ($sRule)
+    {
+        $sUrl = urlencode(BX_DOL_URL_ROOT);
+        return 'http://pagespeed.googlelabs.com/#url=' . $sUrl . '&mobile=false&rule=' . $sRule;
+    }
+
+    protected function sendTestEmail ()
+    {
+        $sEmailToCkeckMailSending = isset($_GET['email']) ? $_GET['email'] : getParam('site_email');
+        $mixedRet = sendMail($sEmailToCkeckMailSending, 'Audit Test Email', 'Sample text for testing<br /><u><b>Sample text for testing</b></u>', '', array(), BX_EMAIL_SYSTEM);
+        if (!$mixedRet) {
+            $aMessage = array('type' => BX_DOL_AUDIT_FAIL, 'msg' => _t('_sys_audit_msg_mail_send_failed'));
+            return $this->getBlock('', '', $this->getMsgHTML(_t('_sys_audit_mail_sending'), $aMessage), false);
+        } else {
+            return _t('_sys_audit_msg_mail_sent', $sEmailToCkeckMailSending);
+        }
+    }
+
+    protected function setErrorReporting ()
+    {
+        if (version_compare(phpversion(), "5.3.0", ">=") == 1)
+            $this->iPhpErrorReporting = error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING & ~E_DEPRECATED & ~E_STRICT);
+        else
+            $this->iPhpErrorReporting = error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
+    }
+
+    protected function restoreErrorReporting ()
+    {
+        error_reporting($this->iPhpErrorReporting);
+    }
+
+    protected function getSection($sTitle, $sTitleAddon, $sContent)
+    {
+        $s = '<b>' . $sTitle . '</b>: ' . $sTitleAddon;
+        $s .= '<ul>';
+        $s .= $sContent;
+        $s .= '</ul>';
+        return $s;
+    }
+
+    protected function getBlock($sName, $sValue = '', $sMsg = '', $bWrapAsListItem = true)
+    {
+        $s = $bWrapAsListItem ? '<li>'  : '';
+        if ($sName !== '')
+            $s .= "$sName ";
+        if ($sValue !== '')
+            $s .= " = $sValue ";
+        if ($sMsg)
+                $s .= ($s ? " - " : '') . $sMsg;
+        return $s . ($bWrapAsListItem ? '</li>' : '') . "\n";
+    }
+
+    protected function getMsgHTML($sName, $a)
+    {
+        $s = '';
+        $s .= '<b class="' . $this->aType2ClassCSS[$a['type']]. '">' . $this->aType2Title[$a['type']]. '</b> ';
+        if (isset($a['msg']))
+            $s .= '(' . $a['msg'] . ')';
+        return $s;
+    }
+}
+
+/** @} */

+ 118 - 0
upgrade/files/11.0.0-11.0.1/files/studio/template/css/forms.css

@@ -0,0 +1,118 @@
+@import url(../../../template/css/forms.css);
+
+/**
+ * Overwrite default styles
+ */
+.bx-form-value .bx-form-input-slider,
+.bx-form-value .bx-form-input-doublerange,
+.bx-form-value .bx-form-input-select_multiple,
+.bx-form-value .bx-form-input-select,
+.bx-form-value .bx-form-input-radio_set,
+.bx-form-value .bx-form-input-checkbox_set,
+.bx-form-value .bx-form-input-number,
+.bx-form-value .bx-form-input-time,
+.bx-form-value .bx-form-input-datepicker,
+.bx-form-value .bx-form-input-datetime,
+.bx-form-value .bx-form-input-textarea,
+.bx-form-value .bx-form-input-wrapper-password,
+.bx-form-value .bx-form-input-text {
+    height: 36px;
+    height: 2.4rem;
+    box-sizing: border-box;
+
+    border-color: rgba(136, 143, 153, 1);
+    border-width: 1px;
+
+    background-color: rgba(255, 255, 255, 1);
+    
+    font-size: 1.075rem;
+}
+
+.bx-form-value .bx-form-input-select_multiple,
+.bx-form-value .bx-form-input-radio_set,
+.bx-form-value .bx-form-input-checkbox_set {
+    height:auto;
+}
+
+.bx-form-value .bx-form-input-textarea {
+    height: 150px;
+    height: 10rem;
+    padding: 0.5rem;
+
+    line-height:normal;
+} 
+
+.bx-form-value .bx-form-input-slider:focus,
+.bx-form-value .bx-form-input-doublerange:focus,
+.bx-form-value .bx-form-input-number:focus,
+.bx-form-value .bx-form-input-time:focus,
+.bx-form-value .bx-form-input-datepicker:focus,
+.bx-form-value .bx-form-input-datetime:focus,
+.bx-form-value .bx-form-input-textarea:focus,
+.bx-form-value .bx-form-input-text:focus,
+.bx-form-value .bx-form-input-wrapper-password.bx-form-input-focus {
+    border-color: rgba(136, 143, 153, 1);
+    background-color: rgba(255, 255, 255, 1);
+}
+
+.bx-form-value .bx-switcher-cont {
+    border-color: rgba(136, 143, 153, 1);
+    border-width: 1px;
+
+    background-color: rgba(255, 255, 255, 1);
+}
+
+.bx-form-value .bx-form-input-select_multiple,
+.bx-form-value .bx-form-input-radio_set,
+.bx-form-value .bx-form-input-checkbox_set {
+    height:auto;
+}
+
+/**
+ * General
+ */
+select.bx-form-field-type option {
+	padding-left: 25px;
+	background-repeat: no-repeat;
+	background-position: 5px 0px;
+}
+
+
+/**
+ * Image uploader with with manager
+ */
+div.bx-form-input-preview {}
+div.bx-form-input-preview img {
+    max-width: 768px; 
+    max-height: 256px;
+}
+div.bx-form-input-preview-empty {}
+
+
+/**
+ * Translatable fields.
+ */
+div.bx-form-input-wrapper-textarea {
+	position: relative;
+}
+
+div.bx-form-input-translator {
+	position: absolute;
+	top: -22px;
+	right: 0px;
+}
+
+div.bx-form-input-language,
+div.bx-form-input-language-current{
+	position: relative;
+	height: 17px;
+	line-height: 17px;
+}
+
+div.bx-form-input-language {
+    white-space: nowrap
+}
+
+div.bx-form-input-language-current {
+	font-weight: bold;
+}

+ 1823 - 0
upgrade/files/11.0.0-11.0.1/files/studio/template/scripts/BxBaseStudioBuilderPage.php

@@ -0,0 +1,1823 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaView UNA Studio Representation classes
+ * @ingroup     UnaStudio
+ * @{
+ */
+
+class BxBaseStudioBuilderPage extends BxDolStudioBuilderPage
+{
+    protected $sParamsDivider = '#';
+    protected $sSelectKeyPrefix = 'id-';
+
+    protected $sActionPageCreate = 'page_create';
+    protected $sActionPageDelete = 'page_delete';
+    protected $sActionPageEdit = 'page_edit';
+    protected $sActionBlockCreate = 'block_create';
+    protected $sActionBlockEdit = 'block_edit';
+
+    protected $sStorage;
+    protected $sTranscoder;
+    protected $aUploaders; 
+    
+    protected $sTranscoderCover;
+    protected $aUploadersCover;
+
+    protected $sBaseUrl;
+    protected $sTypeUrl;
+    protected $sPageUrl;
+
+    protected $aHtmlIds = array(
+        'add_popup_id' => 'adm-bp-add-popup',
+        'edit_popup_id' => 'adm-bp-edit-popup',
+        'uri_field_id' => 'adm-bp-field-uri',
+    	'url_field_id' => 'adm-bp-field-url',
+        'settings_group_id' => 'adm-bp-settings-group-',
+        'settings_groups_id' => 'adm-bp-settings-groups',
+        'create_block_popup_id' => 'adm-bp-create-block-popup',
+        'edit_block_popup_id' => 'adm-bp-edit-block-popup',
+    	'edit_block_editor_id' => 'adm-bp-edit-block-editor',
+        'block_id' => 'adm-bpb-',
+        'block_list_id' => 'adm-bpl-',
+        'block_lists_id' => 'adm-bp-block-lists',
+        'layout_id' => 'adm-bpl-',
+    );
+
+	protected $aPageSettings = array(
+		array('name' => 'options', 'title' => '_adm_bp_mi_page_options', 'active' => 1),
+		array('name' => 'cover', 'title' => '_adm_bp_mi_page_cover', 'active' => 0),
+        array('name' => 'layout', 'title' => '_adm_bp_mi_page_layout', 'active' => 0),
+        array('name' => 'visibility', 'title' => '_adm_bp_mi_page_visibility', 'active' => 0),
+        array('name' => 'cache', 'title' => '_adm_bp_mi_page_cache', 'active' => 0),
+        array('name' => 'seo', 'title' => '_adm_bp_mi_page_seo', 'active' => 0)
+	);
+
+    function __construct($sType = '', $sPage = '')
+    {
+        parent::__construct($sType, $sPage);
+
+        $this->sStorage = BX_DOL_STORAGE_OBJ_IMAGES;
+		$this->sTranscoder = 'sys_builder_page_preview';
+		$this->aUploaders = array('sys_builder_page_simple', 'sys_builder_page_html5');
+
+		$this->sTranscoderCover = 'sys_cover_preview';
+		$this->aUploadersCover = array('sys_std_crop_cover');
+
+        $this->sBaseUrl = BX_DOL_URL_STUDIO . 'builder_page.php';
+        $this->sTypeUrl = $this->sBaseUrl . '?type=%s';
+        $this->sPageUrl = $this->sTypeUrl . '&page=%s';
+    }
+
+    function getPageCss()
+    {
+    	$oTemplate = BxDolStudioTemplate::getInstance();
+    	$aUploaders = array_merge($this->aUploaders, $this->aUploadersCover);
+		foreach($aUploaders as $sUploader) {
+			$oUploader = BxDolUploader::getObjectInstance($sUploader, $this->sStorage, '', $oTemplate);
+			if($oUploader)
+				$oUploader->addCssJs();
+		}
+
+        return array_merge(parent::getPageCss(), array(
+        	BX_DIRECTORY_PATH_PLUGINS_PUBLIC . 'codemirror/|codemirror.css',
+        	'page_layouts.css', 
+        	'builder_page.css'
+        ));
+    }
+
+    function getPageJs()
+    {
+        return array_merge(parent::getPageJs(), array(
+        	'codemirror/codemirror.min.js',
+            'jquery-ui/jquery.ui.core.min.js',
+            'jquery-ui/jquery.ui.widget.min.js',
+            'jquery-ui/jquery.ui.mouse.min.js',
+            'jquery-ui/jquery.ui.sortable.min.js',
+            'jquery.ui.touch-punch.min.js',
+            'jquery.easing.js',
+            'jquery.form.min.js',
+            'functions.js',
+            'builder_page.js'
+        ));
+    }
+
+    function getPageJsObject()
+    {
+        return 'oBxDolStudioBuilderPage';
+    }
+
+    function getPageMenu($aMenu = array(), $aMarkers = array())
+    {
+        $aMenuItems = array(
+            BX_DOL_STUDIO_MODULE_SYSTEM => array(
+                'name' => BX_DOL_STUDIO_MODULE_SYSTEM,
+                'icon' => 'cog',
+            ),
+            BX_DOL_STUDIO_MODULE_CUSTOM => array(
+                'name' => BX_DOL_STUDIO_MODULE_CUSTOM,
+                'icon' => 'wrench',
+                'title' => '_adm_bp_cpt_type_' . BX_DOL_STUDIO_MODULE_CUSTOM,
+            )
+        );
+
+        $aModulesDb = BxDolModuleQuery::getInstance()->getModulesBy(array('type' => 'type', 'value' => array(BX_DOL_MODULE_TYPE_MODULE, BX_DOL_MODULE_TYPE_TEMPLATE)));
+        foreach($aModulesDb as $aModuleDb) {
+        	$sName = $aModuleDb['name'];
+
+            if(!empty($aMenuItems[$sName]))
+                $aMenuItems[$sName] = array_merge($aMenuItems[$sName], $aModuleDb);
+            else
+                $aMenuItems[$sName] = $aModuleDb;
+
+            $aMenuItems[$sName]['title'] = BxDolStudioUtils::getModuleTitle($sName);
+
+			if(empty($aMenuItems[$sName]['icon']))
+				$aMenuItems[$sName]['icon'] = BxDolStudioUtils::getModuleIcon($aModuleDb, 'menu', false); 
+        }
+
+        $aMenu = array();
+        foreach($aMenuItems as $aMenuItem)
+            $aMenu[] = array(
+                'name' => $aMenuItem['name'],
+                'icon' => $aMenuItem['icon'],
+                'link' =>  sprintf($this->sTypeUrl, $aMenuItem['name']),
+                'title' => $aMenuItem['title'],
+                'selected' => $aMenuItem['name'] == $this->sType
+            );
+
+        return parent::getPageMenu($aMenu);
+    }
+
+    function getPageCode($bHidden = false)
+    {
+        $oPage = BxTemplPage::getObjectInstance($this->sPage);
+
+        $oTemplate = BxDolStudioTemplate::getInstance();
+        $sJsObject = $this->getPageJsObject();
+        $sTxtEmpty = _t('_sys_txt_empty');
+
+        $sContent = "";
+        if(($bPage = $this->sPage != '') === true) {
+            /**
+             * Reset and deactivate blocks which cannot be seen, 
+             * because of unsuitable cell number to currently selected layout.
+             */
+            $this->oDb->resetBlocksByPage($this->aPageRebuild['object'], $this->aPageRebuild['layout_cells_number'], true);
+
+            $aTmplVars = array(
+                'page_id' => 'bx-page-' . $this->aPageRebuild['uri']
+            );
+            for($i = 1; $i <= $this->aPageRebuild['layout_cells_number']; $i++) {
+                $aBlocks = array();
+                $this->oDb->getBlocks(array('type' => 'by_object_cell', 'object' => $this->aPageRebuild['object'], 'cell' => $i), $aBlocks, false);
+
+                $aTmplVarsCell = array('id' => $i, 'bx_repeat:blocks' => array());
+                foreach($aBlocks as $aBlock) {
+                	$sTitle = !empty($aBlock['title_system']) ? _t($aBlock['title_system']) : $oPage->getBlockTitle($aBlock);
+                	list($sIcon, $sIconUrl) = $this->getBlockIcon($aBlock);
+
+                    $aTmplVarsCell['bx_repeat:blocks'][] = array(
+                        'html_id' => $this->aHtmlIds['block_id'] . $aBlock['id'],
+                        'bx_if:is_inactive' => array(
+                            'condition' => (int)$aBlock['active'] == 0,
+                            'content' => array()
+                        ),
+                        'bx_if:show_link' => array(
+                            'condition' => true,
+                            'content' => array(
+                                'onclick' => $sJsObject . ".performAction('block_edit', {id: " . $aBlock['id'] . "})",
+                                'title' => !empty($sTitle) ? $sTitle : $sTxtEmpty,
+                            )
+                        ),
+                        'bx_if:show_text' => array(
+                            'condition' => false,
+                            'content' => array()
+                        ),
+                        'bx_if:image' => array (
+			                'condition' => (bool)$sIconUrl,
+			                'content' => array('icon_url' => $sIconUrl),
+			            ),
+						'bx_if:icon' => array (
+			                'condition' => (bool)$sIcon,
+			                'content' => array('icon' => $sIcon),
+			            ),
+                        'module' => $this->getModuleTitle($aBlock['module']),
+                        'visible_for' => _t('_adm_bp_txt_visible_for', BxDolStudioUtils::getVisibilityTitle($aBlock['visible_for_levels'])),
+                        'bx_if:show_checkbox' => array(
+                            'condition' => false,
+                            'content' => array()
+                        ),
+                        'bx_if:show_drag_handle' => array(
+                            'condition' => true,
+                            'content' => array()
+                        )
+                    );
+                }
+
+                $aTmplVars['cell_' . $i] = $oTemplate->parseHtmlByName('bp_cell.html', $aTmplVarsCell);
+            }
+
+            $sContent = $oTemplate->parseHtmlByName($this->aPageRebuild['layout_template'], $aTmplVars);
+        }
+
+        $aLanguages = BxDolStudioLanguagesUtils::getInstance()->getLanguages();
+
+        $aTmplVars = array(
+            'js_object' => $sJsObject,
+            'action_url' => $this->sBaseUrl,
+            'page_url' => sprintf($this->sPageUrl, '{0}', '{1}'),
+            'type' => $this->sType,
+            'page' => $this->sPage,
+            'html_ids' => json_encode($this->aHtmlIds),
+            'languahes' => json_encode($aLanguages),
+            'content' => $this->getBlockCode(array(
+				'items' => $sContent
+			))
+        );
+
+        $oTemplate->addJsTranslation(array(
+        	'_adm_bp_wrn_page_delete',
+        	'_adm_bp_wrn_page_block_delete'
+        ));
+        return $oTemplate->parseHtmlByName('builder_page.html', $aTmplVars);
+    }
+
+    function getBlockPanelTop($aBlock)
+    {
+        return parent::getBlockPanelTop(
+            array('panel_top' => BxDolStudioTemplate::getInstance()->parseHtmlByName('bp_block_panel_top.html', $this->_getTmplVarsBlockPanelTop()))
+        );
+    }
+
+    protected function actionPageCreate()
+    {
+        $sJsObject = $this->getPageJsObject();
+        $oTemplate = BxDolStudioTemplate::getInstance();
+
+        $sModule = BX_DOL_STUDIO_MODULE_CUSTOM;
+
+        $aForm = array(
+            'form_attrs' => array(
+                'id' => 'adm-bp-page-create',
+                'action' => sprintf($this->sPageUrl, $this->sType, $this->sPage) . '&bp_action=' . $this->sActionPageCreate,
+                'method' => 'post'
+            ),
+            'params' => array (
+                'db' => array(
+                    'table' => 'sys_objects_page',
+                    'key' => 'id',
+                    'uri' => '',
+                    'uri_title' => '',
+                    'submit_name' => 'do_submit'
+                ),
+            ),
+            'inputs' => array (
+                'module' => array(
+                    'type' => 'hidden',
+                    'name' => 'module',
+                    'value' => $sModule,
+                    'db' => array (
+                        'pass' => 'Xss',
+                    ),
+                ),
+                'deletable' => array(
+                    'type' => 'hidden',
+                    'name' => 'deletable',
+                    'value' => 1,
+                    'db' => array (
+                        'pass' => 'Int',
+                    ),
+                ),
+                'settings' => array(
+                    'type' => 'custom',
+                    'name' => 'settings',
+                    'content' => $oTemplate->parseHtmlByName('bp_edit_page_form.html', $this->_getTmplVarsPageSettings()),
+                ),
+            )
+        );
+
+        $oForm = new BxTemplStudioFormView($aForm);
+        $oForm->initChecker();
+
+        if($oForm->isSubmittedAndValid()) {
+            unset($oForm->aInputs['settings']);
+            foreach($this->aPageSettings as $aSetting)
+                $oForm->aInputs = array_merge($oForm->aInputs, $this->{'getSettings' . $this->getClassName($aSetting['name']) . 'Fields'}());
+
+            $sLanguage = BxDolStudioLanguagesUtils::getInstance()->getCurrentLangName(false);
+
+            $sObject = BxDolForm::getSubmittedValue('title-' . $sLanguage, $aForm['form_attrs']['method']);
+            $sObject = uriGenerate($sObject, 'sys_objects_page', 'object', 'object');
+
+            $sUri = $oForm->getCleanValue('uri');
+            
+            $aPage = array();
+            $this->oDb->getPages(array('type' => 'by_uri', 'value' => $sUri), $aPage, false);
+            if(!empty($aPage) && is_array($aPage)) 
+            	return array('msg' => _t('_adm_bp_err_page_uri'));
+
+            $iVisibleFor = BxDolStudioUtils::getVisibilityValue($oForm->getCleanValue('visible_for'), $oForm->getCleanValue('visible_for_levels'));
+            BxDolForm::setSubmittedValue('visible_for_levels', $iVisibleFor, $aForm['form_attrs']['method']);
+            unset($oForm->aInputs['visible_for']);
+
+            $iId = (int)$oForm->insert(array('author' => bx_get_logged_profile_id(), 'added' => time(), 'object' => $sObject, 'url' => $this->sPageBaseUrl . $sUri));
+            if($iId != 0)
+                return array('eval' => $sJsObject . '.onCreatePage(\'' . $sModule . '\', \'' . $sObject . '\')');
+            else
+                return array('msg' => _t('_adm_bp_err_page_create'));
+        }
+
+        $sContent = BxTemplStudioFunctions::getInstance()->popupBox($this->aHtmlIds['add_popup_id'], _t('_adm_bp_txt_create_popup'), $oTemplate->parseHtmlByName('bp_add_page.html', array(
+            'js_object' => $sJsObject,
+            'form_id' => $aForm['form_attrs']['id'],
+            'form' => $oForm->getCode(true)
+        )));
+
+        return array('popup' => $sContent);
+    }
+
+    protected function actionPageEdit()
+    {
+        $sJsObject = $this->getPageJsObject();
+        $oTemplate = BxDolStudioTemplate::getInstance();
+
+        $aForm = array(
+            'form_attrs' => array(
+                'id' => 'adm-bp-page-edit',
+                'action' => sprintf($this->sPageUrl, $this->sType, $this->sPage) . '&bp_action=' . $this->sActionPageEdit,
+                'method' => 'post'
+            ),
+            'params' => array (
+                'db' => array(
+                    'table' => 'sys_objects_page',
+                    'key' => 'id',
+                    'uri' => '',
+                    'uri_title' => '',
+                    'submit_name' => 'do_submit'
+                ),
+            ),
+            'inputs' => array (
+                'settings' => array(
+                    'type' => 'custom',
+                    'name' => 'settings',
+                    'content' => $oTemplate->parseHtmlByName('bp_edit_page_form.html', $this->_getTmplVarsPageSettings($this->aPageRebuild, false)),
+                ),
+            )
+        );
+
+        $oForm = new BxTemplStudioFormView($aForm);
+        $oForm->initChecker();
+
+        if($oForm->isSubmittedAndValid()) {
+            unset($oForm->aInputs['settings']);
+            foreach($this->aPageSettings as $aSetting)
+                $oForm->aInputs = array_merge($oForm->aInputs, $this->{'getSettings' . $this->getClassName($aSetting['name']) . 'Fields'}($this->aPageRebuild, false));
+
+            $mixedVisibleFor = $oForm->getCleanValue('visible_for');
+            $mixedVisibleForLevels = $oForm->getCleanValue('visible_for_levels');
+            if($mixedVisibleFor !== false && $mixedVisibleForLevels !== false) {
+                $iVisibleFor = BxDolStudioUtils::getVisibilityValue($mixedVisibleFor, $mixedVisibleForLevels);
+                BxDolForm::setSubmittedValue('visible_for_levels', $iVisibleFor, $aForm['form_attrs']['method']);
+                unset($oForm->aInputs['visible_for']);
+            }
+
+            if($oForm->update($this->aPageRebuild['id'])) {
+                $iLevelId = $oForm->getCleanValue('layout_id');
+                if(!empty($iLevelId) && $iLevelId != $this->aPageRebuild['layout_id']) {
+                    $aLayoutOld = array();
+                    $this->oDb->getLayouts(array('type' => 'by_id', 'value' => $this->aPageRebuild['layout_id']), $aLayoutOld, false);
+
+                    $aLayoutNew = array();
+                    $this->oDb->getLayouts(array('type' => 'by_id', 'value' => $iLevelId), $aLayoutNew, false);
+
+                    if($aLayoutOld['cells_number'] > $aLayoutNew['cells_number'] && $this->oDb->resetBlocksByPage($this->sPage, $aLayoutNew['cells_number']) === false)
+                        return array('msg' => _t('_adm_bp_err_save'));
+
+                    return array('eval' => $sJsObject . '.onSaveSettingsLayout()');
+                }
+
+                return array();
+            } 
+            else
+                return array('msg' => _t('_adm_bp_err_save'));
+        }
+
+        $sContent = BxTemplStudioFunctions::getInstance()->popupBox($this->aHtmlIds['edit_popup_id'], _t('_adm_bp_txt_settings_popup'), $oTemplate->parseHtmlByName('bp_edit_page.html', array(
+            'js_object' => $sJsObject,
+            'form_id' => $aForm['form_attrs']['id'],
+            'form' => $oForm->getCode(true)
+        )));
+
+        return array('popup' => $sContent);
+    }
+
+    protected function actionPageDelete()
+    {
+        if(empty($this->sPage) || empty($this->aPageRebuild) || !is_array($this->aPageRebuild))
+            return array('msg' => _t('_adm_bp_err_page_delete'));
+
+        $oLangauge = BxDolStudioLanguagesUtils::getInstance();
+        $oStorage = BxDolStorage::getObjectInstance(BX_DOL_STORAGE_OBJ_IMAGES);
+
+        $aBlocks = array();
+        $this->oDb->getBlocks(array('type' => 'by_object', 'value' => $this->sPage), $aBlocks, false);
+        if(is_array($aBlocks) && !empty($aBlocks)) {
+            foreach($aBlocks as $aBlock)
+                $this->onBlockDelete($aBlock);
+
+            $this->oDb->deleteBlocks(array('type' => 'by_object', 'value' => $this->sPage));
+        }
+
+        if($this->oDb->deletePages(array('type' => 'by_object', 'value' => $this->sPage))) {
+            $oLangauge->deleteLanguageString($this->aPageRebuild['title_system']);
+            $oLangauge->deleteLanguageString($this->aPageRebuild['title']);
+            return array('eval' => 'window.parent.location.href = "' . sprintf($this->sTypeUrl, $this->sType) . '";');
+        }
+
+        return array('msg' => _t('_adm_bp_err_page_delete'));
+    }
+
+    protected function actionBlockList()
+    {
+        $sModule = BX_DOL_STUDIO_BP_TYPE_DEFAULT;
+        if(bx_get('bp_module') !== false)
+            $sModule = bx_process_input(bx_get('bp_module'));
+
+        return array(
+            'content' => $this->getBlockList($sModule)
+        );
+    }
+
+    protected function actionBlockCreate()
+    {
+        $sJsObject = $this->getPageJsObject();
+        $oTemplate = BxDolStudioTemplate::getInstance();
+
+        $sSelected = BX_DOL_STUDIO_BP_SKELETONS;
+
+        $aForm = array(
+            'form_attrs' => array(
+                'id' => 'adm-bp-block-create',
+                'action' => sprintf($this->sPageUrl, $this->sType, $this->sPage) .  '&bp_action=' . $this->sActionBlockCreate,
+                'method' => 'post'
+            ),
+            'params' => array (
+                'db' => array(
+                    'table' => 'sys_pages_blocks',
+                    'key' => 'id',
+                    'uri' => '',
+                    'uri_title' => '',
+                    'submit_name' => 'do_submit'
+                ),
+            ),
+            'inputs' => array (
+                'blocks' => array(
+                    'type' => 'custom',
+                    'name' => 'blocks',
+                    'content' => '',
+                    'db' => array (
+                        'pass' => 'Int',
+                    ),
+                ),
+            )
+        );
+
+        $aMenu = array(
+            BX_DOL_STUDIO_BP_SKELETONS => array(
+                'name' => BX_DOL_STUDIO_BP_SKELETONS,
+                'icon' => 'qrcode',
+                'title' => '_sys_block_types_skeletons',
+                'selected' => $sSelected == BX_DOL_STUDIO_BP_SKELETONS,
+            ),
+            BX_DOL_STUDIO_MODULE_SYSTEM => array(
+                'name' => BX_DOL_STUDIO_MODULE_SYSTEM,
+                'icon' => 'cog',
+                'title' => '_sys_block_types_system',
+                'selected' => $sSelected == BX_DOL_STUDIO_MODULE_SYSTEM,
+            ),
+            BX_DOL_STUDIO_MODULE_CUSTOM => array(
+                'name' => BX_DOL_STUDIO_MODULE_CUSTOM,
+                'icon' => 'wrench',
+                'title' => '_sys_block_types_custom',
+                'selected' => $sSelected == BX_DOL_STUDIO_MODULE_CUSTOM,
+            )
+        );
+
+        $aModules = BxDolModuleQuery::getInstance()->getModulesBy(array('type' => 'modules'));
+        $aModulesWithBlocks = $this->oDb->getModulesWithCopyableBlocks();
+        foreach($aModules as $aModule) {
+        	$sName = $aModule['name'];
+        	if(!in_array($sName, $aModulesWithBlocks))
+        		continue;
+
+            if(!empty($aMenu[$sName]))
+                $aMenu[$sName] = array_merge($aMenu[$sName], $aModule);
+            else
+                $aMenu[$sName] = $aModule;
+
+			if(empty($aMenu[$sName]['icon']))
+				$aMenu[$sName]['icon'] = BxDolStudioUtils::getModuleIcon($aModule, 'menu', false); 
+        }
+
+        foreach($aMenu as $sKey => $aItem)
+            $aMenu[$sKey]['onclick'] =  $sJsObject . '.onChangeModule(\'' . $aItem['name'] . '\', this);';
+
+        $oMenu = new BxTemplStudioMenu(array('template' => 'menu_side.html', 'menu_items' => $aMenu));
+
+        $aTmplParams = array(
+            'menu' => $oMenu->getCode(),
+            'html_block_lists_id' => $this->aHtmlIds['block_lists_id'],
+            'blocks' => $this->getBlockList($sSelected)
+        );
+
+        $aForm['inputs']['blocks']['content'] = $oTemplate->parseHtmlByName('bp_add_block_form.html', $aTmplParams);
+
+        $oForm = new BxTemplStudioFormView($aForm);
+        $oForm->initChecker();
+
+        if($oForm->isSubmittedAndValid()) {
+            $aIds = $oForm->getCleanValue('blocks');
+
+            $aBlocks = array();
+            $this->oDb->getBlocks(array('type' => 'by_ids', 'value' => $aIds), $aBlocks, false);
+
+            $oLanguage = BxDolStudioLanguagesUtils::getInstance();
+            $oStorege = BxDolStorage::getObjectInstance(BX_DOL_STORAGE_OBJ_IMAGES);
+
+            $bResult = true;
+            foreach($aBlocks as $aBlock) {
+            	$sModule = $aBlock['module'];
+                $sTitleKey = $this->getSystemName($aBlock['title'] . '_' . time());
+                $aTitleValues = $oLanguage->getLanguageString($aBlock['title']);
+
+                unset($aBlock['id']);
+                $aBlock['object'] = $this->sPage;
+                $aBlock['cell_id'] = 1;
+                $aBlock['module'] = $this->getBlockModule($aBlock);
+                $aBlock['title'] = $sTitleKey;
+				$aBlock['copyable'] = $sModule == BX_DOL_STUDIO_BP_SKELETONS ? 1 : 0;
+                $aBlock['deletable'] = 1;
+
+                //--- Process Lang copy
+                $sContentKey = $sContentValue = "";
+                if($aBlock['type'] == BX_DOL_STUDIO_BP_BLOCK_LANG && $aBlock['content'] != '') {
+                    $sContentKey = $this->getSystemName($aBlock['content'] . '_' . time());
+                    $sContentValue = _t($aBlock['content']);
+
+                    $aBlock['content'] = $sContentKey;
+                    $oLanguage->addLanguageString($sContentKey, $sContentValue);
+                }
+
+                //--- Process Image copy
+                $iImageId = $sImageAlign = "";
+                if($aBlock['type'] == BX_DOL_STUDIO_BP_BLOCK_IMAGE && $aBlock['content'] != '') {
+                    list($iImageId, $sImageAlign) = explode($this->sParamsDivider, $aBlock['content']);
+
+                    $aBlock['content'] = "";
+                    if(is_numeric($iImageId) && (int)$iImageId != 0 && ($iImageId = $oStorege->storeFileFromStorage(array('id' => $iImageId))) !== false)
+                        $aBlock['content'] = implode($this->sParamsDivider, array($iImageId, $sImageAlign));
+                }
+
+                // add indexing data
+                $aBlock = array_merge($aBlock, $this->getIndexingDataForBlock($aBlock['type'], $aBlock['content']));
+
+                if(!$this->oDb->insertBlock($aBlock)) {
+                    if($sContentKey != "")
+                        $oLanguage->deleteLanguageString($sContentKey);
+
+                    if($iImageId != "")
+                        $oStorege->deleteFile((int)$iImageId, 0);
+
+                    $bResult = false;
+                    break;
+                }
+
+                //--- Process Title copy
+                foreach($aTitleValues as $iLangId => $aTitleValue)
+                	$oLanguage->addLanguageString($sTitleKey, $aTitleValue['string'], $iLangId);
+            }
+
+            if($bResult)
+                return array('eval' => $sJsObject . '.onCreateBlock(oData)');
+            else
+                return array('msg' => _t('_adm_bp_err_block_added'));
+        }
+
+        $sContent = BxTemplStudioFunctions::getInstance()->popupBox($this->aHtmlIds['create_block_popup_id'], _t('_adm_bp_txt_new_block_popup'), $oTemplate->parseHtmlByName('bp_add_block.html', array(
+        	'action' => 'create',
+            'form_id' => $aForm['form_attrs']['id'],
+            'form' => $oForm->getCode(true)
+        )));
+
+        return array('popup' => $sContent);
+    }
+
+    protected function actionBlockEdit()
+    {
+        $sJsObject = $this->getPageJsObject();
+        $oTemplate = BxDolStudioTemplate::getInstance();
+
+        $iId = (int)bx_get('id');
+        if(!$iId)
+            return array();
+
+        $aBlock = array();
+        $this->oDb->getBlocks(array('type' => 'by_id', 'value' => $iId), $aBlock, false);
+        if(empty($aBlock) || !is_array($aBlock))
+            return array('msg' => _t('_adm_bp_err_block_not_found'));
+
+		bx_import('BxDolStudioUtils');
+        $aForm = array(
+            'form_attrs' => array(
+                'id' => 'adm-bp-block-edit',
+                'action' => sprintf($this->sPageUrl, $this->sType, $this->sPage) . '&bp_action=' . $this->sActionBlockEdit,
+                'method' => 'post'
+            ),
+            'params' => array (
+                'db' => array(
+                    'table' => 'sys_pages_blocks',
+                    'key' => 'id',
+                    'uri' => '',
+                    'uri_title' => '',
+                    'submit_name' => 'do_submit'
+                ),
+            ),
+            'inputs' => array (
+                'id' => array(
+                    'type' => 'hidden',
+                    'name' => 'id',
+                    'value' => $iId,
+                    'db' => array (
+                        'pass' => 'Int',
+                    ),
+                ),
+                'title' => array(
+                    'type' => 'text_translatable',
+                    'name' => 'title',
+                    'caption' => _t('_adm_bp_txt_block_title'),
+                    'info' => _t('_adm_bp_dsc_block_title'),
+                    'value' => $aBlock['title'],
+                    'required' => '1',
+                    'db' => array (
+                        'pass' => 'Xss',
+                    ),
+                    'checker' => array (
+                        'func' => 'LengthTranslatable',
+                        'params' => array(3, 100, 'title'),
+                        'error' => _t('_adm_bp_err_block_title'),
+                    ),
+                ),
+                'designbox_id' => array(
+                    'type' => 'select',
+                    'name' => 'designbox_id',
+                    'caption' => _t('_adm_bp_txt_block_designbox'),
+                    'info' => '',
+                    'value' => $this->sSelectKeyPrefix . $aBlock['designbox_id'],
+                    'values' => array(
+                        array('key' => '', 'value' => _t('_adm_bp_txt_block_designbox_empty')),
+                    ),
+                    'required' => '1',
+                    'db' => array (
+                        'pass' => 'Xss',
+                    ),
+                    'checker' => array (
+                        'func' => 'avail',
+                        'params' => array(),
+                        'error' => _t('_adm_bp_err_block_designbox'),
+                    ),
+                ),
+                'hidden_on' => array(
+                    'type' => 'select_multiple',
+                    'name' => 'hidden_on',
+                    'caption' => _t('_adm_bp_txt_block_hidden_on'),
+                    'info' => '',
+                    'value' => (int)$aBlock['hidden_on'],
+                    'values' => array(
+                        BX_DB_HIDDEN_PHONE => _t('_adm_bp_txt_block_hidden_on_phone'),
+                        BX_DB_HIDDEN_TABLET => _t('_adm_bp_txt_block_hidden_on_tablet'),
+                        BX_DB_HIDDEN_DESKTOP => _t('_adm_bp_txt_block_hidden_on_desktop'),
+                        BX_DB_HIDDEN_MOBILE => _t('_adm_bp_txt_block_hidden_on_mobile')
+                    ),
+                    'db' => array (
+                        'pass' => 'Set',
+                    )
+                ),
+                'visible_for' => array(
+                    'type' => 'select',
+                    'name' => 'visible_for',
+                    'caption' => _t('_adm_bp_txt_block_visible_for'),
+                    'info' => '',
+                    'value' => $aBlock['visible_for_levels'] == BX_DOL_INT_MAX ? BX_DOL_STUDIO_VISIBLE_ALL : BX_DOL_STUDIO_VISIBLE_SELECTED,
+                    'values' => array(
+                        array('key' => BX_DOL_STUDIO_VISIBLE_ALL, 'value' => _t('_adm_bp_txt_block_visible_for_all')),
+                        array('key' => BX_DOL_STUDIO_VISIBLE_SELECTED, 'value' => _t('_adm_bp_txt_block_visible_for_selected')),
+                    ),
+                    'required' => '0',
+                    'attrs' => array(
+                        'onchange' => $sJsObject . '.onChangeVisibleFor(this)'
+                    ),
+                    'db' => array (
+                        'pass' => 'Xss',
+                    )
+                ),
+                'visible_for_levels' => array(
+                    'type' => 'checkbox_set',
+                    'name' => 'visible_for_levels',
+                    'caption' => _t('_adm_bp_txt_block_visible_for_levels'),
+                    'info' => _t('_adm_bp_dsc_block_visible_for_levels'),
+                    'value' => array(),
+                    'values' => array(),
+                    'tr_attrs' => array(
+                        'style' => $aBlock['visible_for_levels'] == BX_DOL_INT_MAX ? 'display:none' : ''
+                    ),
+                    'db' => array (
+                        'pass' => 'Int',
+                    )
+                ),
+                'active' => array(
+                    'type' => 'switcher',
+                    'name' => 'active',
+                    'caption' => _t('_adm_bp_txt_block_active'),
+                    'info' => '',
+                    'value' => '1',
+                    'checked' => $aBlock['active'] == '1',
+                    'db' => array (
+                        'pass' => 'Int',
+                    )
+                ),
+                'controls' => array(
+                    'name' => 'controls',
+                    'type' => 'input_set',
+                    array(
+                        'type' => 'submit',
+                        'name' => 'do_submit',
+                        'value' => _t('_adm_bp_btn_block_save')
+                    ),
+                    array (
+                        'type' => 'reset',
+                        'name' => 'close',
+                        'value' => _t('_adm_bp_btn_block_cancel'),
+                        'attrs' => array(
+                            'onclick' => $sJsObject . '.onEditBlockCancel(this)',
+                            'class' => 'bx-def-margin-sec-left',
+                        ),
+                    )
+                )
+            )
+        );
+
+        $aDBoxes = array();
+        $this->oDb->getDesignBoxes(array('type' => 'ordered'), $aDBoxes, false);
+        foreach($aDBoxes as $aDBox)
+            $aForm['inputs']['designbox_id']['values'][] = array('key' => $this->sSelectKeyPrefix . $aDBox['id'], 'value' => _t($aDBox['title']));
+
+        BxDolStudioUtils::getVisibilityValues($aBlock['visible_for_levels'], $aForm['inputs']['visible_for_levels']['values'], $aForm['inputs']['visible_for_levels']['value']);
+
+        $aForm['inputs'] = $this->addInArray($aForm['inputs'], 'visible_for_levels', $this->getBlockContent($aBlock));
+
+        if((int)$aBlock['deletable'] != 0)
+            $aForm['inputs']['controls'][] = array (
+                'type' => 'reset',
+                'name' => 'close',
+                'value' => _t('_adm_bp_btn_block_delete'),
+                'attrs' => array(
+                    'onclick' => $sJsObject . '.deleteBlock(' . $aBlock['id'] . ')',
+                    'class' => 'bx-def-margin-sec-left',
+                ),
+            );
+
+        $oForm = new BxTemplStudioFormView($aForm);
+        $oForm->initChecker();
+
+        if($oForm->isSubmittedAndValid()) {
+            $this->onSaveBlock($oForm, $aBlock);
+
+            // add indexing data
+            $aBlockAddon = $this->getIndexingDataForBlock($aBlock['type'], $oForm->getCleanValue('content'));
+
+            if($oForm->update($iId, $aBlockAddon) !== false)
+                return array('eval' => $sJsObject . '.onEditBlock(oData)');
+            else
+                return array('msg' => _t('_adm_bp_err_block_edit'));
+        }
+
+        $sContent = BxTemplStudioFunctions::getInstance()->popupBox($this->aHtmlIds['edit_block_popup_id'], _t('_adm_bp_txt_edit_block_popup', _t($aBlock['title'])), $oTemplate->parseHtmlByName('bp_add_block.html', array(
+        	'action' => 'edit',
+            'form_id' => $aForm['form_attrs']['id'],
+            'form' => $oForm->getCode(true)
+        )));
+
+        return array('popup' => array('html' => $sContent, 'options' => array('onBeforeShow' => $sJsObject . '.onEditBlockBeforeShow($el)')));
+    }
+
+    protected function actionBlockDelete()
+    {
+        $sJsObject = $this->getPageJsObject();
+        $iId = (int)bx_get('id');
+
+        $aBlock = array();
+        $this->oDb->getBlocks(array('type' => 'by_id', 'value' => $iId), $aBlock, false);
+        if(empty($aBlock) || !is_array($aBlock))
+            return array('msg' => _t('_adm_bp_err_block_not_found'));
+
+        if((int)$aBlock['deletable'] == 0)
+            return array('msg' => _t('_adm_bp_err_block_not_deletable'));
+
+        if(!$this->oDb->deleteBlocks(array('type' => 'by_id', 'value' => $iId)))
+            return array('msg' => _t('_adm_bp_err_block_delete'));
+
+        $this->onBlockDelete($aBlock);
+        return array('eval' => $sJsObject . '.onDeleteBlock(' . $iId . ', oData)');
+    }
+
+    protected function actionImageDelete()
+    {
+        $sJsObject = $this->getPageJsObject();
+        $iId = (int)bx_get('id');
+
+        $aBlock = array();
+        $this->oDb->getBlocks(array('type' => 'by_id', 'value' => $iId), $aBlock, false);
+        if(empty($aBlock) || !is_array($aBlock))
+            return array('msg' => _t('_adm_bp_err_block_not_found'));
+
+        $iImageId = $sImageAlign = '';
+        if($aBlock['content'] != '')
+            list($iImageId, $sImageAlign) = explode($this->sParamsDivider, $aBlock['content']);
+
+        if(is_numeric($iImageId) && (int)$iImageId != 0) {
+            if(!BxDolStorage::getObjectInstance(BX_DOL_STORAGE_OBJ_IMAGES)->deleteFile((int)$iImageId, 0))
+                return array('msg' => _t('_adm_bp_err_block_content_image_preview_delete'));
+
+            $this->oDb->updateBlock($iId, array('content' => ''));
+        }
+
+        return $this->actionBlockEdit();
+    }
+
+    protected function actionUriGet()
+    {
+        $oLanguage = BxDolStudioLanguagesUtils::getInstance();
+        $aLanguages = $oLanguage->getLanguages();
+
+        $sLanguageDef = $oLanguage->getDefaultLangName();
+        $sLanguageCur = $oLanguage->getCurrentLangName(false);
+
+        $aValues = array();
+        foreach($aLanguages as $sName => $sTitle)
+            if(($mixedValue = bx_get($sName)) !== false)
+                $aValues[$sName] = bx_process_input($mixedValue);
+
+        $sUri = "";
+        if(($mixedValue = bx_get('uri')) !== false)
+        	$sUri = bx_process_input($mixedValue);
+        else if(array_key_exists('en', $aValues) && $aValues['en'] != '')
+            $sUri = $aValues['en'];
+        else if(array_key_exists($sLanguageDef, $aValues) && $aValues[$sLanguageDef] != '')
+            $sUri = $aValues[$sLanguageDef];
+        else if(array_key_exists($sLanguageCur, $aValues) && $aValues[$sLanguageCur] != '')
+            $sUri = $aValues[$sLanguageCur];
+        else if(count($aValues) > 0)
+            foreach($aValues as $sValue)
+                if(!empty($sValue)) {
+                    $sUri = $sValue;
+                    break;
+                }
+
+        $sUri = $sUri != "" ? uriGenerate($sUri, 'sys_objects_page', 'uri') : "";
+        $sUrl = BxDolPermalinks::getInstance()->permalink($this->sPageBaseUrl . $sUri);
+
+        return array('eval' => $this->getPageJsObject() . '.onGetUri(oData)', 'uri' => $sUri, 'url' => $sUrl);
+    }
+
+    protected function getSettingsOptions($aPage = array(), $bCreate = true, $bInputsOnly = false)
+    {
+        $aForm = array(
+            'form_attrs' => array(
+                'id' => 'adm-bp-settings-options',
+            ),
+            'params' => array (
+                'remove_form' => '1',
+                'csrf' => array(
+                    'disable' => true
+                )
+            ),
+            'inputs' => array (
+                'title_system' => array(
+                    'type' => 'text_translatable',
+                    'name' => 'title_system',
+                    'caption' => _t('_adm_bp_txt_page_title_system'),
+                    'info' => _t('_adm_bp_dsc_page_title_system'),
+                    'value' => isset($aPage['title_system']) ? $aPage['title_system'] : '',
+                    'required' => '1',
+                    'db' => array (
+                        'pass' => 'Xss',
+                    ),
+                    'checker' => array (
+                        'func' => 'length',
+                        'params' => array(3,100),
+                        'error' => _t('_adm_bp_err_page_title_system'),
+                    ),
+                ),
+                'title' => array(
+                    'type' => 'text_translatable',
+                    'name' => 'title',
+                    'caption' => _t('_adm_bp_txt_page_title'),
+                    'info' => _t('_adm_bp_dsc_page_title'),
+                    'value' => isset($aPage['title']) ? $aPage['title'] : '',
+                    'required' => '1',
+                    'db' => array (
+                        'pass' => 'Xss',
+                    ),
+                    'checker' => array (
+                        'func' => 'length',
+                        'params' => array(3,100),
+                        'error' => _t('_adm_bp_err_page_title'),
+                    ),
+                ),
+                'url' => array(
+					'type' => 'text',
+					'name' => 'url',
+					'caption' => _t('_adm_bp_txt_page_url'),
+					'info' => _t('_adm_bp_dsc_page_url'),
+					'value' => isset($aPage['url']) ? BxDolPermalinks::getInstance()->permalink($aPage['url']) : '',
+					'required' => '0',
+					'attrs' => array(
+                		'id' => $this->aHtmlIds['url_field_id'],
+						'disabled' => 'disabled'
+					),
+				),
+				'type_id' => array(
+					'type' => 'select',
+					'name' => 'type_id',
+					'caption' => _t('_adm_bp_txt_page_type_id'),
+					'info' => _t('_adm_bp_dsc_page_type_id'),
+					'value' => isset($aPage['type_id']) ? $aPage['type_id'] : '',
+					'required' => '1',
+					'db' => array (
+                        'pass' => 'Int',
+                    ),
+				),
+                'submenu' => array(
+                    'type' => 'select',
+                    'name' => 'submenu',
+                    'caption' => _t('_adm_bp_txt_page_submenu'),
+                    'info' => _t('_adm_bp_dsc_page_submenu'),
+                    'value' => isset($aPage['submenu']) ? $aPage['submenu'] : '',
+                    'required' => '0',
+                    'db' => array (
+                        'pass' => 'Xss',
+                    ),
+                ),
+            )
+        );
+
+        $aTypes = array();
+        $this->oDb->getTypes(array('type' => 'all'), $aTypes, false);
+        foreach($aTypes as $aType)
+        	$aForm['inputs']['type_id']['values'][] = array('key' => $aType['id'], 'value' => _t($aType['title']));
+
+        $aSubmenus = $this->oDb->getSubmenus();
+        $aForm['inputs']['submenu']['values'][] = array('key' => '', 'value' => _t('_sys_no_menu'));
+        foreach ($aSubmenus as $sObject => $sTitle)
+            $aForm['inputs']['submenu']['values'][] = array('key' => $sObject, 'value' => _t($sTitle));
+
+        if($bCreate) {
+        	$sJsObject = $this->getPageJsObject();
+
+        	$aForm['inputs']['title']['attrs']['onblur'] = $sJsObject . '.getUri(this);';
+
+        	$aForm['inputs'] = bx_array_insert_before(array(
+        		'uri' => array(
+					'type' => 'text',
+					'name' => 'uri',
+					'caption' => _t('_adm_bp_txt_page_uri'),
+					'info' => _t('_adm_bp_dsc_page_uri'),
+					'value' => '',
+					'required' => '1',
+        			'attrs' => array(
+						'id' => $this->aHtmlIds['uri_field_id'],
+						'onblur' => $sJsObject . '.getUri(this);'
+					),
+					'db' => array (
+						'pass' => 'Xss',
+					),
+					'checker' => array (
+						'func' => 'length',
+						'params' => array(3,100),
+					    'error' => _t('_adm_bp_err_page_uri'),
+					),
+				)
+        	), $aForm['inputs'], 'url');
+        }
+
+        if($bInputsOnly)
+            return $aForm['inputs'];
+
+        $oForm = new BxTemplStudioFormView($aForm);
+        return $oForm->getCode();
+    }
+
+    protected function getSettingsOptionsFields($aPage = array(), $bCreate = true)
+    {
+    	return $this->getSettingsOptions($aPage, $bCreate, true);
+    }
+
+    protected function getSettingsCover($aPage = array(), $bCreate = true, $bInputsOnly = false)
+    {
+        $aForm = array(
+            'form_attrs' => array(
+                'id' => 'adm-bp-settings-cover',
+            ),
+            'params' => array (
+                'remove_form' => '1',
+                'csrf' => array(
+                    'disable' => true
+                )
+            ),
+            'inputs' => array (
+                'cover' => array(
+                    'type' => 'select',
+                    'name' => 'cover',
+                    'caption' => _t('_adm_bp_txt_page_cover'),
+                    'info' => '',
+                    'value' => isset($aPage['cover']) ? (int)$aPage['cover'] : '1',
+                    'values' => array(
+                        array('key' => 0, 'value' => _t('_adm_bp_txt_page_cover_0')),
+                        array('key' => 1, 'value' => _t('_adm_bp_txt_page_cover_1')),
+                        array('key' => 2, 'value' => _t('_adm_bp_txt_page_cover_2')),
+                        array('key' => 3, 'value' => _t('_adm_bp_txt_page_cover_3'))
+                    ),
+                    'db' => array (
+                        'pass' => 'Int',
+                    )
+                ),
+                'cover_image' => array(
+                    'type' => 'files',
+                    'name' => 'cover_image',
+                    'storage_object' => $this->sStorage,
+                    'images_transcoder' => $this->sTranscoderCover,
+                    'uploaders' => $this->aUploadersCover,
+                    'multiple' => false,
+                    'content_id' => isset($aPage['id']) ? $aPage['id'] : 0,
+                    'ghost_template' => BxDolStudioTemplate::getInstance()->parseHtmlByName('uploader_fgt_cover.html', array(
+                        'name' => 'cover_image',
+                    )),
+                    'caption' => _t('_adm_bp_txt_page_cover_image'),
+                    'db' => array (
+                        'pass' => 'Int',
+                    )
+                )
+            )
+        );
+
+        if($bInputsOnly)
+            return $aForm['inputs'];
+
+        $oForm = new BxTemplStudioFormView($aForm);
+        return $oForm->getCode();
+    }
+
+    protected function getSettingsCoverFields($aPage = array(), $bCreate = true)
+    {
+    	return $this->getSettingsCover($aPage, $bCreate, true);
+    }
+
+    protected function getSettingsLayout($aPage = array(), $bCreate = true, $bInputsOnly = false)
+    {
+        $sJsObject = $this->getPageJsObject();
+        $oTemplate = BxDolStudioTemplate::getInstance();
+
+        $iLayout = isset($aPage['layout_id']) ? (int)$aPage['layout_id'] : 5;
+
+        $aLayouts = array();
+        $this->oDb->getLayouts(array('type' => 'all'), $aLayouts, false);
+
+        $aTmplParams = array(
+            'bx_repeat:layouts' => array(),
+            'form' => ''
+        );
+
+        foreach($aLayouts as $aLayout)
+            $aTmplParams['bx_repeat:layouts'][] = array(
+                'id' => $aLayout['id'],
+                'html_id' => $this->aHtmlIds['layout_id'] . $aLayout['id'],
+                'js_object' => $sJsObject,
+                'bx_if:active' => array(
+                    'condition' => (int)$aLayout['id'] == $iLayout,
+                    'content' => array()
+                ),
+                'icon' => $oTemplate->getImageUrl($aLayout['icon']),
+                'title' => _t($aLayout['title']),
+            );
+
+        $aForm = array(
+            'form_attrs' => array(
+                'id' => 'adm-bp-settings-layout',
+            ),
+            'params' => array (
+                'remove_form' => '1',
+                'csrf' => array(
+                    'disable' => true
+                )
+            ),
+            'inputs' => array (
+                'layout_id' => array(
+                    'type' => 'hidden',
+                    'name' => 'layout_id',
+                    'value' => $iLayout,
+                    'db' => array (
+                        'pass' => 'Int',
+                    ),
+                )
+            )
+        );
+
+        if($bInputsOnly)
+            return $aForm['inputs'];
+
+        $oForm = new BxTemplStudioFormView($aForm);
+        $aTmplParams['form'] = $oForm->getCode();
+
+        return $oTemplate->parseHtmlByName('bp_layouts.html', $aTmplParams);
+    }
+
+    protected function getSettingsLayoutFields($aPage = array(), $bCreate = true)
+    {
+    	return $this->getSettingsLayout($aPage, $bCreate, true);
+    }
+
+    protected function getSettingsVisibility($aPage = array(), $bCreate = true, $bInputsOnly = false)
+    {
+    	$iVisibleForLevels = isset($aPage['visible_for_levels']) ? (int)$aPage['visible_for_levels'] : BX_DOL_INT_MAX;
+
+        if(isset($aPage['visible_for_levels_editable']) && (int)$aPage['visible_for_levels_editable'] == 0)
+            $aInputs = array(
+                'visible_for_levels' => array(
+                    'type' => 'custom',
+                    'name' => 'visible_for_levels',
+                    'content' => MsgBox(_t('_adm_bp_err_page_visible_for_levels'))
+                )
+            );
+        else {
+        	bx_import('BxDolStudioUtils');
+            $aInputs = array(
+                'visible_for' => array(
+                    'type' => 'select',
+                    'name' => 'visible_for',
+                    'caption' => _t('_adm_bp_txt_block_visible_for'),
+                    'info' => '',
+                    'value' => $iVisibleForLevels == BX_DOL_INT_MAX ? BX_DOL_STUDIO_VISIBLE_ALL : BX_DOL_STUDIO_VISIBLE_SELECTED,
+                    'values' => array(
+                        array('key' => BX_DOL_STUDIO_VISIBLE_ALL, 'value' => _t('_adm_bp_txt_block_visible_for_all')),
+                        array('key' => BX_DOL_STUDIO_VISIBLE_SELECTED, 'value' => _t('_adm_bp_txt_block_visible_for_selected')),
+                    ),
+                    'required' => '0',
+                    'attrs' => array(
+                        'onchange' => $this->getPageJsObject() . '.onChangeVisibleFor(this)'
+                    ),
+                    'db' => array (
+                        'pass' => 'Xss',
+                    )
+                ),
+                'visible_for_levels' => array(
+                    'type' => 'checkbox_set',
+                    'name' => 'visible_for_levels',
+                    'caption' => _t('_adm_bp_txt_page_visible_for_levels'),
+                    'info' => _t('_adm_bp_dsc_page_visible_for_levels'),
+                    'value' => array(),
+                    'values' => array(),
+                    'tr_attrs' => array(
+                        'style' => $iVisibleForLevels == BX_DOL_INT_MAX ? 'display:none' : ''
+                    ),
+                    'db' => array (
+                        'pass' => 'Int',
+                    )
+                )
+            );
+        }
+
+        $aForm = array(
+            'form_attrs' => array(
+                'id' => 'adm-bp-settings-visibility',
+            ),
+            'params' => array (
+                'remove_form' => '1',
+                'csrf' => array(
+                    'disable' => true
+                )
+            ),
+            'inputs' => $aInputs
+        );
+
+        BxDolStudioUtils::getVisibilityValues($iVisibleForLevels, $aForm['inputs']['visible_for_levels']['values'], $aForm['inputs']['visible_for_levels']['value']);
+
+        if($bInputsOnly)
+            return $aForm['inputs'];       	
+
+        $oForm = new BxTemplStudioFormView($aForm);
+        return $oForm->getCode();
+    }
+
+    protected function getSettingsVisibilityFields($aPage = array(), $bCreate = true)
+    {
+        return $this->getSettingsVisibility($aPage, $bCreate, true);
+    }
+
+    protected function getSettingsCache($aPage = array(), $bCreate = true, $bInputsOnly = false)
+    {
+        $aForm = array(
+            'form_attrs' => array(
+                'id' => 'adm-bp-settings-cache',
+            ),
+            'params' => array (
+                'remove_form' => '1',
+                'csrf' => array(
+                    'disable' => true
+                )
+            ),
+            'inputs' => array (
+                'cache_lifetime' => array(
+                    'type' => 'text',
+                    'name' => 'cache_lifetime',
+                    'caption' => _t('_adm_bp_txt_page_cache_lifetime'),
+                    'info' => _t('_adm_bp_dsc_page_cache_lifetime'),
+                    'value' => isset($aPage['cache_lifetime']) ? $aPage['cache_lifetime'] : 0,
+                    'required' => '',
+                    'attrs' => array(
+                        'disabled' => isset($aPage['cache_editable']) && (int)$aPage['cache_editable'] == 0 ? 'disabled' : ''
+                    ),
+                    'db' => array (
+                        'pass' => 'Int',
+                    )
+                ),
+            )
+        );
+
+        if($bInputsOnly)
+            return $aForm['inputs'];
+
+        $oForm = new BxTemplStudioFormView($aForm);
+        return $oForm->getCode();
+    }
+
+    protected function getSettingsCacheFields($aPage = array(), $bCreate = true)
+    {
+    	return $this->getSettingsCache($aPage, $bCreate, true);
+    }
+
+    protected function getSettingsSeo($aPage = array(), $bCreate = true, $bInputsOnly = false)
+    {
+        $aForm = array(
+            'form_attrs' => array(
+                'id' => 'adm-bp-settings-seo',
+            ),
+            'params' => array (
+                'remove_form' => '1',
+                'csrf' => array(
+                    'disable' => true
+                )
+            ),
+            'inputs' => array (
+                'meta_description' => array(
+                    'type' => 'textarea',
+                    'name' => 'meta_description',
+                    'caption' => _t('_adm_bp_txt_page_meta_description'),
+                    'info' => _t('_adm_bp_dsc_page_meta_description'),
+                    'value' => isset($aPage['meta_description']) ? $aPage['meta_description'] : '',
+                    'db' => array (
+                        'pass' => 'Xss',
+                    ),
+                ),
+                'meta_keywords' => array(
+                    'type' => 'textarea',
+                    'name' => 'meta_keywords',
+                    'caption' => _t('_adm_bp_txt_page_meta_keywords'),
+                    'info' => _t('_adm_bp_dsc_page_meta_keywords'),
+                    'value' => isset($aPage['meta_keywords']) ? $aPage['meta_keywords'] : '',
+                    'db' => array (
+                        'pass' => 'Xss',
+                    ),
+                ),
+                'meta_robots' => array(
+                    'type' => 'text',
+                    'name' => 'meta_robots',
+                    'caption' => _t('_adm_bp_txt_page_meta_robots'),
+                    'info' => _t('_adm_bp_dsc_page_meta_robots'),
+                    'value' => isset($aPage['meta_robots']) ? $aPage['meta_robots'] : '',
+                    'required' => '0',
+                    'db' => array (
+                        'pass' => 'Xss',
+                    )
+                )
+            )
+        );
+
+        if($bInputsOnly)
+            return $aForm['inputs'];
+
+        $oForm = new BxTemplStudioFormView($aForm);
+        return $oForm->getCode();
+    }
+
+    protected function getSettingsSeoFields($aPage = array(), $bCreate = true)
+    {
+    	return $this->getSettingsSeo($aPage, $bCreate, true);
+    }
+
+    protected function getBlockIcon($aBlock)
+    {
+        $sIcon = $sIconUrl = "";
+
+        $sResult = '';
+        switch($aBlock['type']) {
+            case BX_DOL_STUDIO_BP_BLOCK_RAW:
+            	$sResult = 'far file-alt';
+            	break;
+
+            case BX_DOL_STUDIO_BP_BLOCK_HTML:
+            	$sResult = 'code';
+            	break;
+
+            case BX_DOL_STUDIO_BP_BLOCK_RSS:
+            	$sResult = 'rss';
+            	break;
+
+            case BX_DOL_STUDIO_BP_BLOCK_IMAGE:
+            	$sResult = 'far image';
+            	break;
+
+            case BX_DOL_STUDIO_BP_BLOCK_LANG:
+            	$sResult = 'globe';
+            	break;
+
+            case BX_DOL_STUDIO_BP_BLOCK_MENU:
+            	$sResult = 'list-alt';
+            	break;
+
+            case BX_DOL_STUDIO_BP_BLOCK_WIKI:
+            	$sResult = 'file-word';
+            	break;
+
+            case BX_DOL_STUDIO_BP_BLOCK_CUSTOM:
+                $sResult = 'file';
+            	break;
+
+            case BX_DOL_STUDIO_BP_BLOCK_SERVICE:
+                $sResult = $this->getModuleIcon($aBlock['module'], 'page');
+                break;
+        }
+
+        if(strpos($sResult, '.') === false)
+            $sIcon = $sResult;
+        else
+            $sIconUrl = $sResult;
+
+        return array($sIcon, $sIconUrl);
+    }
+
+    protected function getBlockModule($aBlock)
+    {
+        return  $aBlock['module'] != BX_DOL_STUDIO_BP_SKELETONS ? $aBlock['module'] : BX_DOL_STUDIO_MODULE_CUSTOM;
+    }
+
+    protected function getBlockContent($aBlock)
+    {
+        $aFields = array();
+
+        switch($aBlock['type']) {
+            case BX_DOL_STUDIO_BP_BLOCK_CUSTOM:
+            case BX_DOL_STUDIO_BP_BLOCK_RAW:
+                $aFields = array(
+                    'content' => array(
+                        'type' => 'textarea',
+                        'name' => 'content',
+                        'caption' => _t('_adm_bp_txt_block_content_raw'),
+                        'info' => _t('_adm_bp_dsc_block_content_raw'),
+                        'value' => $aBlock['content'],
+                        'required' => '0',
+                        'code' => 1,
+                        'attrs' => array('class' => 'bx-form-input-textarea-codemirror'),
+                        'db' => array (
+                            'pass' => 'XssHtml',
+                        ),
+                    ),
+                );
+                break;
+
+            case BX_DOL_STUDIO_BP_BLOCK_HTML:
+            	
+                $aFields = array(
+                    'content' => array(
+                        'type' => 'textarea',
+                        'name' => 'content',
+                        'caption' => _t('_adm_bp_txt_block_content_raw'),
+                        'info' => _t('_adm_bp_dsc_block_content_raw'),
+                        'value' => $aBlock['content'],
+                        'required' => '0',
+                        'html' => 2,
+                		'attrs' => array('id' => $this->aHtmlIds['edit_block_editor_id']),
+                        'db' => array (
+                            'pass' => 'XssHtml',
+                        ),
+                    ),
+                    'attachments' => array(
+                    	'type' => 'files',
+	                    'name' => 'attachments',
+						'storage_object' => $this->sStorage,
+	 					'images_transcoder' => $this->sTranscoder,
+	 					'uploaders' => $this->aUploaders,
+						'multiple' => true,
+	 					'content_id' => $aBlock['id'],
+	 					'ghost_template' => BxDolStudioTemplate::getInstance()->parseHtmlByName('bp_fgt_attachments.html', array(
+                    		'js_object' => $this->getPageJsObject(),
+                    		'name' => 'attachments',
+							'editor_id' => $this->aHtmlIds['edit_block_editor_id'],
+                    	)),
+	                    'caption' => _t('_adm_bp_txt_block_content_attachments_html')
+                    )
+                );
+                break;
+
+            case BX_DOL_STUDIO_BP_BLOCK_LANG:
+                $aFields = array(
+                    'content' => array(
+                        'type' => 'textarea_translatable',
+                        'name' => 'content',
+                        'caption' => _t('_adm_bp_txt_block_content_lang'),
+                        'info' => _t('_adm_bp_dsc_block_content_lang'),
+                        'value' => $aBlock['content'],
+                        'required' => '0',
+                        'html' => 0,
+                		'code' => 1,
+                        'db' => array (
+                            'pass' => 'XssHtml',
+                        ),
+                    ),
+                );
+                break;
+
+            case BX_DOL_STUDIO_BP_BLOCK_IMAGE:
+                $iImageId = $sImageAlign = '';
+                if($aBlock['content'] != '')
+                    list($iImageId, $sImageAlign) = explode($this->sParamsDivider, $aBlock['content']);
+
+                $aFields = array(
+                    'content' => array(
+                        'type' => 'hidden',
+                        'name' => 'content',
+                        'value' => '',
+                        'db' => array (
+                            'pass' => 'Xss',
+                        ),
+                    ),
+                    'image_file' => array(
+                        'type' => 'image_uploader',
+                        'name' => 'image_file',
+                        'caption' => _t('_adm_bp_txt_block_content_image_file'),
+                        'caption_preview' => _t('_adm_bp_txt_block_content_image_preview'),
+                        'info' => _t('_adm_bp_dsc_block_content_image_file'),
+                        'ajax_action_delete' => $this->getPageJsObject() . '.deleteBlockImage(' . $aBlock['id'] . ')',
+                        'value' => (int)$iImageId
+                    ),
+                    'image_align' => array(
+                        'type' => 'select',
+                        'name' => 'image_align',
+                        'caption' => _t('_adm_bp_txt_block_content_image_align'),
+                        'info' => '',
+                        'value' => $sImageAlign,
+                        'values' => array(
+                            array('key' => '', 'value' => _t('_adm_bp_txt_block_content_image_align_empty')),
+                            array('key' => 'left', 'value' => _t('_adm_bp_txt_block_content_image_align_left')),
+                            array('key' => 'center', 'value' => _t('_adm_bp_txt_block_content_image_align_center')),
+                            array('key' => 'right', 'value' => _t('_adm_bp_txt_block_content_image_align_right')),
+                        ),
+                        'required' => '0',
+                        'db' => array (
+                            'pass' => 'Xss',
+                        ),
+                    ),
+                );
+                break;
+
+            case BX_DOL_STUDIO_BP_BLOCK_RSS:
+                $sRssUrl = $sRssLength = '';
+                if($aBlock['content'] != '')
+                    list($sRssUrl, $sRssLength) = explode($this->sParamsDivider, $aBlock['content']);
+
+                $aFields = array(
+                    'content' => array(
+                        'type' => 'hidden',
+                        'name' => 'content',
+                        'value' => '',
+                        'db' => array (
+                            'pass' => 'Xss',
+                        ),
+                    ),
+                    'rss_url' => array(
+                        'type' => 'text',
+                        'name' => 'rss_url',
+                        'caption' => _t('_adm_bp_txt_block_content_rss_url'),
+                        'info' => _t('_adm_bp_dsc_block_content_rss_url'),
+                        'value' => $sRssUrl,
+                        'required' => '0',
+                        'db' => array (
+                            'pass' => 'Xss',
+                        ),
+                    ),
+                    'rss_length' => array(
+                        'type' => 'text',
+                        'name' => 'rss_length',
+                        'caption' => _t('_adm_bp_txt_block_content_rss_length'),
+                        'info' => _t('_adm_bp_dsc_block_content_rss_length'),
+                        'value' => $sRssLength,
+                        'required' => '0',
+                        'db' => array (
+                            'pass' => 'Int',
+                        ),
+                    ),
+                );
+                break;
+
+            case BX_DOL_STUDIO_BP_BLOCK_MENU:
+                $aFields = array(
+                    'content' => array(
+                        'type' => 'select',
+                        'name' => 'content',
+                        'caption' => _t('_adm_bp_txt_block_content_menu'),
+                        'info' => '',
+                        'value' => $aBlock['content'],
+                        'values' => array(),
+                        'required' => '0',
+                        'db' => array (
+                            'pass' => 'Xss',
+                        ),
+                    )
+                );
+
+                $sGroup = '';
+                $aMenus = $this->oDb->getMenus(true);
+                foreach($aMenus as $sKey => $aMenu) {
+                    if($sGroup != $aMenu['module']) {
+                        if(!empty($sGroup))
+                            $aFields['content']['values'][$sGroup . '_end'] = array('type' => 'group_end');
+
+                        $sGroup = $aMenu['module'];
+                        $aFields['content']['values'][$sGroup . '_beg'] = array('type' => 'group_header', 'value' => BxDolStudioUtils::getModuleTitle($sGroup));
+                    }
+
+                    $aFields['content']['values'][$sKey] = _t($aMenu['title']);
+                }
+
+                $aFields['content']['values'] = array_merge(array('' => _t('_adm_bp_txt_block_content_menu_empty')), $aFields['content']['values']);
+                break;
+
+            case BX_DOL_STUDIO_BP_BLOCK_SERVICE:
+                $aService = array('module' => '', 'method' => '');
+                if($aBlock['content'] != '')
+                    $aService = unserialize($aBlock['content']);
+
+                $aFields = array(
+                    'service_module' => array(
+                        'type' => 'value',
+                        'name' => 'service_module',
+                        'caption' => _t('_adm_bp_txt_block_content_service_module'),
+                        'value' => $this->getModuleTitle($aService['module'])
+                    ),
+                    'service_method' => array(
+                        'type' => 'value',
+                        'name' => 'service_method',
+                        'caption' => _t('_adm_bp_txt_block_content_service_method'),
+                        'value' => $aService['method']
+                    )
+                );
+                break;
+        }
+
+        return $aFields;
+    }
+
+    protected function getBlockList($sModule)
+    {
+        if(empty($sModule))
+            return '';
+
+        $sJsObject = $this->getPageJsObject();
+        $oTemplate = BxDolStudioTemplate::getInstance();
+
+        $oForm = new BxTemplStudioFormView(array());
+
+        $aInputCheckbox = array(
+            'type' => 'checkbox',
+            'name' => 'blocks[]',
+            'attrs' => array(
+                'onChange' => 'javascript:' . $sJsObject . '.onSelectBlock(this);'
+            ),
+            'value' => ''
+        );
+
+        $aTmplParams = array(
+            'html_block_list_id' => $this->aHtmlIds['block_list_id'] . $sModule,
+            'bx_repeat:blocks' => array()
+        );
+
+        $aBlocks = $this->getBlocks($sModule);
+        foreach($aBlocks as $aBlock) {
+        	list($sIcon, $sIconUrl) = $this->getBlockIcon($aBlock);
+
+            $aInputCheckbox['value'] = $aBlock['id'];
+
+            $aTmplParams['bx_repeat:blocks'][] = array(
+                'js_object' => $sJsObject,
+                'html_id' => $this->aHtmlIds['block_id'] . $aBlock['id'],
+                'bx_if:is_inactive' => array(
+                    'condition' => false,
+                    'content' => array()
+                ),
+                'bx_if:show_link' => array(
+                    'condition' => false,
+                    'content' => array()
+                ),
+                'bx_if:show_text' => array(
+                    'condition' => true,
+                    'content' => array(
+                        'title' => _t(!empty($aBlock['title_system']) ? $aBlock['title_system'] : $aBlock['title']),
+                    )
+                ),
+                'bx_if:image' => array (
+	                'condition' => (bool)$sIconUrl,
+	                'content' => array('icon_url' => $sIconUrl),
+	            ),
+				'bx_if:icon' => array (
+	                'condition' => (bool)$sIcon,
+	                'content' => array('icon' => $sIcon),
+	            ),
+                'module' => $this->getModuleTitle($aBlock['module']),
+                'visible_for' => _t('_adm_bp_txt_visible_for', BxDolStudioUtils::getVisibilityTitle($aBlock['visible_for_levels'])),
+                'bx_if:show_checkbox' => array(
+                    'condition' => true,
+                    'content' => array(
+                        'content' => $oForm->genRow($aInputCheckbox)
+                    )
+                ),
+                'bx_if:show_drag_handle' => array(
+                    'condition' => false,
+                    'content' => array()
+                )
+            );
+        }
+
+        return $oTemplate->parseHtmlByName('bp_blocks_list.html', $aTmplParams);
+    }
+
+    protected function onBlockDelete($aBlock)
+    {
+    	if($aBlock['module'] != BX_DOL_STUDIO_MODULE_CUSTOM)
+    		return;
+
+        BxDolStudioLanguagesUtils::getInstance()->deleteLanguageString($aBlock['title']);
+
+        //--- Process Lang block
+        if($aBlock['type'] == BX_DOL_STUDIO_BP_BLOCK_LANG && $aBlock['content'] != '')
+            BxDolStudioLanguagesUtils::getInstance()->deleteLanguageString($aBlock['content']);
+
+        //--- Process Image block
+        if($aBlock['type'] == BX_DOL_STUDIO_BP_BLOCK_IMAGE && $aBlock['content'] != '') {
+            $iImageId = $sImageAlign = '';
+            list($iImageId, $sImageAlign) = explode($this->sParamsDivider, $aBlock['content']);
+
+            if(is_numeric($iImageId) && (int)$iImageId != 0)
+                BxDolStorage::getObjectInstance(BX_DOL_STORAGE_OBJ_IMAGES)->deleteFile((int)$iImageId, 0);
+        }
+    }
+
+    protected function getBlocks($sModule)
+    {
+        $aBlocks = array();
+        $this->oDb->getBlocks(array('type' => 'by_module_to_copy', 'value' => $sModule), $aBlocks, false);
+
+        return $aBlocks;
+    }
+
+    protected function _getTmplVarsBlockPanelTop()
+    {
+        $sJsObject = $this->getPageJsObject();
+
+        $oForm = new BxTemplStudioFormView(array());
+
+        $aInputPages = array(
+            'type' => 'select',
+            'name' => 'page',
+            'attrs' => array(
+                'onChange' => 'javascript:' . $this->getPageJsObject() . '.onChangePage(this)'
+            ),
+            'value' => $this->sPage,
+            'values' => array(
+                array('key' => '', 'value' => _t('_adm_bp_txt_select_page'))
+            )
+        );
+
+        $aPages = $aCounter = array();
+        $this->oDb->getPages(array('type' => 'by_module', 'value' => $this->sType), $aPages, false);
+        $this->oDb->getBlocks(array('type' => 'counter_by_pages'), $aCounter, false);
+        foreach($aPages as $aPage) {
+            $sTitle = _t($aPage['title_system']);
+            if(empty($sTitle))
+                $sTitle = _t($aPage['title']);
+
+            $aInputPages['values'][] = array(
+                'key' => $aPage['object'], 
+                'value' => $sTitle . " (" . (isset($aCounter[$aPage['object']]) ? $aCounter[$aPage['object']] : "0") . ")"
+            );
+        }
+
+        $aTmplVarsActions = array();
+        if(($this->sPage != '' && !empty($this->aPageRebuild)) !== false)
+            $aTmplVarsActions = $this->_getTmplVarsBlockPanelTopActions();
+
+        return array(
+            'js_object' => $this->getPageJsObject(),
+            'selector' => $oForm->genRow($aInputPages),
+            'action_page_create' => $this->sActionPageCreate,
+            'bx_if:show_actions' => array(
+                'condition' => $this->sPage != '',
+                'content' => $aTmplVarsActions
+            )
+        );
+    }
+
+    protected function _getTmplVarsBlockPanelTopActions()
+    {
+        $sJsObject = $this->getPageJsObject();
+
+        $oPermalinks = BxDolPermalinks::getInstance();
+
+        return array(
+            'js_object' => $sJsObject,
+            'url_view' => BX_DOL_URL_ROOT . $oPermalinks->permalink($this->aPageRebuild['url']),
+            'action_page_edit' => $this->sActionPageEdit,
+            'bx_if:can_delete' => array(
+                'condition' => (int)$this->aPageRebuild['deletable'] == 1,
+                'content' => array(
+                    'js_object' => $sJsObject
+                )
+            ),
+            'action_block_create' => $this->sActionBlockCreate,
+        );
+    }
+
+    protected function _getTmplVarsPageSettings($aPage = array(), $bCreate = true)
+    {
+    	$sJsObject = $this->getPageJsObject();
+
+        $aTmplParams = array(
+            'js_object' => $sJsObject,
+            'menu' => array(),
+            'html_settings_groups_id' => $this->aHtmlIds['settings_groups_id'],
+            'bx_repeat:settings_groups' => array(),
+        	'submit' => _t($bCreate ? '_adm_bp_btn_page_create' : '_adm_bp_btn_page_apply')
+        );
+        foreach($this->aPageSettings as $aSetting) {
+            //--- get menu items
+            $aTmplParams['menu'][$aSetting['name']] = array(
+                'name' => $aSetting['name'],
+                'icon' => '',
+                'onclick' => $sJsObject . '.onChangeSettingGroup(\'' . $aSetting['name'] . '\', this);',
+                'title' => $aSetting['title'],
+                'selected' => isset($aSetting['active']) && (int)$aSetting['active'] == 1
+            );
+
+            //--- get settings
+            $aTmplParams['bx_repeat:settings_groups'][] = array(
+                'html_settings_group_id' => $this->aHtmlIds['settings_group_id'] .  $aSetting['name'],
+                'bx_if:hidden' => array(
+                    'condition' => $aSetting['active'] != 1,
+                    'content' => array()
+                ),
+                'content' => $this->{'getSettings' . $this->getClassName($aSetting['name'])}($aPage, $bCreate)
+            );
+        }
+
+        $oMenu = new BxTemplStudioMenu(array('template' => 'menu_side.html', 'menu_items' => $aTmplParams['menu']));
+        $aTmplParams['menu'] = $oMenu->getCode();
+
+        return $aTmplParams;
+    }
+
+    protected function getIndexingDataForBlock($sType, $sContent)
+    {
+        $aBlock = array();   
+        switch($sType) {
+            case BX_DOL_STUDIO_BP_BLOCK_HTML:
+            case BX_DOL_STUDIO_BP_BLOCK_RAW:
+                $aBlock['text'] = trim(strip_tags($sContent));
+                $aBlock['text_updated'] = time();
+                break;
+        }
+        return $aBlock;
+    }
+}
+
+/** @} */

+ 638 - 0
upgrade/files/11.0.0-11.0.1/files/studio/template/scripts/BxBaseStudioDesigner.php

@@ -0,0 +1,638 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaView UNA Studio Representation classes
+ * @ingroup     UnaStudio
+ * @{
+ */
+
+class BxBaseStudioDesigner extends BxDolStudioDesigner
+{
+    protected $sLogoFormId = 'adm-dsg-logo-form';
+    protected $sLogoIframeId = 'adm-dsg-logo-iframe';
+
+    protected $sIconFormId = 'adm-dsg-icon-form';
+    protected $sIconIframeId = 'adm-dsg-icon-iframe';
+
+    protected $sCoverFormId = 'adm-dsg-cover-form';
+    protected $sCoverIframeId = 'adm-dsg-cover-iframe';
+    protected $sCoverStorage = BX_DOL_STORAGE_OBJ_IMAGES;
+	protected $sCoverTranscoder = 'sys_cover_preview';
+
+    protected $sSplashFormId = 'adm-dsg-splash-form';
+    protected $sSplashIframeId = 'adm-dsg-splash-iframe';
+    protected $sSplashEditorId = 'adm-dsg-splash-editor';
+
+    protected $sInjectionsFormId = 'adm-dsg-injections-form';
+    protected $sInjectionsIframeId = 'adm-dsg-injections-iframe';
+    protected $sInjectionsEditorId = 'adm-dsg-injections-editor';
+
+    protected $aPageCss;
+    protected $aPageJs;
+
+    function __construct($sPage = '')
+    {
+        parent::__construct($sPage);
+
+        $this->aPageCss = array('forms.css', 'designer.css');
+        $this->aPageJs = array('settings.js', 'designer.js');
+    }
+    function getPageCss()
+    {
+        return array_merge(parent::getPageCss(), $this->aPageCss);
+    }
+    function getPageJs()
+    {
+        return array_merge(parent::getPageJs(), $this->aPageJs);
+    }
+    function getPageJsObject()
+    {
+        return 'oBxDolStudioDesigner';
+    }
+    function getPageJsClass()
+    {
+    	return 'BxDolStudioDesigner';
+    }
+    public function getPageJsCode($aOptions = array(), $bWrap = true)
+    {
+    	$aOptions = array_merge(array(
+    		'sActionUrl' => $this->sManageUrl
+    	), $aOptions);
+
+    	return parent::getPageJsCode($aOptions, $bWrap);
+    }
+    function getPageMenu($aMenu = array(), $aMarkers = array())
+    {
+        $sJsObject = $this->getPageJsObject();
+
+        $aMenu = array();
+        $aMenuItems = array(
+            BX_DOL_STUDIO_DSG_TYPE_GENERAL => array('icon' => 'globe'),
+            BX_DOL_STUDIO_DSG_TYPE_LOGO => array('icon' => 'pencil-alt'),
+            BX_DOL_STUDIO_DSG_TYPE_ICON => array('icon' => 'far image'),
+            BX_DOL_STUDIO_DSG_TYPE_COVER => array('icon' => 'far file-image'),
+            BX_DOL_STUDIO_DSG_TYPE_SPLASH => array('icon' => 'far file-image'),
+            BX_DOL_STUDIO_DSG_TYPE_INJECTIONS => array('icon' => 'object-group'),
+            BX_DOL_STUDIO_DSG_TYPE_SETTINGS => array('icon' => 'cogs'),
+        );
+        foreach($aMenuItems as $sMenuItem => $aItem)
+            $aMenu[] = array(
+                'name' => $sMenuItem,
+                'icon' => $aItem['icon'],
+                'link' => BX_DOL_URL_STUDIO . 'designer.php?page=' . $sMenuItem,
+                'title' => _t('_adm_lmi_cpt_' . $sMenuItem),
+                'selected' => $sMenuItem == $this->sPage
+            );
+
+        return parent::getPageMenu($aMenu);
+    }
+    function getPageCode($bHidden = false)
+    {
+        $sMethod = 'get' . ucfirst($this->sPage);
+        if(!method_exists($this, $sMethod))
+            return '';
+
+        return $this->$sMethod();
+    }
+
+    protected function getGeneral()
+    {
+        $sJsObject = $this->getPageJsObject();
+        $oTemplate = BxDolStudioTemplate::getInstance();
+
+        $sResult = '';
+
+        $sTemplate = getParam('template');
+        $aTemplates = get_templates_array(true, false);
+
+        $aTmplVarsTemplates = array ();
+        foreach($aTemplates as $sUri => $aTemplate) {
+        	$sIcon = $this->getModuleIcon($aTemplate, 'store');
+	        $bIcon = strpos($sIcon, '.') === false && strcmp(substr($sIcon, 0, 10), 'data:image') != 0;
+
+            $aTmplVarsTemplates[] = array(
+                'uri' => $sUri,
+                'title' => htmlspecialchars_adv($aTemplate['title']),
+                'version' => htmlspecialchars_adv($aTemplate['version']),
+                'vendor' => htmlspecialchars_adv($aTemplate['vendor']),
+            	'bx_if:icon' => array (
+	                'condition' => $bIcon,
+	                'content' => array('icon' => $sIcon),
+	            ),
+                'bx_if:image' => array (
+	                'condition' => !$bIcon,
+	                'content' => array('icon_url' => $sIcon),
+	            ),
+                'bx_if:default' => array (
+                    'condition' => $sUri == $sTemplate,
+                    'content' => array (),
+                ),
+                'bx_if:make_default' => array (
+                    'condition' => $sUri != $sTemplate,
+                    'content' => array(
+                        'js_object' => $sJsObject,
+                        'uri' => $sUri
+                    ),
+                )
+            );
+        }
+
+        $sContent  = $sResult ? MsgBox($sResult, 10) : '';
+        $sContent .= $oTemplate->parseHtmlByName('templates.html', array(
+            'bx_repeat:templates' => $aTmplVarsTemplates,
+        ));
+
+        return $oTemplate->parseHtmlByName('designer.html', array(
+            'content' => $sContent,
+        	'js_content' => $this->getPageJsCode()
+        ));
+    }
+
+    protected function getLogo()
+    {
+        $oTemplate = BxDolStudioTemplate::getInstance();
+
+        $aForm = array(
+            'form_attrs' => array(
+                'id' => $this->sLogoFormId,
+                'name' => $this->sLogoFormId,
+                'action' => $this->sManageUrl,
+                'method' => 'post',
+                'enctype' => 'multipart/form-data',
+                'target' => $this->sLogoIframeId
+            ),
+            'params' => array(
+                'db' => array(
+                    'table' => '',
+                    'key' => '',
+                    'uri' => '',
+                    'uri_title' => '',
+                    'submit_name' => 'save'
+                ),
+            ),
+            'inputs' => array(
+                'page' => array(
+                    'type' => 'hidden',
+                    'name' => 'page',
+                    'value' => $this->sPage
+                ),
+                'image' => array(
+                    'type' => 'image_uploader',
+                    'name' => 'image',
+                    'caption' => _t('_adm_dsg_txt_upload_image'),
+                    'caption_preview' => _t('_adm_dsg_txt_upload_image_preview'),
+                    'ajax_action_delete' => $this->getPageJsObject() . '.deleteLogo()',
+                    'storage_object' => 'sys_images_custom',
+                    'transcoder_object' => 'sys_custom_images',
+                    'transcoder_image_width' => (int)getParam($this->sParamLogoWidth),
+                    'transcoder_image_height' => (int)getParam($this->sParamLogoHeight),
+                    'value' => (int)getParam($this->sParamLogo),
+                ),
+                'width' => array(
+                    'type' => 'text',
+                    'name' => 'width',
+                    'caption' => _t('_adm_stg_cpt_option_sys_site_logo_width'),
+                    'value' => (int)getParam($this->sParamLogoWidth),
+                    'db' => array (
+                        'pass' => 'Int',
+                    ),
+                ),
+                'height' => array(
+                    'type' => 'text',
+                    'name' => 'height',
+                    'caption' => _t('_adm_stg_cpt_option_sys_site_logo_height'),
+                    'value' => (int)getParam($this->sParamLogoHeight),
+                    'db' => array (
+                        'pass' => 'Int',
+                    ),
+                ),
+                'alt' => array(
+                    'type' => 'text',
+                    'name' => 'alt',
+                    'caption' => _t('_adm_dsg_txt_alt_text'),
+                    'info' => _t('_adm_dsg_dsc_alt_text'),
+                    'value' => getParam($this->sParamLogoAlt),
+                    'checker' => array(
+                        'func' => '',
+                        'params' => array(),
+                        'error' => ''
+                    ),
+                    'db' => array (
+                        'pass' => 'Xss',
+                    ),
+                ),
+                'save' => array(
+                    'type' => 'submit',
+                    'name' => 'save',
+                    'value' => _t('_adm_btn_designer_submit'),
+                )
+            )
+        );
+
+        $oForm = new BxTemplStudioFormView($aForm);
+        $oForm->initChecker();
+
+        if($oForm->isSubmittedAndValid()) {
+            echo $this->submitLogo($oForm);
+            exit;
+        }
+
+        return MsgBox(_t('_adm_dsg_txt_logo_redefinition')) . $oTemplate->parseHtmlByName('designer.html', array(
+            'content' => $this->getBlockCode(array(
+                'items' => $oTemplate->parseHtmlByName('dsr_logo.html', array('logo_iframe_id' => $this->sLogoIframeId, 'form' => $oForm->getCode())),
+            )),
+            'js_content' => $this->getPageJsCode()
+        ));
+    }
+
+    protected function getIcon()
+    {
+        $oTemplate = BxDolStudioTemplate::getInstance();
+
+        $sPreview = "";
+        $aTmplVars = array('bx_repeat:images' => array());
+
+        if(($iId = (int)getParam('sys_site_icon')) != 0) {
+            $aTranscoders = array(
+                BX_DOL_TRANSCODER_OBJ_ICON_APPLE => '_adm_dsg_txt_icon_apple',
+                BX_DOL_TRANSCODER_OBJ_ICON_FACEBOOK => '_adm_dsg_txt_icon_facebook',
+                BX_DOL_TRANSCODER_OBJ_ICON_FAVICON => '_adm_dsg_txt_icon_favicon'
+            );
+
+            foreach($aTranscoders as $sTranscoder => $sTitle) {
+                $oTranscoder = BxDolTranscoderImage::getObjectInstance($sTranscoder);
+
+                $sImageUrl = $oTranscoder->getFileUrl($iId);
+                if($sImageUrl === false) {
+                    setParam('sys_site_icon', 0);
+                    break;
+                }
+
+                $aFilterParams = $oTranscoder->getFilterParams('Resize');
+				$bFilterWidth = !empty($aFilterParams['w']);
+				$bFilterHeight = !empty($aFilterParams['h']);
+
+                $aTmplVars['bx_repeat:images'][] = array(
+                    'caption' => _t($sTitle),
+                    'url' => $sImageUrl,
+                	'bx_if:show_width' => array(
+                		'condition' => $bFilterWidth,
+                		'content' => array(
+                			'width' => $bFilterWidth ? (int)$aFilterParams['w'] : 0
+                		)
+                	),
+                	'bx_if:show_height' => array(
+                		'condition' => $bFilterHeight,
+                		'content' => array(
+                			'height' => $bFilterHeight ? (int)$aFilterParams['h'] : 0
+                		)
+                	), 
+                );
+            }
+
+            $sPreview = $oTemplate->parseHtmlByName('dsr_icon_preview.html', $aTmplVars);
+        }
+
+        $aForm = array(
+            'form_attrs' => array(
+                'id' => $this->sIconFormId,
+                'name' => $this->sIconFormId,
+                'action' => BX_DOL_URL_STUDIO . 'designer.php',
+                'method' => 'post',
+                'enctype' => 'multipart/form-data',
+                'target' => $this->sIconIframeId
+            ),
+            'params' => array(
+                'db' => array(
+                    'table' => '',
+                    'key' => '',
+                    'uri' => '',
+                    'uri_title' => '',
+                    'submit_name' => 'save'
+                ),
+            ),
+            'inputs' => array(
+                'page' => array(
+                    'type' => 'hidden',
+                    'name' => 'page',
+                    'value' => $this->sPage
+                ),
+                'preview' => array(
+                    'type' => 'custom',
+                    'name' => 'preview',
+                    'content' => $sPreview
+                ),
+                'image' => array(
+                    'type' => 'file',
+                    'name' => 'image',
+                    'caption' => _t('_adm_dsg_txt_upload_icon')
+                ),
+                'save' => array(
+                    'type' => 'submit',
+                    'name' => 'save',
+                    'value' => _t('_adm_btn_designer_submit'),
+                )
+            )
+        );
+
+        $oForm = new BxTemplStudioFormView($aForm);
+        $oForm->initChecker();
+
+        if($oForm->isSubmittedAndValid()) {
+            echo $this->submitIcon($oForm);
+            exit;
+        }
+
+        return $oTemplate->parseHtmlByName('designer.html', array(
+            'content' => $this->getBlockCode(array(
+				'items' => $oTemplate->parseHtmlByName('dsr_icon.html', array('icon_iframe_id' => $this->sIconIframeId, 'form' => $oForm->getCode())),
+			)),
+			'js_content' => $this->getPageJsCode()
+        ));
+    }
+
+	protected function getCover()
+    {
+    	$sJsObject = $this->getPageJsObject();
+        $oTemplate = BxDolStudioTemplate::getInstance();
+
+        $oDbSettings = new BxDolStudioSettingsQuery();
+
+        $aFormInputs = array();
+        $aTmplVarsCovers = array();
+        foreach($this->aCovers as $sCover => $aCover) {
+            $aSetting = array();
+            $oDbSettings->getOptions(array('type' => 'by_name', 'value' => $aCover['setting']), $aSetting, false);
+            if(empty($aSetting) || !is_array($aSetting))
+                continue;
+
+            $aFormInputs[$sCover] = array(
+                'type' => 'files',
+				'name' => $sCover,
+				'storage_object' => $this->sCoverStorage,
+				'images_transcoder' => $this->sCoverTranscoder,
+				'uploaders' => array('sys_std_crop_cover'),
+				'multiple' => false,
+				'content_id' => $aSetting['id'],
+				'ghost_template' => BxDolStudioTemplate::getInstance()->parseHtmlByName('uploader_fgt_cover.html', array(
+					'name' => $sCover,
+				)),
+				'caption' => _t('_adm_dsg_txt_upload_' . $sCover),
+				'db' => array (
+                    'pass' => 'Int',
+                )
+            );
+
+        	if(($iImageId = (int)getParam($aCover['setting'])) == 0)
+        		continue;
+
+			$sImageUrl = BxDolTranscoderImage::getObjectInstance($aCover['transcoder'])->getFileUrl($iImageId);
+            if($sImageUrl === false) {
+            	setParam($aCover['setting'], 0);
+                continue;
+			}
+
+			$aTmplVarsCovers[] = array(
+			    'image_id' => $iImageId,
+			    'content' => $oTemplate->parseHtmlByName($aCover['template'], array(
+			        'js_object' => $sJsObject,
+    				'type' => $sCover,
+    				'caption' => _t($aCover['title']),
+    				'image_id' => $iImageId,
+                    'bx_if:show_bg' => array(
+    					'condition' => !empty($sImageUrl),
+    					'content' => array(
+    						'image_url' => $sImageUrl
+    					)
+    				),
+                ))
+			);
+        }
+
+        $aForm = array(
+            'form_attrs' => array(
+                'id' => $this->sCoverFormId,
+                'name' => $this->sCoverFormId,
+                'action' => BX_DOL_URL_STUDIO . 'designer.php',
+                'method' => 'post',
+                'enctype' => 'multipart/form-data',
+                'target' => $this->sCoverIframeId
+            ),
+            'params' => array(
+                'db' => array(
+                    'table' => '',
+                    'key' => '',
+                    'uri' => '',
+                    'uri_title' => '',
+                    'submit_name' => 'save'
+                ),
+            ),
+            'inputs' => array(
+                'page' => array(
+                    'type' => 'hidden',
+                    'name' => 'page',
+                    'value' => $this->sPage
+                ),
+                'preview' => array(
+                    'type' => 'custom',
+                    'name' => 'preview',
+                    'content' => $oTemplate->parseHtmlByName('dsr_cover_preview.html', array(
+                		'bx_repeat:covers' => $aTmplVarsCovers
+                	))
+                ),
+
+                'save' => array(
+                    'type' => 'submit',
+                    'name' => 'save',
+                    'value' => _t('_adm_btn_designer_submit'),
+                )
+            )
+        );
+
+        $aForm['inputs'] = bx_array_insert_after($aFormInputs, $aForm['inputs'], 'preview');
+
+        $oForm = new BxTemplStudioFormView($aForm);
+        $oForm->initChecker();
+
+        if($oForm->isSubmittedAndValid()) {
+            echo $this->submitCover($oForm);
+            exit;
+        }
+
+        $this->aPageCss[] = 'cover.css';
+        return $oTemplate->parseHtmlByName('designer.html', array(
+            'content' => $this->getBlockCode(array(
+				'items' => $oTemplate->parseHtmlByName('dsr_cover.html', array('iframe_id' => $this->sCoverIframeId, 'form' => $oForm->getCode())),
+			)),
+			'js_content' => $this->getPageJsCode()
+        ));
+    }
+
+    protected function getSplash()
+    {
+    	$oTemplate = BxDolStudioTemplate::getInstance();
+
+		$aForm = array(
+			'form_attrs' => array(
+				'id' => $this->sSplashFormId,
+				'name' => $this->sSplashFormId,
+				'action' => BX_DOL_URL_STUDIO . 'designer.php',
+				'method' => 'post',
+				'enctype' => 'multipart/form-data',
+				'target' => $this->sSplashIframeId
+			),
+			'params' => array(
+				'db' => array(
+					'table' => '',
+					'key' => '',
+					'uri' => '',
+					'uri_title' => '',
+					'submit_name' => 'save'
+				),
+			),
+			'inputs' => array(
+				'page' => array(
+					'type' => 'hidden',
+					'name' => 'page',
+					'value' => $this->sPage
+				),
+				'enabled' => array(
+					'type' => 'checkbox',
+					'name' => 'enabled',
+					'caption' => _t('_adm_dsg_txt_splash_enabled'),
+					'value' => 'on',
+					'checked' => getParam('sys_site_splash_enabled') == 'on',
+					'db' => array (
+                        'pass' => 'Xss',
+                    ),
+				),
+				'code' => array(
+                    'type' => 'textarea',
+                    'code' => true,
+                    'name' => 'code',
+					'caption' => '',
+					'value' => getParam('sys_site_splash_code'),
+				),
+				'save' => array(
+					'type' => 'submit',
+					'name' => 'save',
+					'value' => _t('_adm_btn_designer_submit'),
+				)
+			)
+		);
+
+		$oForm = new BxTemplStudioFormView($aForm, $oTemplate);
+		$oForm->initChecker();
+
+		if($oForm->isSubmittedAndValid()) {
+			echo $this->submitSplash($oForm);
+			exit;
+		}
+		
+		$oTemplate->addJs(array('codemirror/codemirror.min.js'));
+        $oTemplate->addCss(BX_DIRECTORY_PATH_PLUGINS_PUBLIC . 'codemirror/|codemirror.css');
+		return $oTemplate->parseHtmlByName('designer.html', array(
+			'content' => $this->getBlockCode(array(
+				'items' => $oTemplate->parseHtmlByName('dsr_splash.html', array(
+					'warning' => MsgBox(_t('_adm_dsg_dsc_splash_warning')),
+					'splash_iframe_id' => $this->sSplashIframeId, 
+					'form' => $oForm->getCode()
+				)),
+			)),
+			'js_content' => $this->getPageJsCode(array(
+				'sCodeMirror' => 'textarea[name=code]'
+			)),
+		));
+    }
+
+	protected function getInjections()
+    {
+    	$oTemplate = BxDolStudioTemplate::getInstance();
+
+		$aForm = array(
+			'form_attrs' => array(
+				'id' => $this->sInjectionsFormId,
+				'name' => $this->sInjectionsFormId,
+				'action' => BX_DOL_URL_STUDIO . 'designer.php',
+				'method' => 'post',
+				'enctype' => 'multipart/form-data',
+				'target' => $this->sInjectionsIframeId
+			),
+			'params' => array(
+				'db' => array(
+					'table' => '',
+					'key' => '',
+					'uri' => '',
+					'uri_title' => '',
+					'submit_name' => 'save'
+				),
+			),
+			'inputs' => array(
+				'page' => array(
+					'type' => 'hidden',
+					'name' => 'page',
+					'value' => $this->sPage
+				),
+				'sys_head' => array(
+                    'type' => 'textarea',
+                    'code' => true,
+                    'name' => 'sys_head',
+					'caption' => _t('_adm_dsg_txt_inj_head'),
+					'info' => _t('_adm_dsg_txt_inj_head_inf'),
+					'value' => $this->oDb->getOne("SELECT `data` FROM `sys_injections` WHERE `name`='sys_head'"),
+				),
+				'sys_body' => array(
+                    'type' => 'textarea',
+                    'code' => true,
+                    'name' => 'sys_body',
+					'caption' => _t('_adm_dsg_txt_inj_body'),
+					'info' => _t('_adm_dsg_txt_inj_body_inf'),
+					'value' => $this->oDb->getOne("SELECT `data` FROM `sys_injections` WHERE `name`='sys_body'"),
+				),
+				'save' => array(
+					'type' => 'submit',
+					'name' => 'save',
+					'value' => _t('_adm_btn_designer_submit'),
+				)
+			)
+		);
+
+		$oForm = new BxTemplStudioFormView($aForm, $oTemplate);
+		$oForm->initChecker();
+
+		if($oForm->isSubmittedAndValid()) {
+			echo $this->submitInjections($oForm);
+			exit;
+		}
+
+        $oTemplate->addJs(array('codemirror/codemirror.min.js'));
+        $oTemplate->addCss(BX_DIRECTORY_PATH_PLUGINS_PUBLIC . 'codemirror/|codemirror.css');
+
+		return $oTemplate->parseHtmlByName('designer.html', array(
+			'content' => $this->getBlockCode(array(
+				'items' => $oTemplate->parseHtmlByName('dsr_injections.html', array(
+					'warning' => '',
+					'splash_iframe_id' => $this->sInjectionsIframeId, 
+					'form' => $oForm->getCode()
+				)),
+			)),
+			'js_content' => $this->getPageJsCode(array(
+				'sCodeMirror' => 'textarea[name=sys_head],textarea[name=sys_body]'
+			))
+		));
+    }
+
+    protected function getSettings()
+    {
+        $oPage = new BxTemplStudioSettings(BX_DOL_STUDIO_STG_TYPE_SYSTEM, BX_DOL_STUDIO_STG_CATEGORY_TEMPLATES);
+
+        return BxDolStudioTemplate::getInstance()->parseHtmlByName('designer.html', array(
+            'content' => $oPage->getPageCode(),
+        	'js_content' => $this->getPageJsCode()
+        ));
+    }
+}
+
+/** @} */

+ 24 - 0
upgrade/files/11.0.0-11.0.1/files/template/comment.html

@@ -0,0 +1,24 @@
+<li id="cmt__id__" class="__style_prefix____class__ bx-def-padding-top bx-def-margin-bottom">
+    <a id="__anchor__" class="__style_prefix__-anchor"></a>
+    <div class="__style_prefix__-cnt__class_cnt__">
+        <div class="__style_prefix__-blink-plate"></div>
+        <div class="__style_prefix__-avatar bx-def-thumb-size">__author_unit__</div>
+        <div class="__style_prefix__-info bx-def-thumb-size-min-height bx-def-thumb-size-left-indent">
+            <div class="__style_prefix__-head bx-clearfix">
+                <bx_if:show_reply_to>
+                    <span class="__style_prefix__-reply-to">
+                        <a href="__par_cmt_link__" title="__par_cmt_title__">
+                            <i class="sys-icon reply"></i> <span><bx_text:_in_reply_to /></span> __par_cmt_author_unit__ <span>__par_cmt_author__</span>
+                        </a>
+                    </span>
+                </bx_if:show_reply_to>
+                <bx_if:meta><span class="__style_prefix__-meta bx-base-general-unit-meta">__meta__</span></bx_if:meta>
+            </div>
+            <div class="__style_prefix__-cont bx-def-margin-sec-topbottom">
+                <div class="__style_prefix__-cont-cnt">__content__</div>
+            </div>
+            __actions__
+        </div>
+    </div>
+    <ul class="cmts cmts-margin">__replies__</ul>
+</li>

+ 10 - 0
upgrade/files/11.0.0-11.0.1/files/template/comments_uploader_nfw.html

@@ -0,0 +1,10 @@
+<div id="bx-uploader-file-{storage_object}-{file_id}" class="bx-uploader-ghost cmts-uploader-file bx-def-margin-sec-top bx-def-margin-sec-left-auto">
+    <input type="hidden" name="__name__[]" value="{file_id}" />
+    <div class="cmts-uploader-file-preview bx-def-round-corners bx-def-margin-sec-right">
+        <img src="{file_icon}" class="bx-def-round-corners bx-base-general-icon bx-base-general-icon-{file_id}" />
+    </div>
+    <div class="cmts-uploader-file-title bx-def-margin-thd-top bx-clearfix">{file_title}</div>
+    <div class="cmts-uploader-file-actions bx-def-margin-thd-top bx-clearfix">
+        <a class="bx-btn bx-btn-small bx-def-margin-sec-right" href="javascript:void(0);" onclick="{js_instance_name}.deleteGhost('{file_id}');"><bx_text:_Delete /></a>
+    </div>
+</div>

+ 117 - 0
upgrade/files/11.0.0-11.0.1/files/template/css/acl.css

@@ -0,0 +1,117 @@
+/*-- Profile Membership block --*/
+div.bx-acl-membeship .bx-acl-m-thumbnail .sys-icon {
+	width: 100px;
+	height: 100px;
+
+	line-height: 100px;
+	font-size: 96px;
+}
+
+div.bx-acl-membeship .bx-acl-m-thumbnail img {
+	width: 100px;
+	height: 100px;
+}
+
+/*-- Profile Membership Stats block --*/
+div.bx-pms-si-graph {
+	display: table;
+	width: 100%;
+}
+div.bx-pms-si-graph .bx-pms-sig-bar,
+div.bx-pms-si-graph .bx-pms-sig-text {
+	display: table-cell;
+}
+div.bx-pms-si-graph .bx-pms-sig-bar {
+    -webkit-animation-fill-mode: both;
+    -moz-animation-fill-mode: both;
+    -o-animation-fill-mode: both;
+    animation-fill-mode: both;
+
+    -webkit-transform-origin: left;
+    -moz-transform-origin: left;
+    -ms-transform-origin: left;
+    -o-transform-origin: left;
+    transform-origin: left;
+
+    -webkit-animation: stats-bar 1000ms ease-out;
+    -o-animation: stats-bar 1000ms ease-out;
+    animation: stats-bar 1000ms ease-out;
+
+    background: #2196F3;
+    background: -moz-linear-gradient(left,#2196F3 0%,#1976D2 100%);
+    background: -webkit-gradient(linear,left top,right top,color-stop(0%,#2196F3),color-stop(100%,#1976D2));
+    background: -webkit-linear-gradient(left,#2196F3 0%,#1976D2 100%);
+    background: -o-linear-gradient(left,#2196F3 0%,#1976D2 100%);
+    background: -ms-linear-gradient(left,#2196F3 0%,#1976D2 100%);
+    background: linear-gradient(to right,#2196F3 0%,#1976D2 100%);
+    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#2196F3',endColorstr='#1976D2',GradientType=1);
+}
+div.bx-pms-si-graph .bx-pms-sig-text {
+	white-space: nowrap;
+}
+
+/*--- Animation ---*/
+@-webkit-keyframes stats-bar {
+    from {
+        -webkit-transform:scaleX(0);
+        -moz-transform:scaleX(0);
+        -ms-transform:scaleX(0);
+        -o-transform:scaleX(0);
+        transform:scaleX(0)
+    }
+    to {
+        -webkit-transform:scaleX(1);
+        -moz-transform:scaleX(1);
+        -ms-transform:scaleX(1);
+        -o-transform:scaleX(1);
+        transform:scaleX(1)
+    }
+}
+@-moz-keyframes stats-bar {
+    from {
+        -webkit-transform:scaleX(0);
+        -moz-transform:scaleX(0);
+        -ms-transform:scaleX(0);
+        -o-transform:scaleX(0);
+        transform:scaleX(0)
+    }
+    to {
+        -webkit-transform:scaleX(1);
+        -moz-transform:scaleX(1);
+        -ms-transform:scaleX(1);
+        -o-transform:scaleX(1);
+        transform:scaleX(1)
+    }
+}
+@-ms-keyframes stats-bar {
+    from {
+        -webkit-transform:scaleX(0);
+        -moz-transform:scaleX(0);
+        -ms-transform:scaleX(0);
+        -o-transform:scaleX(0);
+        transform:scaleX(0)
+    }
+    to {
+        -webkit-transform:scaleX(1);
+        -moz-transform:scaleX(1);
+        -ms-transform:scaleX(1);
+        -o-transform:scaleX(1);
+        transform:scaleX(1)
+    }
+}
+@keyframes stats-bar {
+    from {
+        -webkit-transform:scaleX(0);
+        -moz-transform:scaleX(0);
+        -ms-transform:scaleX(0);
+        -o-transform:scaleX(0);
+        transform:scaleX(0)
+    }
+    to {
+        -webkit-transform:scaleX(1);
+        -moz-transform:scaleX(1);
+        -ms-transform:scaleX(1);
+        -o-transform:scaleX(1);
+        transform:scaleX(1)
+    }
+}

+ 535 - 0
upgrade/files/11.0.0-11.0.1/files/template/css/cmts.css

@@ -0,0 +1,535 @@
+    /*--- General Styles ---*/
+    div.cmts {}
+
+    ul.cmts {
+    	position: relative;
+        margin: 0px;
+        padding: 0px;
+        list-style: none;
+    }
+    ul.cmts-margin,
+    div.cmt-reply-margin {
+    	margin-top: 20px;
+    	margin-top: 1.2rem;
+
+		padding-left: 60px;
+    	padding-left: 3.75rem;
+    }
+    li.cmt {
+    	position: relative;
+    }
+    li.cmt-no .bx-msg-box-container {
+        margin-top: 0px;
+        margin-bottom: 0px;
+    }
+    li.cmt-err {
+        color: red;
+    }
+    li.cmt-hidden {
+       display: none;
+    }
+    li.cmt-replacement {
+    	position: relative;
+		height: 30px;
+		height: 1.875rem;
+		background-color: #ededed;
+		text-indent: 10px;
+    	text-indent: 0.5rem;
+		line-height: 30px;
+    	line-height: 1.875rem;
+    	overflow: hidden;
+    }
+    li.cmt-blink div.cmt-blink-plate {
+		display: block;
+    }
+    div.cmt-cnt {
+		position: relative;
+    }
+
+	/*--- More ---*/
+    li.cmt-more,
+	li.cmt-more-root {
+		text-transform:uppercase;
+	}
+
+	div.cmt-more-cont {
+		font-weight: bold;
+	}
+
+	
+    li.cmt-more-root div.cmt-more-cont {
+    	position: relative;
+    }
+    li.cmt-more-root .bx-btn {
+    	position: relative;
+		float: none;
+    	display: inline-block;
+    	text-transform: uppercase;
+    }    
+
+    /*--- Comment ---*/
+	div.cmt-avatar {
+		position: absolute;
+		top: 0px;
+		left: 0px;
+    	text-align: center;
+    }
+    div.cmt-avatar .sys-icon {
+    	font-size: 60px;
+		font-size: 3.75rem;
+    }
+    div.cmt-info {
+    	position: realtive;
+    	overflow: hidden;
+    }
+    div.cmt-blink-plate {
+        position: absolute;
+        display: none;
+        top: -10px;
+        left: -10px;
+        width: 100%;
+        height: 100%;
+        padding: 10px;
+        background-color: rgb(255, 244, 213);
+        background-color: rgba(255, 234, 193, 0.5);
+    }
+    div.cmt-head {
+        text-align: left;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+        overflow: hidden;
+    }
+    span.cmt-reply-to a {
+        text-decoration: none;
+    }
+    span.cmt-reply-to a > i,
+    span.cmt-reply-to a > span,
+    span.cmt-reply-to a > div {
+        vertical-align: middle;
+    }
+    span.cmt-reply-to a .bx-base-pofile-unit-wo-info {
+        display: inline-block;
+    }
+    span.cmt-reply-to a .bx-base-pofile-unit-wo-info .bx-def-unit-img .bx-base-pofile-unit-thumb {
+        display: block;
+        border-width: 1px;
+    }
+    span.cmt-reply-to a .bx-base-pofile-unit-wo-info .bx-base-pofile-unit-online {
+        display: none;
+    }
+    span.cmt-reply-to a .bx-def-icon-size {
+        width: 1.0rem;
+        height: 1.0rem;
+        line-height: 0.875rem;
+        font-size: 0.6rem;
+    }
+    span.cmt-posted-ago {
+        text-transform: lovercase;
+    }
+    span.cmt-points {
+        text-transform: lovercase;
+    }
+
+    /*--- unit meta ---*/
+    .cmt-meta.bx-base-general-unit-meta {
+        white-space: nowrap;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        text-align: left;
+    }
+    .cmt-meta.bx-base-general-unit-meta .bx-menu-custom.bx-menu-hor {
+        margin: 0;
+    }
+
+    /*--- Comment Body ---*/
+    div.cmt-cont {
+        position: relative;
+    	text-align: left;
+    }
+    div.cmt-cont .bullet {
+        padding: 0px 5px;
+        padding: 0px 0.3rem;
+    }
+    div.cmt-cont .cmt-body > p:first-child {
+        margin-top: 0;
+        margin-bottom: 0;
+    }
+    div.cmt-cont .cmt-body img {
+        max-width: 100%;
+    	height: auto;
+    }
+    div.cmt-cont .cmt-post-reply .bx-form-element-wrapper {
+        margin-top: 10px;
+    	margin-top: 0.5rem;
+    }
+    div.cmt-cont .cmt-post-reply .bx-form-element-wrapper:first-child {
+        margin-top: 0px;
+    }
+    span.cmt-body-see-more a {
+        font-weight: bold;
+    }
+    div.cmt-reactions .bx-vote-counter-wrapper {
+        line-height: 2.0rem;
+        font-size: 1.2rem;
+    }
+    div.cmt-attached-preview {
+        overflow:hidden;
+    }
+    div.cmt-attached-preview,
+    div.cmt-attached-preview img {
+    	width: 100px;
+    	width: 6.25rem;
+    	height: 100px;
+    	height: 6.25rem;
+    }
+    div.cmt-attached-preview {
+    	position: relative;
+    	float: left;
+	}
+    div.cmt-attached-preview img {
+        background-position: center center;
+        background-repeat: no-repeat;
+        background-size: contain;
+    }
+
+    /*--- Comment Actions ---*/
+    div.cmt-actions {
+        position: relative;
+        text-align: left;
+    }
+    div.cmt-meta-ago,
+    div.cmt-comment-manage-link, 
+    div.cmt-actions ul.bx-menu-hor,
+    div.cmt-points-wrp {
+        position: relative;
+        float: left;
+    }
+    div.cmt-actions li .bx-vote {
+       margin-right: 1.25rem;
+    }
+    div.cmt-comment-manage .bx-menu-ver li {
+		white-space: nowrap;
+    }
+
+    /*--- Add Comment/Reply Styles ---*/
+    div.cmt-reply {
+		position: relative;
+    }
+	div.cmt-post-reply {
+        position: relative;
+    }
+
+    /*--- Add Comment/Reply Form Styles ---*/
+    form.cmt-post-reply .bx-form-input-textarea {
+		height: 64px;
+    	height: 4rem;
+    }
+    form.cmt-post-reply div#bx-form-element-cmt_image,
+    form.cmt-post-reply .bx-form-input-files-uploaders {
+		margin-top: 0px;
+    }
+
+    a.cmts-uploader-button {
+		position: absolute;
+    	z-index: 1;
+    	right: 0px;
+
+    	text-decoration: none;
+    }
+    div.cmts-uploader-file {
+        position: relative;
+    	float: left;
+    }
+    div.cmts-uploader-file-preview {
+        overflow: hidden;
+    }
+    div.cmts-uploader-file-preview,
+    div.cmts-uploader-file-preview img,
+    div.cmts-uploader-file-title {
+        width: 100px;
+    	width: 6.25rem;
+    }
+    div.cmts-uploader-file-preview,
+    div.cmts-uploader-file-preview img {
+        height: 100px;
+    	height: 6.25rem;
+    }
+    div.cmts-uploader-file-preview img {
+        background-position: center center;
+        background-repeat: no-repeat;
+    }
+    div.cmts-uploader-file-title {
+        overflow: hidden;
+        text-overflow: ellipsis;
+    }
+    
+    /*--- Minified Post Form ---*/
+    div.cmt-reply.cmt-reply-min div.cmt-body {
+        display: none;
+    }
+    div.cmt-reply div.cmt-body-min {
+        position: relative;
+        display: none;
+    }
+    div.cmt-reply.cmt-reply-min div.cmt-body-min {
+        display: block;
+    }
+    div.cmt-reply div.cmt-body-min .cmt-bm-user {
+        position: absolute;
+        top: 0;
+        left: 0;
+    }
+    div.cmt-reply div.cmt-body-min .cmt-bm-content {
+        display: -webkit-flex;
+        display: flex;
+        -webkit-align-items: center;
+        align-items: center;
+        -webkit-justify-content: stretch;
+        justify-content: stretch;
+    }
+    div.cmt-reply div.cmt-body-min .cmt-bm-content .bx-form-element-wrapper {
+        position: relative;
+        width: 100%;
+    }
+
+    /*--- Live Update Alert Styles ---*/
+    div.cmt-lu-button {
+        position: relative;
+        text-align: center;
+    }
+    @supports(position:sticky) {
+        div.cmt-lu-button {
+            position: sticky;
+            top: 1rem;
+            z-index: 1;
+        }
+    }
+    @supports(position:-webkit-sticky) {
+        div.cmt-lu-button {
+            position: -webkit-sticky;
+            top: 1rem;
+            z-index: 1;
+        }
+    }
+    div.cmt-lu-button .bx-btn {
+        display: inline-block;
+        float: none;
+    }
+
+    /*--- Live Update Notifications Styles ---*/
+    .cmt-lu-notif {
+        position: relative;
+        display: block;
+    }
+    div.cmt-lu-notif-cnt {
+        position: relative;
+    }
+    div.cmt-lu-notif-avatar {
+        position: absolute;
+        top: 0px;
+        left: 0px;
+        text-align: center;
+    }
+    div.cmt-lu-notif-info {
+        position: realtive;
+        display: -webkit-flex;
+        display: flex;
+        -webkit-align-items: center;
+        align-items: center;
+    }
+    div.cmt-lu-notifi-username {
+        white-space: nowrap;
+        text-overflow: ellipsis;
+        overflow: hidden;
+    }
+
+/*--- Comment Element Styles ---*/
+div.cmt div.cmt-element-holder,
+div.cmt div.cmt-counter-holder {
+    position: relative;
+    float: left;
+}
+span.cmt span.cmt-element-holder,
+span.cmt span.cmt-counter-holder {
+    position: relative;
+    display: inline-block;
+    vertical-align: middle;
+}
+
+.cmt .cmt-element-holder,
+.cmt .cmt-counter-holder {
+    line-height: 20px;
+    line-height: 1.25rem;
+}
+
+.cmt-button .cmt-element-holder,
+.cmt-button .cmt-counter-holder {
+    line-height: 36px;
+    line-height: 2.2rem;
+}
+
+.cmt-button-small .cmt-element-holder,
+.cmt-button-small .cmt-counter-holder {
+    line-height: 24px;
+    line-height: 1.6rem;
+}
+
+.cmt .cmt-element-holder .cmt-do-comment.cmt-disabled,
+.cmt .cmt-element-holder .cmt-do-comment.cmt-disabled .sys-icon {
+    color: #ddd;
+}
+
+.cmt-button .cmt-counter-holder,
+.cmt-button-small .cmt-counter-holder {
+    font-size: 1.5rem;
+}
+
+.cmt-link .cmt-counter-holder.bx-btn-border {
+    border-width: 0px;
+}
+
+/* +++ Comment Element: Counter +++ */
+.cmt-counter {
+    position: relative;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-align-items: center;
+    align-items: center;
+    -webkit-flex-wrap:nowrap;
+    flex-wrap:nowrap;
+}
+.cmt-counter > * {
+    position: relative;
+}
+.cmt-counter div.cmt-counter-icon,
+.cmt-counter div.bx-base-pofile-unit-wo-info {
+    margin-left: -0.5rem;
+}
+.cmt-counter div.cmt-counter-icon:first-child,
+.cmt-counter div.bx-base-pofile-unit-wo-info:first-child {
+    margin-left: 0 !important;
+}
+.bx-media-phone .cmt-counter div.cmt-counter-icon,
+.bx-media-phone .cmt-counter div.bx-base-pofile-unit-wo-info {
+    margin-left: -1.0rem;
+}
+.bx-media-tablet .bx-layout-column-thin .cmt-counter div.bx-base-pofile-unit-wo-info,
+.bx-media-tablet .bx-layout-column-half .cmt-counter div.bx-base-pofile-unit-wo-info {
+    display: none;
+}
+.cmt-counter div.cmt-counter-icon {
+    box-sizing: border-box;
+    z-index: 2;
+
+    margin-right: 0.5rem;
+
+    background-color: rgba(232, 232, 232, 1.0);
+    border: 1px solid #ffffff;
+    border-radius: 1rem;
+
+    text-align: center;
+}
+.cmt-counter div.cmt-counter-icon .sys-icon {
+    vertical-align: top;
+}
+.cmt-counter .bx-def-icon-size {
+    width: 2.0rem;
+    height: 2.0rem;
+
+    font-size: 1.0rem;
+}
+.cmt-counter p.bx-base-pofile-unit-thumb.bx-def-icon-size {
+    display: -webkit-flex;
+    display: flex;
+
+    -webkit-justify-content: center;
+    justify-content: center;
+
+    -webkit-align-items: center;
+    align-items: center;
+
+    font-size: 1.0rem;
+}
+.cmt-counter .bx-def-icon-size-min-height {
+    min-height: 1.0rem;
+}
+
+.cmt-counter .bx-def-icon-size-max-height {
+    max-height: 1.0rem;
+}
+
+.cmt-counter .bx-def-icon-size-left-indent {
+    margin-left: 1.25rem;
+}
+
+.cmt-counter .bx-def-unit-img .bx-base-pofile-unit-thumb {
+    display: block;
+    border-width: 1px;
+}
+
+.cmt-counter .bx-base-pofile-unit-online {
+    display: none;
+}
+
+
+/*----------------------*/
+/*--- Reviews Styles ---*/
+/*----------------------*/
+
+/*--- Mood Element Styles ---*/
+div.cmt-mood {
+    position: relative;
+    display: inline-block;
+}
+div.cmt-mood-underlay {}
+div.cmt-mood-slider {
+    position: absolute;
+    z-index: 1;
+    top: 0px;
+    left: 0px;
+    white-space: nowrap;
+    overflow: hidden;
+}
+div.cmt-mood a.cmt-mood-button,
+div.cmt-mood i.cmt-mood-star {
+    position: relative;
+    display: inline-block;
+    vertical-align: top;
+    height: 16px;
+    width: 16px;
+    margin: 0;
+    padding: 0;
+    text-align: center;
+}
+div.cmt-mood-underlay i.cmt-mood-star {
+    color: rgba(255, 204, 102, 1);
+}
+div.cmt-mood-slider i.cmt-mood-star,
+div.cmt-mood-buttons a:hover i.cmt-mood-star {
+    color: rgba(255, 100, 0, 1);
+}
+
+div.cmt-mood-buttons {
+    position: absolute;
+    z-index: 2;
+    top: 0px;
+    left: 0px;
+}
+div.cmt-mood-buttons a:hover {
+	text-decoration: none;
+}
+div.cmt-mood-buttons i.cmt-mood-star {
+    color: transparent;
+}
+.cmt-meta .bx-badge-compact {
+    display: inline-block;
+}
+
+/*--- Rating Block Styles ---*/
+.cmts-box-rating div.cmt-mood i.cmt-mood-star,
+.cmt-rating-element div.cmt-mood i.cmt-mood-star,
+.cmt-rating-element div.cmt-mood a.cmt-mood-button {
+    height: 24px;
+    width: 24px;
+    font-size: 20px;
+}

+ 637 - 0
upgrade/files/11.0.0-11.0.1/files/template/css/forms.css

@@ -0,0 +1,637 @@
+
+.bx-form-advanced-wrapper .bx-form-section-wrapper:first-child {
+    margin-top:0;
+}
+
+.bx-form-advanced-wrapper .bx-form-section-divider:first-child {
+    border-top:none; 
+    padding-top:0; 
+}
+
+.bx-form-advanced-wrapper .bx-form-section-divider:first-child .bx-form-section-content {
+    padding-top:0; 
+}
+
+.bx-form-advanced-wrapper .bx-form-section-wrapper:last-child .bx-form-section-content {
+    padding-bottom:0;
+}
+
+.bx-form-advanced-wrapper .bx-form-section-wrapper:first-child .bx-form-row-view-wrapper:first-child {
+    padding-top:0;
+}
+
+.bx-form-section-wrapper {
+    position:relative;
+}
+
+.bx-form-section {
+    padding:0px;
+}
+
+.bx-form-section {
+    margin:0;
+    border: none;
+}
+
+.bx-form-section-header legend {
+    position:relative;
+    text-transform:uppercase;
+}
+
+.bx-form-section-header legend u {
+    cursor:pointer;
+}
+
+.bx-form-section-header span {
+	white-space: nowrap;
+	text-transform: none;
+	font-size: 0.75rem;
+}
+
+.bx-form-section-hidden .bx-form-section-content {
+    display:none;
+}
+
+.bx-form-element-wrapper:first-child {
+    margin-top:0px;
+}
+
+.bx-form-caption {
+    margin-bottom:5px;
+}
+
+.bx-form-required {
+    color:red;
+}
+
+.bx-form-warn {
+    color:red;
+    font-style:italic;
+}
+
+.bx-form-info {
+
+}
+
+/* section toggler */
+
+.bx-form-collapsable .bx-form-section-toggler {
+
+    position:absolute;
+    left: -2px;
+    top: 2px;
+
+    width:20px;
+    height:20px;
+
+    text-decoration:none;
+
+    -moz-transform: rotate(90deg);
+    -webkit-transform: rotate(90deg);
+    -ms-transform: rotate(90deg);
+    -o-transform: rotate(90deg);
+
+    -webkit-transition: all 0.5s ease;
+    -moz-transition: all 0.5s ease;
+    -ms-transition: all 0.5s ease;
+    -o-transition: all 0.5s ease;
+}
+
+.bx-form-collapsed .bx-form-section-toggler {
+    -moz-transform: rotate(0deg);
+    -webkit-transform: rotate(0deg);
+    -ms-transform: rotate(0deg);
+    -o-transform: rotate(0deg);
+}
+
+.bx-form-section-toggler .sys-icon {
+    display: inline-block;
+    vertical-align: top;
+    margin: 0 0 0 5px;
+    font-size:0.875rem;
+    line-height:1.2rem;
+}
+
+/* form inputs */
+.bx-form-input-wrapper {
+    position: relative;
+}
+
+.bx-form-input-disabled {}
+
+.bx-form-input-slider,
+.bx-form-input-doublerange,
+.bx-form-input-select_multiple,
+.bx-form-input-select,
+.bx-form-input-radio_set,
+.bx-form-input-checkbox_set,
+.bx-form-input-number,
+.bx-form-input-time,
+.bx-form-input-datepicker,
+.bx-form-input-datetime,
+.bx-form-input-textarea,
+.bx-form-input-wrapper-password,
+.bx-form-input-text {
+
+    height:40px;
+    height:2.5rem;
+    width:100%;
+    
+    -moz-box-sizing: border-box;
+    -ms-box-sizing: border-box;
+    -webkit-box-sizing: border-box;
+    box-sizing: border-box;
+
+    padding:7px 3px 7px 3px;
+    padding:0.2rem 0.4rem;
+
+    vertical-align:middle;
+
+    border:2px solid #79bd9a;
+    border:0.125rem solid #79bd9a;
+	
+    -webkit-border-radius:0.3rem;
+    -moz-border-radius:0.3rem;	
+    border-radius:0.3rem;
+
+    outline:none;
+
+    background-color: #fff;
+
+    -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=90)"; 
+    filter: alpha(opacity=90); 
+    -moz-opacity: 0.9;
+    -khtml-opacity: 0.9;
+    opacity: 0.9;    
+}
+
+.bx-form-input-slider:focus,
+.bx-form-input-doublerange:focus,
+.bx-form-input-number:focus,
+.bx-form-input-time:focus,
+.bx-form-input-datepicker:focus,
+.bx-form-input-datetime:focus,
+.bx-form-input-textarea:focus,
+.bx-form-input-text:focus,
+.bx-form-input-wrapper-password.bx-form-input-focus {
+    border-color:#6caa8a;
+
+    -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)"; 
+    filter: alpha(opacity=100); 
+    -moz-opacity: 1.0;
+    -khtml-opacity: 1.0;
+    opacity: 1.0;
+
+    -webkit-transition: all 0.2s linear;
+    -moz-transition: all 0.2s linear;
+    -o-transition: all 0.2s linear;
+    transition: all 0.2s linear;
+}
+.bx-form-input-wrapper-password {
+    padding: 0 2.125rem 0 0;
+}
+.bx-form-input-wrapper-password > a {
+    position: absolute;
+    z-index: 1000;
+    
+    display: -webkit-flex;
+    display: flex;
+    -webkit-align-items: center;
+    align-items: center;
+    -webkit-justify-content: center;
+    justify-content: center;
+
+    top: 0;
+    right: 0;    
+    height: 100%;
+
+    margin: 0 0.5rem;
+}
+.bx-form-input-password {
+    position: relative;
+
+    -moz-box-sizing: border-box;
+    -ms-box-sizing: border-box;
+    -webkit-box-sizing: border-box;
+    box-sizing: border-box;
+
+    width: 100%;
+    height: 100%;
+
+    margin: 0;
+    padding: 7px 3px 7px 3px;
+    padding: 0.2rem 0.4rem;
+
+    border: 0;
+    background-color: transparent;
+}
+.bx-form-input-password:focus {
+    border-color: transparent;
+    outline: none;
+    box-shadow: none;
+}
+
+.bx-form-input-select_multiple,
+.bx-form-input-radio_set,
+.bx-form-input-checkbox_set {
+    height:auto;
+}
+
+.bx-form-input-textarea {
+    padding:0.5rem;
+    height:150px;
+    height:10rem;
+
+    line-height:normal;
+}
+
+.bx-form-input-datepicker {
+    width:7.5rem;
+}
+
+.bx-form-input-datetime {
+    width:10.2rem;
+}
+
+.bx-form-datepicker-modal {
+    z-index:1001 !important;
+}
+
+.bx-form-input-number,
+.bx-form-input-time
+{
+    width:6rem;
+}
+
+.bx-form-input-wrapper-file .bx-btn {
+	float: none;
+	display: inline-block;
+}
+.bx-form-input-file {
+	position: fixed;
+	top: -1000px;
+
+    border: none;
+    background-color: transparent;
+}
+
+.bx-form-input-wrapper-slider,
+.bx-form-input-wrapper-doublerange {
+    position:relative;
+}
+
+.bx-form-input-wrapper-slider .ui-slider,
+.bx-form-input-wrapper-doublerange .ui-slider {
+    position:absolute;
+    top:0px;
+    right:0px;
+}
+
+.bx-form-input-wrapper-slider .ui-slider .ui-slider-handle,
+.bx-form-input-wrapper-doublerange .ui-slider .ui-slider-handle { 
+    z-index: 1;
+}
+
+/* switcher */
+
+.bx-switcher-cont {
+	position:relative;
+    width:60px;
+	width:3.8rem;
+    height:24px;
+	height:1.5rem;
+	overflow:hidden;
+	cursor:pointer;
+
+    background-color:#fff;
+
+    border:2px solid #79bd9a;
+    border:0.125rem solid #79bd9a;
+    -moz-border-radius: 1rem;
+    -webkit-border-radius: 1rem;
+    border-radius: 1rem;
+}
+.bx-switcher-cont input {
+    position:absolute; 
+    top:-32px;
+    top:-2rem;
+}
+.bx-switcher-canvas {
+	position:relative;
+	width:90px;
+	width:5.7rem;
+
+    -webkit-transition: left 0.2s ease-in-out;
+    -moz-transition: left 0.2s ease-in-out;
+    -ms-transition: left 0.2s ease-in-out;
+    -o-transition: left 0.2s ease-in-out;
+}
+.bx-switcher-cont.on .bx-switcher-canvas {
+	left:3px;
+	left:0.2rem;
+}
+.bx-switcher-cont.off .bx-switcher-canvas {
+	left:-34px;
+	left:-2.1rem;
+}
+.bx-switcher-on,
+.bx-switcher-off,
+.bx-switcher-handler {
+    position:absolute;	
+    top:0rem;
+	line-height:24px;
+	line-height:1.5rem;
+    color:#999;
+}
+.bx-switcher-on {
+    left:0rem;
+    width:34px;
+    width:2.1rem;
+    height:24px;
+    height:1.5rem;
+    margin-left:8px;
+    margin-left:0.5rem;
+}
+.bx-switcher-off {	
+    left:65px;
+    left:4.05rem;
+    width:34px;
+    width:2.1rem;
+    height:24px;
+    height:1.5rem;
+    margin-left:3px;
+    margin-left:0.2rem;
+}
+.bx-switcher-handler {
+    left:36px;
+    left:2.23rem;
+    top:2px;
+    top:0.13rem;
+    width:20px;
+    width:1.25rem;
+	height:20px;
+	height:1.25rem;
+	margin:0rem;
+
+    -moz-border-radius: 1rem;
+    -webkit-border-radius: 1rem;
+    border-radius: 1rem;
+
+    -webkit-transition: background-color 0.2s ease-in-out;
+    -moz-transition: background-color 0.2s ease-in-out;
+    -ms-transition: background-color 0.2s ease-in-out;
+    -o-transition: background-color 0.2s ease-in-out;
+    transition: background-color 0.2s ease-in-out;
+}
+.bx-switcher-cont.on .bx-switcher-handler {
+    background-color:#d0efa2;
+}
+.bx-switcher-cont.off .bx-switcher-handler {
+    background-color:#e7938a;
+}
+.bx-form-element-wrapper-oneline .bx-form-element {
+	display: -webkit-flex;
+    display: flex;
+    -webkit-flex-flow: row nowrap;
+    flex-flow: row nowrap;
+    -webkit-align-items: flex-start;
+    align-items: flex-start;
+    -webkit-justify-content: flex-start;
+    justify-content: flex-start;
+}
+.bx-form-value-oneline,
+.bx-form-caption-oneline {
+    float: none;
+}
+.bx-form-caption-oneline-switcher {
+    line-height:26px;
+}
+.bx-form-input-wrapper-submit .bx-btn {
+    float: none;
+}
+
+.bx-form-input-wrapper-input_set .bx-btn {
+    float:left;
+}
+
+/* extra features */
+
+.bx-form-right-line-aligned {
+    float:right;
+    margin-top: 5px;
+}
+
+/* view mode */
+.bx-form-row-view-wrapper > div {
+	position: relative;
+    display: inline-block;
+    width: 100%;
+
+    vertical-align: bottom;
+    white-space: normal;
+    text-overflow: ellipsis;
+    overflow: hidden;
+}
+
+.bx-form-row-view-wrapper-textarea > div {
+    vertical-align:top;
+}
+
+.bx-form-row-view-caption {
+    font-weight:bold;
+}
+
+
+/****************************************
+*		Custom reCaptcha Theme
+****************************************/
+.bx-form-input-captcha-custom {
+	background-color: #b71800;
+	padding: 0.5em;
+	border-radius: 1em;
+	position: relative;
+	overflow: auto;
+	max-width: 480px;
+	font: 0.75em/1em Helvetica, Arial, sans-serif;
+	color: #ffffff;
+}
+.bx-form-input-captcha-custom a {
+	color: #ffffff;
+	text-decoration: none;
+}
+.bx-form-input-captcha-custom img,
+.bx-form-input-captcha-custom #recaptcha_image {
+	width: 100% !important;
+	height: auto !important;
+	-webkit-border-radius: 0.5em;
+	-moz-border-radius: 0.5em;
+	border-radius: 0.5em;
+	-moz-background-clip: padding;
+	-webkit-background-clip: padding-box;
+	background-clip: padding-box;
+}
+.bx-form-input-captcha-custom .solution {
+	position: relative;
+	float: left;
+	width: 75%;
+}
+.bx-form-input-captcha-custom .solution-cnt {
+	margin-top: 0.5em;
+	padding: 1.2em 1em;
+	-webkit-border-radius: 0.5em;
+	-moz-border-radius: 0.5em;
+	border-radius: 0.5em;
+	-moz-background-clip: padding;
+	-webkit-background-clip: padding-box;
+	background-clip: padding-box;
+	background-color: #fbe098;
+	color: #000000;
+	display: block;
+}
+.bx-form-input-captcha-custom .solution-cnt input {
+	display: block;
+	width: calc(90% - 2px);
+	margin: 0.35em auto;
+	-webkit-appereance: none;
+	border: 1px solid #f7c236;
+	font: inherit;
+	font-size: 150%;
+	outline: none;
+}
+.bx-form-input-captcha-custom .solution-cnt input:focus {
+	border-color: #b71800;
+}
+.bx-form-input-captcha-custom .options {
+	position: relative;
+	float: right;
+	width: 25%;
+	
+}
+.bx-form-input-captcha-custom .options-cnt {
+	margin-top: 0.5em;
+	padding-left: 0.5em;
+	-webkit-border-radius: 0.5em;
+	-moz-border-radius: 0.5em;
+	border-radius: 0.5em;
+	-moz-background-clip: padding;
+	-webkit-background-clip: padding-box;
+	background-clip: padding-box;
+}
+.bx-form-input-captcha-custom .options-cnt a {
+	background-color: #c5523d;
+	display: block;
+	padding: 0.5em 1.0em;
+	border-top: 1px solid #000000;
+	border-left: 1px solid #000000;
+	border-right: 1px solid #000000;
+}
+.bx-form-input-captcha-custom .options-cnt a:first-child {
+	border-radius: 0.5em 0.5em 0 0;
+}
+.bx-form-input-captcha-custom .options-cnt a:last-child {
+	border-radius: 0 0 0.5em 0.5em;
+	border-bottom: 1px solid #000000;
+}
+
+
+/****************************************
+* Overwrite 3d Party MiniColors plug-in
+****************************************/
+.bx-form-input-wrapper.bx-form-input-wrapper-text .minicolors-theme-bootstrap .minicolors-swatch {
+	top: 6px;
+	left: 6px;
+
+	width: 25px;
+	width: 1.5625rem;
+	height: 25px;
+	height: 1.5625rem;
+
+	border-radius: 20px;
+	border-radius: 1.25rem;
+}
+
+/* auto-siggestion input */
+
+.bx-form-value .bx-form-input-autotoken {
+    float:left;
+    padding:0px;
+    height:auto;
+	box-shadow: none;
+    min-height: 2.8rem;
+}
+.bx-form-value .bx-form-input-autotoken b {
+    cursor:pointer;
+    display:block;
+    float:left;
+    line-height: 40px;
+    line-height: 2.5rem;
+    padding-right: 1.5em; 
+    margin-left:0.1em;
+    margin-right:0.1em;
+    font-weight:normal;
+	border-radius: 1.25rem;
+}
+.bx-form-value .bx-form-input-autotoken b:hover {
+    opacity:0.7;
+}
+.bx-form-value .bx-form-input-autotoken b:after {
+    font-family: 'sys-user', 'sys-user-fal';
+    content: "\f00d";    
+    position:absolute;    
+    padding:0 0.5em;
+    margin-left: 0.5em;
+    font-size:0.7em;
+    font-weight:bold;
+}
+.bx-form-value .bx-form-input-autotoken.bx-form-input-disabled b {
+    cursor: default;
+}
+.bx-form-value .bx-form-input-autotoken.bx-form-input-disabled b:after{
+    display: none;
+}
+.bx-form-value .bx-form-input-autotoken input {
+    border:0;
+    margin:0;
+    padding:0 0 0 5px;
+    width:auto;
+    float:left;
+    box-shadow: none;
+}
+.bx-form-value .bx-form-input-autotoken .bx-def-thumb-size {
+    width: 2.5rem;
+    height: 2.5rem;
+    line-height: 2.5rem;
+	float:left;	
+}
+.bx-form-value .bx-form-input-autotoken .bx-base-pofile-unit-wo-info {
+    display: inline-block;
+    vertical-align: middle;
+
+    margin-right: 8px;
+    margin-right: 0.5rem;
+}
+.bx-form-value .bx-form-input-autotoken-labels b {
+    padding-left: 1em;
+}
+.bx-form-value .bx-form-input-autotoken-labels-show-more,
+.bx-form-value .bx-form-input-autotoken-labels-sel b {
+    display:inline-block;
+    line-height:1.5rem;
+    font-size:85%;    
+    padding-right:1em;
+    margin-top:0.625rem;
+    font-style:normal;
+}
+.bx-form-value .bx-form-input-autotoken-labels-show-more {
+    margin-left:0.1em;
+    margin-right:0.1em;
+}
+.bx-form-value .bx-form-input-autotoken-labels-sel b:after {
+    display:none;
+}
+.bx-form-input-autotoken-labels-more {
+    display:none;
+}
+.select2-container {
+    width:100% !important;
+}

+ 1 - 0
upgrade/files/11.0.0-11.0.1/files/template/menu_more_auto.html

@@ -0,0 +1 @@
+<div class="bx-menu-more-auto-wrapper" style="overflow:hidden;">__menu__</div>

+ 14 - 0
upgrade/files/11.0.0-11.0.1/files/template/profile_membership_stats.html

@@ -0,0 +1,14 @@
+<div class="bx-profile-membership-stats">
+    <div class="bx-pms-membership">__membership__</div>
+    <div class="bx-pms-stats bx-def-box bx-def-round-corners bx-def-color-bg-box bx-def-padding">
+        <bx_repeat:stat_items>
+            <div class="bx-pms-stat-item bx-def-margin-top-auto">
+                <div class="bx-pms-si-title">__title__</div>
+                <div class="bx-pms-si-graph bx-def-margin-thd-top">
+                    <div class="bx-pms-sig-bar bx-def-round-corners" style="width:__width__">&nbsp;</div>
+                    <div class="bx-pms-sig-text bx-def-padding-left bx-def-font-middle bx-def-font-grayed">__value__ (__percent__)</div>
+                </div>
+            </div>
+        </bx_repeat:stat_items>
+    </div>
+</div>

+ 112 - 0
upgrade/files/11.0.0-11.0.1/files/template/scripts/BxBaseCmtsMenuUnitMeta.php

@@ -0,0 +1,112 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaBaseView UNA Base Representation Classes
+ * @{
+ */
+
+/**
+ * Menu representation.
+ * @see BxDolMenu
+ */
+class BxBaseCmtsMenuUnitMeta extends BxTemplMenuUnitMeta
+{
+    protected $_oCmts;
+    protected $_aCmtsSystem;
+    protected $_aCmt;
+
+    public function __construct ($aObject, $oTemplate)
+    {
+        parent::__construct ($aObject, $oTemplate);
+    }
+
+    public function setCmtsData($oCmts, $iCmtId)
+    {
+        if(empty($oCmts) || empty($iCmtId))
+            return;
+
+        $this->_oCmts = $oCmts;
+        $this->_aCmtsSystem = $oCmts->getSystemInfo();
+        $this->_aCmt = $oCmts->getCommentRow($iCmtId);
+
+        $this->_sStylePrefix = (!empty($this->_aCmtsSystem['root_style_prefix']) ? $this->_aCmtsSystem['root_style_prefix'] : 'cmt') . '-meta bx-base-general-unit-meta';
+
+        $this->addMarkers(array(
+            'cmt_system' => $this->_oCmts->getSystemName(),
+            'cmt_object_id' => $this->_oCmts->getId(),
+            'cmt_id' => $iCmtId,
+            'content_id' => $iCmtId,
+        ));
+    }
+
+    protected function _getMenuItemAuthor($aItem)
+    {
+        list($sAuthorName, $sAuthorLink, $sAuthorIcon, $sAuthorUnit, $sAuthorBadges) = $this->_oCmts->getAuthorInfo($this->_aCmt['cmt_author_id']);
+    
+        if(!empty($sAuthorLink))
+            return $this->getUnitMetaItemLink($sAuthorName, array(
+                'href' => $sAuthorLink,
+                'class' => $this->_sStylePrefix . '-username',
+                'title' => bx_html_attribute($sAuthorName),
+            )). $sAuthorBadges;
+        else
+            return $this->getUnitMetaItemText($sAuthorName, array(
+                'class' => $this->_sStylePrefix . '-username'
+            )). $sAuthorBadges;
+    }
+    
+    protected function _getMenuItemDate($aItem)
+    {
+        $sAgo = bx_time_js($this->_aCmt['cmt_time']);
+
+        if(!empty($this->_aCmtsSystem['trigger_field_title']))
+            return $this->getUnitMetaItemLink($sAgo, array(
+                'href' => $this->_oCmts->getViewUrl($this->_aCmt['cmt_id']),
+                'class' => $this->_sStylePrefix . '-ago'
+            ));
+        else
+            return $this->getUnitMetaItemText($sAgo, array(
+                'class' => $this->_sStylePrefix . '-ago'
+            ));
+    }
+
+    protected function _getMenuItemMembership($aItem)
+    {
+        $iUserId = bx_get_logged_profile_id();
+        $iAuthorId = (int)$this->_aCmt['cmt_author_id'];
+        if($iAuthorId < 0  && (abs($iAuthorId) == $iUserId || $this->_oCmts->isModerator()))
+            $iAuthorId *= -1;
+
+        $aMembership = BxDolAcl::getInstance()->getMemberMembershipInfo($iAuthorId);
+        if(empty($aMembership) || !is_array($aMembership))
+            return '';
+
+        return $this->getUnitMetaItemText($iAuthorId < 0 ? _t('_anonymous') : _t($aMembership['name']), array(
+            'class' => $this->_sStylePrefix . '-membership'
+        ));
+    }
+
+    protected function _getMenuItemDefault($aItem)
+    {
+        $sResult = false;
+
+        bx_alert('comment', 'menu_custom_item', 0, 0, array(
+            'res' => &$sResult, 
+            'menu' => $this->_sObject, 
+            'menu_object' => $this, 
+            'item' => $aItem,
+            'content_id' => $this->_aCmt['cmt_id'],
+            'content_data' => $this->_aCmt,
+            'cmts_object' => $this->_oCmts
+        ));
+
+        if (false !== $sResult)
+            return $sResult;
+
+        return parent::_getMenuItemDefault($aItem);
+    }
+}
+
+/** @} */

+ 143 - 0
upgrade/files/11.0.0-11.0.1/files/template/scripts/BxBaseMenuMoreAuto.php

@@ -0,0 +1,143 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaBaseView UNA Base Representation Classes
+ *
+ * @{
+ */
+
+define('BX_DEF_MENU_ITEM_MORE_AUTO', 'more-auto');
+
+class BxBaseMenuMoreAuto extends BxTemplMenu
+{
+    protected $_sTmplNameItemMore;
+    protected $_sTmplNameItemMorePopup;
+
+    protected $_bMoreAuto;
+    protected $_iMoreAutoItemsStatic;
+    protected $_sJsObjectMoreAuto;
+
+    protected $_aHtmlIds;
+
+    public function __construct($aObject, $oTemplate = false)
+    {
+        parent::__construct($aObject, $oTemplate);
+
+        $this->_sTmplNameItemMore = 'menu_item_more.html';
+        $this->_sTmplNameItemMorePopup = 'menu_item_more_popup.html';
+
+        $this->_bMoreAuto = null;
+        $this->_iMoreAutoItemsStatic = 1;
+        $this->_sJsObjectMoreAuto = 'oMenuMoreAuto' . bx_gen_method_name($this->_sObject);
+
+        $sPrefix = str_replace('_', '-', $this->_sObject);
+        $this->_aHtmlIds = array(
+            'main' => $sPrefix,
+            'more_auto_popup' => $sPrefix . '-ma-popup',
+        );
+    }
+
+    public function getCode()
+    {
+        $sResult = parent::getCode();
+
+        if($this->_isMoreAuto()) {
+            $this->_oTemplate->addJs(array('BxDolMenuMoreAuto.js'));
+
+            if(!empty($sResult))
+                $sResult = $this->_oTemplate->parseHtmlByName('menu_more_auto.html', array(
+                    'menu' => $sResult
+                ));
+        }
+
+        return $sResult;
+    }
+
+    protected function _isMoreAuto()
+    {
+        if(is_bool($this->_bMoreAuto))
+            return $this->_bMoreAuto;
+
+        $aResult = $this->getMenuItems();
+
+        $sItem = BX_DEF_MENU_ITEM_MORE_AUTO;
+        if(!empty($this->_aObject['menu_items'][$sItem]) && is_array($this->_aObject['menu_items'][$sItem]) && (int)$this->_aObject['menu_items'][$sItem]['active'] == 1)
+            $this->_bMoreAuto = true;
+        else
+            $this->_bMoreAuto = false;
+
+        return $this->_bMoreAuto;
+    }
+
+    protected function _getTemplateVars()
+    {
+        $bMoreAuto = $this->_isMoreAuto();
+
+        $aResult = array_merge(parent::_getTemplateVars(), array(
+            'html_id' => $this->_getHtmlIdMain(),
+            'bx_if:show_more_auto_class' => array(
+                'condition' => $bMoreAuto,
+                'content' => array()
+            ),
+            'js_code' => $bMoreAuto ? $this->_getJsCodeMoreAuto() : ''
+        ));
+
+        return $aResult;
+    }
+
+    protected function _getJsCodeMoreAuto()
+    {
+        $sJsObject = $this->_getJsObjectMoreAuto();
+        $aJsParams = array(
+            'sObject' => $this->_sObject,
+            'iItemsStatic' => $this->_iMoreAutoItemsStatic,
+            'aHtmlIds' => $this->_getHtmlIds()
+        );
+
+        return $this->_oTemplate->_wrapInTagJsCode("if(!" . $sJsObject . ") {var " . $sJsObject . " = new BxDolMenuMoreAuto(" . json_encode($aJsParams) . "); " . $sJsObject . ".init();}");
+    }
+    
+    protected function _getMenuItem ($aItem)
+    {
+        $aItem['popup'] = '';
+
+        if($aItem['name'] != BX_DEF_MENU_ITEM_MORE_AUTO)
+            return parent::_getMenuItem($aItem);
+
+        $aItem['onclick'] = $this->_getJsObjectMoreAuto() . '.more(this);';
+        $aItem['popup'] = BxTemplFunctions::getInstance()->transBox($this->_aHtmlIds['more_auto_popup'], $this->_oTemplate->parseHtmlByName($this->_getTmplNameItemMorePopup(), array(
+            'content' => ''
+        )), true);
+
+        return parent::_getMenuItem($aItem);
+    }
+
+    protected function _getHtmlIds()
+    {
+        return $this->_aHtmlIds;
+    }
+
+    protected function _getHtmlIdMain()
+    {
+        return $this->_aHtmlIds['main'];
+    }
+
+    protected function _getJsObjectMoreAuto()
+    {
+        return $this->_sJsObjectMoreAuto;
+    }
+
+    protected function _getTmplNameItemMore()
+    {
+        return $this->_sTmplNameItemMore;
+    }
+
+    protected function _getTmplNameItemMorePopup()
+    {
+        return $this->_sTmplNameItemMorePopup;
+    }
+}
+
+/** @} */

+ 399 - 0
upgrade/files/11.0.0-11.0.1/files/template/scripts/BxBaseSearchResult.php

@@ -0,0 +1,399 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaBaseView UNA Base Representation Classes
+ * @{
+ */
+
+bx_import('BxDolSearch');
+
+class BxBaseSearchResult extends BxDolSearchResult
+{
+    public $isError;
+
+    protected $sBrowseUrl; ///< currect browse url, used for paginate and other links in browsing
+
+    protected $sUnitTemplate = 'unit.html'; ///< common template to try to use for displaying one item
+    protected $sUnitTemplateLiveSearch = 'unit_live_search.html'; ///< common template to try to use for displaying live search results
+
+    protected $aGetParams = array(); ///< get params to keep in paginate and other browsing links
+
+    protected $iDesignBoxTemplate = 11; ///< design box ID to wrap result in, or false to disable design box wrapping
+
+    protected $aConstants;
+
+    protected $sCenterContentUnitSelector = false;
+
+    protected $aContainerAttrs = array();
+    protected $aContainerClasses = array('bx-search-result-block', 'bx-clearfix');
+
+    protected $aUnitParams = array(); ///< additional params array to pass to unit display function
+
+    function __construct($oFunctions = false)
+    {
+        parent::__construct();
+
+        if ($oFunctions)
+            $this->oFunctions = $oFunctions;
+        else
+            $this->oFunctions = BxTemplFunctions::getInstance();
+    }
+
+    function getMain()
+    {
+        // override this to return main module class
+    }
+
+    function displayResultBlock ()
+    {
+        $sCode = '';
+        bx_alert('simple_search', 'before_get_data', 0, false, array('object' => &$this->aCurrent, 'mode' => $this->_sMode));
+        $aData = $this->getSearchData();
+        bx_alert('simple_search', 'get_data', 0, false, array('object' => &$this->aCurrent, 'search_results' => &$aData));
+        if ($this->aCurrent['paginate']['num'] > 0) {
+
+            $sCode .= $this->addCustomParts();
+
+            foreach ($aData as $aValue)
+                $sCode .= $this->displaySearchUnit($aValue);
+
+            $sSearchResultBlockId = 'bx-search-result-block-' . rand(0, PHP_INT_MAX);
+            $sClasses = implode(' ', $this->aContainerClasses);
+
+            $sAttributes = '';
+            if(is_array($this->aContainerAttrs) && !empty($this->aContainerAttrs))
+            	foreach ($this->aContainerAttrs as $sName => $sValue)
+            		$sAttributes .= ' ' . $sName . '="' . $sValue . '"';
+
+            $sCode = '<div id="' . $sSearchResultBlockId . '" class="' . $sClasses . '"' . $sAttributes . '>' . $sCode . '</div>';
+
+            if (!$this->_bLiveSearch && $this->sCenterContentUnitSelector) {
+                $sCode .= "
+                    <script>
+                        $(document).ready(function() {
+                            bx_center_content('#{$sSearchResultBlockId}', '{$this->sCenterContentUnitSelector}', true);
+                        });
+                    </script>";
+            }
+        }
+        return $sCode;
+    }
+
+    function displaySearchBox ($sContent, $sPaginate = '')
+    {
+		$sContent .= $sPaginate;
+		$sMenu = $this->getDesignBoxMenu();
+
+        if ($this->id) {
+            $sTitle = _t($this->aCurrent['title']);
+        	$sCode = $this->oFunctions->designBoxContent($sTitle, $sContent, $this->iDesignBoxTemplate, $sMenu);
+            return '<div class="bx-page-block-container bx-search-results bx-def-padding-sec-topbottom bx-clearfix" id="bx-page-block-' . $this->id . '">' . $sCode . '</div>';
+        }
+
+        $this->addPageRssLink ();
+
+        return array(        	
+        	'content' => $sContent,
+        	'menu' => $sMenu,
+        );
+    }
+
+    function displaySearchUnit ($aData)
+    {
+        $oMain = $this->getMain();
+        return $oMain->_oTemplate->unit($aData, $this->bProcessPrivateContent, $this->_bLiveSearch ? $this->sUnitTemplateLiveSearch : $this->sUnitTemplate, $this->aUnitParams);
+    }
+
+    function setUnitTemplate ($s)
+    {
+        $this->sUnitTemplate = $s;
+    }
+
+    function getDesignBoxMenu ()
+    {
+        return array();
+    }
+
+    protected function addPageRssLink ()
+    {
+        if (false === ($sLink = $this->getRssPageUrl ()))
+            return;
+
+        if (!($oTemplate = BxDolTemplate::getInstance()))
+            return;
+
+        $oTemplate->addPageRssLink($this->aCurrent['title'], $sLink);
+    }
+
+    function getRssPageUrl ()
+    {
+        if (!isset($this->aCurrent['rss']) || !$this->aCurrent['rss']['link'])
+            return false;
+
+        $oPermalinks = BxDolPermalinks::getInstance();
+        return BX_DOL_URL_ROOT . bx_append_url_params($oPermalinks->permalink($this->aCurrent['rss']['link']), 'rss=1');
+    }
+
+    function showAdminActionsPanel($sWrapperId, $aButtons, $sCheckboxName = 'entry', $bSelectAll = true, $bSelectAllChecked = false, $sCustomHtml = '')
+    {
+        $aBtns = array();
+        foreach ($aButtons as $k => $v) {
+            if(is_array($v)) {
+                $aBtns[] = $v;
+                continue;
+            }
+
+            $aBtns[] = array(
+                'type' => 'submit',
+                'name' => $k,
+                'value' => '_' == $v[0] ? _t($v) : $v,
+                'onclick' => '',
+            );
+        }
+
+        return BxDolTemplate::getInstance()->parseHtmlByName('adminActionsPanel.html', array(
+            'bx_repeat:buttons' => $aBtns,
+            'bx_if:custom_html' => array(
+                'condition' => strlen($sCustomHtml) > 0,
+                'content' => array(
+                    'custom_html' => $sCustomHtml,
+                )
+            ),
+            'bx_if:selectAll' => array(
+                'condition' => $bSelectAll,
+                'content' => array(
+                    'wrapper_id' => $sWrapperId,
+                    'checkbox_name' => $sCheckboxName,
+                    'checked' => ($bSelectAll && $bSelectAllChecked ? 'checked="checked"' : '')
+                )
+            ),
+        ));
+    }
+
+    function showAdminFilterPanel($sFilterValue, $sInputId = 'filter_input_id', $sCheckboxId = 'filter_checkbox_id', $sFilterName = 'filter', $sOnApply = '')
+    {
+        $sFilterValue = bx_html_attribute($sFilterValue);
+        $isChecked = $sFilterValue ? ' checked="checked" ' : '';
+
+        if(empty($sOnApply))
+            $sOnApply = "on_filter_apply(this, '" . $sInputId . "', '" . $sFilterName . "')";
+
+        return BxDolTemplate::getInstance()->parseHtmlByName('adminFilterPanel.html', array(
+            'input_id' => $sInputId,
+            'filter_value' => $sFilterValue,
+            'checkbox_id' => $sCheckboxId,
+            'is_checked' => $isChecked,
+            'on_apply' => $sOnApply,
+        ));
+    }
+
+    function showPagination($bAdmin = false, $bChangePage = true, $bPageReload = true)
+    {
+        $oMain = $this->getMain();
+
+        $sPageUrl = $this->getCurrentUrl(array(), false);
+        $sOnClick = $this->getCurrentOnclick(array(), false);
+
+        $oPaginate = new BxTemplPaginate(array(
+            'page_url' => $sPageUrl,
+            'on_change_page' => $sOnClick,
+            'num' => $this->aCurrent['paginate']['num'],
+            'per_page' => $this->aCurrent['paginate']['perPage'],
+            'start' => $this->aCurrent['paginate']['start'],
+        ));
+
+        return $sOnClick ? $oPaginate->getSimplePaginate() : $oPaginate->getPaginate();
+    }
+
+    /**
+     * Get current browse URL with current page and additional params
+     * @param $aAdditionalParams set custom additional params as key value pair
+     * @param $bReplacePagesParams replace paginate params with current values or leave markers for use in paginate class
+     * @return ready to use URL string with BX_DOL_URL_ROOT added in the beginning
+     */
+    protected function getCurrentUrl($aAdditionalParams = array(), $bReplacePagesParams = true)
+    {
+        if (BX_DOL_SEARCH_KEYWORD_PAGE === $this->sBrowseUrl || $this->bForceAjaxPaginate)
+            return 'javascript:void(0);';
+
+        $oPermalinks = BxDolPermalinks::getInstance();
+
+        $sUrlStart = BX_DOL_URL_ROOT . $oPermalinks->permalink($this->sBrowseUrl);
+
+        return $this->addAdditionalUrlParams($sUrlStart, $aAdditionalParams, $bReplacePagesParams);
+    }
+
+    /**
+     * Get current browse URL with current page and additional params
+     * @param $aAdditionalParams set custom additional params as key value pair
+     * @param $bReplacePagesParams replace paginate params with current values or leave markers for use in paginate class
+     * @return ready to use URL string with BX_DOL_URL_ROOT added in the beginning
+     */
+    protected function getCurrentOnclick($aAdditionalParams = array(), $bReplacePagesParams = true)
+    {
+        if (BX_DOL_SEARCH_KEYWORD_PAGE !== $this->sBrowseUrl && !$this->bForceAjaxPaginate)
+            return '';
+
+        if (BX_DOL_SEARCH_KEYWORD_PAGE === $this->sBrowseUrl) {
+
+            $sLoadDynamicUrl = BX_DOL_URL_ROOT . 'searchKeywordContent.php?searchMode=ajax&section[]=' . $this->aCurrent['name'] . ($this->_sCategoryObject ? '&cat=' . $this->_sCategoryObject : '');
+            $sLoadDynamicUrl = $this->addAdditionalUrlParams($sLoadDynamicUrl, $aAdditionalParams, $bReplacePagesParams);
+
+            $sKeyword = bx_get('keyword');
+            if ($sKeyword !== false && mb_strlen($sKeyword) > 0)
+                $sLoadDynamicUrl = bx_append_url_params($sLoadDynamicUrl, 'keyword=' . rawurlencode($sKeyword));
+
+            return "return !loadDynamicBlockAuto(this, '{$sLoadDynamicUrl}');";
+
+        } else {
+            
+            return "return !loadDynamicBlockAutoPaginate(this, '{start}', '{per_page}', " . (empty($aAdditionalParams) ? 'undefined' : "'" . trim($this->addAdditionalUrlParams('', $aAdditionalParams, false, false), '&?') . "'") . ");"; 
+
+        }
+    }
+
+    protected function addAdditionalUrlParams($sUrl, $aAdditionalParams, $bReplacePagesParams, $bAddPaginateParams = true)
+    {
+        if ($bAddPaginateParams) { // add pages params
+            $sUrl = bx_append_url_params($sUrl, array (
+                'type' => $this->_sMetaType,
+                'start' => $bReplacePagesParams ? (int)$this->aCurrent['paginate']['start'] : '{start}',
+                'per_page' => $bReplacePagesParams ? (int)$this->aCurrent['paginate']['perPage'] : '{per_page}',
+            ));
+        }
+
+        foreach ($this->aGetParams as $sGetParam) {
+            $sValue = false;
+            if (isset($aAdditionalParams[$sGetParam]))
+                $sValue = $aAdditionalParams[$sGetParam];
+            elseif (false !== bx_get($sGetParam))
+                $sValue = bx_get($sGetParam);
+            if (false !== $sValue)
+                $sUrl = bx_append_url_params($sUrl, $sGetParam . '=' . rawurlencode($sValue));
+        }
+
+        return $sUrl;
+    }
+
+    function clearFilters ($aPassParams = array(), $aPassJoins = array())
+    {
+        //clear sorting
+        $this->aCurrent['sorting'] = 'last';
+
+        //clear restrictions
+        foreach ($this->aCurrent['restriction'] as $sKey => $aValue) {
+            if (!in_array($sKey, $aPassParams))
+                $this->aCurrent['restriction'][$sKey]['value'] = '';
+        }
+
+        //clear unnecessary joins (remains only profile join)
+        $aPassJoins[] = 'profile';
+        $aTemp = array();
+        foreach ($aPassJoins as $sValue) {
+            if (isset($this->aCurrent['join'][$sValue]) && is_array($this->aCurrent['join'][$sValue]))
+                $aTemp[$sValue] = $this->aCurrent['join'][$sValue];
+        }
+        $this->aCurrent['join'] = $aTemp;
+    }
+
+    function fillFilters ($aParams)
+    {
+        // transform all given values to fields values
+        if (is_array($aParams)) {
+            foreach ($aParams as $sKey => $mixedValue) {
+                if (isset($this->aCurrent['restriction'][$sKey]))
+                    $this->aCurrent['restriction'][$sKey]['value'] = $mixedValue;
+            }
+        }
+    }
+    
+    function fillFiltersByObjects ($aParams)
+    {
+        // transform all given values to fields values
+        if (is_array($aParams)) {
+            foreach ($aParams as $sKey => $mixedValue) {
+                $this->aCurrent['restriction'][$sKey] = $mixedValue;
+            }
+        }
+    }
+
+    /**
+     * Set design box template id to use to wrap search results in
+     */
+    function setDesignBoxTemplateId ($i)
+    {
+        $this->iDesignBoxTemplate = $i;
+    }
+
+    /**
+     * Set unit class selector for content centering 
+     */
+    function setCenterContentUnitSelector ($s)
+    {
+        $this->sCenterContentUnitSelector = $s;
+    }
+
+    /**
+     * Add class to search result container 
+     * @param $mixed CSS class name string or array of classes
+     */
+    function addContainerClass ($mixed)
+    {
+        if (!is_array($mixed))
+            $mixed = array($mixed);
+
+        foreach ($mixed as $s)
+            if (false === array_search($s, $this->aContainerClasses))
+                $this->aContainerClasses[] = $s;
+    }
+
+    /**
+     * Remove class from search result container 
+     * @param $mixed CSS class name string or array of classes
+     */
+    function removeContainerClass ($mixed)
+    {
+        if (!is_array($mixed))
+            $mixed = array($mixed);
+
+        foreach ($mixed as $s)
+            if (false !== ($i = array_search($s, $this->aContainerClasses)))
+                unset($this->aContainerClasses[$i]);
+    }
+
+    /**
+     * Add attribute(s) to search result container 
+     * @param $aAttributes an array of Key -> Value pair(s).
+     */
+    function addContainerAttribute ($aAttributes)
+    {
+    	if(empty($aAttributes) || !is_array($aAttributes))
+    		return;
+
+    	$this->aContainerAttrs = array_merge($this->aContainerAttrs, $aAttributes);
+    }
+
+	/**
+     * Remove attribute(s) from search result container 
+     * @param $mixedName attribute name or an array of names
+     */
+    function removeContainerAttribute ($mixedName)
+    {
+        if(!is_array($mixedName))
+            $mixedName = array($mixedName);
+
+		$this->aContainerAttrs = array_diff_assoc($this->aContainerAttrs, array_flip($mixedName));
+    }
+
+    public function setUnitParams ($aParamsAdd = array(), $aParamsRemove = array())
+    {
+        if ($aParamsAdd)
+            $this->aUnitParams = array_merge($this->aUnitParams, $aParamsAdd);
+        if ($aParamsRemove)
+            $this->aUnitParams = array_diff_key($this->aUnitParams, $aParamsRemove);
+    }
+}
+
+/** @} */

+ 401 - 0
upgrade/files/11.0.0-11.0.1/files/template/scripts/BxBaseServiceProfiles.php

@@ -0,0 +1,401 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaBaseView UNA Base Representation Classes
+ * @{
+ */
+
+/**
+ * System service for profiles handling functionality.
+ */
+class BxBaseServiceProfiles extends BxDol
+{
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    public function serviceGetProfileTypes()
+    {
+        $aTypes = array(
+			'' => _t('_Select_one')
+        );
+
+        $aModules = $this->_getProfilesModules();
+        foreach($aModules as $aModule) {
+			if(empty($aModule) || !is_array($aModule))
+				continue;
+
+            $sTitleKey = '_' . $aModule['name'];
+            $sTitle = _t($sTitleKey);
+
+            $aTypes[$aModule['name']] = !empty($sTitle) && $sTitle != $sTitleKey ? $sTitle : $aModule['title'];
+		}
+
+        return $aTypes;
+    }
+
+    public function serviceProfileStats ($iProfileId = 0)
+    {
+        if (!$iProfileId && !($iProfileId = bx_get_logged_profile_id()))
+            return '';
+
+        $oProfile = BxDolProfile::getInstance($iProfileId);
+        if(!$oProfile)
+            return '';
+
+        $oAcl = BxDolAcl::getInstance();
+        $aAcl = $oAcl->getMemberMembershipInfo($iProfileId);
+        $aAclInfo = $oAcl->getMembershipInfo($aAcl['id']);
+
+        $aVars = array(
+            'profile_id' => $oProfile->id(),
+            'profile_url' => $oProfile->getUrl(),
+            'profile_edit_url' => $oProfile->getEditUrl(),
+            'profile_title' => $oProfile->getDisplayName(),
+            'profile_title_attr' => bx_html_attribute($oProfile->getDisplayName()),
+            'profile_ava_url' => $oProfile->getAvatar(),
+            'profile_unit' => $oProfile->getUnit(0, array('template' => array(
+                'name' => 'unit_wo_info',
+                'size' => 'ava'
+            ))),
+            'profile_acl_title' => _t($aAclInfo['name']),
+            'profile_acl_icon' => $aAclInfo['icon'],
+            'menu' => BxDolMenu::getObjectInstance('sys_profile_stats')->getCode(),
+        );
+
+        return BxDolTemplate::getInstance()->parseHtmlByName('profile_stats.html', $aVars);
+    }
+
+    public function serviceGetMenuAddonProfileEdit($iProfileId = 0, $sCaption = '')
+    {
+        /**
+         * Disabled.
+         */
+        return '';
+
+    	if(empty($sCaption))
+            $sCaption = _t('_Edit');
+
+        $oProfile = BxDolProfile::getInstance($iProfileId);
+        if(!$oProfile)
+            return '';
+
+        $sModule = $oProfile->getModule();
+        $sMethod = 'profile_edit_url';
+        if(!BxDolRequest::serviceExists($sModule, $sMethod))
+            return '';
+
+        return BxDolTemplate::getInstance()->parseLink(BxDolService::call($sModule, $sMethod, array($oProfile->getContentId())), $sCaption);
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_system_general System Services 
+     * @subsection bx_system_general-profiles Profiles
+     * @subsubsection bx_system_general-profile_membership profile_membership
+     * 
+     * @code bx_srv('system', 'profile_membership', [2], 'TemplServiceProfiles'); @endcode
+     * @code {{~system:profile_membership:TemplServiceProfiles[2]~}} @endcode
+     * 
+     * Get membership level for specified profile.
+     * @param $iProfileId profile ID
+     * 
+     * @see BxBaseServiceProfiles::serviceProfileMembership
+     */
+    /** 
+     * @ref bx_system_general-profile_membership "profile_membership"
+     */
+    public function serviceProfileMembership ($iProfileId = 0)
+    {
+        if (!$iProfileId)
+            $iProfileId = bx_get_logged_profile_id();
+
+		return BxDolAcl::getInstance()->getProfileMembership($iProfileId);
+    }
+
+    public function serviceProfileMembershipStats ($iProfileId = 0)
+    {
+        if(!$iProfileId)
+            $iProfileId = bx_get_logged_profile_id();
+
+        $sTxtUnlimited = _t('_unlimited');
+        $sTxtFiles = _t('_sys_storage_files');
+        $aQuota = BxDolProfileQuery::getInstance()->getProfileQuota($iProfileId);
+
+        $aTmplVarsStats = array();
+        if(!empty($aQuota['quota_size'])) {
+            $iWidth = (int)round(100 * $aQuota['current_size']/$aQuota['quota_size']);
+            $sPercent = $iWidth . '%';
+        }
+        else {
+            $iWidth = 0;
+            $sPercent = $sTxtUnlimited;
+        }
+        $aTmplVarsStats[] = array('title' => _t('_sys_profile_storage_quota_size'), 'width' => $iWidth, 'value' => _t_format_size($aQuota['current_size']), 'percent' => $sPercent);
+
+        if(!empty($aQuota['quota_number'])) {
+            $iWidth = (int)round(100 * $aQuota['current_number']/$aQuota['quota_number']);
+            $sPercent = $iWidth . '%';
+        }
+        else {
+            $iWidth = 0;
+            $sPercent = $sTxtUnlimited;
+        } 
+        $aTmplVarsStats[] = array('title' => _t('_sys_profile_storage_quota_number'), 'width' => $iWidth, 'value' => $aQuota['current_number'] . ' ' . $sTxtFiles, 'percent' => $sPercent);
+
+        return BxDolTemplate::getInstance()->parseHtmlByName('profile_membership_stats.html', array(
+            'membership' => BxDolAcl::getInstance()->getProfileMembership($iProfileId),
+            'bx_repeat:stat_items' => $aTmplVarsStats
+        ));
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_system_general System Services 
+     * @subsection bx_system_general-profiles Profiles
+     * @subsubsection bx_system_general-profile_notifications profile_notifications
+     * 
+     * @code bx_srv('system', 'profile_notifications', [], 'TemplServiceProfiles'); @endcode
+     * @code {{~system:profile_notifications:TemplServiceProfiles~}} @endcode
+     * 
+     * Get number of unread notifications for logged in profile
+     * 
+     * @see BxBaseServiceProfiles::serviceProfileNotifications
+     */
+    /** 
+     * @ref bx_system_general-profile_notifications "profile_notifications"
+     */
+    public function serviceProfileNotifications ($iProfileId = 0)
+    {
+        if (!$iProfileId)
+            $iProfileId = bx_get_logged_profile_id();
+
+        $oMenu = BxDolMenu::getObjectInstance('sys_account_notifications');
+        if(!$oMenu)
+            return 0;
+
+        $iNum = 0;
+        $aMenuItems = $oMenu->getMenuItems ();
+        foreach ($aMenuItems as $r) {
+            if (isset($r['bx_if:addon']) && $r['bx_if:addon']['condition'])
+                $iNum += $r['bx_if:addon']['content']['addon'];
+        }
+
+        return $iNum;
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_system_general System Services 
+     * @subsection bx_system_general-profiles Profiles
+     * @subsubsection bx_system_general-get_count_online_profiles get_count_online_profiles
+     * 
+     * @code bx_srv('system', 'get_count_online_profiles', [], 'TemplServiceProfiles'); @endcode
+     * @code {{~system:get_count_online_profiles:TemplServiceProfiles~}} @endcode
+     * 
+     * Get number of online profiles
+     * 
+     * @see BxBaseServiceProfiles::serviceGetCountOnlineProfiles
+     */
+    /** 
+     * @ref bx_system_general-get_count_online_profiles "get_count_online_profiles"
+     */
+    public function serviceGetCountOnlineProfiles ()
+    {
+        $oProfilesQuery = BxDolProfileQuery::getInstance();
+        return $oProfilesQuery->getOnlineCount();
+    }
+    
+    public function serviceGetProfilesModules ()
+    {
+        if (getParam('sys_db_cache_enable')) { // get list of profiles  modules from db cache, cache is invalidated when new module is installed
+
+            $oDb = BxDolDb::getInstance();
+
+            $oCache = $oDb->getDbCacheObject ();
+
+            $sKey = $oDb->genDbCacheKey('profiles_modules_array');
+            $sKeyTs = $oDb->genDbCacheKey('profiles_modules_ts');
+
+            $mixedRetTs = $oCache->getData($sKeyTs);
+            $mixedRet = $oCache->getData($sKey);
+
+            $iNewestModuleTs = $this->_getLatestModuleTimestamp ();
+
+            if ($mixedRetTs != null && $mixedRet !== null && $mixedRetTs == $iNewestModuleTs) {
+
+                $aModulesArray = $mixedRet;
+
+            } else {
+
+                $aModulesArray = $this->_getProfilesModules ();
+
+                $oCache->setData($sKey, $aModulesArray);
+                $oCache->setData($sKeyTs, $iNewestModuleTs);
+            }
+
+        } else {
+
+            $aModulesArray = $this->_getProfilesModules ();
+
+        }
+
+        return $aModulesArray;
+    }
+
+    public function serviceProfilesFriends ($iLimit = 20)
+    {
+        if (!($iProfileId = bx_get_logged_profile_id()))
+            return array();
+        
+        $oConnection = BxDolConnection::getObjectInstance('sys_profiles_friends');
+        if (!$oConnection)
+            return array();
+
+        if (!($a = $oConnection->getConnectedContent ($iProfileId, true, 0, $iLimit)))
+            return array();
+
+        $aRet = array();
+        foreach ($a as $iId) {
+            $oProfile = BxDolProfile::getInstance($iId);
+
+            $aRet[] = array (
+            	'label' => $oProfile->getDisplayName(), 
+                'value' => $iId, 
+                'url' => $oProfile->getUrl(),
+            	'thumb' => $oProfile->getThumb(),
+                'unit' => $oProfile->getUnit(0, array('template' => 'unit_wo_info'))
+            );
+        }
+        return $aRet;
+    }
+
+    public function serviceProfilesSearch ($sTerm, $iLimit = 20)
+    {
+        // display friends by default
+        if (!$sTerm)
+            return $this->serviceProfilesFriends($iLimit);
+
+        // get list of "profiles" modules
+        $aModules = $this->serviceGetProfilesModules();
+
+        // search in each module
+        $a = array();
+        foreach ($aModules as $aModule) {
+            if (!BxDolService::call($aModule['name'], 'act_as_profile'))
+                continue;
+            $a = array_merge($a, BxDolService::call($aModule['name'], 'profiles_search', array($sTerm, getParam('sys_per_page_search_keyword_single'))));
+        }
+
+        // sort result
+        usort($a, function($r1, $r2) {
+            return strcmp($r1['label'], $r2['label']);
+        });
+
+        bx_alert('system', 'profiles_search', 0, 0, array(
+           'term' => $sTerm,
+           'result' => &$a
+        ));
+        
+        // return as array
+        return array_slice($a, 0, $iLimit);
+    }
+
+    public function serviceProfilesList ($iAccountId = 0)
+    {
+        $oProfilesQuery = BxDolProfileQuery::getInstance();
+
+        $aProfiles = $oProfilesQuery->getProfilesByAccount($iAccountId ? $iAccountId : getLoggedId());
+        if (!$aProfiles)
+            return false;
+
+        $s = '';
+        foreach ($aProfiles as $aProfile)
+            if ($aProfile['type'] != 'system')
+                $s .= BxDolService::call($aProfile['type'], 'profile_unit', array($aProfile['content_id']));
+
+        if (!$s)
+            $s = MsgBox(_t('_sys_txt_empty'));
+
+        return $s . '<div class="bx-clear"></div>';
+    }
+
+    public function serviceAccountProfileSwitcher ($iAccountId = false, $iActiveProfileId = null, $sUrlProfileAction = '', $bShowAll = 0, $sButtonTitle = '', $sProfileTemplate = '')
+    {
+    	$oTemplate = BxDolTemplate::getInstance();
+    	BxDolInformer::getInstance($oTemplate)->setEnabled(false);
+
+        $oProfilesQuery = BxDolProfileQuery::getInstance();
+
+        $aProfiles = $oProfilesQuery->getProfilesByAccount($iAccountId ? $iAccountId : getLoggedId());
+        if (!$aProfiles)
+            return false;
+
+        if (null === $iActiveProfileId)
+            $iActiveProfileId = bx_get_logged_profile_id();
+
+		$oModuleDb = BxDolModuleQuery::getInstance();
+
+        $aVars = array (
+            'bx_repeat:row' => array(),
+        );
+        foreach ($aProfiles as $aProfile) {
+            if (!$bShowAll && $iActiveProfileId == $aProfile['id'])
+                continue;
+
+        	if(!$oModuleDb->isEnabledByName($aProfile['type']))
+        		continue;
+
+            if (!BxDolService::call($aProfile['type'], 'act_as_profile'))
+                continue;
+            $aVars['bx_repeat:row'][] = array (
+                'class' => $iActiveProfileId == $aProfile['id'] ? '' : 'bx-def-color-bg-box',
+                'bx_if:active' => array (
+                    'condition' => $iActiveProfileId == $aProfile['id'],
+                    'content' => array('id' => $aProfile['id']),
+                ),
+                'bx_if:inactive' => array (
+                    'condition' => $iActiveProfileId != $aProfile['id'],
+                    'content' => array(
+                        'id' => $aProfile['id'], 
+                        'button_title' => $sButtonTitle ? $sButtonTitle : _t('_sys_txt_switch_profile_context'),
+                        'url_switch' => $sUrlProfileAction ? str_replace('{profile_id}', $aProfile['id'], $sUrlProfileAction) : BxDolPermalinks::getInstance()->permalink('page.php?i=account-profile-switcher', array('switch_to_profile' => $aProfile['id'], 'redirect_back' => 1))
+                    ),
+                ),
+                'unit' => BxDolService::call($aProfile['type'], 'profile_unit', array($aProfile['content_id'], array('template' => $sProfileTemplate))),
+            );
+        }
+
+        $oTemplate->addCss('account.css');
+        return array(
+            'content' => $oTemplate->parseHtmlByName('profile_switch_row.html', $aVars),
+        );
+    }
+
+    protected function _getLatestModuleTimestamp ()
+    {
+        $aModules = BxDolModuleQuery::getInstance()->getModulesBy(array('type' => 'modules', 'active' => 1, 'order_by' => '`date` ASC'));
+        if (empty($aModules))
+            return 0;
+        $aModuleNewest = array_pop($aModules);
+        return $aModuleNewest['date'];
+    }
+
+    protected function _getProfilesModules ()
+    {
+        $aRet = array();
+        $aModules = BxDolModuleQuery::getInstance()->getModulesBy(array('type' => 'modules', 'active' => 1));
+        foreach ($aModules as $aModule) {
+            $oModule = BxDolModule::getInstance($aModule['name']);
+            if ($oModule instanceof iBxDolProfileService && $oModule->serviceActAsProfile())
+                $aRet[] = $aModule;
+        }
+        return $aRet;
+    }
+
+}
+
+/** @} */

+ 7 - 0
upgrade/files/11.0.0-11.0.1/files_delete.php

@@ -0,0 +1,7 @@
+<?php
+
+$aFilesDelete = array(
+    'plugins_public/croppie/LICENSE',
+    'plugins_public/croppie/package.json',
+);
+

+ 3 - 0
upgrade/files/11.0.0-11.0.1/script.php

@@ -0,0 +1,3 @@
+<?php
+
+    return true;

+ 16 - 0
upgrade/files/11.0.0-11.0.1/sql.sql

@@ -0,0 +1,16 @@
+
+SET @iCategoryId = (SELECT `id` FROM `sys_options_categories` WHERE `name` = 'hidden');
+UPDATE `sys_options` SET `category_id` = @iCategoryId WHERE `name` = 'sys_account_activation_letter';
+
+DELETE FROM `sys_options` WHERE `name` = 'sys_account_hide_unconfirmed_accounts';
+SET @iCategoryId = (SELECT `id` FROM `sys_options_categories` WHERE `name` = 'account');
+INSERT INTO `sys_options`(`category_id`, `name`, `caption`, `value`, `type`, `extra`, `check`, `check_error`, `order`) VALUES
+(@iCategoryId, 'sys_account_hide_unconfirmed_accounts', '_adm_stg_cpt_option_sys_account_hide_unconfirmed_accounts', 'on', 'checkbox', '', '', '', 17);
+
+DELETE FROM `sys_pages_blocks` WHERE `object` = 'sys_dashboard' AND `title` = '_sys_page_block_title_membership_stats';
+INSERT INTO `sys_pages_blocks` (`object`, `cell_id`, `module`, `title_system`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `active`, `order`) VALUES
+('sys_dashboard', 2, 'system', '', '_sys_page_block_title_membership_stats', 11, 2147483647, 'service', 'a:4:{s:6:"module";s:6:"system";s:6:"method";s:24:"profile_membership_stats";s:6:"params";a:0:{}s:5:"class";s:20:"TemplServiceProfiles";}', 0, 1, 1, 0);
+
+-- Last step is to update current version
+
+UPDATE `sys_modules` SET `version` = '11.0.1' WHERE (`version` = '11.0.0') AND `name` = 'system';

Некоторые файлы не были показаны из-за большого количества измененных файлов