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

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

AntonLV 3 лет назад
Родитель
Сommit
7a97f22b33
88 измененных файлов с 22108 добавлено и 0 удалено
  1. 14 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/check.php
  2. 2294 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/inc/classes/BxDolCmts.php
  3. 895 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/inc/classes/BxDolConnection.php
  4. 481 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/inc/classes/BxDolMenu.php
  5. 201 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/inc/classes/BxDolPageQuery.php
  6. 1145 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/inc/classes/BxDolSearch.php
  7. 1111 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/inc/js/classes/BxDolUploader.js
  8. 12 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/inc/version.inc.php
  9. 146 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/modules/base/general/classes/BxBaseModGeneralCmtsSearchResult.php
  10. 82 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/modules/base/groups/classes/BxBaseModGroupsConnectionFans.php
  11. 167 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/modules/base/groups/classes/BxBaseModGroupsMenuSnippetMeta.php
  12. 286 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/modules/base/profile/classes/BxBaseModProfileSearchResult.php
  13. 278 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/modules/base/text/classes/BxBaseModTextSearchResult.php
  14. 50 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/oembed.php
  15. 7 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins/autoload.php
  16. 15 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins/composer/autoload_files.php
  17. 75 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins/composer/autoload_real.php
  18. 224 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins/composer/autoload_static.php
  19. 2018 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins/composer/installed.json
  20. 348 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins/composer/installed.php
  21. 93 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins/guzzlehttp/psr7/composer.json
  22. 134 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins/guzzlehttp/psr7/src/Header.php
  23. 49 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins/guzzlehttp/psr7/src/LazyOpenStream.php
  24. 246 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins/guzzlehttp/psr7/src/Message.php
  25. 263 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins/guzzlehttp/psr7/src/MessageTrait.php
  26. 159 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins/guzzlehttp/psr7/src/MultipartStream.php
  27. 344 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins/guzzlehttp/psr7/src/ServerRequest.php
  28. 740 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins/guzzlehttp/psr7/src/Uri.php
  29. 35 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins/psr/http-factory/composer.json
  30. 26 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins/psr/http-message/composer.json
  31. 130 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins/psr/http-message/docs/PSR7-Interfaces.md
  32. 159 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins/psr/http-message/docs/PSR7-Usage.md
  33. 189 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins/psr/http-message/src/MessageInterface.php
  34. 131 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins/psr/http-message/src/RequestInterface.php
  35. 70 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins/psr/http-message/src/ResponseInterface.php
  36. 263 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins/psr/http-message/src/ServerRequestInterface.php
  37. 160 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins/psr/http-message/src/StreamInterface.php
  38. 125 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins/psr/http-message/src/UploadedFileInterface.php
  39. 326 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins/psr/http-message/src/UriInterface.php
  40. 39 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/ar-ar.js
  41. 8 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/cmd.sh
  42. 39 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/cs-cz.js
  43. 40 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/da-dk.js
  44. 39 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/de-de.js
  45. 39 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/el-el.js
  46. 39 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/en-en.js
  47. 39 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/es-es.js
  48. 39 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/fa-ir.js
  49. 39 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/fi-fi.js
  50. 39 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/fr-fr.js
  51. 39 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/he-he.js
  52. 39 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/hr-hr.js
  53. 39 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/hu-hu.js
  54. 39 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/id-id.js
  55. 39 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/it-it.js
  56. 39 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/ja-ja.js
  57. 39 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/lt-lt.js
  58. 39 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/nl-nl.js
  59. 39 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/no-nb.js
  60. 39 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/pl-pl.js
  61. 39 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/pt-br.js
  62. 39 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/ro-ro.js
  63. 39 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/ru-ru.js
  64. 39 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/sk-sk.js
  65. 39 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/sv-se.js
  66. 39 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/tr-tr.js
  67. 39 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/uk-ua.js
  68. 39 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/vi-vi.js
  69. 39 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/zh-cn.js
  70. 39 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/zh-tw.js
  71. 44 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/studio/polyglot.php
  72. 33 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/studio/template/bp_blocks.html
  73. 255 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/studio/template/css/builder_page.css
  74. 221 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/studio/template/css/general.css
  75. 1 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/studio/template/images/icons/api.svg
  76. 954 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/studio/template/scripts/BxBaseStudioNavigationItems.php
  77. 1614 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/template/scripts/BxBaseCmts.php
  78. 296 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/template/scripts/BxBaseCmtsMenuActions.php
  79. 396 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/template/scripts/BxBaseMenu.php
  80. 154 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/template/scripts/BxBaseMenuCustom.php
  81. 1076 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/template/scripts/BxBasePage.php
  82. 440 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/template/scripts/BxBaseSearchResult.php
  83. 464 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/template/scripts/BxBaseServiceConnections.php
  84. 1296 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/template/scripts/BxBaseServices.php
  85. 174 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files/template/scripts/BxBaseUploaderHTML5.php
  86. 12 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/files_delete.php
  87. 3 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/script.php
  88. 5 0
      upgrade/files/13.0.0.RC5-13.0.0.RC6/sql.sql

+ 14 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/check.php

@@ -0,0 +1,14 @@
+<?php
+
+$mixCheckResult = 'Update can not be applied';
+
+if (!(PHP_VERSION_ID >= 70400)) {
+    $mixCheckResult = 'This update requires a PHP version ">= 7.4.0". You are running ' . PHP_VERSION;
+}
+else {
+    $sVer = $this->oDb->getOne("SELECT `version` FROM `sys_modules` WHERE `name` = 'system'");
+    if ('13.0.0.RC5' == $sVer || '13.0.0-RC5' == $sVer)
+        $mixCheckResult = true;
+}
+
+return $mixCheckResult;

+ 2294 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/inc/classes/BxDolCmts.php

@@ -0,0 +1,2294 @@
+<?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');
+bx_import('BxDolPrivacy');
+
+define('BX_DOL_CMT_TYPE_COMMENT', 'comment');
+define('BX_DOL_CMT_TYPE_REVIEW', 'review');
+
+define('BX_CMT_OLD_VOTES', 365*86400); ///< comment votes older than this number of seconds will be deleted automatically
+
+define('BX_CMT_ACTION_POST', 'post');
+define('BX_CMT_ACTION_EDIT', 'edit');
+
+define('BX_CMT_DISPLAY_FLAT', 'flat');
+define('BX_CMT_DISPLAY_THREADED', 'threaded');
+
+define('BX_CMT_BROWSE_HEAD', 'head');
+define('BX_CMT_BROWSE_TAIL', 'tail');
+define('BX_CMT_BROWSE_POPULAR', 'popular');
+define('BX_CMT_BROWSE_CONNECTION', 'connection');
+
+define('BX_CMT_ORDER_BY_DATE', 'date');
+define('BX_CMT_ORDER_BY_POPULAR', 'popular');
+
+define('BX_CMT_FILTER_ALL', 'all');
+define('BX_CMT_FILTER_OTHERS', 'others');
+define('BX_CMT_FILTER_FRIENDS', 'friends');
+define('BX_CMT_FILTER_SUBSCRIPTIONS', 'subscriptions');
+define('BX_CMT_FILTER_PINNED', 'pinned');
+
+define('BX_CMT_ORDER_WAY_ASC', 'asc');
+define('BX_CMT_ORDER_WAY_DESC', 'desc');
+
+define('BX_CMT_PFP_TOP', 'top');
+define('BX_CMT_PFP_BOTTOM', 'bottom');
+
+define('BX_CMT_RATE_VALUE_PLUS', 1);
+define('BX_CMT_RATE_VALUE_MINUS', -1);
+
+define('BX_CMT_USAGE_BLOCK', 'block');
+define('BX_CMT_USAGE_INLINE', 'inline');
+define('BX_CMT_USAGE_DEFAULT', BX_CMT_USAGE_BLOCK);
+
+define('BX_CMT_STATUS_ACTIVE', 'active');
+define('BX_CMT_STATUS_HIDDEN', 'hidden');
+define('BX_CMT_STATUS_PENDING', 'pending');
+
+
+/**
+ * Comments for any content
+ *
+ * Related classes:
+ * - BxDolCmtsQuery - comments database queries
+ * - BxBaseCmts - comments base representation
+ * - BxTemplCmts - custom template representation
+ *
+ * AJAX comments for any content.
+ * Self moderated - users rate all comments, and if comment is
+ * below viewing treshold it is hidden by default.
+ *
+ * To add comments section to your module you need to add a record to 'sys_objects_cmts' table:
+ *
+ * - ID - autoincremented id for internal usage
+ * - ObjectName - your unique module name, with vendor prefix, lowercase and spaces are underscored
+ * - TableCmts - table name where comments are stored
+ * - Html - 0 - convert new lines to <br /> on saving, 1 - standard(default) visual editor, 2 - full visual editor, 3 - mini visual editor.
+ * - PerView - number of comments on a page
+ * - IsRatable - 0 or 1 allow to rate comments or not
+ * - ViewingThreshold - comment viewing treshost, if comment is below this number it is hidden by default
+ * - IsOn - is this comment object enabled
+ * - RootStylePrefix - toot comments style prefix, if you need root comments look different\
+ * - ObjectVote - Vote object name to process comments' votes. May be empty if Comment Vote is not needed.
+ * - TriggerTable - table to be updated upon each comment
+ * - TriggerFieldId - TriggerTable table field with unique record id of
+ * - TriggerFieldComments - TriggerTable table field with comments count, it will be updated automatically upon eaech comment
+ * - ClassName - your custom class name if you need to override default class, this class must have the same constructor arguments
+ * - ClassFile - file where your ClassName is stored.
+ *
+ * You can refer to BoonEx modules for sample record in this table.
+ *
+ *
+ *
+ * @section example Example of usage:
+ * After filling in the table you can show comments section in any place, using the following code:
+ *
+ * @code
+ * $o = new BxTemplCmts('value of ObjectName field', $iYourEntryId);
+ * if ($o->isEnabled())
+ *     echo $o->getCommentsBlock ();
+ * @endcode
+ *
+ * Please note that you never need to use BxDolCmts class directly, use BxTemplCmts instead.
+ * Also if you override comments class with your own then make it child of BxTemplCmts class.
+ *
+ *
+ *
+ * @section acl Memberships/ACL:
+ * - comments post
+ * - comments edit own
+ * - comments remove own
+ * - comments edit all
+ *
+ *
+ *
+ * @section alerts Alerts:
+ * Alerts type/unit - every module has own type/unit, it equals to ObjectName.
+ *
+ * The following alerts are rised
+ *
+ * - commentPost - comment was posted
+ *      - $iObjectId - entry id
+ *      - $iSenderId - author of comment
+ *      - $aExtra['comment_id'] - just added comment id
+ *
+ * - commentRemoved - comments was removed
+ *      - $iObjectId - entry id
+ *      - $iSenderId - comment deleter id
+ *      - $aExtra['comment_id'] - removed comment id
+ *
+ * - commentUpdated - comments was updated
+ *      - $iObjectId - entry id
+ *      - $iSenderId - comment deleter id
+ *      - $aExtra['comment_id'] - updated comment id
+ *
+ * - commentRated - comments was rated
+ *      - $iObjectId - entry id
+ *      - $iSenderId - comment rater id
+ *      - $aExtra['comment_id'] - rated comment id
+ *      - $aExtra['rate'] - comment rate 1 or -1
+ *
+ */
+class BxDolCmts extends BxDolFactory implements iBxDolReplaceable, iBxDolContentInfoService
+{
+    /**
+     * System tables which are UNITED for all 
+     * comment systems and cannot be overwritten.
+     */
+    public static $sTableSystems = 'sys_objects_cmts';
+    public static $sTableIds = 'sys_cmts_ids';
+
+    /**
+     * System tables which are used by default and
+     * can be overwritten in comment systems.
+     * @see BxDolCmts::getTableNameImages and BxDolCmts::getTableNameImages2Entries
+     */
+    protected $_sTableImages = 'sys_cmts_images';
+    protected $_sTableImages2Entries = 'sys_cmts_images2entries';
+
+    protected $_bIsApi;
+
+    protected $_sType;
+    protected $_oQuery = null;
+    protected $_oTemplate = null;
+
+    protected $_bPostQuote;
+    protected $_bMinPostForm;
+    protected $_sFormObject;
+    protected $_sFormDisplayPost;
+    protected $_sFormDisplayEdit;
+
+    protected $_sConnObjFriends;
+    protected $_sConnObjSubscriptions;
+
+    protected $_sMenuObjManage;
+    protected $_sMenuObjActions;
+    protected $_sMenuObjCounters;
+    protected $_sMenuObjMeta;
+
+    protected $_sMetatagsObj;
+
+    protected $_sViewUrl = '';
+    protected $_sBaseUrl = '';
+    protected $_sListAnchor = '';
+    protected $_sItemAnchor = '';
+
+    protected $_sSystem = 'profile'; ///< current comment system name
+    protected $_aSystem = array (); ///< current comments system array
+    protected $_iId = 0; ///< obect id to be commented
+    protected $_iAuthorId = 0; ///< currently logged in user who browse, post, etc.
+
+    protected $_aT = array (); ///< an array of lang keys
+    protected $_aMarkers = array ();
+
+    protected $_sDisplayType = '';
+    protected $_sDpSessionKey = '';
+    protected $_iDpMaxLevel = 0;
+
+    protected $_sBrowseType = '';
+    protected $_bBrowseFilter = false;
+    protected $_sBrowseFilter = '';
+    protected $_sBpSessionKeyType = '';
+    protected $_sBpSessionKeyFilter = '';
+    protected $_aOrder = array();
+
+    protected $_sSnippetLenthLiveSearch = 50;
+
+    protected $_iRememberTime = 2592000;
+
+    /**
+     * Constructor
+     * $sSystem - comments system name
+     * $iId - obect id to be commented
+     */
+    protected function __construct($sSystem, $iId, $iInit = true, $oTemplate = false)
+    {
+        parent::__construct();
+
+        $this->_bIsApi = bx_is_api();
+
+        $this->_sType = BX_DOL_CMT_TYPE_COMMENT;
+
+        $this->_aSystems = $this->getSystems();
+        if(!isset($this->_aSystems[$sSystem]))
+            return;
+
+        $this->_sSystem = $sSystem;
+        $this->_aSystem = $this->_aSystems[$sSystem];
+        $this->_aSystem['is_browse_filter'] = (int)$this->_bBrowseFilter;
+
+        $this->_iAuthorId = $this->_getAuthorId();
+
+        $this->_iDpMaxLevel = (int)$this->_aSystem['number_of_levels'];
+        $this->_sDisplayType = $this->_iDpMaxLevel == 0 ? BX_CMT_DISPLAY_FLAT : BX_CMT_DISPLAY_THREADED;
+        $this->_sDpSessionKey = 'bx_' . $this->_sSystem . '_dp_';
+
+        $this->_sBrowseType = $this->_aSystem['browse_type'];
+        $this->_sBrowseFilter = BX_CMT_FILTER_ALL;
+        $this->_sBpSessionKeyType = 'bx_' . $this->_sSystem . '_bpt_';
+        $this->_sBpSessionKeyFilter = 'bx_' . $this->_sSystem . '_bpf_';
+        $this->_aOrder = array(
+            'by' => BX_CMT_ORDER_BY_DATE,
+            'way' => BX_CMT_ORDER_WAY_ASC
+        );
+
+        list($mixedUserDp, $mixedUserBpType, $mixedUserBpFilter) = $this->_getUserChoice();
+        if(!empty($mixedUserDp))
+            $this->_sDisplayType = $mixedUserDp;
+        if(!empty($mixedUserBpType))
+            $this->_sBrowseType = $mixedUserBpType;
+        if(!empty($mixedUserBpFilter))
+            $this->_sBrowseFilter = $mixedUserBpFilter;
+
+        $this->_sViewUrl = 'page.php?i=cmts-view';
+        $this->_sBaseUrl = $this->_aSystem['base_url'];
+
+        $this->_sListAnchor = "cmts-anchor-%s-%d";
+        $this->_sItemAnchor = "cmt-anchor-%s-%d-%d";
+
+        $this->_oQuery = new BxDolCmtsQuery($this);
+
+        $this->_bPostQuote = false;
+        $this->_bMinPostForm = true;
+        $this->_sFormObject = 'sys_comment';
+        $this->_sFormDisplayPost = 'sys_comment_post';
+        $this->_sFormDisplayEdit = 'sys_comment_edit';
+
+        $this->_sConnObjFriends = 'sys_profiles_friends';
+        $this->_sConnObjSubscriptions = 'sys_profiles_subscriptions';
+
+        $this->_sMenuObjManage = 'sys_cmts_item_manage';
+        $this->_sMenuObjActions = 'sys_cmts_item_actions';
+        $this->_sMenuObjCounters = 'sys_cmts_item_counters';
+        $this->_sMenuObjMeta = 'sys_cmts_item_meta';
+
+        $this->_sMetatagsObj = 'sys_cmts';
+
+        $this->_aT = array(
+            'block_comments_title' => '_cmt_block_comments_title',
+            'txt_sample_single' => '_cmt_txt_sample_comment_single',
+            'txt_sample_vote_single' => '_cmt_txt_sample_vote_single',
+            'txt_sample_reaction_single' => '_cmt_txt_sample_reaction_single',
+            'txt_sample_score_up_single' => '_cmt_txt_sample_score_up_single',
+            'txt_sample_score_down_single' => '_cmt_txt_sample_score_down_single',
+            'txt_min_form_placeholder' => '_cmt_txt_min_form_placeholder'
+        );
+
+        if ($iInit)
+            $this->init($iId);
+
+        if ($oTemplate)
+            $this->_oTemplate = $oTemplate;
+        else
+            $this->_oTemplate = BxDolTemplate::getInstance();
+    }
+
+    /**
+     * get comments object instanse
+     * @param $sSys comments object name
+     * @param $iId associated content id, where comments are postred in
+     * @param $iInit perform initialization
+     * @return null on error, or ready to use class instance
+     */
+    public static function getObjectInstance($sSys, $iId, $iInit = true, $oTemplate = false)
+    {
+        if(isset($GLOBALS['bxDolClasses']['BxDolCmts!' . $sSys . $iId]))
+            return $GLOBALS['bxDolClasses']['BxDolCmts!' . $sSys . $iId];
+
+        $aSystems = self::getSystems();
+        if (!isset($aSystems[$sSys]))
+            return null;
+
+        $sClassName = 'BxTemplCmts';
+        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']['BxDolCmts!' . $sSys . $iId] = $o);
+    }
+    
+    /**
+     * get comments object instanse
+     * @param $iUniqId unique comment id
+     * @return null on error, or ready to use class instance
+     */
+    public static function getObjectInstanceByUniqId($iUniqId, $iInit = true, $oTemplate = false)
+    {
+        $aData = BxDolCmtsQuery::getInfoByUniqId($iUniqId);
+        if(empty($aData) || !is_array($aData))
+            return null;
+
+        return self::getObjectInstance($aData['system_name'], $aData['cmt_object_id']);
+    }
+
+    public static function &getSystems ()
+    {
+        $sKey = 'bx_dol_cache_memory_cmts_systems';
+
+        if (!isset($GLOBALS[$sKey])) {
+            $GLOBALS[$sKey] = BxDolDb::getInstance()->fromCache('sys_objects_cmts', 'getAllWithKey', '
+                SELECT
+                    `ID` as `system_id`,
+                    `Name` AS `name`,
+                    `Module` AS `module`,
+                    `Table` AS `table`,
+                    `CharsPostMin` AS `chars_post_min`,
+                    `CharsPostMax` AS `chars_post_max`,
+                    `CharsDisplayMax` AS `chars_display_max`,
+                    `Html` AS `html`,
+                    `PerView` AS `per_view`,
+                    `PerViewReplies` AS `per_view_replies`,
+                    `BrowseType` AS `browse_type`,
+                    `IsBrowseSwitch` AS `is_browse_switch`,
+                    `PostFormPosition` AS `post_form_position`,
+                    `NumberOfLevels` AS `number_of_levels`,
+                    `IsDisplaySwitch` AS `is_display_switch`,
+                    `IsRatable` AS `is_ratable`,
+                    `ViewingThreshold` AS `viewing_threshold`,
+                    `IsOn` AS `is_on`,
+                    `RootStylePrefix` AS `root_style_prefix`,
+                    `BaseUrl` AS `base_url`,
+                    `ObjectVote` AS `object_vote`,
+                    `ObjectReaction` AS `object_reaction`,
+                    `ObjectScore` AS `object_score`,
+                    `ObjectReport` AS `object_report`,
+                    `TriggerTable` AS `trigger_table`,
+                    `TriggerFieldId` AS `trigger_field_id`,
+                    `TriggerFieldAuthor` AS `trigger_field_author`,
+                    `TriggerFieldTitle` AS `trigger_field_title`,
+                    `TriggerFieldComments` AS `trigger_field_comments`,
+                    `ClassName` AS `class_name`,
+                    `ClassFile` AS `class_file`
+                FROM `' . self::$sTableSystems . '`', 'name');
+        }
+        return $GLOBALS[$sKey];
+    }
+
+    public static function getGlobalInfo ($iUniqueId)
+    {
+        return BxDolDb::getInstance()->getRow("SELECT `ti`.*, `ts`.`Name` AS `system_name` FROM `" . self::$sTableIds . "` AS `ti` LEFT JOIN `" . self::$sTableSystems . "` AS `ts` ON `ti`.`system_id`=`ts`.`ID` WHERE `ti`.`id`=:id LIMIT 1", array(
+            'id' => (int)$iUniqueId
+        ));
+    }
+
+    public static function getGlobalNumByParams($aParams = [])
+    {
+        $sQuery = "SELECT COUNT(*) FROM `" . self::$sTableIds . "` WHERE 1";
+        foreach($aParams as $aValue)
+            $sQuery .= " AND `" . $aValue['key'] ."` " . $aValue['operator'] . " '" . $aValue['value'] . "'";
+
+        return BxDolDb::getInstance()->getOne($sQuery);
+    }
+
+    public function init ($iId)
+    {
+        if (empty($this->iId) && $iId)
+            $this->setId($iId);
+
+        $this->addMarkers(array(
+            'object_id' => $this->getId(),
+            'user_id' => $this->_getAuthorId()
+        ));
+    }
+
+    public function getId ()
+    {
+        return $this->_iId;
+    }
+
+    public function isEnabled ()
+    {
+        return isset($this->_aSystem['is_on']) && $this->_aSystem['is_on'];
+    }
+
+    public function getSystemId()
+    {
+        return $this->_aSystem['system_id'];
+    }
+
+    public function getSystemName()
+    {
+        return $this->_sSystem;
+    }
+
+    public function getSystemInfo()
+    {
+        return $this->_aSystem;
+    }
+
+    public function getStorageObjectName()
+    {
+    	return $this->_getFormObject()->getStorageObjectName();
+    }
+
+    public function getTranscoderPreviewName()
+    {
+    	return $this->_getFormObject()->getTranscoderPreviewName();
+    }
+    
+    public function getFormObject()
+    {
+    	return $this->_getFormObject();
+    }
+
+    public function getTableNameImages()
+    {
+        return $this->_sTableImages;
+    }
+
+    public function getTableNameImages2Entries()
+    {
+        return $this->_sTableImages2Entries;
+    }
+
+    public function getLanguageKey($sIndex)
+    {
+        return isset($this->_aT[$sIndex]) ? $this->_aT[$sIndex] : '';
+    }
+
+    public function getMaxLevel()
+    {
+        return $this->_iDpMaxLevel;
+    }
+
+    public function getOrder ()
+    {
+        return $this->_sOrder;
+    }
+
+    public function getPerView ($iCmtParentId = 0)
+    {
+        return $iCmtParentId == 0 ? $this->_aSystem['per_view'] : $this->_aSystem['per_view_replies'];
+    }
+
+    public function getStatusAdmin()
+    {
+        return $this->isEditAllowedAll() || $this->isRemoveAllowedAll() || $this->isAutoApprove() ? BX_CMT_STATUS_ACTIVE : BX_CMT_STATUS_PENDING;
+    }
+
+    public function getViewUrl($iCmtId, $bAbsolute = true)
+    {
+    	if(empty($this->_aSystem['trigger_field_title']))
+            return '';
+
+        $s = BxDolPermalinks::getInstance()->permalink($this->_sViewUrl, [
+            'sys' => $this->_sSystem,
+            'id' => $this->_iId,
+            'cmt_id' => $iCmtId
+        ]);
+
+    	return $bAbsolute ? bx_absolute_url($s) : $s;
+    }
+
+    public function getViewText($mixedItem)
+    {
+        if(!is_array($mixedItem))
+            $mixedItem = $this->getCommentSimple((int)$mixedItem);
+
+        if(empty($mixedItem) || !is_array($mixedItem))
+            return '';
+
+        return $this->_prepareTextForOutput($mixedItem['cmt_text'], (int)$mixedItem['cmt_id']);
+    }
+
+    public function getBaseUrl()
+    {
+        $sUrl = $this->_replaceMarkers($this->_sBaseUrl);
+        $sUrl = BxDolPermalinks::getInstance()->permalink($sUrl);
+        if(get_mb_substr($sUrl, 0, 6) != 'http:/' && get_mb_substr($sUrl, 0, 7) != 'https:/')
+            $sUrl = BX_DOL_URL_ROOT . $sUrl;
+        return $sUrl;
+    }
+
+    public function getListUrl()
+    {
+        $sBaseUrl = $this->getBaseUrl();
+        if(empty($sBaseUrl))
+            return '';
+
+        return $sBaseUrl . $this->getListAnchor(true);
+    }
+
+    public function getItemUrl($iItemId)
+    {
+        $sBaseUrl = $this->getBaseUrl();
+        if(empty($sBaseUrl))
+            return '';
+
+        return $sBaseUrl . $this->getItemAnchor($iItemId, true);
+    }
+
+    public function getListAnchor($bWithHash = false)
+    {
+        return ($bWithHash ? '#' : '') . sprintf($this->_sListAnchor, str_replace('_', '-', $this->getSystemName()), $this->getId());
+    }
+
+    public function getItemAnchor($iItemId, $bWithHash = false)
+    {
+        return ($bWithHash ? '#' : '') . sprintf($this->_sItemAnchor, str_replace('_', '-', $this->getSystemName()), $this->getId(), $iItemId);
+    }
+
+    public function getAttachments($iCmtId)
+    {
+        $aResult = array();
+
+        if(!$this->isAttachImageEnabled())
+            return $aResult; 
+
+        $aFiles = $this->_oQuery->getFiles($this->_aSystem['system_id'], $iCmtId);
+        if(empty($aFiles) || !is_array($aFiles)) 
+            return $aResult;
+
+        $oStorage = BxDolStorage::getObjectInstance($this->getStorageObjectName());
+        $oTranscoder = BxDolTranscoderImage::getObjectInstance($this->getTranscoderPreviewName());
+
+        foreach($aFiles as $aFile) {
+            $bImage = $oTranscoder && $oTranscoder->isMimeTypeSupported($aFile['mime_type']);
+
+            $sPreview = '';
+            if($bImage)
+                $sPreview = $oTranscoder->getFileUrl($aFile['image_id']);
+
+            if(!$sPreview)
+                $sPreview = $this->_oTemplate->getIconUrl($oStorage->getIconNameByFileName($aFile['file_name']));
+
+            $sUrl = $oStorage->getFileUrlById($aFile['image_id']);
+
+            $aResult[] = array(
+                'id' => $aFile['image_id'],
+                'src' => $sPreview,
+                'src_orig' => $bImage ? $sUrl : '',
+                'url' => !$bImage ? $sUrl : ''
+            );
+        }
+
+        return $aResult;
+    }
+
+    public function getConnectionObject($sType)
+    {
+        $sResult = '';
+
+        switch($sType) {
+            case BX_CMT_FILTER_FRIENDS:
+                $sResult = $this->_sConnObjFriends;
+                break;
+            case BX_CMT_FILTER_SUBSCRIPTIONS:
+                $sResult = $this->_sConnObjSubscriptions;
+                break;
+        }
+
+        return $sResult;
+    }
+
+    public function getVoteObject($iEniqId)
+    {
+        if(empty($this->_aSystem['object_vote']))
+            $this->_aSystem['object_vote'] = 'sys_cmts';
+
+        $oVote = BxDolVote::getObjectInstance($this->_aSystem['object_vote'], $iEniqId, true, $this->_oTemplate);
+        if(!$oVote || !$oVote->isEnabled())
+            return false;
+
+        return $oVote;
+    }
+
+    public function getReactionObject($iEniqId)
+    {
+        if(empty($this->_aSystem['object_reaction']))
+            $this->_aSystem['object_reaction'] = 'sys_cmts_reactions';
+
+        $oReaction = BxDolVote::getObjectInstance($this->_aSystem['object_reaction'], $iEniqId, true, $this->_oTemplate);
+        if(!$oReaction || !$oReaction->isEnabled())
+            return false;
+
+        return $oReaction;
+    }
+
+    public function getScoreObject($iEniqId)
+    {
+        if(empty($this->_aSystem['object_score']))
+            $this->_aSystem['object_score'] = 'sys_cmts';
+
+        $oScore = BxDolScore::getObjectInstance($this->_aSystem['object_score'], $iEniqId, true, $this->_oTemplate);
+        if(!$oScore || !$oScore->isEnabled())
+            return false;
+
+        return $oScore;
+    }
+
+    public function getReportObject($iEniqId)
+    {
+        if(empty($this->_aSystem['object_report']))
+            $this->_aSystem['object_report'] = 'sys_cmts';
+
+        $oReport = BxDolReport::getObjectInstance($this->_aSystem['object_report'], $iEniqId, true, $this->_oTemplate);
+        if(!$oReport || !$oReport->isEnabled())
+            return false;
+
+        return $oReport;
+    }
+
+    public function getNotificationId()
+    {
+        return 'cmts-notification-' . $this->_sSystem . '-' . $this->_iId;
+    }
+
+    /**
+     * @deprecated since version 10.0.0-B3 and can be removed in later versions.
+     */
+    public function setTableNameFiles($sTable)
+    {
+        $this->_sTableImages = $sTable;
+    	$this->_oQuery->setTableNameFiles($sTable);
+    }
+
+    /**
+     * @deprecated since version 10.0.0-B3 and can be removed in later versions.
+     */
+    public function setTableNameFiles2Entries($sTable)
+    {
+    	$this->_sTableImages2Entries = $sTable;
+    	$this->_oQuery->setTableNameFiles2Entries($sTable);
+    }
+
+    public function isHtml ()
+    {
+        return $this->_aSystem['html'] > 0;
+    }
+
+    public function isRatable ()
+    {
+        return $this->_aSystem['is_ratable'];
+    }
+
+    public function isAttachImageEnabled()
+    {
+        return true;
+    }
+
+    public function isAutoApprove()
+    {
+        return getParam('sys_cmts_enable_auto_approve') == 'on';
+    }
+
+    /**
+     * set id to operate with votes
+     */
+    public function setId ($iId)
+    {
+        if ($iId == $this->getId()) return;
+        $this->_iId = $iId;
+    }
+
+    /**
+     * Add replace markers.
+     * @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;
+    }
+
+    /**
+     * Database functions
+     */
+    public function getQueryObject ()
+    {
+        return $this->_oQuery;
+    }
+
+    public function getCommentsTableName ()
+    {
+        return $this->_oQuery->getTableName ();
+    }
+
+    public function getObjectAuthorId ($iObjectId = 0)
+    {
+    	if(empty($this->_aSystem['trigger_field_author']))
+    		return 0;
+
+        return $this->_oQuery->getObjectAuthorId ($iObjectId ? $iObjectId : $this->getId());
+    }
+
+    public function getObjectTitle ($iObjectId = 0)
+    {
+        if(empty($this->_aSystem['trigger_field_title']))
+            return '';
+
+        $sTitle = $this->_oQuery->getObjectTitle ($iObjectId ? $iObjectId : $this->getId());
+        if(get_mb_substr($sTitle, 0, 1) == '_')
+            $sTitle = _t($sTitle);
+
+        return $sTitle;
+    }
+    
+    public function getObjectPrivacyView ($iObjectId = 0)
+    {
+        if(empty($iObjectId))
+            $iObjectId = $this->getId();
+
+        $sFieldPrivacyView = '';
+        if(!empty($this->_aSystem['trigger_field_privacy_view']))
+            $sFieldPrivacyView = $this->_aSystem['trigger_field_privacy_view'];
+
+        if(($iPrivacyView = $this->_oQuery->getObjectPrivacyView($iObjectId, $sFieldPrivacyView)) !== false)
+            return $iPrivacyView;
+
+        return BX_DOL_PG_ALL;
+    }
+
+    /*
+     * Gets Content Filter object.
+     */
+    public function getObjectContentFilter ()
+    {
+        $oCf = BxDolContentFilter::getInstance();
+        if(!$oCf->isEnabledForComments())
+            return false;
+
+        return $oCf;
+    }
+
+    public function getCommentsCountAll ($iObjectId = 0, $bForceCalculate = false)
+    {
+        return $this->_oQuery->getCommentsCountAll ($iObjectId ? $iObjectId : $this->getId(), $this->_getAuthorId(), $bForceCalculate);
+    }
+
+    public function getCommentsCount ($iObjectId = 0, $iCmtVParentId = -1, $sFilter = '')
+    {
+        return $this->_oQuery->getCommentsCount ($iObjectId ? $iObjectId : $this->getId(), $iCmtVParentId, $this->_getAuthorId(), $sFilter);
+    }
+
+    public function getCommentsArray ($iVParentId, $sFilter, $aOrder, $iStart = 0, $iCount = -1)
+    {
+        return $this->_oQuery->getComments ($this->getId(), $iVParentId, $this->_getAuthorId(), $sFilter, $aOrder, $iStart, $iCount);
+    }
+
+    public function getCommentsBy ($aParams = [])
+    {
+        return $this->_oQuery->getCommentsBy($aParams); 
+    }
+
+    /**
+     * Get comment's unique id.
+     */
+    public function getCommentUniqId($iCmtId, $iAuthorId = 0)
+    {
+        return $this->_oQuery->getUniqId($this->getSystemId(), $iCmtId, ['author_id' => $iAuthorId]);
+    }
+
+    /**
+     * Get comment's short info.
+     */
+    public function getCommentSimple ($iCmtId)
+    {
+        return $this->_oQuery->getCommentSimple ($this->getId(), $iCmtId);
+    }
+
+    /**
+     * Get comment's full info.
+     */
+    public function getCommentRow ($iCmtId)
+    {
+        return $this->_oQuery->getComment ($this->getId(), $iCmtId);
+    }
+
+    public function onObjectDelete ($iObjectId = 0)
+    {
+        // delete comments
+        $aFiles = $aCmtIds = [];
+        $this->_oQuery->deleteObjectComments ($iObjectId ? $iObjectId : $this->getId(), $aFiles, $aCmtIds);
+
+        // delete votes
+        $this->deleteVotes($aCmtIds);
+
+        // delete reactions
+        $this->deleteReactions($aCmtIds);
+
+        // delete scores
+        $this->deleteScores($aCmtIds);
+
+        // delete reports
+        $this->deleteReports($aCmtIds);
+
+        // delete meta info
+        $this->deleteMetaInfo($aCmtIds);
+
+        // delete files
+        if ($aFiles) {
+            $oStorage = BxDolStorage::getObjectInstance($this->getStorageObjectName());
+            if ($oStorage)
+                $oStorage->queueFilesForDeletion($aFiles);
+        }
+
+        // delete unique IDs
+        $this->deleteUniqueIds($aCmtIds);
+    }
+
+    public static function onAuthorDelete ($iAuthorId)
+    {
+        $aSystems = self::getSystems();
+        foreach($aSystems as $sSystem => $aSystem) {
+            $o = self::getObjectInstance($sSystem, 0);
+            $oQuery = $o->getQueryObject();
+
+            // delete comments
+            $aFiles = $aCmtIds = array ();
+            $oQuery->deleteAuthorComments($iAuthorId, $aFiles, $aCmtIds);
+
+            // delete votes
+            $o->deleteVotes($aCmtIds);
+
+            // delete reactions
+            $o->deleteReactions($aCmtIds);
+
+            // delete scores
+            $o->deleteScores($aCmtIds);
+
+            // delete reports
+            $o->deleteReports($aCmtIds);
+
+            // delete meta info
+            $o->deleteMetaInfo($aCmtIds);
+    
+            // delete files
+            $oStorage = BxDolStorage::getObjectInstance($o->getStorageObjectName());
+            if ($oStorage)
+                $oStorage->queueFilesForDeletion($aFiles);
+            
+            // delete unique IDs
+            $o->deleteUniqueIds($aCmtIds);
+        }
+        return true;
+    }
+
+    public static function onModuleEnable ($sModuleName)
+    {
+        $aSystems = self::getSystems();
+        foreach($aSystems as $sSystem => $aSystem) {
+            if ($sModuleName !== $aSystem['module'])
+                continue;
+
+            $o = self::getObjectInstance($sSystem, 0);
+            $o->registerTranscoders();
+        }
+
+        return true;
+    }
+
+    public static function onModuleDisable ($sModuleName)
+    {
+        $aSystems = self::getSystems();
+        foreach($aSystems as $sSystem => $aSystem) {
+            if ($sModuleName !== $aSystem['module'])
+                continue;
+
+            $o = self::getObjectInstance($sSystem, 0);
+            $o->unregisterTranscoders();
+        }
+
+        return true;
+    }
+
+    public static function onModuleUninstall ($sModuleName, &$iFiles = null)
+    {
+        $aSystems = self::getSystems();
+        foreach($aSystems as $sSystem => $aSystem) {
+            if ($sModuleName !== $aSystem['module'])
+                continue;
+
+            $o = self::getObjectInstance($sSystem, 0);
+            $oQuery = $o->getQueryObject();
+
+            // delete comments
+            $aFiles = $aCmtIds = array ();
+            $oQuery->deleteAll($aSystem['system_id'], $aFiles, $aCmtIds);
+
+            // delete votes
+            $o->deleteVotes($aCmtIds);
+
+            // delete reactions
+            $o->deleteReactions($aCmtIds);
+
+            // delete scores
+            $o->deleteScores($aCmtIds);
+
+            // delete reports
+            $o->deleteReports($aCmtIds);
+
+            // delete meta info
+            $o->deleteMetaInfo($aCmtIds);
+
+            // delete files
+            $oStorage = BxDolStorage::getObjectInstance($o->getStorageObjectName());
+            if ($oStorage && $aFiles)
+                $oStorage->queueFilesForDeletion($aFiles);
+
+            if (null !== $iFiles)
+                $iFiles += count($aFiles);
+
+            // delete unique IDs
+            $o->deleteUniqueIds($aCmtIds);
+        }
+
+        return true;
+    }
+
+    public function deleteVotes ($mixedCmtId)
+    {
+        if(!is_array($mixedCmtId))
+            $mixedCmtId = [$mixedCmtId];
+
+        foreach($mixedCmtId as $iCmtId)
+            if(($oReport = $this->getVoteObject($this->_oQuery->getUniqId($this->_aSystem['system_id'], $iCmtId))) !== false)
+                $oReport->onObjectDelete();
+    }
+
+    public function deleteReactions ($mixedCmtId)
+    {
+        if(!is_array($mixedCmtId))
+            $mixedCmtId = [$mixedCmtId];
+
+        foreach($mixedCmtId as $iCmtId)
+            if(($oReport = $this->getReactionObject($this->_oQuery->getUniqId($this->_aSystem['system_id'], $iCmtId))) !== false)
+                $oReport->onObjectDelete();
+    }
+
+    public function deleteScores ($mixedCmtId)
+    {
+        if(!is_array($mixedCmtId))
+            $mixedCmtId = [$mixedCmtId];
+
+        foreach($mixedCmtId as $iCmtId)
+            if(($oReport = $this->getScoreObject($this->_oQuery->getUniqId($this->_aSystem['system_id'], $iCmtId))) !== false)
+                $oReport->onObjectDelete();
+    }
+
+    public function deleteReports ($mixedCmtId)
+    {
+        if(!is_array($mixedCmtId))
+            $mixedCmtId = [$mixedCmtId];
+
+        foreach($mixedCmtId as $iCmtId)
+            if(($oReport = $this->getReportObject($this->_oQuery->getUniqId($this->_aSystem['system_id'], $iCmtId))) !== false)
+                $oReport->onObjectDelete();
+    }
+
+    public function deleteMetaInfo ($mixedCmtId)
+    {
+        if(!is_array($mixedCmtId))
+            $mixedCmtId = array($mixedCmtId);
+
+        $oMetatags = false;
+        if(!empty($this->_sMetatagsObj))
+            $oMetatags = BxDolMetatags::getObjectInstance($this->_sMetatagsObj);
+
+        if($oMetatags)
+            foreach($mixedCmtId as $iCmtId)
+                $oMetatags->onDeleteContent($this->_oQuery->getUniqId($this->_aSystem['system_id'], $iCmtId));
+    }
+
+    public function deleteUniqueIds ($mixedCmtId)
+    {
+        if(!is_array($mixedCmtId))
+            $mixedCmtId = array($mixedCmtId);
+
+        foreach($mixedCmtId as $iCmtId)
+            $this->_oQuery->deleteCmtIds($this->_aSystem['system_id'], $iCmtId);
+    }
+
+    /**
+     * Permissions functions
+     */
+    public function isAdmin($iCmtAuthorId)
+    {
+        return BxDolAcl::getInstance()->isMemberLevelInSet(array(MEMBERSHIP_ID_MODERATOR, MEMBERSHIP_ID_ADMINISTRATOR), $iCmtAuthorId);
+    }
+
+    public function checkAction ($sAction, $isPerformAction = false)
+    {
+        $iId = $this->_getAuthorId();
+        $a = checkActionModule($iId, $sAction, 'system', $isPerformAction);
+        return $a[CHECK_ACTION_RESULT] === CHECK_ACTION_RESULT_ALLOWED;
+    }
+
+    public function checkActionErrorMsg ($sAction)
+    {
+        $iId = $this->_getAuthorId();
+        $a = checkActionModule($iId, $sAction, 'system');
+        return $a[CHECK_ACTION_RESULT] !== CHECK_ACTION_RESULT_ALLOWED ? $a[CHECK_ACTION_MESSAGE] : '';
+    }
+
+    public function isViewAllowed ($isPerformAction = false)
+    {
+        if (BxDolRequest::serviceExists($this->_aSystem['module'], 'check_allowed_comments_view')) {
+            $mixedResult = BxDolService::call($this->_aSystem['module'], 'check_allowed_comments_view', array($this->getId(), $this->getSystemName()));
+            if($mixedResult !== CHECK_ACTION_RESULT_ALLOWED)
+                return $mixedResult;
+        }
+
+        return CHECK_ACTION_RESULT_ALLOWED;
+    }
+
+    public function isVoteAllowed ($aCmt, $isPerformAction = false)
+    {
+        if(!$this->isRatable())
+            return false;
+
+        $oVote = $this->getVoteObject($aCmt['cmt_id']);
+        if($oVote === false)
+            return false;
+
+        $iUserId = (int)$this->_getAuthorId();
+        if($iUserId == 0)
+            return false;
+
+        if(isAdmin())
+            return true;
+
+        return $oVote->isAllowedVote($isPerformAction);
+    }
+
+    public function isScoreAllowed ($aCmt, $isPerformAction = false)
+    {
+        if(!$this->isRatable())
+            return false;
+
+        $oScore = $this->getScoreObject($aCmt['cmt_id']);
+        if($oScore === false)
+            return false;
+
+        $iUserId = (int)$this->_getAuthorId();
+        if($iUserId == 0)
+            return false;
+
+        if(isAdmin())
+            return true;
+
+        return $oScore->isAllowedVote($isPerformAction);
+    }
+
+    public function isReportAllowed ($aCmt, $isPerformAction = false)
+    {
+        $oReport = $this->getReportObject($aCmt['cmt_id']);
+        if($oReport === false)
+            return false;
+
+        $iUserId = (int)$this->_getAuthorId();
+        if($iUserId == 0)
+            return false;
+
+        if(isAdmin())
+            return true;
+
+        return $oReport->isAllowedReport($isPerformAction);
+    }
+
+    public function isPostAllowed ($isPerformAction = false)
+    {
+        if (BxDolRequest::serviceExists($this->_aSystem['module'], 'check_allowed_comments_post')) {
+            $mixedResult = BxDolService::call($this->_aSystem['module'], 'check_allowed_comments_post', array($this->getId(), $this->getSystemName()));
+            if($mixedResult !== CHECK_ACTION_RESULT_ALLOWED)
+                return false;
+        }
+
+        return $this->checkAction ('comments post', $isPerformAction);
+    }
+
+    public function msgErrPostAllowed ()
+    {
+        return $this->checkActionErrorMsg('comments post');
+    }
+
+    /**
+     * Determines whether a 'reply' action allowed or not.
+     * @param integer or array $mixedCmt - ID or an array which describes the comment to be replied
+     * @param boolean $isPerformAction
+     * @return boolean
+     */
+    public function isReplyAllowed ($mixedCmt, $isPerformAction = false)
+    {
+        return $this->isPostAllowed($isPerformAction);
+    }
+
+    public function msgErrReplyAllowed ()
+    {
+        return $this->msgErrPostAllowed ();
+    }
+
+    public function isQuoteAllowed ($mixedCmt, $isPerformAction = false)
+    {
+        return $this->_bPostQuote && $this->isReplyAllowed ($mixedCmt, $isPerformAction);
+    }
+
+    public function msgErrQuoteAllowed ()
+    {
+        return $this->msgErrReplyAllowed ();
+    }
+
+    public function isPinAllowed ($aCmt, $isPerformAction = false)
+    {
+        if((int)$aCmt['cmt_parent_id'] != 0 || (int)$aCmt['cmt_pinned'] != 0)
+            return false;
+
+        if(isAdmin())
+            return true;
+        
+        return $this->checkAction ('comments pin', $isPerformAction);
+    }
+
+    public function msgErrPinAllowed ()
+    {
+        return $this->checkActionErrorMsg('comments pin');
+    }
+
+    public function isUnpinAllowed ($aCmt, $isPerformAction = false)
+    {
+        if((int)$aCmt['cmt_pinned'] == 0)
+            return false;
+
+        if(isAdmin())
+            return true;
+        
+        return $this->checkAction ('comments pin', $isPerformAction);
+    }
+
+    public function msgErrUnpinAllowed ()
+    {
+        return $this->checkActionErrorMsg('comments pin');
+    }
+
+    public function isEditAllowed ($aCmt, $isPerformAction = false)
+    {
+        if(isAdmin())
+            return true;
+
+        if($this->isEditAllowedAll($isPerformAction))
+            return true;
+
+        $mixedResult = BxDolService::call($this->_aSystem['module'], 'check_allowed_comments_post', array($this->getId(), $this->getSystemName()));
+        if($mixedResult !== CHECK_ACTION_RESULT_ALLOWED)
+            return false;
+
+        return abs($aCmt['cmt_author_id']) == $this->_getAuthorId() && $this->checkAction ('comments edit own', $isPerformAction);
+    }
+
+    public function msgErrEditAllowed ()
+    {
+        return $this->checkActionErrorMsg ('comments edit own');
+    }
+
+    public function isEditAllowedAll ($isPerformAction = false)
+    {
+        return $this->checkAction('comments edit all', $isPerformAction);
+    }
+
+    public function isRemoveAllowed ($aCmt, $isPerformAction = false)
+    {
+        if(isAdmin())
+            return true;
+
+        if($this->isRemoveAllowedAll($isPerformAction))
+            return true;
+
+        if(abs($aCmt['cmt_author_id']) == $this->_getAuthorId() && $this->checkAction ('comments remove own', $isPerformAction))
+            return true;
+        
+        $aContentInfo = BxDolRequest::serviceExists($this->_aSystem['module'], 'get_all') ? BxDolService::call($this->_aSystem['module'], 'get_all', array(array('type' => 'id', 'id' => $this->getId()))) : array();
+        $oModule = BxDolModule::getInstance($this->_aSystem['module']);
+        $CNF = $oModule->_oConfig->CNF;
+        
+        if (isset($CNF['FIELD_AUTHOR']) && $aContentInfo[$CNF['FIELD_AUTHOR']] == $this->_getAuthorId() && $this->checkAction('comments remove in own content', $isPerformAction)){
+            return true;
+        }
+        
+        if (isset($CNF['FIELD_ALLOW_VIEW_TO']) &&  $aContentInfo[$CNF['FIELD_ALLOW_VIEW_TO']] < 0){
+            $iGroupProfileId = -(int)$aContentInfo[$CNF['FIELD_ALLOW_VIEW_TO']];
+            $oProfileContext = BxDolProfile::getInstance($iGroupProfileId);
+            $oModule = BxDolModule::getInstance($oProfileContext->getModule()); 
+            if ($oModule->_oConfig->isRoles() && $oModule->getRole($iGroupProfileId, $this->_getAuthorId()) === BX_BASE_MOD_GROUPS_ROLE_ADMINISTRATOR && $this->checkAction('comments remove in group context', $isPerformAction)){
+                return true;
+            }
+        }
+        
+        return false;
+    }
+
+    public function isRemoveAllowedAll ($isPerformAction = false)
+    {
+        return $this->checkAction ('comments remove all', $isPerformAction);
+    }
+
+    public function msgErrRemoveAllowed ()
+    {
+        return $this->checkActionErrorMsg('comments remove own');
+    }
+
+    public function isMoreAllowed ($aCmt, $isPerformAction = false)
+    {
+        $oMenuManage = BxDolMenu::getObjectInstance($this->_sMenuObjManage);
+        $oMenuManage->setCmtsData($this, $aCmt['cmt_id']);
+    	return $oMenuManage->isVisible();
+    }
+
+    public function isModerator($isPerformAction = false)
+    {
+        return $this->isEditAllowedAll($isPerformAction) || $this->isRemoveAllowedAll($isPerformAction);
+    }
+
+    /**
+     * Actions functions
+     */
+    public function actionPin ()
+    {
+        if (!$this->isEnabled())
+            return echoJson(array());
+
+        $iCmtId = 0;
+        if(bx_get('Cmt') !== false)
+            $iCmtId = bx_process_input(bx_get('Cmt'), BX_DATA_INT);
+
+        $aCmt = $this->getCommentRow($iCmtId);
+        if(empty($aCmt) || !is_array($aCmt))
+            return echoJson(array());
+
+        $iWay = 0;
+        if(bx_get('way') !== false)
+            $iWay = bx_process_input(bx_get('way'), BX_DATA_INT);
+
+        if(!$this->_oQuery->updateComments(array('cmt_pinned' => $iWay ? time() : 0), array('cmt_id' => $iCmtId)))
+            return echoJson(array());
+
+        $aBp = $aDp = array();
+        $this->_getParams($aBp, $aDp);
+        $this->_prepareParams($aBp, $aDp);
+
+        echoJson(array(
+            'parent_id' => $aCmt['cmt_parent_id'],
+            'per_view' => $aBp['per_view']
+        ));
+    }
+    
+    public function actionGetFormPost ()
+    {
+        if (!$this->isEnabled())
+            return '';
+
+        $iCmtParentId = bx_get('CmtParent');
+        $iCmtParentId = $iCmtParentId !== false ? bx_process_input($iCmtParentId, BX_DATA_INT) : 0;
+        
+        $sCmtBrowse = bx_get('CmtBrowse');
+        $sCmtBrowse = $sCmtBrowse !== false ? bx_process_input($sCmtBrowse, BX_DATA_TEXT) : '';
+
+        $sCmtDisplay = bx_get('CmtDisplay');
+        $sCmtDisplay = $sCmtDisplay !== false ? bx_process_input($sCmtDisplay, BX_DATA_TEXT) : '';
+
+        $bQuote = bx_get('CmtQuote');
+        $bQuote = $bQuote !== false ? bx_process_input($bQuote, BX_DATA_INT) == 1 : false;
+
+        $bMinPostForm = bx_get('CmtMinPostForm');
+        $bMinPostForm = $bMinPostForm !== false ? bx_process_input($bMinPostForm, BX_DATA_INT) == 1 : $this->_bMinPostForm;
+
+        return $this->getFormBoxPost(array('parent_id' => $iCmtParentId, 'type' => $sCmtBrowse), array('type' => $sCmtDisplay, 'dynamic_mode' => true, 'quote' => $bQuote, 'min_post_form' => $bMinPostForm));
+    }
+
+    public function actionGetFormEdit ()
+    {
+        if (!$this->isEnabled())
+            return echoJson(array());
+
+        $iCmtId = bx_process_input(bx_get('Cmt'), BX_DATA_INT);
+        echoJson($this->getFormEdit($iCmtId, array('dynamic_mode' => true)));
+    }
+
+    public function actionGetCmt ()
+    {
+        if(!$this->isEnabled())
+            return '';
+
+        if($this->isViewAllowed() !== CHECK_ACTION_RESULT_ALLOWED)
+            return '';
+
+        $iCmtId = bx_process_input($_REQUEST['Cmt'], BX_DATA_INT);
+        $sCmtBrowse = isset($_REQUEST['CmtBrowse']) ? bx_process_input($_REQUEST['CmtBrowse'], BX_DATA_TEXT) : '';
+        $sCmtDisplay = isset($_REQUEST['CmtDisplay']) ? bx_process_input($_REQUEST['CmtDisplay'], BX_DATA_TEXT) : '';
+
+        $aCmt = $this->getCommentRow($iCmtId);
+        echoJson([
+            'parent_id' => $aCmt['cmt_parent_id'],
+            'vparent_id' => $aCmt['cmt_parent_id'],
+            'content' => $this->getComment($aCmt, ['type' => $sCmtBrowse], ['type' => $sCmtDisplay, 'dynamic_mode' => true])
+        ]);
+    }
+
+    public function actionGetCmts ()
+    {
+        if (!$this->isEnabled())
+            return '';
+
+        if($this->isViewAllowed() !== CHECK_ACTION_RESULT_ALLOWED)
+            return '';
+
+        $aBp = $aDp = array();
+        $this->_getParams($aBp, $aDp);
+
+        $aDp['dynamic_mode'] = true;
+
+        //--- Beg: Using pregenerated structure
+        if(isset($aDp['structure']) && $aDp['structure'] === true) {
+            $iCmtId = isset($aBp['parent_id']) ? (int)$aBp['parent_id'] : 0;
+
+            $mixedStructure = $this->getCommentStructure($iCmtId, $aBp, $aDp);
+            if($mixedStructure === false || empty($mixedStructure[$iCmtId]['items'])) 
+                return '';
+
+            $aDp['structure'] = $mixedStructure[$iCmtId]['items'];
+            return $this->getCommentsByStructure($aBp, $aDp);
+        }
+        //--- End: Using pregenerated structure
+        else
+            return $this->{'getComments' . ($aBp['pinned'] ? 'Pinned' : '')}($aBp, $aDp);
+    }
+
+    public function actionSubmitPostForm()
+    {
+        if(!$this->isEnabled())
+            return echoJson(array());
+
+        $iCmtParentId = 0;
+        if(bx_get('cmt_parent_id') !== false)
+            $iCmtParentId = bx_process_input(bx_get('cmt_parent_id'), BX_DATA_INT);
+
+        echoJson($this->getFormPost($iCmtParentId, array('dynamic_mode' => true)));
+    }
+
+    public function actionSubmitEditForm()
+    {
+        if (!$this->isEnabled()) {
+            echoJson(array());
+            return;
+        };
+
+        $iCmtId = 0;
+        if(bx_get('cmt_id') !== false)
+            $iCmtId = bx_process_input(bx_get('cmt_id'), BX_DATA_INT);
+
+        echoJson($this->getFormEdit($iCmtId, array('dynamic_mode' => true)));
+    }
+
+    public function actionRemove()
+    {
+        if (!$this->isEnabled())
+            return echoJson([]);
+
+        $iCmtId = 0;
+        if(bx_get('Cmt') !== false)
+            $iCmtId = bx_process_input(bx_get('Cmt'), BX_DATA_INT);
+
+        echoJson($this->remove($iCmtId));
+    }
+    
+    public function remove($iCmtId)
+    {
+        $aCmt = $this->_oQuery->getCommentsBy(array('type' => 'id', 'id' => $iCmtId));
+        if(!$aCmt)
+            return array('msg' => _t('_No such comment'));
+        
+        $iObjId = $this->getId();
+        if(!$iObjId){
+            $this->setId($aCmt['cmt_object_id']);
+            $iObjId = $aCmt['cmt_object_id'];
+        }
+        $iObjAthrId = $this->getObjectAuthorId($iObjId);
+        $iObjAthrPrivacyView = $this->getObjectPrivacyView($iObjId);
+
+        if($aCmt['cmt_replies'] > 0) {
+            if(!$this->isModerator())
+                return array('msg' => _t('_Can not delete comments with replies'));
+
+            $aReplies = $this->_oQuery->getCommentsBy(array('type' => 'parent_id', 'parent_id' => $iCmtId));
+            foreach($aReplies as $aReply)
+                $this->remove($aReply['cmt_id']);
+        }
+
+        $iPerformerId = $this->_getAuthorId();
+        if(!$this->isRemoveAllowed($aCmt))
+            return array('msg' => $aCmt['cmt_author_id'] == $iPerformerId ? strip_tags($this->msgErrRemoveAllowed()) : _t('_Access denied'));
+
+        $iCmtPrntId = (int)$aCmt['cmt_parent_id'];
+        if(!$this->_oQuery->removeComment($iObjId, $iCmtId, $iCmtPrntId)) 
+            return array('msg' => _t('_cmt_err_cannot_perform_action'));
+
+        $this->_triggerComment();
+
+        $oStorage = BxDolStorage::getObjectInstance($this->getStorageObjectName());
+
+        $aImages = $this->_oQuery->getFiles($this->_aSystem['system_id'], $iCmtId);
+        foreach($aImages as $aImage)
+            $oStorage->deleteFile($aImage['image_id']);
+
+        $this->_oQuery->deleteImages($this->_aSystem['system_id'], $iCmtId);
+
+        $this->isRemoveAllowed($aCmt, true);
+
+        $this->deleteVotes($iCmtId);
+
+        $this->deleteReactions($iCmtId);
+
+        $this->deleteScores($iCmtId);
+
+        $this->deleteReports($iCmtId);
+
+        $this->deleteMetaInfo($iCmtId);
+
+        $aAuditParams = $this->_prepareAuditParams($iCmtId, array('comment_author_id' => $aCmt['cmt_author_id'], 'comment_text' => $aCmt['cmt_text']));
+        bx_audit($iObjId, $this->_aSystem['module'], '_sys_audit_action_delete_comment', $aAuditParams);
+
+        $aAlertParams = $this->_prepareAlertParams($aCmt);
+        bx_alert($this->_sSystem, 'commentRemoved', $iObjId, $iPerformerId, $aAlertParams);
+        bx_alert('comment', 'deleted', $iCmtId, $iPerformerId, $aAlertParams);
+
+        if(!empty($iCmtPrntId)) {
+            $aCmtPrnt = $this->_oQuery->getCommentSimple($iObjId, $iCmtPrntId);
+            if(!empty($aCmtPrnt) && is_array($aCmtPrnt)) {
+                $aAlertParamsReply = $this->_prepareAlertParamsReply($aCmt, $aCmtPrnt);
+                bx_alert($this->_sSystem, 'replyRemoved', $iCmtPrntId, $iPerformerId, $aAlertParamsReply);
+                bx_alert('reply', 'deleted', $iCmtId, $iPerformerId, $aAlertParamsReply);
+            }
+        }
+
+        $this->deleteUniqueIds($iCmtId);
+
+        $iCount = (int)$this->getCommentsCountAll(0, true);
+        return [
+            'id' => $iCmtId,
+            'count' => $iCount,
+            'countf' => $iCount > 0 ? $this->getCounter() : ''
+        ];
+    }
+
+    public function add($aValues)
+    {
+        return $this->_getFormAdd($aValues);
+    }
+
+    public function actionResumeLiveUpdate()
+    {
+    	$sKey = $this->getNotificationId();
+
+    	bx_import('BxDolSession');
+    	BxDolSession::getInstance()->unsetValue($sKey);
+    }
+
+    public function actionPauseLiveUpdate()
+    {
+    	$sKey = $this->getNotificationId();
+
+    	bx_import('BxDolSession');
+    	BxDolSession::getInstance()->setValue($sKey, 1);
+    }
+
+    public function actionGetSiblingFiles()
+    {
+        $iFileId = 0;
+        if(bx_get('file_id') !== false)
+            $iFileId = bx_process_input(bx_get('file_id'), BX_DATA_INT);
+
+        if(!($aFileInfo = $this->_oQuery->getFileInfoById((int)$iFileId))) 
+            return echoJson(['error' => _t('_sys_txt_error_occured')]);
+
+        $aSiblings = [];
+        $aFiles = $this->_oQuery->getFiles($aFileInfo['system_id'], $aFileInfo['cmt_id']);
+        foreach($aFiles as $iIndex => $aFile) {
+            if($aFile['id'] != $iFileId)
+                continue;
+
+            $aSiblings['prev'] = isset($aFiles[$iIndex - 1]) ? $aFiles[$iIndex - 1] : false;
+            $aSiblings['next'] = isset($aFiles[$iIndex + 1]) ? $aFiles[$iIndex + 1] : false;
+        }
+
+        $oStorage = BxDolStorage::getObjectInstance($this->getStorageObjectName());
+        $oTranscoder = BxDolTranscoderImage::getObjectInstance($this->getTranscoderPreviewName());
+
+        foreach($aSiblings as $sSibling => $aSibling) {
+            if(!$aSibling)
+                continue;
+
+            $sFile = '';
+            if($oTranscoder && $oTranscoder->isMimeTypeSupported($aSibling['mime_type']))
+                $sFile = $oStorage->getFileUrlById($aSibling['image_id']);
+            else
+                $sFile = $this->_oTemplate->getIconUrl($oStorage->getIconNameByFileName($aFile['file_name']));
+
+            $aImageInfo = BxDolImageResize::getInstance()->getImageSize($sFile);
+
+            $iWidth = $iHeight = 0;
+            if(isset($aImageInfo['w']))
+                $iWidth = (int)$aImageInfo['w'];
+            if(isset($aImageInfo['h']))
+                $iHeight = (int)$aImageInfo['h'];
+
+            $aSiblings[$sSibling] = array_merge($aSibling, [
+                'file' => $sFile,
+                'file_id' => $aSibling['id'],
+                'w' => $iWidth, 
+                'h' => $iHeight
+            ]);
+        }
+
+        return echoJson($aSiblings);
+    }
+
+    public function onPostAfter($iCmtId, $aDp = [])
+    {
+        $iObjId = (int)$this->getId();
+
+        $aCmt = $this->_oQuery->getCommentSimple($iObjId, $iCmtId);
+        if(empty($aCmt) || !is_array($aCmt))
+            return false;
+
+        $iCmtPrntId = (int)$aCmt['cmt_parent_id'];
+        $iPerformerId = (int)$aCmt['cmt_author_id'];
+
+        $aAlertParams = $this->_prepareAlertParams($aCmt);
+        bx_alert($this->_sSystem, 'commentPost', $iObjId, $iPerformerId, $aAlertParams);
+        bx_alert('comment', 'added', $iCmtId, $iPerformerId, $aAlertParams);
+
+        $aAuditParams = $this->_prepareAuditParams($iCmtId, array('comment_author_id' => $aCmt['cmt_author_id'], 'comment_text' => $aCmt['cmt_text']));
+        bx_audit($iObjId, $this->_aSystem['module'], '_sys_audit_action_add_comment', $aAuditParams);
+
+        if(!empty($iCmtPrntId)) {
+            $aCmtPrnt = $this->_oQuery->getCommentSimple($iObjId, $iCmtPrntId);
+            if(!empty($aCmtPrnt) && is_array($aCmtPrnt)) {
+                $aAlertParamsReply = $this->_prepareAlertParamsReply($aCmt, $aCmtPrnt);
+                bx_alert($this->_sSystem, 'replyPost', $iCmtPrntId, $iPerformerId, $aAlertParamsReply);
+                bx_alert('comment', 'replied', $iCmtId, $iPerformerId, $aAlertParamsReply);
+            }
+        }
+
+        $iCount = (int)$this->getCommentsCountAll(0, true);
+
+        return [
+            'id' => $iCmtId, 
+            'parent_id' => $iCmtPrntId,
+            'count' => $iCount,
+            'countf' => $iCount > 0 ? $this->getCounter() : ''
+        ];
+    }
+
+    public function onEditAfter($iCmtId, $aDp = [])
+    {
+    	$aCmt = $this->getCommentRow($iCmtId);
+        if(empty($aCmt) || !is_array($aCmt))
+            return false;
+
+        $aBp = [];
+        $this->_getParams($aBp, $aDp);
+
+        $iObjId = (int)$this->getId();
+        $iPerformerId = $this->_getAuthorId();
+
+        $aAlertParams = $this->_prepareAlertParams($aCmt);
+        bx_alert($this->_sSystem, 'commentUpdated', $iObjId, $iPerformerId, $aAlertParams);
+        bx_alert('comment', 'edited', $iCmtId, $iPerformerId, $aAlertParams);
+
+        $aAuditParams = $this->_prepareAuditParams($iCmtId, ['comment_author_id' => $aCmt['cmt_author_id'], 'comment_text' => $aCmt['cmt_text']]);
+        bx_audit($iObjId, $this->_aSystem['module'], '_sys_audit_action_edit_comment', $aAuditParams);
+
+        return ['id' => $iCmtId, 'content' => $this->_getContent($aCmt, $aBp, $aDp)];
+    }
+
+    public function serviceGetAuthor ($iContentId)
+    {
+        $aComment = $this->_oQuery->getCommentsBy(array('type' => 'id', 'id' => $iContentId));
+        $this->setId($aComment['cmt_object_id']);
+
+        return $aComment['cmt_author_id'];
+    }
+
+    public function serviceGetDateAdded ($iContentId)
+    {
+        $aComment = $this->_oQuery->getCommentsBy(array('type' => 'id', 'id' => $iContentId));
+        $this->setId($aComment['cmt_object_id']);
+
+        return $aComment['cmt_time'];
+    }
+
+    public function serviceGetDateChanged ($iContentId)
+    {
+        return 0;
+    }
+    public function serviceGetLink ($iContentId)
+    {
+        $aComment = $this->_oQuery->getCommentsBy(array('type' => 'id', 'id' => $iContentId));
+        $this->setId($aComment['cmt_object_id']);
+
+        return $this->getViewUrl($iContentId);
+    }
+
+    public function serviceGetTitle ($iContentId)
+    {
+        return '';
+    }
+
+    public function serviceGetText ($iContentId)
+    {
+        $aComment = $this->_oQuery->getCommentsBy(array('type' => 'id', 'id' => $iContentId));
+        $this->setId($aComment['cmt_object_id']);
+
+        return $aComment['cmt_text'];
+    }
+
+    public function serviceGetThumb ($iContentId)
+    {
+        return '';
+    }
+
+    public function serviceGetInfo ($iContentId, $bSearchableFieldsOnly = true)
+    {
+        $aComment = $this->_oQuery->getCommentsBy(array('type' => 'id', 'id' => $iContentId));
+        $this->setId($aComment['cmt_object_id']);
+
+        return BxDolContentInfo::formatFields($aComment);
+    }
+
+    public function serviceGetSearchResultUnit ($iContentId, $sUnitTemplate = '')
+    {
+        $aComment = $this->_oQuery->getCommentsBy(array('type' => 'id', 'id' => $iContentId));
+        if(empty($aComment) || !is_array($aComment))
+            return '';
+
+        $this->setId($aComment['cmt_object_id']);
+
+        return $this->getComment($aComment, array(), array('type' => BX_CMT_DISPLAY_FLAT, 'view_only' => true));
+    }
+
+    public function serviceGetAll ($aParams = array())
+    {
+        if(empty($aParams) || !is_array($aParams))
+            $aParams = array('type' => 'all');
+
+        return $this->_oQuery->getCommentsBy($aParams);
+    }
+
+    public function serviceGetSearchableFieldsExtended($aInputsAdd = array())
+    {
+        $oForm = BxDolForm::getObjectInstance('sys_comment', 'sys_comment_post', $this->_oTemplate);
+        if(!$oForm)
+            return array();
+
+        $aSrchNamesExcept = array();
+        $aSrchCaptionsSystem = array(
+            'cmt_author_id' => '_sys_form_comment_input_caption_system_cmt_author_id',
+            'cmt_text' => '_sys_form_comment_input_caption_system_cmt_text'
+        );
+        $aSrchCaptions = array(
+            'cmt_author_id' => '_sys_form_comment_input_caption_cmt_author_id',
+            'cmt_text' => '_sys_form_comment_input_caption_cmt_text'
+        );
+
+        $aResult = array(
+            'cmt_author_id' => array(
+                'type' => 'text_auto', 
+                'caption_system' => $aSrchCaptionsSystem['cmt_author_id'],
+                'caption' => $aSrchCaptions['cmt_author_id'],
+                'info' => '',
+                'value' => '',
+                'values' => '',
+                'pass' => ''
+            )
+        );
+
+        $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'], $aSrchNamesExcept))
+                $aResult[$aInput['name']] = array(
+                    'type' => $aInput['type'], 
+                    'caption_system' => !empty($aInput['caption_system_src']) ? $aInput['caption_system_src'] : '',
+                    'caption' => !empty($aInput['caption_src']) ? $aInput['caption_src'] : (!empty($aSrchCaptions[$aInput['name']]) ? $aSrchCaptions[$aInput['name']] : ''),
+                    'info' => !empty($aInput['info_src']) ? $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'] : ''
+                );
+
+        return $aResult;
+    }
+
+    /**
+     * Overwrite this method and register transcoder(s) if comments object uses custom transcoder(s), 
+     * which differs from default one 'sys_cmts_images_preview'
+     */
+    public function registerTranscoders()
+    {}
+
+	/**
+     * Overwrite this method and unregister transcoder(s) if comments object uses custom transcoder(s), 
+     * which differs from default one 'sys_cmts_images_preview'
+     */
+    public function unregisterTranscoders()
+    {}
+
+    public function serviceGetSearchResultExtended($aParams, $iStart = 0, $iPerPage = 0, $bFilterMode = false)
+    {
+        if((empty($aParams) || !is_array($aParams)) && !$bFilterMode)
+            return array();
+
+        return $this->_oQuery->getCommentsBy(array('type' => 'search_ids', 'search_params' => $aParams, 'start' => $iStart, 'per_page' => $iPerPage));
+    }
+
+    public function getAuthorInfo($iAuthorId = 0)
+    {
+        return $this->_getAuthorInfo($iAuthorId);
+    }
+    
+    public function getParams(&$aBp, &$aDp)
+    {
+        return $this->_getParams($aBp, $aDp);
+    }
+    
+    public function prepareParams(&$aBp, &$aDp)
+    {
+         return $this->_prepareParams($aBp, $aDp);
+    }
+
+    /**
+     * Internal functions
+     */
+    protected function _getAuthorId ()
+    {
+        return isMember() ? bx_get_logged_profile_id() : 0;
+    }
+
+    protected function _getAuthorPassword ()
+    {
+        return getLoggedPassword();
+    }
+
+    protected function _getAuthorIp ()
+    {
+        return getVisitorIP();
+    }
+
+    protected function _getAuthorInfo($iAuthorId = 0)
+    {
+        $iUserId = $this->_getAuthorId();
+        $iAuthorId = (int)$iAuthorId;
+        $oProfile = $this->_getAuthorObject($iAuthorId);
+        if (!$oProfile->isActive() && !isAdmin() && !BxDolAcl::getInstance()->isMemberLevelInSet(array(MEMBERSHIP_ID_MODERATOR, MEMBERSHIP_ID_ADMINISTRATOR)))
+            $oProfile = BxDolProfileUndefined::getInstance();
+
+        return array(
+            $oProfile->getDisplayName(),
+            $oProfile->getUrl(),
+            $oProfile->getThumb(),
+            $oProfile->getUnit(0, array('template' => 'unit_wo_info')),
+            $oProfile->getBadges()
+        );
+    }
+
+    protected function _getAuthorObject($iAuthorId = 0)
+    {
+        return BxDolProfile::getInstanceMagic((int)$iAuthorId);
+    }
+
+    protected function _getFormObject($sAction = BX_CMT_ACTION_POST)
+    {
+        $sDisplayName = '_sFormDisplay' . ucfirst($sAction);
+
+        return BxDolForm::getObjectInstance($this->_sFormObject, $this->$sDisplayName, false, $this->_sSystem);
+    }
+    
+    protected function _unsetFormObject($sAction = BX_CMT_ACTION_POST)
+    {
+        $sDisplayName = '_sFormDisplay' . ucfirst($sAction);
+
+        return BxDolForm::unsetObjectInstance($this->_sFormObject, $this->$sDisplayName, false, $this->_sSystem);
+    }
+
+    protected function _getParams(&$aBp, &$aDp)
+    {
+        //--- Process 'Browse' params. 
+        $aBp['parent_id'] = isset($aBp['parent_id']) ? (int)$aBp['parent_id'] : 0;
+
+        $aBp['vparent_id'] = isset($aBp['vparent_id']) ? (int)$aBp['vparent_id'] : 0;
+        if(bx_get('CmtParent') !== false)
+            $aBp['vparent_id'] = bx_process_input(bx_get('CmtParent'), BX_DATA_INT);
+
+    	$aBp['type'] = isset($aBp['type']) ? $aBp['type'] : '';
+    	if(bx_get('CmtBrowse') !== false) 
+        	$aBp['type'] = bx_process_input(bx_get('CmtBrowse'), BX_DATA_TEXT);
+
+    	$aBp['filter'] = isset($aBp['filter']) ? $aBp['filter'] : '';
+    	if(bx_get('CmtFilter') !== false) 
+    	    $aBp['filter'] = bx_process_input(bx_get('CmtFilter'), BX_DATA_TEXT);
+
+        $aBp['start'] = isset($aBp['start']) ? (int)$aBp['start'] : -1;
+        if(bx_get('CmtStart') !== false) 
+            $aBp['start'] = bx_process_input($_REQUEST['CmtStart'], BX_DATA_INT);
+
+        $aBp['per_view'] = isset($aBp['per_view']) ? (int)$aBp['per_view'] : -1;
+        if(bx_get('CmtPerView') !== false) 
+            $aBp['per_view'] = bx_process_input($_REQUEST['CmtPerView'], BX_DATA_INT);
+
+        $aBp['pinned'] = isset($aBp['pinned']) ? (int)$aBp['pinned'] : 0;
+        if(bx_get('CmtPinned') !== false) 
+            $aBp['pinned'] = bx_process_input(bx_get('CmtPinned'), BX_DATA_INT);
+
+        //--- Process 'Display' params.
+        $aDp['type'] = isset($aDp['type']) ? $aDp['type'] : '';
+        if(bx_get('CmtDisplay') !== false) 
+            $aDp['type'] = bx_process_input($_REQUEST['CmtDisplay'], BX_DATA_TEXT);
+
+        if(bx_get('CmtDisplayStructure') !== false) {
+            $aDp['structure'] = bx_process_input($_REQUEST['CmtDisplayStructure'], BX_DATA_INT) == 1;
+
+            if($aDp['structure'] && bx_get('CmtParent') !== false)
+                $aBp['parent_id'] = bx_process_input(bx_get('CmtParent'), BX_DATA_INT);
+        }
+
+        $aDp['blink'] = isset($aDp['blink']) ? $aDp['blink'] : '';
+        if(bx_get('CmtBlink') !== false) 
+            $aDp['blink'] = bx_process_input($_REQUEST['CmtBlink'], BX_DATA_TEXT);
+
+        $aDp['quote'] = isset($aDp['quote']) ? (int)$aDp['quote'] : 0;
+        if(bx_get('CmtQuote') !== false) 
+            $aDp['quote'] = bx_process_input($_REQUEST['CmtQuote'], BX_DATA_INT);
+
+        $aDp['min_post_form'] = isset($aDp['min_post_form']) ? (bool)$aDp['min_post_form'] : $this->_bMinPostForm;
+        if(bx_get('CmtMinPostForm') !== false)
+            $aDp['min_post_form'] = bx_process_input(bx_get('CmtMinPostForm'), BX_DATA_INT) == 1;
+
+        $aDp['in_designbox'] = isset($aDp['in_designbox']) ? (bool)$aDp['in_designbox'] : true;
+        $aDp['dynamic_mode'] = isset($aDp['dynamic_mode']) ? (bool)$aDp['dynamic_mode'] : false;
+        $aDp['show_empty'] = isset($aDp['show_empty']) ? (bool)$aDp['show_empty'] : false;
+    }
+
+    protected function _prepareAlertParams($aCmt)
+    {
+        $iObjId = (int)$this->getId();
+        $iObjAthrId = $this->getObjectAuthorId($iObjId);
+        $iObjAthrPrivacyView = $this->getObjectPrivacyView($iObjId);
+
+        $iCmtId = (int)$aCmt['cmt_id'];
+        $iCmtUniqId = $this->getCommentUniqId($iCmtId);
+        $iCmtCf = isset($aCmt['cmt_cf']) ? (int)$aCmt['cmt_cf'] : BxDolContentFilter::getInstance()->getDefaultValue();
+
+        return array(
+            'source' => 'sys_cmts_' . $iCmtUniqId,
+
+            'object_system' => $this->_sSystem, 
+            'object_id' => $iObjId, 
+            'object_author_id' => $iObjAthrId,
+
+            'comment_id' => $iCmtId, 
+            'comment_uniq_id' => $iCmtUniqId,
+            'comment_author_id' => $aCmt['cmt_author_id'], 
+            'comment_text' => $aCmt['cmt_text'],
+
+            'privacy_view' => $iObjAthrPrivacyView,
+            'cf' => $iCmtCf
+        );
+    }
+
+    protected function _prepareAlertParamsReply($aCmt, $aCmtPrnt)
+    {
+        $iObjId = (int)$this->getId();
+        $iObjAthrId = $this->getObjectAuthorId($iObjId);
+        $iObjAthrPrivacyView = $this->getObjectPrivacyView($iObjId);
+
+        $iCmtPrntId = (int)$aCmt['cmt_parent_id'];
+        $iCmtPrntUniqId = $this->getCommentUniqId($iCmtPrntId);
+
+        $iCmtId = (int)$aCmt['cmt_id'];
+        $iCmtUniqId = $this->getCommentUniqId($iCmtId);
+
+        return array(
+            'source' => 'sys_cmts_' . $iCmtUniqId,
+
+            'object_system' => $this->_sSystem, 
+            'object_id' => $iObjId, 
+            'object_author_id' => $iObjAthrId,
+
+            'parent_id' => $iCmtPrntId,
+            'parent_uniq_id' => $iCmtPrntUniqId,
+            'parent_author_id' => $aCmtPrnt['cmt_author_id'],
+
+            'comment_id' => $iCmtId,
+            'comment_uniq_id' => $iCmtUniqId,
+            'comment_author_id' => $aCmt['cmt_author_id'],  
+            'comment_text' => $aCmt['cmt_text'],
+
+            'privacy_view' => $iObjAthrPrivacyView,
+        );
+    }
+
+    protected function _prepareAuditParams($iId, $aData)
+    {
+        $sModule = $this->_aSystem['module'];
+        $oModule = BxDolModule::getInstance($sModule);
+        $CNF = isset($oModule->_oConfig->CNF) ? $oModule->_oConfig->CNF : array();
+
+        $aContentInfo = BxDolRequest::serviceExists($sModule, 'get_all') ? BxDolService::call($sModule, 'get_all', array(array('type' => 'id', 'id' => $this->getId()))) : array();
+        $iContextId = 0;
+        if (!empty($aContentInfo)){
+            $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' => $this->getObjectTitle() ,
+            'context_profile_id' => $iContextId,
+            'content_info_object' =>  isset($CNF['OBJECT_CMTS_CONTENT_INFO']) ? $CNF['OBJECT_CMTS_CONTENT_INFO'] : '',
+            'data' => array_merge(array('comment_id' => $iId), $aData)
+        );
+        if ($iContextId > 0)
+            $AuditParams['context_profile_title'] = BxDolProfile::getInstance($iContextId)->getDisplayName();
+        
+        return $AuditParams;
+    }
+
+    protected function _prepareTextForOutput ($s, $iCmtId = 0)
+    {
+    	$iDataAction = !$this->isHtml() ? BX_DATA_TEXT_MULTILINE : BX_DATA_HTML;
+    	$s = bx_process_output($s, $iDataAction);
+    	$s = bx_linkify_html($s, 'class="' . BX_DOL_LINK_CLASS . '"');
+
+        if($iCmtId && $this->_sMetatagsObj && ($oMetatags = BxDolMetatags::getObjectInstance($this->_sMetatagsObj)) !== false)
+            $s = $oMetatags->metaParse($this->_oQuery->getUniqId($this->_aSystem['system_id'], $iCmtId), $s);
+
+        return $s;
+    }
+
+    protected function _prepareStructureBp($sDType, &$aBp)
+    {
+        $aBp['type'] = !empty($aBp['type']) ? $aBp['type'] : $this->_sBrowseType;
+        $aBp['filter'] = !empty($aBp['filter']) ? $aBp['filter'] : $this->_sBrowseFilter;
+        $aBp['parent_id'] = isset($aBp['parent_id']) ? $aBp['parent_id'] : 0;
+        $aBp['start'] = isset($aBp['start']) ? $aBp['start'] : -1;
+        $aBp['init_view'] = isset($aBp['init_view']) ? $aBp['init_view'] : -1;
+        $aBp['per_view'] = isset($aBp['per_view']) ? $aBp['per_view'] : -1;
+        $aBp['order']['by'] = isset($aBp['order_by']) ? $aBp['order_by'] : $this->_aOrder['by'];
+        $aBp['order']['way'] = isset($aBp['order_way']) ? $aBp['order_way'] : $this->_aOrder['way'];
+
+        if($aBp['per_view'] == -1)
+            switch($sDType) {
+                case BX_CMT_DISPLAY_FLAT:
+                    $aBp['per_view'] = $this->getPerView(0);
+                    break;
+
+                case BX_CMT_DISPLAY_THREADED:
+                    $aBp['per_view'] = $this->getPerView($aBp['parent_id']);
+                    break;
+            }
+    }
+
+    protected function _prepareParams(&$aBp, &$aDp)
+    {
+        $aBp['type'] = !empty($aBp['type']) ? $aBp['type'] : $this->_sBrowseType;
+        $aBp['filter'] = !empty($aBp['filter']) ? $aBp['filter'] : $this->_sBrowseFilter;
+        $aBp['parent_id'] = isset($aBp['parent_id']) ? $aBp['parent_id'] : 0;
+        $aBp['start'] = isset($aBp['start']) ? $aBp['start'] : -1;
+        $aBp['init_view'] = isset($aBp['init_view']) ? $aBp['init_view'] : -1;
+        $aBp['per_view'] = isset($aBp['per_view']) ? $aBp['per_view'] : -1;
+        $aBp['pinned'] = isset($aBp['pinned']) ? (int)$aBp['pinned'] : 0;
+        $aBp['order']['by'] = isset($aBp['order_by']) ? $aBp['order_by'] : $this->_aOrder['by'];
+        $aBp['order']['way'] = isset($aBp['order_way']) ? $aBp['order_way'] : $this->_aOrder['way'];
+
+        $aDp['type'] = !empty($aDp['type']) ? $aDp['type'] : $this->_sDisplayType;
+        $aDp['blink'] = !empty($aDp['blink']) ? $aDp['blink'] : array();
+        if(!is_array($aDp['blink']))
+            $aDp['blink'] = explode(',', $aDp['blink']);
+        $aDp['quote'] = !empty($aDp['quote']) ? (int)$aDp['quote'] : 0;
+        if(!isset($aDp['min_post_form']))
+            $aDp['min_post_form'] = $this->_bMinPostForm;
+
+        switch($aDp['type']) {
+            case BX_CMT_DISPLAY_FLAT:
+                $aBp['vparent_id'] = -1;
+                $aBp['per_view'] = $aBp['per_view'] != -1 ? $aBp['per_view'] : $this->getPerView(0);
+                break;
+
+            case BX_CMT_DISPLAY_THREADED:
+                $iParent = 0;
+                if(isset($aBp['vparent_id']))
+                    $iParent = $aBp['vparent_id'];
+                else if(isset($aBp['parent_id']))
+                    $iParent = $aBp['parent_id'];
+
+                $aBp['per_view'] = $aBp['per_view'] != -1 ? $aBp['per_view'] : $this->getPerView($iParent);
+                break;
+        }
+
+        switch ($aBp['type']) {
+            case BX_CMT_BROWSE_POPULAR:
+                $aBp['order'] = array(
+                    'by' => BX_CMT_ORDER_BY_POPULAR,
+                    'way' => BX_CMT_ORDER_WAY_DESC
+                );
+                break;
+        }
+
+        if(!isset($aBp['count']))
+            $aBp['count'] = $this->getCommentsCount($this->_iId, $aBp['vparent_id'], $aBp['filter']);
+
+        if($aBp['start'] != -1)
+            return;
+
+        $aBp['start'] = 0;
+        if($aBp['type'] == BX_CMT_BROWSE_TAIL) {
+            $sPerView = ($aBp['init_view'] != -1 ? 'init' : 'per') . '_view';
+
+            $aBp['start'] = $aBp['count'] - $aBp[$sPerView];
+            if($aBp['start'] < 0) {
+                $aBp[$sPerView] += $aBp['start'];
+                $aBp['start'] = 0;
+            }
+        }
+
+        $this->_setUserChoice($aDp['type'], $aBp['type'], $aBp['filter']);
+    }
+
+    protected function _triggerComment()
+    {
+        if(!$this->_aSystem['trigger_table'] || !$this->_aSystem['trigger_field_id'] || !$this->_aSystem['trigger_field_comments'])
+            return false;
+
+        $iId = $this->getId();
+        if(!$iId)
+            return false;
+
+        $iCount = $this->getCommentsCount($iId);
+        return $this->_oQuery->updateTriggerTable($iId, $iCount);
+    }
+
+    /**
+     * 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);
+    }
+
+    protected function _getUserChoice()
+    {
+        $mixedDp = $mixedBpType = $mixedBpFilter = false;
+        if(!isLogged())
+            return array($mixedDp, $mixedBpType, $mixedBpFilter);
+
+        $iUserId = $this->_getAuthorId();
+
+        $oSession = BxDolSession::getInstance();
+
+        $mixedDp = $oSession->getValue($this->_sDpSessionKey . $iUserId);
+        $mixedBpType = $oSession->getValue($this->_sBpSessionKeyType . $iUserId);
+        $mixedBpFilter = $oSession->getValue($this->_sBpSessionKeyFilter . $iUserId);
+
+        return array($mixedDp, $mixedBpType, $mixedBpFilter);
+    }
+
+    protected function _setUserChoice($sDp, $sBpType, $sBpFilter)
+    {
+        if(!isLogged())
+            return;
+
+        $iUserId = $this->_getAuthorId();
+
+        $oSession = BxDolSession::getInstance();
+
+        if(!empty($sDp))
+            $oSession->setValue($this->_sDpSessionKey . $iUserId, $sDp);
+
+        if(!empty($sBpType))
+            $oSession->setValue($this->_sBpSessionKeyType . $iUserId, $sBpType);
+
+        if(!empty($sBpFilter))
+            $oSession->setValue($this->_sBpSessionKeyFilter . $iUserId, $sBpFilter);
+    }
+
+    protected function _sendNotificationEmail($iCmtId, $iCmtParentId)
+    {
+        $aCmt = $this->getCommentRow($iCmtId);
+        $aCmtParent = $this->getCommentRow($iCmtParentId);
+        if(empty($aCmt) || !is_array($aCmt) || empty($aCmtParent) || !is_array($aCmtParent) || (int)$aCmt['cmt_author_id'] == (int)$aCmtParent['cmt_author_id'])
+            return;
+
+        $oProfile = $this->_getAuthorObject($aCmtParent['cmt_author_id']);
+
+        if($oProfile instanceof BxDolProfileUndefined)
+        	return;
+
+        $iAccount = $oProfile->getAccountId();
+        $aAccount = BxDolAccount::getInstance($iAccount)->getInfo();
+
+        $aPlus = array();
+        $aPlus['sender_display_name'] = $oProfile->getDisplayName();
+        $aPlus['reply_text'] = $this->_prepareTextForOutput($aCmt['cmt_text'], $iCmtId);
+
+        $sPageUrl = $this->getBaseUrl();
+        if(empty($sPageUrl))
+            $sPageUrl = $this->getViewUrl($iCmtParentId);
+        else 
+            $sPageUrl .= $this->getItemAnchor($iCmtParentId, true);
+        $aPlus['page_url'] = $sPageUrl;
+
+        $sPageTitle = $this->getObjectTitle();
+        if(empty($sPageTitle))
+            $sPageTitle = _t('_Content');
+        $aPlus['page_title'] = $sPageTitle;
+
+        $aTemplate = BxDolEmailTemplates::getInstance()->parseTemplate('t_CommentReplied', $aPlus);
+        return $aTemplate && sendMail($aAccount['email'], $aTemplate['Subject'], $aTemplate['Body']);
+    }
+
+    protected function _isShowDoComment($aParams, $isAllowedComment, $bCount)
+    {
+        $bShowDoComment = !isset($aParams['show_do_comment']) || $aParams['show_do_comment'] == true;
+
+        return $bShowDoComment && ($isAllowedComment || $bCount);
+    }
+
+    protected function _isShowCounter($aParams, $isAllowedComment, $bCount)
+    {
+        $bShowCounter = isset($aParams['show_counter']) && $aParams['show_counter'] === true;
+
+        return $bShowCounter && ($isAllowedComment || $bCount);
+    }
+
+    /**
+     * Note. By default image based controls aren't used.
+     * Therefore it can be overwritten in custom template.
+     */
+    protected function _getImageDo()
+    {
+    	return '';
+    }
+
+    protected function _getIconDo()
+    {
+    	return 'far comment';
+    }
+
+    protected function _getTitleDo()
+    {
+    	return '_cmt_txt_do';
+    }
+    
+    public function _getStructure($mixedItem, $aBp, &$iLevel, &$aStructure)
+    {
+        $bItem = !empty($mixedItem) && is_array($mixedItem);
+
+        $iCmtId = $bItem ? (int)$mixedItem['cmt_id'] : 0;
+        $iCmtIndex = $iCmtId;
+
+        if($bItem)
+            $aStructure[$iCmtIndex] = [
+                'id' => $mixedItem['cmt_id'], 
+                'order' => isset($mixedItem['cmt_order']) ? $mixedItem['cmt_order'] : 0, 
+                'items' => [],
+            ];
+
+        if(!$bItem || (int)$mixedItem['cmt_replies'] > 0) {
+            $aItems = $this->_oQuery->getStructure($this->_iId, $this->_iAuthorId, $iCmtId, $aBp['filter'], $aBp['order']);
+            if(!empty($aItems)) {
+                if($bItem && $iLevel < (int)$this->_aSystem['number_of_levels']) {
+                    $iPassLevel = $iLevel + 1;
+                    $aPassStructure = &$aStructure[$iCmtIndex]['items'];
+                }
+                else {
+                    $iPassLevel = $iLevel;
+                    $aPassStructure = &$aStructure; //--- Is NEEDED here to correctly check sublevels.
+                }
+
+                foreach($aItems as $aItem)
+                    $this->_getStructure($aItem, $aBp, $iPassLevel, $aPassStructure);
+
+                //--- Sort subitems
+                $iWay = isset($aBp['order']['way']) && $aBp['order']['way'] == 'desc' ? -1 : 1;
+                uasort($aPassStructure, function($aItem1, $aItem2) use ($iWay) {
+                    if($aItem1['order'] == $aItem2['order'])
+                        return 0;
+
+                    return $iWay * ($aItem1['order'] < $aItem2['order'] ? -1 : 1);
+                });
+            }
+        }
+    }
+
+    public function _getStructureAPI($mixedItem, $aBp, &$iLevel, &$aStructure)
+    {
+        $bItem = !empty($mixedItem) && is_array($mixedItem);
+
+        $iCmtId = $bItem ? (int)$mixedItem['cmt_id'] : 0;
+        $sCmtIndex = 'i' . $iCmtId;
+
+        if($bItem) {
+            $oMenuActions = BxDolMenu::getObjectInstance($this->_sMenuObjActions);
+            $oMenuActions->setCmtsData($this, $iCmtId, $aBp);
+
+            $aData = $this->getCommentSimple($iCmtId);
+            $aData = array_merge($aData, [
+                'author_data' => BxDolProfile::getData($aData['cmt_author_id']),
+                'menu_actions' => $oMenuActions->getCodeAPI()
+            ]);
+
+            $aStructure[$sCmtIndex] = [
+                'id' => $iCmtId, 
+                'order' => isset($mixedItem['cmt_order']) ? $mixedItem['cmt_order'] : 0, 
+                'data' => $aData,
+                'files' => $this->_getAttachments($mixedItem),
+                'items' => [],
+            ];
+        }
+
+        if(!$bItem || (int)$mixedItem['cmt_replies'] > 0) {
+            $aItems = $this->_oQuery->getStructure($this->_iId, $this->_iAuthorId, $iCmtId, $aBp['filter'], $aBp['order']);
+            if(!empty($aItems)) {
+                if($bItem && $iLevel < (int)$this->_aSystem['number_of_levels']) {
+                    $iPassLevel = $iLevel + 1;
+                    $aPassStructure = &$aStructure[$sCmtIndex]['items'];
+                }
+                else {
+                    $iPassLevel = $iLevel;
+                    $aPassStructure = [];
+                }
+                
+                foreach($aItems as $aItem)
+                    $this->_getStructureAPI($aItem, $aBp, $iPassLevel, $aPassStructure);
+                
+                //--- Sort subitems
+                $iWay = isset($aBp['order']['way']) && $aBp['order']['way'] == 'desc' ? -1 : 1;
+
+                uasort($aPassStructure, function($aItem1, $aItem2) use ($iWay) {
+                    if($aItem1['order'] == $aItem2['order'])
+                        return 0;
+
+                    return $iWay * ($aItem1['order'] < $aItem2['order'] ? -1 : 1);
+                });
+
+                $aStructure[$sCmtIndex]['items'] = $aPassStructure;
+            }
+        }
+    }
+}
+
+/** @} */

+ 895 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/inc/classes/BxDolConnection.php

@@ -0,0 +1,895 @@
+<?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
+ * @{
+ */
+
+/**
+ * Default limit for connections list in Counter.
+ */
+define('BX_CONNECTIONS_LIST_COUNTER', 5);
+
+/**
+ * Default limit for connections lists
+ */
+define('BX_CONNECTIONS_LIST_LIMIT', 1000);
+
+/**
+ * No limit for connections lists. 
+ * Is needed for Total Number calculation.
+ */
+define('BX_CONNECTIONS_LIST_NO_LIMIT', -1);
+
+/**
+ * Connections order: no order
+ */
+define('BX_CONNECTIONS_ORDER_NONE', 0);
+
+/**
+ * Connections order: by addded time, asceding
+ */
+define('BX_CONNECTIONS_ORDER_ADDED_ASC', 1);
+
+/**
+ * Connections order: by addded time, desceding
+ */
+define('BX_CONNECTIONS_ORDER_ADDED_DESC', 2);
+
+/**
+ * Connection type: one-way
+ */
+define('BX_CONNECTIONS_TYPE_ONE_WAY', 'one-way');
+
+/**
+ * Connection type: mutual
+ */
+define('BX_CONNECTIONS_TYPE_MUTUAL', 'mutual');
+
+/**
+ * Connections content type: content
+ */
+define('BX_CONNECTIONS_CONTENT_TYPE_CONTENT', 'content');
+
+/**
+ * Connections content type: initiators
+ */
+define('BX_CONNECTIONS_CONTENT_TYPE_INITIATORS', 'initiators');
+
+/**
+ * Connections content type: common
+ */
+define('BX_CONNECTIONS_CONTENT_TYPE_COMMON', 'common');
+
+/**
+ * Connection is usefull when you need to organize some sorts of connections between different content,
+ * for example: friends, contacts, favorites, block lists, subscriptions, etc.
+ *
+ * Two types of connections are supported one way connections (block list, favourites) and mutual (friends).
+ *
+ * For automatic handling of connections (like, add/remove connection in frontend) refer to JS function: @see bx_conn_action()
+ *
+ * @section connection_create Creating the Connection object:
+ *
+ * Step 1:
+ * Add record to 'sys_objects_connection' table:
+ * - object: name of the connection object, in the format: vendor prefix, underscore, module prefix, underscore, internal identifier or nothing;
+ *           for example: bx_blogs_favorites - favorite blogs for users in blogs module.
+ * - table: table name with connections, see step 2
+ * - type: 'one-way' or 'mutual'
+ * - override_class_name: user defined class name which is derived from one of base classes.
+ * - override_class_file: the location of the user defined class, leave it empty if class is located in system folders.
+ *
+ * Step 2:
+ * Create table for connections:
+ * @code
+ * CREATE TABLE `my_sample_connections` (
+ *   `id` int(11) NOT NULL AUTO_INCREMENT,
+ *   `initiator` int(11) NOT NULL,
+ *   `content` int(11) NOT NULL,
+ *   `mutual` tinyint(4) NOT NULL, -- can re removed for one-way connections
+ *   `added` int(10) unsigned NOT NULL,
+ *   PRIMARY KEY (`id`),
+ *   UNIQUE KEY `initiator` (`initiator`,`content`),
+ *   KEY `content` (`content`)
+ * )
+ * @endcode
+ *
+ *
+ * @section example Example of usage
+ *
+ * Check if two profiles are friends:
+ * @code
+ *   $oConnectionFriends = BxDolConnection::getObjectInstance('bx_profiles_friends'); // get friends connections object
+ *   if ($oConnectionFriends) // check if connections is available for using
+ *      echo $oConnectionFriends->isConnected (100, 200, true) ? "100 and 200 are friends" : "100 and 200 aren't friends"; // check if profiles with IDs 100 and 200 have mutual connections
+ * @endcode
+ *
+ * Get mutual content IDs (friends IDs)
+ * @code
+ *   $oConnectionFriends = BxDolConnection::getObjectInstance('bx_profiles_friends'); // get friends connections object
+ *   if ($oConnectionFriends) // check if connections is available for using
+ *       print_r($oConnection->getConnectedContent(100, 1)); // print array of friends IDs of 100's profile
+ * @endcode
+ *
+ */
+class BxDolConnection extends BxDolFactory implements iBxDolFactoryObject
+{
+    protected $_sObject;
+    protected $_aObject;
+    protected $_oQuery;
+    protected $_sType;
+
+    /**
+     * Constructor
+     * @param $aObject array of connection options
+     */
+    protected function __construct($aObject)
+    {
+        parent::__construct();
+
+        $this->_sObject = $aObject['object'];
+        $this->_aObject = $aObject;
+        $this->_aObject['per_page_default'] = 20;
+
+        $this->_sType = $aObject['type'];
+
+        $this->_oQuery = new BxDolConnectionQuery($aObject);
+    }
+
+    /**
+     * Get connection object instance by object name
+     * @param $sObject object name
+     * @return object instance or false on error
+     */
+    static public function getObjectInstance($sObject)
+    {
+        if (!$sObject)
+            return false;
+
+        if (isset($GLOBALS['bxDolClasses']['BxTemplConnection!'.$sObject]))
+            return $GLOBALS['bxDolClasses']['BxTemplConnection!'.$sObject];
+
+        $aObject = BxDolConnectionQuery::getConnectionObject($sObject);
+        if (!$aObject || !is_array($aObject))
+            return false;
+
+        $sClass = empty($aObject['override_class_name']) ? 'BxTemplConnection' : $aObject['override_class_name'];
+        if (!empty($aObject['override_class_file']))
+            require_once(BX_DIRECTORY_PATH_ROOT . $aObject['override_class_file']);
+
+        $o = new $sClass($aObject);
+
+        return ($GLOBALS['bxDolClasses']['BxTemplConnection!'.$sObject] = $o);
+    }
+
+    /**
+     * Get connection type.
+     * return BX_CONNECTIONS_TYPE_ONE_WAY or BX_CONNECTIONS_TYPE_MUTUAL
+     */ 
+    public function getType()
+    {
+        return $this->_sType;
+    }
+
+    /**
+     * Check whether connection between Initiator and Content can be established.
+     */
+    public function checkAllowedConnect ($iInitiator, $iContent, $isPerformAction = false, $isMutual = false, $isInvertResult = false, $isSwap = false, $isCheckExists = true)
+    {
+        if(!$iInitiator || !$iContent || $iInitiator == $iContent)
+            return _t('_sys_txt_access_denied');
+
+        $oInitiator = BxDolProfile::getInstance($iInitiator);
+        $oContent = BxDolProfile::getInstance($iContent);
+        if(!$oInitiator || !$oContent)
+            return _t('_sys_txt_access_denied');
+
+        // check ACL
+        if(($mixedResult = $this->_checkAllowedConnectInitiator($oInitiator, $isPerformAction)) !== CHECK_ACTION_RESULT_ALLOWED)
+            return $mixedResult;
+
+        // check content's visibility
+        if(!$this->isConnected($iContent, $iInitiator) && ($mixedResult = $this->_checkAllowedConnectContent($oContent)) !== CHECK_ACTION_RESULT_ALLOWED)
+            return $mixedResult;
+
+        if(!$isCheckExists)
+            return CHECK_ACTION_RESULT_ALLOWED;
+
+        if($isSwap)
+            $isConnected = $this->isConnected($iContent, $iInitiator, $isMutual);
+        else
+            $isConnected = $this->isConnected($iInitiator, $iContent, $isMutual);
+
+        if($isInvertResult)
+            $isConnected = !$isConnected;
+
+        return $isConnected ? _t('_sys_txt_access_denied') : CHECK_ACTION_RESULT_ALLOWED;
+    }
+
+    protected function _checkAllowedConnectInitiator ($oInitiator, $isPerformAction = false)
+    {
+        $aCheck = checkActionModule($oInitiator->id(), 'connect', 'system', $isPerformAction);
+        if($aCheck[CHECK_ACTION_RESULT] !== CHECK_ACTION_RESULT_ALLOWED)
+            return $aCheck[CHECK_ACTION_MESSAGE];
+
+        return CHECK_ACTION_RESULT_ALLOWED;
+    }
+
+    protected function _checkAllowedConnectContent ($oContent)
+    {
+        return $oContent->checkAllowedProfileView();
+    }
+
+    /**
+     * Add new connection.
+     * @param $iContent content to make connection to, in most cases some content id, or other profile id in case of friends
+     * @return array
+     */
+    public function actionAdd ($iContent = 0, $iInitiator = false)
+    {
+        if (!$iContent)
+            $iContent = bx_process_input($_POST['id'], BX_DATA_INT);
+
+        return $this->_action ($iInitiator ? $iInitiator : bx_get_logged_profile_id(), $iContent, 'addConnection', '_sys_conn_err_connection_already_exists', true);
+    }
+
+    /**
+     * Remove connection. This method is wrapper for @see removeConnection to be called from @see conn.php upon AJAX request to this file.
+     * @param $iContent content to make connection to, in most cases some content id, or other profile id in case of friends
+     * @return array
+     */
+    public function actionRemove ($iContent = 0, $iInitiator = false)
+    {
+        if (!$iContent)
+            $iContent = bx_process_input($_POST['id'], BX_DATA_INT);
+        
+        if ($iContent != bx_get_logged_profile_id() && BX_CONNECTIONS_TYPE_MUTUAL == $this->_aObject['type']) {
+            $a = $this->actionReject($iContent);
+            if (false == $a['err'])
+                return $a;
+        }
+
+        return $this->_action ($iInitiator ? $iInitiator : bx_get_logged_profile_id(), $iContent, 'removeConnection', '_sys_conn_err_connection_does_not_exists', false, true);
+    }
+
+    /**
+     * Reject connection request. This method is wrapper for @see removeConnection to be called from @see conn.php upon AJAX request to this file.
+     * @param $iContent content to make connection to, in most cases some content id, or other profile id in case of friends
+     * @return array
+     */
+    public function actionReject ($iContent = 0, $iInitiator = false)
+    {
+        if (!$iContent)
+            $iContent = bx_process_input($_POST['id'], BX_DATA_INT);
+
+        return $this->_action($iContent, $iInitiator ? $iInitiator : bx_get_logged_profile_id(), 'removeConnection', '_sys_conn_err_connection_does_not_exists', false, true);
+    }
+
+    protected function _action ($iInitiator, $iContent, $sMethod, $sErrorKey, $isMutual = false, $isInvert = false)
+    {
+        bx_import('BxDolLanguages');
+
+        if(!$iContent || !$iInitiator)
+            return ['err' => true, 'msg' => _t('_sys_conn_err_input_data_is_not_defined')];
+
+        $sMethodCheck = 'checkAllowed' . bx_gen_method_name($sMethod);
+        if(($mixedResult = $this->{method_exists($this, $sMethodCheck) ? $sMethodCheck : 'checkAllowedConnect'}($iInitiator, $iContent, false, false, $isInvert)) !== CHECK_ACTION_RESULT_ALLOWED)
+            return ['err' => true, 'msg' => $mixedResult];
+
+        if (!$this->$sMethod((int)$iInitiator, (int)$iContent)) {
+            if ($isMutual && BX_CONNECTIONS_TYPE_MUTUAL == $this->_sType && $this->isConnected((int)$iInitiator, (int)$iContent, false) && !$this->isConnected((int)$iInitiator, (int)$iContent, true))
+                return ['err' => true, 'msg' => _t('_sys_conn_err_connection_is_awaiting_confirmation')];
+
+            return ['err' => true, 'msg' => _t($sErrorKey)];
+        }
+
+        return ['err' => false, 'msg' => _t('_sys_conn_msg_success')];
+    }
+
+    public function outputActionResult ($mixed, $sFormat = 'json')
+    {
+        switch ($sFormat) {
+            case 'html':
+                echo $mixed;
+                break;
+                
+            case 'json':
+            default:
+                header('Content-Type: application/json; charset=utf-8');
+                echo json_encode($mixed);     
+        }
+        exit;
+    }
+
+    /**
+     * Add new connection.
+     * @param $iInitiator initiator of the connection, in most cases some profile id
+     * @param $iContent content to make connection to, in most cases some content id, or other profile id in case of friends
+     * @return true - if connection was added, false - if connection already exists or error occured
+     */
+    public function addConnection ($iInitiator, $iContent, $aParams = array())
+    {
+        $iMutual = 0;
+        $iInitiator = (int)$iInitiator;
+        $iContent = (int)$iContent;
+
+        $aAlertExtras = array(
+            'initiator' => &$iInitiator,
+            'content' => &$iContent,
+            'mutual' => &$iMutual,
+            'object' => $this,
+        );
+        if(!empty($aParams['alert_extras']) && is_array($aParams['alert_extras']))
+            $aAlertExtras = array_merge($aAlertExtras, $aParams['alert_extras']);
+        
+        bx_alert($this->_sObject, 'connection_before_add', 0, bx_get_logged_profile_id(), $aAlertExtras);
+        
+        if (!$this->_oQuery->addConnection((int)$iInitiator, (int)$iContent, $iMutual))
+            return false;
+        
+        bx_alert($this->_sObject, 'connection_added', 0, bx_get_logged_profile_id(), $aAlertExtras);
+
+        $this->checkAllowedConnect ($iInitiator, $iContent, true, $iMutual, false);
+
+        return true;
+    }
+
+    /**
+     * Remove connection.
+     * @param $iInitiator initiator of the connection
+     * @param $iContent connected content or other profile id in case of friends
+     * @return true - if connection was removed, false - if connection isn't exist or error occured
+     */
+    public function removeConnection ($iInitiator, $iContent)
+    {
+        if (!($aConnection = $this->_oQuery->getConnection((int)$iInitiator, (int)$iContent))) // connection doesn't exist
+            return false;
+
+        if (!$this->_oQuery->removeConnection((int)$iInitiator, (int)$iContent))
+            return false;
+
+        bx_alert($this->_sObject, 'connection_removed', 0, bx_get_logged_profile_id(), array(
+            'initiator' => (int)$iInitiator,
+            'content' => (int)$iContent,
+            'mutual' => isset($aConnection['mutual']) ? $aConnection['mutual'] : 0,
+            'object' => $this,
+        ));
+
+        return true;
+    }
+
+    /**
+     * Compound function, which calls getCommonContent, getConnectedContent or getConnectedInitiators depending on $sContentType
+     * @param $sContentType content type to get BX_CONNECTIONS_CONTENT_TYPE_CONTENT, BX_CONNECTIONS_CONTENT_TYPE_INITIATORS or BX_CONNECTIONS_CONTENT_TYPE_COMMON
+     * @param $iId1 one content or initiator
+     * @param $iId2 second content or initiator only in case of BX_CONNECTIONS_CONTENT_TYPE_COMMON content type
+     * @param $isMutual get mutual connections only
+     * @return array of available connections
+     */
+    public function getConnectionsAsArray ($sContentType, $iId1, $iId2, $isMutual = false, $iStart = 0, $iLimit = BX_CONNECTIONS_LIST_LIMIT, $iOrder = BX_CONNECTIONS_ORDER_NONE)
+    {
+        if (BX_CONNECTIONS_CONTENT_TYPE_COMMON == $sContentType)
+            return $this->getCommonContent($iId1, $iId2, $isMutual, $iStart, $iLimit, $iOrder);
+
+        if (BX_CONNECTIONS_CONTENT_TYPE_INITIATORS == $sContentType)
+            $sMethod = 'getConnectedInitiators';
+        else
+            $sMethod = 'getConnectedContent';
+
+        return $this->$sMethod($iId1, $isMutual, $iStart, $iLimit, $iOrder);
+    }
+
+    /**
+     * Get common content IDs between two initiators
+     * @param $iInitiator1 one initiator
+     * @param $iInitiator2 second initiator
+     * @param $isMutual get mutual connections only
+     * @return array of available connections
+     */
+    public function getCommonContent ($iInitiator1, $iInitiator2, $isMutual = false, $iStart = 0, $iLimit = BX_CONNECTIONS_LIST_LIMIT, $iOrder = BX_CONNECTIONS_ORDER_NONE)
+    {
+        return $this->_oQuery->getCommonContent($iInitiator1, $iInitiator2, $isMutual, $iStart, $iLimit, $iOrder);
+    }
+    
+    /**
+     * Get common content count between two initiators
+     * @param $iInitiator1 one initiator
+     * @param $iInitiator2 second initiator
+     * @param $isMutual get mutual connections only
+     * @return number of connections
+     */
+    public function getCommonContentCount ($iInitiator1, $iInitiator2, $isMutual = false)
+    {
+        return $this->_oQuery->getCommonContentCount($iInitiator1, $iInitiator2, $isMutual);
+    }
+
+    /**
+     * Get connected content count
+     * @param $iInitiator initiator of the connection
+     * @param $isMutual get mutual connections only
+     * @return number of connections
+     */
+    public function getConnectedContentCount ($iInitiator, $isMutual = false)
+    {
+        return $this->_oQuery->getConnectedContentCount($iInitiator, $isMutual);
+    }
+
+    /**
+     * Get connected initiators count
+     * @param $iContent content of the connection
+     * @param $isMutual get mutual connections only
+     * @return number of connections
+     */
+    public function getConnectedInitiatorsCount ($iContent, $isMutual = false)
+    {
+        return $this->_oQuery->getConnectedInitiatorsCount($iContent, $isMutual);
+    }
+
+    /**
+     * Get connected content IDs
+     * @param $iInitiator initiator of the connection
+     * @param $isMutual get mutual connections only
+     * @return array of available connections
+     */
+    public function getConnectedContent ($iInitiator, $isMutual = false, $iStart = 0, $iLimit = BX_CONNECTIONS_LIST_LIMIT, $iOrder = BX_CONNECTIONS_ORDER_NONE)
+    {
+        return $this->_oQuery->getConnectedContent($iInitiator, $isMutual, $iStart, $iLimit, $iOrder);
+    }
+    
+    /**
+     * Get connected content IDs for specified type
+     * @param $iInitiator initiator of the connection
+     * @param $mixedType type of content or an array of types
+     * @param $isMutual get mutual connections only
+     * @return array of available connections
+     */
+    public function getConnectedContentByType ($iInitiator, $mixedType, $isMutual = false, $iStart = 0, $iLimit = BX_CONNECTIONS_LIST_LIMIT, $iOrder = BX_CONNECTIONS_ORDER_NONE)
+    {
+        return $this->_oQuery->getConnectedContentByType($iInitiator, $mixedType, $isMutual, $iStart, $iLimit, $iOrder);
+    }
+
+    /**
+     * Get connected initiators IDs
+     * @param $iContent content of the connection
+     * @param $isMutual get mutual connections only
+     * @return array of available connections
+     */
+    public function getConnectedInitiators ($iContent, $isMutual = false, $iStart = 0, $iLimit = BX_CONNECTIONS_LIST_LIMIT, $iOrder = BX_CONNECTIONS_ORDER_NONE)
+    {
+        return $this->_oQuery->getConnectedInitiators($iContent, $isMutual, $iStart, $iLimit, $iOrder);
+    }
+
+    /**
+     * Get connected initiators IDs
+     * @param $iContent content of the connection
+     * @param $mixedType type of content or an array of types
+     * @param $isMutual get mutual connections only
+     * @return array of available connections
+     */
+    public function getConnectedInitiatorsByType ($iContent, $mixedType, $isMutual = false, $iStart = 0, $iLimit = BX_CONNECTIONS_LIST_LIMIT, $iOrder = BX_CONNECTIONS_ORDER_NONE)
+    {
+        return $this->_oQuery->getConnectedInitiatorsByType($iContent, $mixedType, $isMutual, $iStart, $iLimit, $iOrder);
+    }
+
+    /**
+     * Similar to getConnectionsAsArray, but for getCommonContentAsSQLParts, getConnectedContentAsSQLParts or getConnectedInitiatorsAsSQLParts methods
+     * @see getConnectionsAsArray
+     */
+    public function getConnectionsAsSQLParts ($sContentType, $sContentTable, $sContentField, $iId1, $iId2, $isMutual = false)
+    {
+        if (BX_CONNECTIONS_CONTENT_TYPE_COMMON == $sContentType)
+            return $this->getCommonContentAsSQLParts($sContentTable, $sContentField, $iId1, $iId2, $isMutual);
+
+        if (BX_CONNECTIONS_CONTENT_TYPE_INITIATORS == $sContentType)
+            $sMethod = 'getConnectedInitiatorsAsSQLParts';
+        else
+            $sMethod = 'getConnectedContentAsSQLParts';
+
+        return $this->$sMethod($sContentTable, $sContentField, $iId1, $isMutual);
+    }
+
+    /**
+     * Get necessary parts of SQL query to use connections in other queries
+     * @param $sContentTable content table or alias
+     * @param $sContentField content table field or field alias
+     * @param $iInitiator initiator of the connection
+     * @param $isMutual get mutual connections only
+     * @return array of SQL string parts, for now 'join' part only is returned
+     */
+    public function getCommonContentAsSQLParts ($sContentTable, $sContentField, $iInitiator1, $iInitiator2, $isMutual = false)
+    {
+        return $this->_oQuery->getCommonContentSQLParts($sContentTable, $sContentField, $iInitiator1, $iInitiator2, $isMutual);
+    }
+
+    /**
+     * Get necessary parts of SQL query to use connections in other queries
+     * @param $sContentTable content table or alias
+     * @param $sContentField content table field or field alias
+     * @param $iInitiator initiator of the connection
+     * @param $isMutual get mutual connections only
+     * @return array of SQL string parts, for now 'join' part only is returned
+     */
+    public function getConnectedContentAsSQLParts ($sContentTable, $sContentField, $iInitiator, $isMutual = false)
+    {
+        return $this->_oQuery->getConnectedContentSQLParts($sContentTable, $sContentField, $iInitiator, $isMutual);
+    }
+    
+    public function getConnectedContentAsSQLPartsExt ($sContentTable, $sContentField, $iInitiator, $isMutual = false)
+    {
+        return $this->_oQuery->getConnectedContentSQLPartsExt($sContentTable, $sContentField, $iInitiator, $isMutual);
+    }
+
+	/**
+     * Get necessary parts of SQL query to use connections in other queries
+     * @param $sContentTable content table or alias
+     * @param $sContentField content table field or field alias
+     * @param $sInitiatorTable initiator table or alias
+     * @param $sInitiatorField initiator table field or field alias
+     * @param $iInitiator initiator of the connection
+     * @param $isMutual get mutual connections only
+     * @return array of SQL string parts, for now 'join' part only is returned
+     */
+    public function getConnectedContentAsSQLPartsMultiple ($sContentTable, $sContentField, $sInitiatorTable, $sInitiatorField, $isMutual = false)
+    {
+        return $this->_oQuery->getConnectedContentSQLPartsMultiple($sContentTable, $sContentField, $sInitiatorTable, $sInitiatorField, $isMutual);
+    }
+
+    /**
+     * Get necessary parts of SQL query to use connections in other queries
+     * @param $sInitiatorTable initiator table or alias
+     * @param $sInitiatorField initiator table field or field alias
+     * @param $iContent content of the connection
+     * @param $isMutual get mutual connections only
+     * @return array of SQL string parts, for now 'join' part only is returned
+     */
+    public function getConnectedInitiatorsAsSQLParts ($sInitiatorTable, $sInitiatorField, $iContent, $isMutual = false)
+    {
+        return $this->_oQuery->getConnectedInitiatorsSQLParts($sInitiatorTable, $sInitiatorField, $iContent, $isMutual);
+    }
+
+	/**
+     * Get necessary parts of SQL query to use connections in other queries
+     * @param $sInitiatorTable initiator table or alias
+     * @param $sInitiatorField initiator table field or field alias
+     * @param $sContentTable content table or alias
+     * @param $sContentField content table field or field alias
+     * @param $isMutual get mutual connections only
+     * @return array of SQL string parts, for now 'join' part only is returned
+     */
+    public function getConnectedInitiatorsAsSQLPartsMultiple ($sInitiatorTable, $sInitiatorField, $sContentTable, $sContentField, $isMutual = false)
+    {
+        return $this->_oQuery->getConnectedInitiatorsSQLPartsMultiple ($sInitiatorTable, $sInitiatorField, $sContentTable, $sContentField, $isMutual);
+    }
+
+    /**
+     * Similar to getConnectionsAsArray, but for getCommonContentAsCondition, getConnectedContentAsCondition or getConnectedInitiatorsAsCondition methods
+     * @see getConnectionsAsArray
+     */
+    public function getConnectionsAsCondition ($sContentType, $sContentField, $iId1, $iId2, $isMutual = false)
+    {
+        if (BX_CONNECTIONS_CONTENT_TYPE_COMMON == $sContentType)
+            return $this->getCommonContentAsCondition($sContentField, $iId1, $iId2, $isMutual);
+
+        if (BX_CONNECTIONS_CONTENT_TYPE_INITIATORS == $sContentType)
+            $sMethod = 'getConnectedInitiatorsAsCondition';
+        else
+            $sMethod = 'getConnectedContentAsCondition';
+
+        return $this->$sMethod($sContentField, $iId1, $isMutual);
+    }
+
+    /**
+     * Get necessary condition array to use connections in search classes
+     * @param $sContentField content table field name
+     * @param $iInitiator initiator of the connection
+     * @param $iMutual get mutual connections only
+     * @return array of conditions, for now with 'restriction' and 'join' parts
+     */
+    public function getCommonContentAsCondition ($sContentField, $iInitiator1, $iInitiator2, $iMutual = false)
+    {
+        return array(
+
+            'restriction' => array (
+                'connections_' . $this->_sObject => array(
+                    'value' => $iInitiator1,
+                    'field' => 'initiator',
+                    'operator' => '=',
+                    'table' => 'c',
+                ),
+                'connections_mutual_' . $this->_sObject => array(
+                    'value' => $iMutual,
+                    'field' => 'mutual',
+                    'operator' => '=',
+                    'table' => 'c',
+                ),
+                'connections2_' . $this->_sObject => array(
+                    'value' => $iInitiator2,
+                    'field' => 'initiator',
+                    'operator' => '=',
+                    'table' => 'c2',
+                ),
+                'connections2_mutual_' . $this->_sObject => array(
+                    'value' => $iMutual,
+                    'field' => 'mutual',
+                    'operator' => '=',
+                    'table' => 'c2',
+                ),
+            ),
+
+            'join' => array (
+                'connections_' . $this->_sObject => array(
+                    'type' => 'INNER',
+                    'table' => $this->_aObject['table'],
+                    'table_alias' => 'c',
+                    'mainField' => $sContentField,
+                    'onField' => 'content',
+                    'joinFields' => array(),
+                ),
+                'connections2_' . $this->_sObject => array(
+                    'type' => 'INNER',
+                    'table' => $this->_aObject['table'],
+                    'table_alias' => 'c2',
+                    'mainTable' => 'c',
+                    'mainField' => 'content',
+                    'onField' => 'content',
+                    'joinFields' => array(),
+                ),
+            ),
+
+        );
+    }
+
+    /**
+     * Get necessary condition array to use connections in search classes
+     * @param $sContentField content table field name
+     * @param $iInitiator initiator of the connection
+     * @param $iMutual get mutual connections only
+     * @return array of conditions, for now with 'restriction' and 'join' parts
+     */
+    public function getConnectedContentAsCondition ($sContentField, $iInitiator, $iMutual = false)
+    {
+        $sOperation = '=';
+        if(is_array($iInitiator))
+            $sOperation = 'in';
+
+        return array(
+
+            'restriction' => array (
+                'connections_' . $this->_sObject => array(
+                    'value' => $iInitiator,
+                    'field' => 'initiator',
+                    'operator' => $sOperation,
+                    'table' => $this->_aObject['table'],
+                ),
+                'connections_mutual_' . $this->_sObject => array(
+                    'value' => $iMutual,
+                    'field' => 'mutual',
+                    'operator' => '=',
+                    'table' => $this->_aObject['table'],
+                ),
+            ),
+
+            'join' => array (
+                'connections_' . $this->_sObject => array(
+                    'type' => 'INNER',
+                    'table' => $this->_aObject['table'],
+                    'mainField' => $sContentField,
+                    'onField' => 'content',
+                    'joinFields' => array(),//'initiator'),
+                ),
+            ),
+
+        );
+    }
+
+    /**
+     * Get necessary condition array to use connections in search classes
+     * @param $sContentField content table field name
+     * @param $iInitiator initiator of the connection
+     * @param $iMutual get mutual connections only
+     * @return array of conditions, for now with 'restriction' and 'join' parts
+     */
+    public function getConnectedInitiatorsAsCondition ($sContentField, $iContent, $iMutual = false)
+    {
+        $sOperation = '=';
+        if(is_array($iContent))
+            $sOperation = 'in';
+
+        return array(
+
+            'restriction' => array (
+                'connections_' . $this->_sObject => array(
+                    'value' => $iContent,
+                    'field' => 'content',
+                    'operator' => $sOperation,
+                    'table' => $this->_aObject['table'],
+                ),
+                'connections_mutual_' . $this->_sObject => array(
+                    'value' => $iMutual,
+                    'field' => 'mutual',
+                    'operator' => '=',
+                    'table' => $this->_aObject['table'],
+                ),
+            ),
+
+            'join' => array (
+                'connections_' . $this->_sObject => array(
+                    'type' => 'INNER',
+                    'table' => $this->_aObject['table'],
+                    'mainField' => $sContentField,
+                    'onField' => 'initiator',
+                    'joinFields' => array(),//'initiator'),
+                ),
+            ),
+
+        );
+    }
+
+    /**
+     * Check if initiator and content are connected.
+     * In case if friends this function in conjunction with isMutual parameter can be used to check pending friend requests.
+     * @param $iInitiator initiator of the connection
+     * @param $iContent connected content or other profile id in case of friends
+     * @return true - if content and initiator are connected or false - in all other cases
+     */
+    public function isConnected ($iInitiator, $iContent, $isMutual = false)
+    {
+        $oConnection = $this->_oQuery->getConnection ($iInitiator, $iContent);
+        if (!$oConnection)
+            return false;
+        return false === $isMutual ? true : (isset($oConnection['mutual']) ? $oConnection['mutual'] : false);
+    }
+
+    /**
+     * Check if initiator and content are connected but connetion is not mutual, for checking pending connection requests.
+     * This method makes sense only when type of connection is mutual.
+     * @param $iInitiator initiator of the connection
+     * @param $iContent connected content or other profile id in case of friends
+     * @return true - if content and initiator are connected but connection is not mutual or false in all other cases
+     */
+    public function isConnectedNotMutual ($iInitiator, $iContent)
+    {
+        $oConnection = $this->_oQuery->getConnection ($iInitiator, $iContent);
+        if (!$oConnection)
+            return false;
+        return $oConnection['mutual'] ? false : true;
+    }
+
+    public function getConnection ($iInitiator, $iContent)
+    {
+        return $this->_oQuery->getConnection($iInitiator, $iContent);
+    }
+
+    public function getConnectionById ($iId)
+    {
+        return $this->_oQuery->getConnectionById($iId);
+    }
+
+    /**
+     * Must be called when some content is deleted which can have connections as 'content' or as 'initiator', to delete any associated data
+     * @param $iId which can be as conetnt ot initiator
+     * @return true if some connections were deleted
+     */
+    public function onDeleteInitiatorAndContent ($iId)
+    {
+        $b = $this->onDeleteInitiator ($iId);
+        $b = $this->onDeleteContent ($iId) || $b;
+        return $b;
+    }
+
+    /**
+     * Must be called when some content is deleted which can have connections as 'initiator', to delete any associated data
+     * @param $iIdInitiator initiator id
+     * @return true if some connections were deleted
+     */
+    public function onDeleteInitiator ($iIdInitiator)
+    {
+        if(!$this->_oQuery->onDelete ($iIdInitiator, 'initiator'))
+            return false;
+
+        bx_alert($this->_sObject, 'connection_removed_all', 0, bx_get_logged_profile_id(), array(
+            'initiator' => (int)$iIdInitiator,
+            'object' => $this,
+        ));
+
+        return true;
+    }
+
+    /**
+     * Must be called when some content is deleted which can have connections as 'content', to delete any associated data
+     * @param $iIdInitiator initiator id
+     * @return true if some connections were deleted
+     */
+    public function onDeleteContent ($iIdContent)
+    {
+        if(!$this->_oQuery->onDelete ($iIdContent, 'content'))
+            return false;
+
+        bx_alert($this->_sObject, 'connection_removed_all', 0, bx_get_logged_profile_id(), array(
+            'content' => (int)$iIdContent,
+            'object' => $this,
+        ));
+
+        return true;
+    }
+
+
+    /**
+     * Must be called when module (which can have connections as 'content' or as 'initiator') is deleted, to delete any associated data.
+     * This method call may be automated via @see BxBaseModGeneralInstaller::_aConnections property.
+     * @param $sTable table name with data which have assiciations with the connection
+     * @param $sFieldId id field name which is associated with the connection
+     * @return number of deleted connections
+     */
+    public function onModuleDeleteInitiatorAndContent ($sTable, $sFieldId)
+    {
+        $iAffected = $this->onModuleDeleteInitiator ($sTable, $sFieldId);
+        $iAffected += $this->onModuleDeleteContent ($sTable, $sFieldId);
+        return $iAffected;
+    }
+
+    /**
+     * Must be called when module (which can have connections as 'initiator') is deleted, to delete any associated data.
+     * This method call may be automated via @see BxBaseModGeneralInstaller::_aConnections property.
+     * @param $sTable table name with data which have assiciations with the connection
+     * @param $sFieldId id field name which is associated with the connection
+     * @return number of deleted connections
+     */
+    public function onModuleDeleteInitiator ($sTable, $sFieldId)
+    {
+        return $this->_oQuery->onModuleDelete ($sTable, $sFieldId, 'initiator');
+    }
+
+    /**
+     * Must be called when module (which can have connections as 'content') is deleted, to delete any associated data.
+     * This method call may be automated via @see BxBaseModGeneralInstaller::_aConnections property.
+     * @param $sTable table name with data which have assiciations with the connection
+     * @param $sFieldId id field name which is associated with the connection
+     * @return number of deleted connections
+     */
+    public function onModuleDeleteContent ($sTable, $sFieldId)
+    {
+        return $this->_oQuery->onModuleDelete ($sTable, $sFieldId, 'content');
+    }
+
+
+    /**
+     * Must be called when module (which can have connections as 'content' or as 'initiator' with 'sys_profiles' table) is deleted, to delete any associated data.
+     * This method call may be automated via @see BxBaseModGeneralInstaller::_aConnections property.
+     * @param $sModuleName module name to delete connections for
+     * @return number of deleted connections
+     */
+    public function onModuleProfileDeleteInitiatorAndContent ($sModuleName)
+    {
+        $iAffected = $this->onModuleProfileDeleteInitiator ($sModuleName);
+        $iAffected += $this->onModuleProfileDeleteContent ($sModuleName);
+        return $iAffected;
+    }
+
+    /**
+     * Must be called when module (which can have connections as 'initiator' with 'sys_profiles' table) is deleted, to delete any associated data.
+     * This method call may be automated via @see BxBaseModGeneralInstaller::_aConnections property.
+     * @param $sModuleName module name to delete connections for
+     * @return number of deleted connections
+     */
+    public function onModuleProfileDeleteInitiator ($sModuleName)
+    {
+        return $this->_oQuery->onModuleProfileDelete ($sModuleName, 'initiator');
+    }
+
+    /**
+     * Must be called when module (which can have connections as 'content' with 'sys_profiles' table) is deleted, to delete any associated data.
+     * This method call may be automated via @see BxBaseModGeneralInstaller::_aConnections property.
+     * @param $sModuleName module name to delete connections for
+     * @return number of deleted connections
+     */
+    public function onModuleProfileDeleteContent ($sModuleName)
+    {
+        return $this->_oQuery->onModuleProfileDelete ($sModuleName, 'content');
+    }
+}
+
+/** @} */

+ 481 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/inc/classes/BxDolMenu.php

@@ -0,0 +1,481 @@
+<?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
+ * @{
+ */
+
+/**
+ * Menus.
+ *
+ * Menu uses some set of items and the template to display it,
+ * so it is possible to have several menus which uses the same set of items but different templates.
+ *
+ * Menu is any set of some links or actions, for example menu can be links in site's footer or actions in profile view.
+ *
+ *
+ * @section menu_create Creating the Menu object:
+ *
+ * 1. Add record to 'sys_objects_menu' table:
+ *
+ * - object: name of the menu object, in the format: vendor prefix, underscore, module prefix, underscore, internal identifier or nothing; for example: bx_groups_actions - actions menu in group view.
+ * - title: name of the menu, displayed in the studio menu builder.
+ * - set_name: name of items' set.
+ * - module: the module this menu belongs to.
+ * - template_id: the template to use for menu displaying, this is id from 'sys_menu_templates' table.
+ * - deletable: it determines if menu can be deleted from the studio menu builder.
+ * - active: it is possible to disable particular menu, then it will not be displayed.
+ * - override_class_name: user defined class name which is derived from BxTemplMenu.
+ * - override_class_file: the location of the user defined class, leave it empty if class is located in system folders.
+ *
+ * Menu templates are stored in 'sys_menu_templates' table:
+ * - id: template id.
+ * - template: template file.
+ * - title: template title to display in the studio menu builder.
+ * All menu templates iterate through 'bx_repeat:menu_items' and use the following template variables for each menu item: __link__, __target__, __onclick__, __title__, __class_add__.
+ *
+ *
+ * 2. Add menu an empty menu set to 'sys_menu_sets' table (if you want to use new set of items for created menu):
+ * - set_name: the set name.
+ * - module: the module this set belongs to.
+ * - title: name of the set, displayed in studio menu builder.
+ * - deletable: it determines if the set can be deleted from menu builder.
+ *
+ *
+ * 3. Add menu items to the set by adding records to 'sys_menu_items' table:
+ * - set_name: the set name this item belogs to.
+ * - module: the module this item belongs to.
+ * - name: name of the item (not displayed to the end user), unique in the particular set.
+ * - title: menu item title to display to the end user, please note that some templates can still display menu as icons without text titles.
+ * - link: menu item URL.
+ * - onclick: menu item onclick event.
+ * - target: menu item target.
+ * - icon: menu item icon, please note that some templates can still display menu as text without icons.
+ * - addon: display additional data near menu item, only for supported menu templates, this is serialized array of service call parameters: module - module name, method - service method name, params - array of parameters.
+ * - markers: service method to provide additional replacement markers, this is serialized array of service call parameters: module - module name, method - service method name, params - array of parameters.
+ * - visible_for_levels: bit field with set of member level ids. To use member level id in bit field - the level id minus 1 as power of 2 is used, 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: it is possible to disable particular menu item, then it will not be displayed.
+ * - order: menu item order in the particular set.
+ *
+ *
+ * 4. Display Menu.
+ * Use the following sample code to display menu:
+ * @code
+ *     $oMenu = BxTemplMenu::getObjectInstance('sample_menu'); // 'sample_menu' is 'object' field from 'sys_objects_menu' table.
+ *     if ($oMenu)
+ *         echo $oMenu->getCode; // display menu
+ * @endcode
+ *
+ * But in most cases you don't need to use above code to display menu,
+ * menu objects are integrated into pages - there is special 'menu' page block type for it.
+ *
+ */
+class BxDolMenu extends BxDolFactory implements iBxDolFactoryObject, iBxDolReplaceable
+{
+    protected static $SEL_MODULE = '';
+    protected static $SEL_NAME = '';
+
+    protected $_bIsApi;
+
+    protected $_bDynamicMode;
+    protected $_bAddNoFollow;
+
+    protected $_sSelModule;
+    protected $_sSelName;
+
+    protected $_sObject;
+    protected $_aObject;
+    protected $_oQuery;
+    protected $_oPermalinks;
+    protected $_aMarkers = array();
+    protected $_bMultilevel = false;
+
+    protected $_sSessionKeyCollapsed;
+
+    /**
+     * Constructor
+     * @param $aObject array of menu options
+     */
+    protected function __construct($aObject)
+    {
+        parent::__construct();
+
+        $this->_bIsApi = bx_is_api();
+
+        $this->_bDynamicMode = false;
+        $this->_bAddNoFollow = getParam('sys_add_nofollow') == 'on';
+
+        $this->_sObject = isset($aObject['object']) ? $aObject['object'] : 'bx-menu-obj-' . time() . rand(0, PHP_INT_MAX);
+        $this->_aObject = $aObject;
+        $this->_oQuery = new BxDolMenuQuery($this->_aObject);
+        $this->_oPermalinks = BxDolPermalinks::getInstance();
+
+        $this->_bMultilevel = !empty($this->_aObject['set_name']) && $this->_oQuery->isSetMultilevel($this->_aObject['set_name']);
+
+        $this->_sSessionKeyCollapsed = 'bx_menu_collapsed_';
+
+        $this->addMarkers([
+            'object' => $this->_sObject
+        ]);
+
+        if(isLogged() && ($oProfile = BxDolProfile::getInstance()) !== false) {
+            $sUrl = $oProfile->getUrl();
+            if($this->_bIsApi)
+                $sUrl = bx_api_get_relative_url($sUrl);
+
+            $this->addMarkers([
+                'member_id' => $oProfile->id(),
+                'member_display_name' => $oProfile->getDisplayName(),
+                'member_url' => $sUrl,
+                'content_id' => $oProfile->getContentId()
+            ]);
+        }
+    }
+
+    /**
+     * Get menu object instance by object name
+     * @param $sObject object name
+     * @return object instance or false on error
+     */
+    static public function getObjectInstance($sObject, $oTemplate = false)
+    {
+        $oMenu = false;
+        if (!isset($GLOBALS['bxDolClasses']['BxDolMenu!'.$sObject])) {
+            $aObject = BxDolMenuQuery::getMenuObject($sObject);
+            if (!$aObject || !is_array($aObject) || (int)$aObject['active'] == 0)
+                return false;
+
+            $sClass = 'BxTemplMenu';
+            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']);
+            }
+
+            $oMenu = new $sClass($aObject, $oTemplate);
+            $GLOBALS['bxDolClasses']['BxDolMenu!'.$sObject] = $oMenu;
+        }
+        else
+            $oMenu = $GLOBALS['bxDolClasses']['BxDolMenu!'.$sObject];
+
+        bx_alert('system', 'get_object', 0, false, array(
+            'type' => 'menu',
+            'name' => $sObject,
+            'object' => &$oMenu,
+        ));
+
+        return $oMenu;
+    }
+
+    /**
+     * Set selected menu item globally.
+     * @param $sModule menu item module to set as selected
+     * @param $sName menu item name to set as selected
+     */
+    static public function setSelectedGlobal ($sModule, $sName)
+    {
+        self::$SEL_MODULE = $sModule;
+        self::$SEL_NAME = $sName;
+    }
+
+    /**
+     * Process menu triggers.
+     * Menu triggers allow to automatically add menu items to modules with no different if dependant module was install before or after the module menu item belongs to.
+     * For example module "Notes" adds menu items to all profiles modules (Persons, Organizations, etc)
+     * with no difference if persons module was installed before or after "Notes" module was installed.
+     * @param $sMenuTriggerName trigger name to process, usually specified in module installer class - @see BxBaseModGeneralInstaller
+     * @return always true, always success
+     */
+    static public function processMenuTrigger ($sMenuTriggerName)
+    {
+        // get list of active modules
+        $aModules = BxDolModuleQuery::getInstance()->getModulesBy(array(
+            'type' => 'modules',
+            'active' => 1,
+        ));
+
+        // get list of menu triggers
+        $aMenuItems = BxDolMenuQuery::getMenuTriggers($sMenuTriggerName);
+
+        // check each menu item trigger for all modules
+        foreach ($aMenuItems as $aMenuItem) {
+            foreach ($aModules as $aModule) {
+                if (!BxDolRequest::serviceExists($aModule['name'], 'get_menu_set_name_for_menu_trigger'))
+                    continue;
+
+                $mixedMenuSet = BxDolService::call($aModule['name'], 'get_menu_set_name_for_menu_trigger', array($sMenuTriggerName));
+                if(empty($mixedMenuSet))
+                    continue;
+
+                if(is_string($mixedMenuSet))
+                    $mixedMenuSet = array($mixedMenuSet);
+
+                foreach($mixedMenuSet as $sMenuSet) {
+                    if(empty($sMenuSet))
+                        continue;
+
+                    $aMenuItem['set_name'] = $sMenuSet;
+                    BxDolMenuQuery::addMenuItemToSet($aMenuItem);
+                }
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Check if the menu is visible. The menu is visible if at least one menu item is visible.
+     * @return boolean
+     */
+    public function isVisible()
+    {
+        if((int)$this->_aObject['active'] == 0)
+            return false;
+
+    	if(!isset($this->_aObject['menu_items']))
+            $this->_aObject['menu_items'] = $this->_oQuery->getMenuItems();
+
+    	$bVisible = false;
+    	foreach ($this->_aObject['menu_items'] as $a) {
+            if(!$this->_isActive($a) || !$this->_isVisible($a))
+                continue;
+
+            $bVisible = true;
+            break;
+    	}
+
+    	return $bVisible;
+    }
+
+    public function getTemplateId()
+    {
+        return $this->_aObject['template_id'];
+    }
+
+    /**
+     * Get template name with checking for custom template related to exactly this menu object.
+     * @return string with template name.
+     */
+    public function getTemplateName($sName = '')
+    {
+        if(empty($sName))
+            $sName = $this->_aObject['template'];
+
+        $sNameCustom = str_replace('.html', '_' . $this->_sObject . '.html', $sName);
+        return $this->_oTemplate->isHtml($sNameCustom) ? $sNameCustom : $sName;
+    }
+
+    public function setTemplateById ($iTemplateId)
+    {
+        $aTemplate = $this->_oQuery->getMenuTemplateById($iTemplateId);
+        if(empty($aTemplate) || !is_array($aTemplate))
+            return;
+
+        $this->_aObject['template'] = $aTemplate['template'];
+    }
+    
+    /**
+     * Set selected menu item for current menu object only.
+     * @param $sModule menu item module to set as selected
+     * @param $sName menu item name to set as selected
+     */
+    public function setSelected ($sModule, $sName)
+    {
+        $this->_sSelModule = $sModule;
+        $this->_sSelName = $sName;
+    }
+
+    public function setDynamicMode ($bDynamicMode)
+    {
+        $this->_bDynamicMode = $bDynamicMode;
+    }
+
+    /**
+     * Get an arrey of replacable markers.
+     * @return an array with markers
+     */
+    public function getMarkers()
+    {
+        return $this->_aMarkers;
+    }
+    
+    /**
+     * Add replace markers.
+     * @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;
+    }
+
+    /**
+     * Remove marker
+     * @param $s marker key
+     */
+    public function removeMarker ($s) 
+    {
+        unset($this->_aMarkers[$s]);
+    }
+
+    public function performActionSetCollapsed($mixedValue)
+    {
+        $this->_setCollapsed($this->_sObject, (int)$mixedValue);
+    }
+
+    public function performActionSetCollapsedSubmenu($sMenuItem, $mixedValue)
+    {
+        $this->_setCollapsed($this->_sObject . '_' . $sMenuItem, (int)$mixedValue);
+    }
+
+    public function getUserChoiceCollapsed($sObject = '')
+    {
+        $iProfile = bx_get_logged_profile_id();
+        if(!$iProfile)
+            return false;
+
+        if(!$sObject)
+            $sObject = $this->_sObject;
+
+        $sSessionKey = $this->_sSessionKeyCollapsed . $iProfile;
+        $aCollapsed = BxDolSession::getInstance()->getValue($sSessionKey);
+        if(!isset($aCollapsed[$sObject]))
+            return false;
+
+        return (int)$aCollapsed[$sObject];
+    }
+
+    public function getUserChoiceCollapsedSubmenu($mixedItem, $sObject = '')
+    {
+        if(!$sObject)
+            $sObject = $this->_sObject;
+
+        if(is_array($mixedItem) && isset($mixedItem['name']))
+            $sObject .= '_' . $mixedItem['name'];
+        else if(is_string($mixedItem))
+            $sObject .= '_' . $mixedItem;
+
+        return $this->getUserChoiceCollapsed($sObject);
+    }
+
+    protected function _setCollapsed($sName, $mixedValue)
+    {
+        $iProfile = bx_get_logged_profile_id();
+        if(!$iProfile)
+            return;
+
+        $oSession = BxDolSession::getInstance();
+        $sSessionKey = $this->_sSessionKeyCollapsed . $iProfile;
+
+        $aCollapsed = $oSession->getValue($sSessionKey);
+        if(!is_array($aCollapsed))
+            $aCollapsed = [];
+
+        $aCollapsed[$sName] = $mixedValue;
+        $oSession->setValue($sSessionKey, $aCollapsed);
+    }
+
+    /**
+     * Check if menu items is selected.
+     * @param $a menu item array
+     * @return boolean
+     */
+    protected function _isSelected ($a)
+    {
+        if ($this->_sSelModule || $this->_sSelName)
+            return (!isset($a['module']) || $a['module'] == $this->_sSelModule) && (isset($a['name']) && $a['name'] == $this->_sSelName) ? true : false;
+        return (!isset($a['module']) || $a['module'] == self::$SEL_MODULE) && (isset($a['name']) && $a['name'] == self::$SEL_NAME) ? true : false;
+    }
+
+    /**
+     * Check if menu items is active.
+     * @param $a menu item array
+     * @return boolean
+     */
+    protected function _isActive ($a)
+    {
+        if($this->_bIsApi)
+            return !isset($a['active_api']) || (int)$a['active_api'] !=0;
+        else
+            return !isset($a['active']) || (int)$a['active'] != 0;
+    }
+
+    /**
+     * Check if menu items is visible.
+     * @param $a menu item array
+     * @return boolean
+     */
+    protected function _isVisible ($a)
+    {
+        if(isset($a['visible_for_levels']) && !BxDolAcl::getInstance()->isMemberLevelInSet($a['visible_for_levels']))
+            return false;
+
+        if(!empty($a['visibility_custom']) && !BxDolService::callSerialized($a['visibility_custom'], $this->_aMarkers))
+            return false;
+
+        if($this->_iPageType && !empty($a['hidden_on_pt']) && ((1 << ($this->_iPageType - 2)) & (int)$a['hidden_on_pt']))
+            return false;
+
+        return true;
+    }
+    
+    protected function _getVisibilityClass($a)
+    {
+        $aHiddenOn = array(
+            pow(2, BX_DB_HIDDEN_PHONE - 1) => 'bx-def-media-phone-hide',
+            pow(2, BX_DB_HIDDEN_TABLET - 1) => 'bx-def-media-tablet-hide',
+            pow(2, BX_DB_HIDDEN_DESKTOP - 1) => 'bx-def-media-desktop-hide',
+            pow(2, BX_DB_HIDDEN_MOBILE - 1) => 'bx-def-mobile-app-hide'
+        );
+        
+        $aHiddenOnCol = array(
+            pow(2, 1) => 'bx-def-thin-col-hide',
+            pow(2, 2) => 'bx-def-half-col-hide',
+            pow(2, 3) => 'bx-def-wide-col-hide',
+            pow(2, 4) => 'bx-def-full-col-hide'
+        );
+        
+        $sHiddenOnCssClasses = '';
+        if(!empty($a['hidden_on']))
+            foreach($aHiddenOn as $iHiddenOn => $sClass)
+                if((int)$a['hidden_on'] & $iHiddenOn)
+                    $sHiddenOnCssClasses .= ' ' . $sClass;
+        
+        
+        if(!empty($a['hidden_on_col'])){    
+            foreach($aHiddenOnCol as $iHiddenOn => $sClass)
+                if((int)$a['hidden_on_col'] & $iHiddenOn)
+                    $sHiddenOnCssClasses .= ' ' . $sClass;
+        }
+        
+        return $sHiddenOnCssClasses;
+    }
+
+    /**
+     * Replace provided markers in menu item array, curently markers are replaced in title, link and onclick fields.
+     * @param $a menu item array
+     * @return array where markes are replaced with real values
+     */
+    protected function _replaceMarkers ($a)
+    {
+        if (empty($this->_aMarkers))
+            return $a;
+        $aReplacebleFields = array ('title', 'link', 'onclick');
+        foreach ($aReplacebleFields as $sField)
+            if (isset($a[$sField]))
+                $a[$sField] = bx_replace_markers($a[$sField], $this->_aMarkers);
+        return $a;
+    }
+
+}
+
+/** @} */

+ 201 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/inc/classes/BxDolPageQuery.php

@@ -0,0 +1,201 @@
+<?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 pages.
+ * @see BxDolPage
+ */
+class BxDolPageQuery extends BxDolDb
+{
+    protected $_aObject;
+
+    public function __construct($aObject)
+    {
+        parent::__construct();
+        $this->_aObject = $aObject;
+    }
+
+    static public function getPageObject ($sObject)
+    {
+        $oDb = BxDolDb::getInstance();
+        $sQuery = $oDb->prepare("SELECT `o`.*, `l`.`template`, `l`.`cells_number` FROM `sys_objects_page` AS `o` INNER JOIN `sys_pages_layouts` AS `l` ON (`l`.`id` = `o`.`layout_id`) WHERE `o`.`object` = ?", $sObject);
+        $aObject = $oDb->getRow($sQuery);
+        if (!$aObject || !is_array($aObject))
+            return false;
+
+        return $aObject;
+    }
+
+    static public function getPageObjectNameByURI($sURI, $sModule = false, $bSearchRedirects = false)
+    {
+        $oDb = BxDolDb::getInstance();
+        $a = array('uri' => $sURI);
+        $sQuery = "SELECT `object` FROM `sys_objects_page` WHERE `uri` = :uri";
+        if ($sModule) {
+            $a['module'] = $sModule;
+            $sQuery .= " AND `module` = :module";
+        }
+        $sObject = $oDb->getOne($sQuery, $a);
+
+        if ($bSearchRedirects && !$sObject) {
+            $sQuery = "SELECT `p`.`object` FROM `sys_objects_page` AS `p` INNER JOIN `sys_seo_uri_rewrites` AS `r` ON (`p`.`uri` = `r`.`uri_orig`) WHERE `r`.`uri_rewrite` = :uri";
+            if ($sModule) {
+                $a['module'] = $sModule;
+                $sQuery .= " AND `module` = :module";
+            }
+            $sObject = $oDb->getOne($sQuery, $a);
+        }
+
+        return $sObject;
+    }
+    
+    static public function getContentInfoObjectNameByURI($sURI)
+    {
+        $oDb = BxDolDb::getInstance();
+        $a = array('uri' => $sURI);
+        $sQuery = "SELECT `module`, `content_info` FROM `sys_objects_page` WHERE `uri` = :uri";
+        $aRow = $oDb->getRow($sQuery, $a);
+        if ($aRow['content_info'] != '') 
+            return $aRow['content_info'];
+        
+        return $aRow['module'];
+    }
+
+	static public function getPageTriggers($sTriggerName)
+    {
+        $oDb = BxDolDb::getInstance();
+        $sQuery = $oDb->prepare("SELECT * FROM `sys_pages_blocks` WHERE `object` = ? ORDER BY `id` ASC", $sTriggerName);
+        return $oDb->getAll($sQuery);
+    }
+
+    static public function addPageBlockToPage($aPageBlock)
+    {
+        $oDb = BxDolDb::getInstance();
+
+        if (empty($aPageBlock['object']))
+            return false;
+
+        // check if block already exists, 
+        // so the block position will not reset when it's unnecessary
+        $sQuery = $oDb->prepare("SELECT `id` FROM `sys_pages_blocks` WHERE `object` = ? AND `type` = ? AND `title` = ?", $aPageBlock['object'], $aPageBlock['type'], $aPageBlock['title']);
+        if ($oDb->getOne($sQuery))
+            return true;
+        
+        // get order
+        if (empty($aPageBlock['order'])) {
+        	$iCellId = !empty($aPageBlock['cell_id']) ? (int)$aPageBlock['cell_id'] : 1;
+            $sQuery = $oDb->prepare("SELECT `order` FROM `sys_pages_blocks` WHERE `object` = ? AND `cell_id` = ? AND `active` = 1 ORDER BY `order` DESC LIMIT 1", $aPageBlock['object'], $iCellId);
+            $aPageBlock['order'] = (int)$oDb->getOne($sQuery) + 1;
+        }
+
+        // add new block
+        unset($aPageBlock['id']);
+        return $oDb->query("INSERT INTO `sys_pages_blocks` SET " . $oDb->arrayToSQL($aPageBlock));
+    }
+
+    static public function getPageType($iId)
+    {
+        return BxDolDb::getInstance()->getRow("SELECT * FROM `sys_pages_types` WHERE `id`=:id LIMIT 1", [
+            'id' => $iId
+        ]);
+    }
+
+    static public function getPageTypes()
+    {
+        return BxDolDb::getInstance()->getAll("SELECT * FROM `sys_pages_types` WHERE 1");
+    }
+
+    public function getPageBlocks($bIsApi = false)
+    {
+        $sActiveClause = $bIsApi ? "`active_api` = 1" : "`active` = 1";
+
+        $aRet = [];
+        for($i = 1; $i <= $this->_aObject['cells_number']; ++$i)
+            $aRet['cell_'.$i] = $this->getAll("SELECT * FROM `sys_pages_blocks` WHERE `object` = :object AND `cell_id` = :cell_id AND " . $sActiveClause . " ORDER BY `order` ASC", [
+                'object' => $this->_aObject['object'],
+                'cell_id' => $i
+            ]);
+
+        return $aRet;
+    }
+
+    public function getPageBlock($iBlockId)
+    {
+        $sQuery = $this->prepare("SELECT * FROM `sys_pages_blocks` WHERE `object` = ? AND `id` = ?", $this->_aObject['object'], $iBlockId);
+        return $this->getRow($sQuery);
+    }
+
+    public function getPageBlockContent($iId)
+    {
+        $sQuery = $this->prepare("SELECT `content` FROM `sys_pages_blocks` WHERE `id` = ?", $iId);
+        return $this->getOne($sQuery);
+    }
+
+    public function getPageBlockContentPlaceholder($iId)
+    {
+        $sQuery = $this->prepare("SELECT `id`, `module`, `template` FROM `sys_pages_content_placeholders` WHERE `id` = ?", $iId);
+        return $this->getRow($sQuery);
+    }
+
+    static public function getSeoUriRewrites()
+    {
+        $oDb = BxDolDb::getInstance();
+        return $oDb->fromMemory('sys_seo_uri_rewrites', 'getPairs', 'SELECT `uri_orig`, `uri_rewrite` FROM `sys_seo_uri_rewrites`', 'uri_orig', 'uri_rewrite');
+    }
+
+    static public function getSeoLink($sModule, $sPageUri, $aCond = [])
+    {
+        $oDb = BxDolDb::getInstance();
+        $sWhere = " 1 ";
+        if ($aCond)
+            $sWhere = $oDb->arrayToSQL($aCond, " AND ");
+        return $oDb->getRow("SELECT `uri`, `param_name`, `param_value` FROM `sys_seo_links` WHERE " . $sWhere . " AND `module` = :module AND `page_uri` = :page_uri", [
+            'module' => $sModule,
+            'page_uri' => $sPageUri,
+        ]);
+    }
+
+    static public function insertSeoLink($sModule, $sPageUri, $sSeoParamName, $sSeoParamValue, $sUri)
+    {
+        return BxDolDb::getInstance()->query("INSERT INTO `sys_seo_links` SET `module` = :module, `page_uri` = :page_uri, `param_name` = :param_name, `param_value` = :param_value, `uri` = :uri, `added` = :ts", [
+            'module' => $sModule,
+            'page_uri' => $sPageUri,
+            'param_name' => $sSeoParamName,
+            'param_value' => $sSeoParamValue,
+            'uri' => $sUri,
+            'ts' => time(),
+        ]);
+    }
+
+    static public function deleteSeoLink($sModule, $sContentInfoObject, $sId)
+    {
+        return BxDolDb::getInstance()->query("DELETE FROM `sys_seo_links` WHERE `module` = :module AND `page_uri` IN (SELECT `uri` FROM `sys_objects_page` WHERE `module` = :content_info OR `content_info` = :content_info) AND `param_value` = :param_value", [
+            'module' => $sModule,
+            'content_info' => $sContentInfoObject,
+            'param_value' => $sId,
+        ]);
+    }
+
+    static public function deleteSeoLinkByParam($sParamName, $sId)
+    {
+        return BxDolDb::getInstance()->query("DELETE FROM `sys_seo_links` WHERE `param_name` = :param_name AND `param_value` = :param_value", [
+            'param_name' => $sParamName,
+            'param_value' => $sId,
+        ]);
+    }
+
+    static public function deleteSeoLinkByModule($sModule)
+    {
+        return BxDolDb::getInstance()->query("DELETE FROM `sys_seo_links` WHERE `module` = :module", [
+            'module' => $sModule,
+        ]);
+    }
+}
+
+/** @} */

+ 1145 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/inc/classes/BxDolSearch.php

@@ -0,0 +1,1145 @@
+<?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() : '';
+
+        $bIsApi = bx_is_api();
+        $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_recursive($oEx->aCurrent, $this->_aCustomCurrentCondition);
+            if ($this->_sUnitTemplate)
+                $oEx->setUnitTemplate($this->_sUnitTemplate);
+
+            if ($this->_bDataProcessing) {
+                $aSearchData = $oEx->getSearchData();
+
+                $sCode[$sKey] = $bIsApi ? $oEx->decodeData($aSearchData) : $aSearchData;
+            }
+            else
+                $sCode .= $this->_bRawProcessing ? $oEx->processingRaw() : $oEx->processing();
+        }
+
+        return $sCode;
+    }
+
+    public function getEmptyResult ()
+    {
+        $sKey = _t('_Empty');
+        return DesignBoxContent($sKey, MsgBox($sKey), BX_DB_PADDING_DEF);
+    }
+
+    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 setCustomCurrentCondition($a)
+    {
+        $this->aCurrent = array_merge_recursive($this->aCurrent, $a);
+    }
+
+    public function setCategoriesCondition($sKeyword)
+    {
+		$this->aCurrent['join']['multicat'] = array(
+            'type' => 'INNER',
+            'table' => 'sys_categories2objects',
+            'mainField' => 'id',
+            'onField' => 'object_id',
+            'joinFields' => array(),
+        );
+		
+		if (isset($this->aCurrent['tableSearch'])){
+			$this->aCurrent['join']['multicat']['mainTable'] = $this->aCurrent['tableSearch'];
+		}
+		
+        $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 ()
+    {
+        if (defined('BX_API')) {
+            return $this->processingAPI ();
+        }
+
+        $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;
+    }
+
+    function processingAPI () 
+    {
+        $sUnitType = 'content';
+        if(method_exists($this->oModule, 'serviceActAsProfile') && $this->oModule->serviceActAsProfile())
+             $sUnitType = 'profile';
+        if(method_exists($this->oModule, 'serviceIsGroupProfile') && $this->oModule->serviceIsGroupProfile())
+             $sUnitType = 'context';
+
+        $sUnit =  'list';
+        if ($this->sUnitViewDefault == 'showcase' || $this->sUnitViewDefault == 'gallery')
+            $sUnit = 'card';
+        
+        return [
+            'module' => $this->oModule->getName(),
+            'unit' => 'general-' . $sUnitType . '-' . $sUnit,
+            'request_url' => '/api.php?r=' . $this->oModule->getName() . '/browse/&params[]=',
+            'data' => $this->decodeData($this->getSearchData()),
+            'params' => [
+                'per_page' => $this->aCurrent['paginate']['perPage'],
+                'start' => $this->aCurrent['paginate']['start'],
+                'type' => $this->_sMode,
+            ],
+        ];
+    }
+
+    function decodeData ($a)
+    {
+        return $a;
+    }
+
+    /**
+     * 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 " . (!empty($aValue['on_sql']) ? $aValue['on_sql'] : "`{$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';
+
+        if(isset($this->aCurrent['distinct']) && $this->aCurrent['distinct'] === true)
+            $sqlQuery .= ' DISTINCT';
+
+        $sqlQuery .= ' ' . $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()
+    {
+        $this->aCurrent['paginate']['num'] = 0;
+
+        // keyword
+        $sKeyword = bx_process_input(isset($this->_aCustomSearchCondition['keyword']) ? $this->_aCustomSearchCondition['keyword'] : bx_get('keyword'));
+
+        if ($this->_bLiveSearch && empty($sKeyword))
+            return;
+
+        if ($sKeyword !== false) {
+            if (substr($sKeyword, 0, 1) == '@') {
+                $sModule = $this->aCurrent['module_name'];
+                $sMethod = 'act_as_profile';
+                if(!bx_is_srv($sModule, $sMethod) || !bx_srv($sModule, $sMethod))
+                    return;
+
+                $sKeyword = substr($sKeyword, 1);
+            }
+
+            $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) {
+                if ($this->aCurrent['name'] == $o->getSearchObject()) {
+                    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;
+    }
+
+    /**
+     * 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;
+                       case 'in_set':
+                           $sqlCondition = "1 << (" . $sqlCondition . " - 1) & " . (int)$aValue['value'];
+                           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')
+    {
+        if (isset($this->aCurrent['sorting_sql']))
+            return array('order' => $this->aCurrent['sorting_sql']);
+        
+        $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;
+        
+        if ($this->iPaginatePerPage != BX_DOL_SEARCH_RESULTS_PER_PAGE_DEFAULT)
+            $iPerPage = $this->iPaginatePerPage;
+
+        $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);
+    }
+}
+
+/** @} */

+ 1111 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/inc/js/classes/BxDolUploader.js

@@ -0,0 +1,1111 @@
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaCore UNA Core
+ * @{
+ */
+
+/**
+ * Simple Uploader js class
+ */
+
+
+function BxDolUploaderBase (sUploaderObject, sStorageObject, sUniqId, options) {
+
+    this.init(sUploaderObject, sStorageObject, sUniqId, options);
+
+    this._sIframeId = 'bx-form-input-files-' + sUniqId + '-iframe';
+    this._eForm = null;
+}
+
+BxDolUploaderBase.prototype.init = function (sUploaderObject, sStorageObject, sUniqId, options) {
+    this._isUploadsInProgress = false;
+
+    this._sUploaderObject = sUploaderObject;
+    this._sStorageObject = sStorageObject;
+
+    this._sUniqId = sUniqId;
+
+    this._sUploaderJsInstance = 'glUploader_' + sUniqId + '_' + this._sUploaderObject;
+    this._sUploadInProgressContainerId = 'bx-form-input-files-' + sUniqId + '-upload-in-progress-' + this._sUploaderObject;
+    this._sPopupContainerId = 'bx-form-input-files-' + sUniqId + '-popup-wrapper-' + this._sUploaderObject;
+
+    this._sResultContainerId = 'bx-form-input-files-' + sUniqId + '-upload-result';
+    this._sErrorsContainerId = 'bx-form-input-files-' + sUniqId + '-errors'; 
+    this._sProgressContainerId = 'bx-form-input-files-' + sUniqId + '-progress'; 
+
+    this._sFormContainerId = 'bx-form-input-files-' + sUniqId + '-form-cont';
+
+    this._sTemplateGhost = options.template_ghost ? options.template_ghost : '<div id="' + this._getFileContainerId('{file_id}') + '"><input type="hidden" name="f[]" value="{file_id}" />{file_name} (<a href="javascript:void(0);" onclick="{js_instance_name}.deleteGhost(\'{file_id}\')">delete</a>)</div>';
+    this._sTemplateReorder = options.template_reorder ? options.template_reorder : '<div class="bx-uploader-ghost-reorder bx-btn bx-btn-small"><i class="sys-icon bars"></i></div>';
+    this._sTemplateError = options.template_error_msg ? options.template_error_msg : '<div>{error}</div>' ;
+    this._sTemplateErrorGhosts = options.template_error_ghosts ? options.template_error_ghosts : this._sTemplateError;
+
+    this._isMultiple = undefined == options.multiple || !options.multiple ? false : true;
+    this._isReordering = undefined == options.reordering || !options.reordering ? false : true;
+
+    this._iContentId = undefined == options.content_id || '' == options.content_id ? '' : parseInt(options.content_id);
+
+    this._sImagesTranscoder = options.images_transcoder ? options.images_transcoder : '';
+
+    this._isPrivate = undefined == options.storage_private || parseInt(options.storage_private) ? 1 : 0;
+
+    this._isErrorShown = false;
+}
+
+BxDolUploaderBase.prototype.isMultiple = function () {    
+    return this._isMultiple;
+}
+
+BxDolUploaderBase.prototype.getCurrentFilesCount = function () {
+    return $('#' + $this._sResultContainerId + ' .bx-uploader-ghost').length;
+}
+
+BxDolUploaderBase.prototype.showUploaderForm = function () {
+    var $this = this;
+    var sUrl = this._getUrlWithStandardParams() + '&a=show_uploader_form&m=' + (this._isMultiple ? 1 : 0) + '&c=' + this._iContentId + '&p=' + this._isPrivate + '&_t=' + escape(new Date());
+
+    $(window).dolPopupAjax({
+        url: sUrl,
+        id: {force: true, value: this._sPopupContainerId},
+        onBeforeShow: function() {
+            if ($this.isMultiple())
+                $('#' + $this._sPopupContainerId + ' .bx-uploader-add-more-files').show();
+            else
+                $('#' + $this._sPopupContainerId + ' .bx-uploader-add-more-files').hide();
+            $('#' + $this._sPopupContainerId + ' .bx-popup-element-close').click(function() {
+                $this.onClickCancel();
+            });
+			$("#" + $this._sFormContainerId + " .bx-btn.bx-btn-primary:not(.bx-crop-upload)").hide();
+            if ('undefined' !== typeof($this.onBeforeShowPopup))
+                $this.onBeforeShowPopup();
+        },
+        onShow: function () {
+            if ('undefined' !== typeof($this.onShowPopup))
+                $this.onShowPopup();
+        },
+        closeElement: false,
+        closeOnOuterClick: false
+    });
+}
+
+BxDolUploaderBase.prototype.onClickCancel = function () {
+	var $this = this;
+    if (this._isUploadsInProgress) {
+    	bx_confirm(_t('_sys_uploader_confirm_close_popup'), function() {
+    		$this.cancelAll();
+            $('#' + $this._sPopupContainerId).dolPopupHide({});
+    	});
+    } else {
+        $('#' + this._sPopupContainerId).dolPopupHide();
+    }
+}
+
+BxDolUploaderBase.prototype.onBeforeUpload = function (params) {
+
+    this._eForm = params;
+
+    this._loading(true, false);
+        
+    this._isUploadsInProgress = true;
+    this._lockPageFromLeaving();
+    this._clearErrors();
+}
+
+BxDolUploaderBase.prototype.onProgress = function (params) {
+
+}
+
+BxDolUploaderBase.prototype.onUploadCompleted = function (sErrorMsg) {
+
+    this._isUploadsInProgress = false;
+    this._unlockPageFromLeaving();
+    this._loading(false, true);
+
+    if (sErrorMsg.length) {
+        this.restoreGhosts();        
+        this._showError(sErrorMsg);
+    } else {        
+        this.restoreGhosts();
+        $('#' + this._sPopupContainerId).dolPopupHide({});
+    }
+}
+
+BxDolUploaderBase.prototype.cancelAll = function () {
+    $('#' + this._sIframeId).attr('src', 'javascript:false;');
+    this.onUploadCompleted(_t('_sys_uploader_upload_canceled'));
+}
+
+BxDolUploaderBase.prototype.restoreGhosts = function (bInitReordering, onComplete) {
+    var sUrl = this._getUrlWithStandardParams() + '&img_trans=' + this._sImagesTranscoder + '&a=restore_ghosts&f=json' + '&c=' + this._iContentId + '&_t=' + escape(new Date());
+    var $this = this;
+
+    bInitReordering = bInitReordering !== undefined ? bInitReordering : this._isReordering;
+
+    $.getJSON(sUrl, function (aData) {
+
+        if (!$this.isMultiple())
+            $('#' + $this._sResultContainerId + ' .bx-uploader-ghost').remove();
+
+        if ('object' === typeof(aData)) {
+            if('object' === typeof(aData.g) && 'object' === typeof(aData.o)) 
+                for(var i in aData.o) {
+                    var iFileId = aData.o[i];
+                    $this.showGhost(iFileId, aData.g[iFileId]);
+                }
+            else
+                $.each(aData, function(iFileId, oVars) {
+                    $this.showGhost(iFileId, oVars);
+                });
+
+            $('#' + $this._sResultContainerId).bx_show_more_check_overflow();
+
+            if(bInitReordering) {
+                var sClassGhost = 'bx-uploader-ghost';
+                $('#' + $this._sResultContainerId).find('.' + sClassGhost).each(function() {
+                    if($(this).find('.bx-uploader-ghost-reorder').length == 0)
+                        $(this).find('.bx-base-general-uploader-ghost').prepend($this._sTemplateReorder);
+                });
+
+                var fInitReordering = function() {                    
+                    $('#' + $this._sResultContainerId).sortable({
+                        items: '.' + sClassGhost, 
+                        start: function(oEvent, oUi) {
+                            oUi.item.addClass('bx-uploader-ghost-dragging');
+                        },
+                        stop: function(oEvent, oUi) {
+                            oUi.item.removeClass('bx-uploader-ghost-dragging');
+
+                            $this.reorderGhosts(oUi.item);
+                        }
+                    });
+                };
+
+                if($.sortable !== undefined)
+                    fInitReordering();
+                else
+                    setTimeout(fInitReordering, 2000);
+            }
+        }
+
+        if(typeof onComplete === 'function')
+            return onComplete(aData);
+    });
+};
+
+BxDolUploaderBase.prototype.reorderGhosts = function(oDraggable) {
+    var sUrl = this._getUrlWithStandardParams() + '&a=reorder_ghosts&f=json' + '&c=' + this._iContentId + '&' + $('#' + this._sResultContainerId).sortable('serialize', {key: 'ghosts[]'}) + '&_t=' + escape(new Date());
+
+    $.getJSON(sUrl, function (aData) {
+        processJsonData(aData);
+    });
+};
+
+BxDolUploaderBase.prototype.showGhost = function(iId, oVars) {
+    var oFileContainer = $('#' + this._getFileContainerId(iId));
+    if(oFileContainer.length > 0)
+        return;
+
+    var sHTML;
+    if (typeof this._sTemplateGhost == 'object')
+        sHTML = this._sTemplateGhost[iId];
+    else
+        sHTML = this._sTemplateGhost;
+
+    for(var i in oVars)
+        sHTML = sHTML.replace (new RegExp('{' + i + '}', 'g'), oVars[i]);
+    
+    $('#' + this._sResultContainerId).append(sHTML).trigger( "contentchange" );
+
+    oFileContainer.find('.bx-uploader-ghost-preview img').hide().fadeIn(1000);
+};
+
+BxDolUploaderBase.prototype.deleteGhost = function (iFileId) {
+    var sUrl = this._getUrlWithStandardParams() + '&a=delete&id=' + iFileId;
+    var $this = this;
+
+    var sFileContainerId = $this._getFileContainerId(iFileId);
+    bx_loading(sFileContainerId, true);
+
+    $.post(sUrl, {_t: escape(new Date())}, function (sMsg) {
+        bx_loading(sFileContainerId, false);
+        if ('ok' == sMsg) {
+            $('#' + sFileContainerId).slideUp('slow', function () {
+                $(this).remove();
+            });
+        } else {
+            $('#' + this._sResultContainerId).prepend($this._sTemplateErrorGhosts.replace('{error}', sMsg));
+        }        
+    });
+};
+
+BxDolUploaderBase.prototype._clearErrors = function () {
+    $(' #' + this._sErrorsContainerId).html('');
+    this._isErrorShown = false;
+}
+
+BxDolUploaderBase.prototype._showError = function (s, bAppend) {
+    if (s == undefined || !s.length)
+        return;
+
+    var eCont = $('#' + this._sPopupContainerId).find('[id=' + this._sErrorsContainerId + ']').size() ? $('#' + this._sPopupContainerId).find('[id=' + this._sErrorsContainerId + ']') : $('#' + this._sErrorsContainerId);
+
+    if (!bAppend)
+        eCont.html(this._sTemplateError.replace('{error}', s));
+    else
+        eCont.prepend(this._sTemplateError.replace('{error}', s));
+
+    this._isErrorShown = true;
+};
+
+BxDolUploaderBase.prototype._getFileContainerId = function (iFileId) {
+	return 'bx-uploader-file-' + this._sStorageObject + '-' + iFileId;
+};
+
+BxDolUploaderBase.prototype._getUrlWithStandardParams = function () {
+    return sUrlRoot + 'storage_uploader.php?uo=' + this._sUploaderObject + '&so=' + this._sStorageObject + '&uid=' + this._sUniqId;
+}
+
+BxDolUploaderBase.prototype._lockPageFromLeaving = function () {
+    $(window).bind('beforeunload', function (e) {
+        var e = e || window.event;
+        // for ie, ff
+        e.returnValue = _t('_sys_uploader_confirm_leaving_page');
+        // for webkit
+        return _t('_sys_uploader_confirm_leaving_page');
+    });
+}
+
+BxDolUploaderBase.prototype._unlockPageFromLeaving = function () {
+    $(window).unbind('beforeunload');
+}
+
+BxDolUploaderBase.prototype._loading = function (bShowProgress, bShowForm) {
+
+    var eForm = $('#' + this._sFormContainerId + ' .bx-uploader-files-list');
+    var eBtn = $('#' + this._sFormContainerId + ' .bx-btn-primary');
+
+    if (bShowForm) {
+        if (null != this._eForm) {
+            eForm.find('.bx-uploader-simple-file').filter(':not(:first)').remove();
+            if ('undefined' !== typeof(this._eForm.reset)) 
+                this._eForm.reset();
+        }
+        eForm.show();
+        eBtn.show();
+    } else {
+        eForm.hide();
+        eBtn.hide();
+    }
+
+    bx_loading($('#' + this._sFormContainerId + ' .bx-uploader-loading').get(0), bShowProgress);
+}
+
+BxDolUploaderBase.prototype.getMimeTypefromString  = function (ext) {
+    
+    var mimeTypes = 
+    {
+        
+        '3gp'    : 'video/3gpp',
+        '3g2'    : 'video/3gpp2',
+        'a'      : 'application/octet-stream',
+        'ai'     : 'application/postscript',
+        'aif'    : 'audio/x-aiff',
+        'aifc'   : 'audio/x-aiff',
+        'aiff'   : 'audio/x-aiff',
+        'asf'   : 'video/x-ms-asf',
+        'au'     : 'audio/basic',
+        'avi'    : 'video/x-msvideo',
+        'avi'    : 'video/avi',
+        'bat'    : 'text/plain',
+        'bin'    : 'application/octet-stream',
+        'bmp'    : 'image/x-ms-bmp',
+        'c'      : 'text/plain',
+        'cdf'    : 'application/x-cdf',
+        'csh'    : 'application/x-csh',
+        'css'    : 'text/css',
+        'divx'   : 'video/divx',
+        'dll'    : 'application/octet-stream',
+        'doc'    : 'application/msword',
+        'dot'    : 'application/msword',
+        'drc'    : 'video/drc',
+        'dvi'    : 'application/x-dvi',
+        'eml'    : 'message/rfc822',
+        'eps'    : 'application/postscript',
+        'etx'    : 'text/x-setext',
+        'exe'    : 'application/octet-stream',
+        'gif'    : 'image/gif',
+        'gtar'   : 'application/x-gtar',
+        'h'      : 'text/plain',
+        'hdf'    : 'application/x-hdf',
+        'htm'    : 'text/html',
+        'html'   : 'text/html',
+        'jpe'    : 'image/jpeg',
+        'jpeg'   : 'image/jpeg',
+        'jpg'    : 'image/jpeg',
+        'js'     : 'application/x-javascript',
+        'ksh'    : 'text/plain',
+        'latex'  : 'application/x-latex',
+        'm1v'    : 'video/mpeg',
+        'man'    : 'application/x-troff-man',
+        'me'     : 'application/x-troff-me',
+        'mht'    : 'message/rfc822',
+        'mhtml'  : 'message/rfc822',
+        'mkv'    : 'video/x-matroska',
+        'mif'    : 'application/x-mif',
+        'mov'    : 'video/quicktime',
+        'movie'  : 'video/x-sgi-movie',
+        'mp2'    : 'audio/mpeg',
+        'mp3'    : 'audio/mpeg',
+        'mp4'    : 'video/mp4',  
+        'm4v'    : 'video/mp4', 
+        'mpa'    : 'video/mpeg',
+        'mpe'    : 'video/mpeg',
+        'mpeg'   : 'video/mpeg',
+        'mpg'    : 'video/mpeg',
+        'ms'     : 'application/x-troff-ms',
+        'nc'     : 'application/x-netcdf',
+        'nws'    : 'message/rfc822',
+        'o'      : 'application/octet-stream',
+        'obj'    : 'application/octet-stream',
+        'oda'    : 'application/oda',
+        'ogv'    : 'video/ogg',
+        'ogg'    : 'video/ogg',
+        'pbm'    : 'image/x-portable-bitmap',
+        'pdf'    : 'application/pdf',
+        'pfx'    : 'application/x-pkcs12',
+        'pgm'    : 'image/x-portable-graymap',
+        'png'    : 'image/png',
+        'pnm'    : 'image/x-portable-anymap',
+        'pot'    : 'application/vnd.ms-powerpoint',
+        'ppa'    : 'application/vnd.ms-powerpoint',
+        'ppm'    : 'image/x-portable-pixmap',
+        'pps'    : 'application/vnd.ms-powerpoint',
+        'ppt'    : 'application/vnd.ms-powerpoint',
+        'pptx'   : 'application/vnd.ms-powerpoint',
+        'ps'     : 'application/postscript',
+        'pwz'    : 'application/vnd.ms-powerpoint',
+        'py'     : 'text/x-python',
+        'pyc'    : 'application/x-python-code',
+        'pyo'    : 'application/x-python-code',
+        'qt'     : 'video/quicktime',
+        'ra'     : 'audio/x-pn-realaudio',
+        'ram'    : 'application/x-pn-realaudio',
+        'ras'    : 'image/x-cmu-raster',
+        'rdf'    : 'application/xml',
+        'rgb'    : 'image/x-rgb',
+        'rm'     : 'application/vnd.rn-realmedia',
+        'rmvb'   : 'application/vnd.rn-realmedia-vbr',
+        'roff'   : 'application/x-troff',
+        'rtx'    : 'text/richtext',
+        'sgm'    : 'text/x-sgml',
+        'sgml'   : 'text/x-sgml',
+        'sh'     : 'application/x-sh',
+        'shar'   : 'application/x-shar',
+        'snd'    : 'audio/basic',
+        'so'     : 'application/octet-stream',
+        'src'    : 'application/x-wais-source',
+        'swf'    : 'application/x-shockwave-flash',
+        't'      : 'application/x-troff',
+        'tar'    : 'application/x-tar',
+        'tcl'    : 'application/x-tcl',
+        'tex'    : 'application/x-tex',
+        'texi'   : 'application/x-texinfo',
+        'texinfo': 'application/x-texinfo',
+        'tif'    : 'image/tiff',
+        'tiff'   : 'image/tiff',
+        'tr'     : 'application/x-troff',
+        'tsv'    : 'text/tab-separated-values',
+        'txt'    : 'text/plain',
+        'ustar'  : 'application/x-ustar',
+        'vcf'    : 'text/x-vcard',
+        'wav'    : 'audio/x-wav',
+        'wav'    : 'audio/wav',
+        'webm'   : 'video/webm',
+        'wmv'    : 'video/wmv',
+        'wiz'    : 'application/msword',
+        'wsdl'   : 'application/xml',
+        'xbm'    : 'image/x-xbitmap',
+        'xlb'    : 'application/vnd.ms-excel',
+        'xls'    : 'application/vnd.ms-excel',
+        'xlsx'    : 'application/vnd.ms-excel',
+        'xml'    : 'text/xml',
+        'xpdl'   : 'application/xml',
+        'xpm'    : 'image/x-xpixmap',
+        'xsl'    : 'application/xml',
+        'xvid'   : 'video/xvid',
+        'webp'   : 'image/webp',
+        'xwd'    : 'image/x-xwindowdump',
+        'svg'    : 'image/svg+xml',
+        'zip'    : ['application/zip', 'application/x-zip-compressed']
+       
+    }
+    return mimeTypes[ext.replace('.', '')];
+}
+
+/**
+ * HTML5 Uploader js class
+ */
+function BxDolUploaderHTML5 (sUploaderObject, sStorageObject, sUniqId, options) {
+
+    this.init(sUploaderObject, sStorageObject, sUniqId, options);
+
+    this._sIframeId = 'bx-form-input-files-' + sUniqId + '-iframe';
+    this._eForm = null;    
+    this._sDivId = 'bx-form-input-files-' + sUniqId + '-div-' + this._sUploaderObject;
+    this._sFocusDivId = 'bx-form-input-files-' + sUniqId + '-focus-' + this._sUploaderObject;
+    this._uploader = null;
+    this._aFiles = [];
+  
+
+    this.initUploader = function (o) {
+
+        var $this = this;
+
+        if (null != this._uploader)
+            this._uploader = null;
+
+        var $this = this;
+        var _options = {
+            allowProcess: false,
+            allowPaste: false,
+            allowRevert: false,
+            allowRemove: false,
+            imagePreviewHeight: 100,
+            credits: {},
+            allowMultiple: $this._isMultiple ? true : false,
+            maxFiles: $this.isMultiple() ? 50 : 1,
+            maxFileSize: o.maxFilesize > 2 ? Math.round(o.maxFilesize) + 'MB' : Math.round(o.maxFilesize*1024) + 'KB',
+            instantUpload: true,
+            onaddfile: (error, file) => { 
+                if (error){
+                    $this._uploader.removeFile(file);
+                    if($this._uploader.status != 3)
+                        $this.onUploadCompleted(''); 
+                }
+                else{
+                    $this.onBeforeUpload(''); 
+                }
+            },
+            onerror: (error) => { 
+                if (error.main)
+                    $this._showError(error.main + '. ' + error.sub);
+            },
+            onprocessfiles: (files) => { 
+                $this.onUploadCompleted(''); 
+                oProgress.hide();
+                $this._aFiles = [];
+            },
+            onprocessfileprogress(file, progress) {
+                $this._aFiles[file.source.lastModified+'-'+file.source.size+'-'+file.source.name] = progress * file.source.size;
+                iTotal = 0;
+                iUploaded = 0; 
+                for (const propertyName in $this._aFiles) {
+                    iUploaded += $this._aFiles[propertyName];
+                }
+                
+                for (i = 0; i < $this._uploader.getFiles().length; i++){
+                    iTotal += $this._uploader.getFiles()[i].source.size;
+                }
+            
+                iProgress = iUploaded / iTotal * 100;
+            
+                oProgress =  $('#' + $this._sProgressContainerId);
+                
+                if (oProgress.parents('.bx-db-container').find('.uploader_progress').length > 0){
+                    oProgress = oProgress.parents('.bx-db-container').find('.uploader_progress');
+                }
+                
+                oProgress.show();
+                oProgress.find('.progress_line').css('width', iProgress + '%');
+            },
+            server: {
+				process: (fieldName, file, metadata, load, error, progress, abort) => {
+					const formData = new FormData();
+					formData.append('file', file, file.name);
+					formData.append('uo', this._sUploaderObject);
+					formData.append('so', this._sStorageObject);
+					formData.append('uid', this._sUniqId);
+					formData.append('m', this._isMultiple ? 1 : 0);
+					formData.append('c', this._iContentId);
+					formData.append('p', this._isPrivate);
+					formData.append('a', 'upload');
+
+					const request = new XMLHttpRequest();
+					request.open('POST', sUrlRoot + 'storage_uploader.php');
+
+					request.upload.onprogress = (e) => {
+						progress(e.lengthComputable, e.loaded, e.total);
+					};
+
+					request.onload = function(res) {
+						if (request.status >= 200 && request.status < 300) {
+							var o = null;
+                            try {
+                                o = JSON.parse(request.responseText);
+                            } 
+                            catch (e) {}
+                            if (o && 'undefined' !== typeof(o.error)) {
+                                $this._showError(o.error, true);
+                            }
+                            
+                            load(request.responseText);
+						}
+						else {
+							error('error');
+						}
+					};
+					request.send(formData);
+				}
+			},
+        };
+        
+        aAcceptableFiles = [];
+        if (o.acceptedFiles && o.acceptedFiles != ''){
+            a = o.acceptedFiles.trim().split(/\s*,\s*/);
+            a.forEach(function(item, i, arr) {
+                if ($this.getMimeTypefromString(item)){
+                    oT = $this.getMimeTypefromString(item);
+                    if (Array.isArray(oT)){
+                        oT.forEach(function(oTs, j, ar) {
+                            aAcceptableFiles.push(oTs);
+                        });
+                    }
+                    else{
+                        aAcceptableFiles.push(oT);
+                    }
+                }
+                    
+            });
+        }
+        
+        if (aAcceptableFiles.length){
+            _options.acceptedFileTypes = aAcceptableFiles;
+        }
+        
+      
+        
+        if (o.resizeWidth || o.resizeHeight){
+            _options.allowImageResize = true;
+            _options.imageResizeTargetWidth = o.resizeWidth;
+            _options.imageResizeTargetHeight = o.resizeHeight;
+            _options.imageResizeMode = o.resizeMethod;
+        }
+        else{
+            _options.allowImageResize = false;
+            _options.allowImageTransform = false;
+        }
+        
+        FilePond.registerPlugin(
+            FilePondPluginImagePreview,
+            FilePondPluginFileValidateType,
+            FilePondPluginFileValidateSize,
+            FilePondPluginImageTransform,
+            FilePondPluginImageCrop,
+            FilePondPluginImageResize,
+            
+        );
+
+        this._uploader = FilePond.create(
+            document.querySelector('#' + this._sDivId),
+            $.extend({}, _options, o)
+        );
+        this._uploader.setOptions(glFilepondLocale);
+
+        // need to be activated if catch paste needed
+        //this.initPasteEditor();
+    }
+
+    this.onUploadCompleted = function (sErrorMsg) {        
+        if (sErrorMsg.length)
+            this._showError(sErrorMsg);        
+        
+        var $this = this;
+        if($this._uploader.status != 3){
+            this._isUploadsInProgress = false;
+            this.restoreGhosts();
+            if (!this._isErrorShown) {
+                $('#' + this._sPopupContainerId).dolPopupHide({});
+                this.removeFiles();
+            }
+        }
+    }
+
+    this.removeFiles = function () {
+        oFiles = this._uploader.getFiles();
+        for (i = 0; i < oFiles.length; i++){
+            this._uploader.removeFile(i);
+        }
+    }
+
+    this.cancelAll = function () {
+        this.removeFiles();
+    }
+
+    this.onBeforeUpload = function (params) {
+        this._isUploadsInProgress = true;
+        this._clearErrors();
+    }
+
+    this.onProgress = function (params) {
+    }
+
+    this.onClickCancel = function () {
+        var $this = this;
+        if (this._isUploadsInProgress) {
+            bx_confirm(_t('_sys_uploader_confirm_close_popup'), function() {
+                $this.removeFiles();
+                $this.cancelAll();
+                $('#' + $this._sPopupContainerId).dolPopupHide({});
+            });
+        } else {
+            $this.removeFiles();
+            $('#' + this._sPopupContainerId).dolPopupHide();
+        }
+        
+        BxDolUploaderBase.prototype._clearErrors.call(this);
+    }
+
+    this.onShowPopup = function () {
+        var $this = this;
+        setTimeout(function () {
+            $this.focusPasteEditor();
+        }, 200);
+    }
+
+    this.focusPasteEditor = function () {
+        $('#' + this._sFocusDivId).focus();
+    }
+
+    this.initPasteEditor = function () {
+        var $this = this;
+        $('#' + this._sFocusDivId).on('paste', function (e) {
+            var aFiles = [];
+            if (e.type == 'paste') {
+                var aItems = (e.clipboardData || e.originalEvent.clipboardData).items;
+                
+                for (var i in aItems) {
+                    var oItem = aItems[i];
+                    if (oItem.kind === 'file')
+                        aFiles.push(oItem.getAsFile());
+                }
+                $this._uploader.addFiles(aFiles);
+            }
+        });
+    }
+    
+    this.showUploaderForm = function() {
+        this._uploader.browse();
+    }
+    
+    this.initUploader({
+        'maxFilesize': options.maxFilesize,
+        'acceptedFiles': options.acceptedFiles,
+        'resizeWidth': options.resizeWidth,
+        'resizeHeight': options.resizeHeight,
+        'resizeMethod': options.resizeMethod,
+        'dictDefaultMessage': options.dictDefaultMessage,
+        'dictFileTooBig': options.dictFileTooBig,
+        'dictMaxFilesExceeded': options.dictMaxFilesExceeded,
+        'dictInvalidFileType': options.dictInvalidFileType,
+    });
+}
+
+BxDolUploaderHTML5.prototype = BxDolUploaderBase.prototype;
+
+function BxDolUploaderSimple(sUploaderObject, sStorageObject, sUniqId, options)
+{
+    BxDolUploaderHTML5.call(this, sUploaderObject, sStorageObject, sUniqId, options);
+}
+
+BxDolUploaderSimple.prototype = Object.create(BxDolUploaderHTML5.prototype);
+BxDolUploaderSimple.prototype.constructor = BxDolUploaderSimple;
+
+/**
+ * Crop Image Uploader js class
+ */
+function BxDolUploaderCrop (sUploaderObject, sStorageObject, sUniqId, options) {
+
+    this.init(sUploaderObject, sStorageObject, sUniqId, options);
+
+    this._eForm = null;
+
+    this.initUploader = function (oOptions) {
+
+        var $this = this;
+
+        var aExt = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
+
+        var eCroppie = $("#" + this._sFormContainerId + " .bx-croppie-element").croppie(oOptions);
+
+		$("#" + this._sFormContainerId + ' .bx-crop-rotate').on('click', function(ev) {
+			eCroppie.croppie('rotate', parseInt($(this).data('deg')));
+		});
+        
+        $("#" + this._sFormContainerId + " input[name=f]").on("change", function() {
+            var input = this;            
+
+            if (input.files && input.files[0]) {
+
+                var m = input.files[0].name.match(/\.([A-Za-z0-9]+)$/);
+
+                if (2 != m.length || -1 == aExt.indexOf(m[1].toLowerCase())) {                    
+                    $(input).replaceWith($(input).val('').clone(true));
+                    $this._showError(_t('_sys_uploader_crop_wrong_ext'));
+                    return;
+                }
+
+                $this._clearErrors();
+
+                var reader = new FileReader()
+
+                reader.onload = function(e) {
+                    eCroppie.croppie('bind', {
+                        url: e.target.result
+                    });
+                    $("#" + $this._sFormContainerId + " .bx-croppie-element").addClass('ready');
+                    $("#" + $this._sFormContainerId + " .bx-crop-action").removeClass('bx-btn-disabled');
+                    $("#" + $this._sFormContainerId + " .bx-croppie-element").data('bx-filename', input.files[0].name.replace(/(\.[A-Za-z0-9]+)$/, '.jpg'));
+                }
+                reader.readAsDataURL(input.files[0]);
+            }
+        });
+
+        $("#" + this._sFormContainerId + " .bx-crop-upload").on('click', function(ev) {
+            eCroppie.croppie('result', {
+                type: 'canvas',
+                size: 'original',
+                format: 'jpeg',
+                quality: '0.85',
+            }).then(function(resp) {
+                var fd = new FormData();
+
+                fd.append("f", dataURItoBlob(resp), $("#" + $this._sFormContainerId + " .bx-croppie-element").data('bx-filename'));
+                $.each(oOptions.bx_form, function (sName) {
+                    fd.append(sName, this);
+                });
+                
+                $this.onBeforeUpload(fd);
+
+                $.ajax({
+                    url: sUrlRoot + 'storage_uploader.php',
+                    type: "POST",
+                    processData: false,
+                    contentType: false,
+                    data: fd,
+                    success: function(data) {
+                        eval(data);
+                    },
+                    error: function() {
+                        $this._showError(_t('_sys_uploader_crop_err_upload'));
+                    }
+                })
+
+            });
+        });
+
+        function dataURItoBlob(dataURI) {
+            var split = dataURI.split(','),
+                dataTYPE = split[0].match(/:(.*?);/)[1],
+                binary = atob(split[1]),
+                array = [];
+                
+            for (var i = 0; i < binary.length; i++) 
+                array.push(binary.charCodeAt(i));
+
+            return new Blob([new Uint8Array(array)], {
+                type: dataTYPE
+            });
+        }
+
+        
+    };
+}
+
+BxDolUploaderCrop.prototype = BxDolUploaderBase.prototype;
+
+
+/**
+ * Record Video Uploader js class
+ */
+function BxDolUploaderRecordVideo (sUploaderObject, sStorageObject, sUniqId, options) {
+    this._camera = null;
+    this._blob = null;
+    this._recorder = null;
+    this._camera_type = 'user';
+
+    this._audio_bitrate = undefined !== options.audio_bitrate ? parseInt(options.audio_bitrate) : 128000;
+    this._video_bitrate = undefined !== options.video_bitrate ? parseInt(options.video_bitrate) : 1000000;
+
+    this.init(sUploaderObject, sStorageObject, sUniqId, options);
+
+    this.showUploaderForm = function() {
+        if (typeof MediaRecorder == 'undefined' || !('requestData' in MediaRecorder.prototype)) {
+            bx_alert(_t('_sys_uploader_unsupported_browser'));
+            return;
+        }
+
+        BxDolUploaderBase.prototype.showUploaderForm.call(this);
+    }
+
+    this.onBeforeShowPopup = function() {
+        this._blob = null;
+        this._camera = null;
+        this._recorder = null
+        this._clearErrors();
+
+        $('#bx-upoloader-recording-preview').hide();
+        $('#bx-upoloader-camera-capture').show();
+
+        $('#' + this._sFormContainerId + ' .bx-uploader-record-video-controls').hide();
+    }
+
+    this.switchCamera = function () {
+        this._camera_type = this._camera_type == 'user' ? 'environment' : 'user';
+
+        this.releaseCamera();
+        this.onShowPopup();
+    }
+
+    this.onShowPopup = function () {
+        var $this = this;
+        try {
+            navigator.mediaDevices.getUserMedia({ audio: true, video: {facingMode: this._camera_type} }).then(function(camera) {
+                $this._camera = camera;
+                $this.showCameraCapture();
+
+                $('#' + $this._sFormContainerId + ' .bx-uploader-recording-start').show();
+                $('#' + $this._sFormContainerId + ' .bx-uploader-recording-stop').hide();
+                $('#' + $this._sFormContainerId + ' .bx-uploader-record-video-controls').show();
+
+                navigator.mediaDevices.enumerateDevices().then(function(mediaDevices){
+                    let constraints = navigator.mediaDevices.getSupportedConstraints();
+                    if ($this.getDevicesNum(mediaDevices) > 1 && typeof constraints.facingMode != 'undefined' && constraints.facingMode) {
+                        $('#' + $this._sFormContainerId + ' .bx-record-camera-switch').show();
+                    } else {
+                        $('#' + $this._sFormContainerId + ' .bx-record-camera-switch').hide();
+                    }
+                });
+
+            })
+        } catch (err) {
+            $this._showError(_t('_sys_uploader_camera_capture_failed'));
+        }
+    }
+
+    this.onClickCancel = function () {
+        this.releaseCamera();
+
+        BxDolUploaderBase.prototype.onClickCancel.call(this);
+    }
+
+    this.startRecording = function() {
+        this._recorder = RecordRTC(this._camera, {
+            type: 'video',
+            audioBitsPerSecond: this._audio_bitrate,
+            videoBitsPerSecond: this._video_bitrate,
+            disableLogs: true
+        });
+
+        if (!this._recorder) {
+            this._showError(_t('_sys_uploader_unsupported_browser'));
+            return;
+        }
+
+        $("#" + this._sFormContainerId + " .bx-btn.bx-btn-primary:not(.bx-crop-upload)").hide();
+        $('#' + this._sFormContainerId + ' .bx-uploader-recording-start').hide();
+        $('#' + this._sFormContainerId + ' .bx-uploader-recording-stop').show();
+
+        this._recorder.startRecording();
+
+        this.showCameraCapture();
+    }
+
+    this.stopRecording = function(bSubmitWhenReady) {
+        $('#' + this._sFormContainerId + ' .bx-uploader-recording-start').show();
+        $('#' + this._sFormContainerId + ' .bx-uploader-recording-stop').hide();
+
+        var $this = this;
+        this._recorder.stopRecording(function(){
+            $this._blob = $this._recorder.getBlob();
+            $this.showRecordingPreview();
+
+            $this._recorder.destroy();
+            $this._recorder = null;
+
+            $("#" + $this._sFormContainerId + " .bx-btn.bx-btn-primary:not(.bx-crop-upload)").show();
+            if (bSubmitWhenReady)
+                $this.submitRecording($('#' + $this._sFormContainerId + ' form').get(0));
+        });
+    }
+
+    this.showCameraCapture = function() {
+        var video = $('#bx-upoloader-recording-preview').hide().get(0);
+        if (video.pause !== 'undefined') video.pause();
+        video.removeAttribute('src');
+
+        video = $('#bx-upoloader-camera-capture').show().get(0);
+        video.srcObject = this._camera;
+        video.muted = true;
+        video.volume = 0;
+        $('#' + this._sFormContainerId + ' .bx-record-video-preview .bx-record-video-preview-filesize').html('');
+    }
+
+    this.showRecordingPreview = function() {
+        $('#bx-upoloader-camera-capture').hide();
+        $('#bx-upoloader-recording-preview').show().get(0).src = URL.createObjectURL(this._blob);
+
+        var mbytes = (this._blob.size/1024/1024).toFixed(2);
+        $('#' + this._sFormContainerId + ' .bx-record-video-preview .bx-record-video-preview-filesize').html(mbytes + ' ' + _t('_sys_uploader_record_video_mb'));
+    }
+
+    this.submitRecording = function(form) {
+        if (this._recorder) {
+            this.stopRecording(true);
+            return;
+        }
+
+        this.onBeforeUpload(form);
+
+        var data = new FormData(form);
+        if (this._blob != null) data.append("f[]", this._blob, new Date().toISOString() + '.webm');
+
+        var $this = this;
+        $.ajax({
+            url: $(form).attr('action'),
+            type: "POST",
+            data: data,
+            processData: false,
+            contentType: false,
+            success:function(sErrorMsg, textStatus, jqXHR) {
+                $this.onUploadCompleted(sErrorMsg);
+                if (!sErrorMsg.length) {
+                    $this.releaseCamera();
+                }
+            },
+        });
+    }
+
+    this.releaseCamera = function() {
+        //stop playing recorded file
+        var video = $('#bx-upoloader-recording-preview').get(0);
+        if (video.pause != 'undefined') video.pause();
+        video.removeAttribute('src');
+
+        if (this._recorder) {
+            this._recorder.destroy();
+            this._recorder = null;
+        }
+
+        if (this._camera) {
+            if (typeof this._camera.stop != 'undefined') this._camera.stop();
+            this._camera.getTracks().forEach(function (track) {
+                if (track.readyState == 'live') {
+                    track.stop();
+                }
+            });
+        }
+    }
+
+    this.getDevicesNum = function(mediaDevices) {
+        let count = 0;
+        mediaDevices.forEach(mediaDevice => {
+            if (mediaDevice.kind === 'videoinput') count++;
+        });
+        return count;
+    }
+}
+
+BxDolUploaderRecordVideo.prototype = BxDolUploaderBase.prototype;
+
+
+function BxDolImageTweak (unique_id, action_url, content_id, field, js_object, allow_tweak) {
+    var $this = this;
+    this._sUniqueId = unique_id;
+    this._sActionUrl = action_url;
+    this._sContentId = content_id;
+    this._sField = field;
+    this._sJsObject = js_object;
+    this._bAllowTweak = allow_tweak;
+    this._oContainerButtons = $('.bx-image-edit-buttons-' + unique_id);
+    this._oContainerButtons.removeClass('hidden');
+    $("#bx-form-input-files-" + this._sUniqueId + "-upload-result").bind( "contentchange", function(){ 
+        $this.uploadComplete($(this));
+    });
+    
+    if (this._bAllowTweak){
+        this._oContainerButtons.find('.bx-image-edit-buttons-edit').removeClass('hidden');
+    }
+}
+
+BxDolImageTweak.prototype.showUploaderForm = function(){
+    var $this = this;
+    $('#bx-form-input-files-' + $this._sUniqueId + '-upload-result').html('');
+    eval($this._sJsObject + '.showUploaderForm()');
+}
+    
+BxDolImageTweak.prototype.uploadComplete = function(obj){
+    var $this = this;
+    var sUrl = $this._sActionUrl + "updateImage/" + $this._sField + "/" + $this._sContentId + "/" + obj.html() + "/"; 
+    
+    $this._oContainerButtons.removeClass('bx-image-edit-buttons-no-image');
+    $.post(sUrl, function (aData) {
+        if ($this._bAllowTweak){
+            $(".bx-image-edit-source-" + $this._sUniqueId).css("background-image", "url(" + aData + ")").css('background-position', '');
+            $(".bx-image-edit-source-" + $this._sUniqueId).parents('.bx-base-pofile-cover-image.bx-bpci-holder').removeClass('bx-bpci-holder h-32 lg:h-32').addClass('h-64 lg:h-80')
+        }
+        else{
+            $(".bx-image-edit-source-" + $this._sUniqueId).attr("src", aData).show();
+            $(".bx-image-edit-source-" + $this._sUniqueId).parents('.bx-base-pofile-cover-thumb').find('p.bx-base-pofile-unit-thumb').hide();
+        }
+    });
+}
+
+BxDolImageTweak.prototype.changePosition = function (){
+    var $this = this;
+    $(".bx-image-edit-source-" + $this._sUniqueId).parent().append("<div class='bx-image-edit-move-info'><i class='sys-icon sys-colored arrows-alt '></i>" + _t('_sys_uploader_image_reposition_info') + "</div>");
+    $(".bx-image-edit-source-" + $this._sUniqueId).addClass('bx-image-edit-move').bind('dragover', function(e){
+        $(".bx-image-edit-source-" + $this._sUniqueId).parent().find('.bx-image-edit-move-info').remove();
+        $(".bx-image-edit-source-" + $this._sUniqueId).css('background-position', " 0px " + e.offsetY / $(e.currentTarget).height() * 100 + '%');
+    });
+    with ($this._oContainerButtons) {
+        find('.bx-image-edit-buttons-cancel').removeClass('hidden');
+        find('.bx-image-edit-buttons-save').removeClass('hidden');
+        find('.bx-image-edit-buttons-edit').addClass('hidden');
+        find('.bx-image-edit-buttons-upload').addClass('hidden');
+    }
+}
+
+BxDolImageTweak.prototype.cancelPosition = function (){
+    var $this = this;
+    $(".bx-image-edit-source-" + $this._sUniqueId).parent().find('.bx-image-edit-move-info').remove();
+    $(".bx-image-edit-source-" + $this._sUniqueId).addClass('bx-image-edit-move').unbind('dragover'); 
+    
+    with ($this._oContainerButtons) {
+        find('.bx-image-edit-buttons-cancel').addClass('hidden');
+        find('.bx-image-edit-buttons-save').addClass('hidden');
+        find('.bx-image-edit-buttons-edit').removeClass('hidden');
+        find('.bx-image-edit-buttons-upload').removeClass('hidden');
+    }
+}
+    
+BxDolImageTweak.prototype.savePosition = function (){
+    var $this = this;
+    
+    $(".bx-image-edit-source-" + $this._sUniqueId).parent().find('.bx-image-edit-move-info').remove();
+    $(".bx-image-edit-source-" + $this._sUniqueId).addClass('bx-image-edit-move').unbind('dragover'); 
+    $(".bx-image-edit-source-" + $this._sUniqueId).removeClass('bx-image-edit-move')
+    
+    aPos = $(".bx-image-edit-source-" + $this._sUniqueId).css('background-position').split(' ');
+    $.post($this._sActionUrl + 'updateImagePosition/' + $this._sContentId + '/' + $this._sField + '/' + aPos[0].replace('%','').replace('px','') + '/' + aPos[1].replace('%','').replace('px','') + '/', function () {
+        with ($this._oContainerButtons) {
+            find('.bx-image-edit-buttons-cancel').addClass('hidden');
+            find('.bx-image-edit-buttons-save').addClass('hidden');
+            find('.bx-image-edit-buttons-edit').removeClass('hidden');
+            find('.bx-image-edit-buttons-upload').removeClass('hidden');
+        }
+    });
+}
+
+/** @} */

+ 12 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/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', '13.0.0-RC6'); ///< system files version, for DB version @see bx_get_ver, DB and files version must match
+
+/** @} */

+ 146 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/modules/base/general/classes/BxBaseModGeneralCmtsSearchResult.php

@@ -0,0 +1,146 @@
+<?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
+ *
+ * @{
+ */
+
+class BxBaseModGeneralCmtsSearchResult extends BxBaseModGeneralSearchResult
+{
+    protected $sModule;
+    protected $sModuleObjectComments;
+    protected $aCommentsAddons;
+
+    function __construct($sMode = '', $aParams = array())
+    {
+        parent::__construct($sMode, $aParams);
+
+        $this->oModule = $this->getMain();
+        $this->sModuleObjectComments = $this->oModule->_oConfig->CNF['OBJECT_COMMENTS'];
+        $this->aCommentsAddons = array();        
+
+        $this->aCurrent = array(
+            'name' => $this->oModule->_oConfig->getName() . '_cmts',
+            'module_name' => $this->oModule->_oConfig->getName(),
+            'object_metatags' => 'sys_cmts',
+            'title' => '',
+            'table' => $this->oModule->_oConfig->getDbPrefix() . 'cmts',
+            'ownFields' => array('cmt_id', 'cmt_object_id', 'cmt_author_id', 'cmt_text', 'cmt_time'),
+            'searchFields' => array('cmt_text'),
+            'restriction' => array(
+                'author' => array('value' => '', 'field' => 'cmt_author_id', 'operator' => '='),
+            ),
+            'paginate' => array('start' => 0),
+            'sorting' => 'last',
+            'ident' => 'cmt_id'
+        );
+
+        $this->_joinTableUniqueIds();
+
+        $this->sBrowseUrl = BX_DOL_SEARCH_KEYWORD_PAGE;
+    }
+
+    function getMain()
+    {
+        return BxDolModule::getInstance($this->sModule);
+    }
+
+    function displaySearchUnit($aData)
+    {
+        $oCmts = BxDolCmts::getObjectInstance($this->sModuleObjectComments, $aData['object_id']);
+        $oCmts->addCssJs();
+
+        if($this->_bLiveSearch) {
+            $CNF = &$this->oModule->_oConfig->CNF;
+
+            return $oCmts->getCommentLiveSearch($aData['id'], array(
+                'txt_sample_single' => isset($CNF['T']['txt_sample_comment_single']) ? $CNF['T']['txt_sample_comment_single'] : ''
+            ));
+        }
+        else {
+            if(!isset($this->aCommentsAddons[$aData['object_id']]))
+                $this->aCommentsAddons[$aData['object_id']] = '';
+
+            return $oCmts->getCommentSearch(isset($aData['cmt_id']) ? $aData['cmt_id'] : $aData['id'], $this->aCommentsAddons[$aData['object_id']]);
+        }
+    }
+
+    function displayResultBlock ()
+    {
+        $sCode = '<ul class="cmts">' . parent::displayResultBlock() . '</ul>';
+
+        if(!empty($this->aCommentsAddons) && is_array($this->aCommentsAddons))
+            foreach($this->aCommentsAddons as $sCommentsAddon)
+                $sCode .= $sCommentsAddon;
+
+        return $sCode;
+    }
+
+    function getAlterOrder()
+    {
+        if($this->aCurrent['sorting'] == 'last')
+            return array(
+            	'order' => " ORDER BY `" . $this->aCurrent['table'] . "`.`cmt_time` DESC"
+            );
+
+        return array();
+    }
+
+    function decodeData ($a)
+    {
+        $oContentInfo = $this->getContentInfoObject();
+
+        foreach($a as $i => $r) {
+            if(isset($r['author']))
+                $a[$i]['author_data'] = BxDolProfile::getData($r['author']);
+
+            $a[$i]['url'] = $this->decodeDataUrl($oContentInfo, $r);
+        }
+
+        return $a;
+    }
+
+    function _getPseud ()
+    {
+        return array(
+            'id' => 'cmt_id',
+        	'object_id' => 'cmt_object_id',
+            'author' => 'cmt_author_id',
+            'text' => 'cmt_text',
+            'added' => 'cmt_time'
+        );
+    }
+
+    protected function _joinTableUniqueIds()
+    {
+        $aCommentsSystem = BxDolCmtsQuery::getSystemBy(['type' => 'name', 'name' => $this->sModuleObjectComments]);
+        if(empty($aCommentsSystem) || !is_array($aCommentsSystem))
+            return;
+
+        $sTableMain = $this->aCurrent['table'];
+        $sTableUniqueIds = 'sys_cmts_ids';
+        $sTableUniqueIdsAlias = 'tci';
+
+        $this->aCurrent['restriction']['status_admin'] = [
+            'value' => 'active', 
+            'field' => 'status_admin', 
+            'operator' => '=', 
+            'table' => $sTableUniqueIdsAlias
+        ];
+
+        $this->aCurrent['join']['unique_ids'] = [
+            'type' => 'INNER',
+            'table' => $sTableUniqueIds,
+            'table_alias' => $sTableUniqueIdsAlias,
+            'mainField' => 'cmt_id',
+            'on_sql' => "`{$sTableMain}`.`cmt_id`=`{$sTableUniqueIdsAlias}`.`cmt_id` AND `{$sTableUniqueIdsAlias}`.`system_id`='{$aCommentsSystem['ID']}' ",
+            'joinFields' => array('status_admin'),
+        ];
+    }
+}
+
+/** @} */

+ 82 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/modules/base/groups/classes/BxBaseModGroupsConnectionFans.php

@@ -0,0 +1,82 @@
+<?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 BxBaseModGroupsConnectionFans extends BxTemplConnection
+{
+    protected $_sModule;
+    protected $_oModule;
+
+    public function __construct($aObject)
+    {
+        parent::__construct($aObject);
+
+        $this->_oModule = BxDolModule::getInstance($this->_sModule);
+    }
+
+    public function getActionTitle ($sAction, $iInitiatorId, $iContentId, $bFlip = false)
+    {
+        $aResult = [];
+        if($this->isConnectedNotMutual($iInitiatorId, $iContentId))
+            $aResult = [
+                'add' => '',
+                'remove' => _t('_sys_menu_item_title_sm_leave_cancel'),
+            ];
+        else if($this->isConnectedNotMutual($iContentId, $iInitiatorId))
+            $aResult = [
+                'add' => _t('_sys_menu_item_title_sm_join_confirm'),
+                'remove' => _t('_sys_menu_item_title_sm_leave_reject'),
+            ];
+        else if($this->isConnected($iInitiatorId, $iContentId, true))
+            $aResult = [
+                'add' => '',
+                'remove' => _t('_sys_menu_item_title_sm_leave'),
+            ];
+        else
+            $aResult = [
+                'add' => _t('_sys_menu_item_title_sm_join'),
+                'remove' => '',
+            ];
+
+        $aFlip = ['add' => 'remove', 'remove' => 'add'];
+        if($bFlip)
+            $sAction = $aFlip[$sAction];
+
+        return !empty($aResult[$sAction]) ? _t($aResult[$sAction]) : '';
+    }
+
+    protected function _checkAllowedConnectInitiator ($oInitiator, $isPerformAction = false)
+    {
+        if(!bx_srv($oInitiator->getModule(), 'act_as_profile'))
+            return CHECK_ACTION_RESULT_ALLOWED;
+
+        return parent::_checkAllowedConnectInitiator($oInitiator, $isPerformAction);
+    }
+
+    public function _checkAllowedConnectContent ($oContent)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if(bx_srv($oContent->getModule(), 'act_as_profile'))
+            return CHECK_ACTION_RESULT_ALLOWED;
+
+        if(!empty($CNF['OBJECT_PRIVACY_VIEW']) && ($oPrivacy = BxDolPrivacy::getObjectInstance($CNF['OBJECT_PRIVACY_VIEW'])) !== false) {
+            $iContentId = $oContent->getContentId();
+            $aContentInfo = $this->_oModule->_oDb->getContentInfoById($iContentId);
+
+            if(in_array($aContentInfo[$CNF['FIELD_ALLOW_VIEW_TO']], array_merge($oPrivacy->getPartiallyVisiblePrivacyGroups(), ['s'])))
+                return CHECK_ACTION_RESULT_ALLOWED;
+        }
+
+        return parent::_checkAllowedConnectContent($oContent);
+    }
+}
+
+/** @} */

+ 167 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/modules/base/groups/classes/BxBaseModGroupsMenuSnippetMeta.php

@@ -0,0 +1,167 @@
+<?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
+ *
+ * @{
+ */
+
+class BxBaseModGroupsMenuSnippetMeta extends BxBaseModProfileMenuSnippetMeta
+{
+    public function __construct($aObject, $oTemplate = false)
+    {
+        parent::__construct($aObject, $oTemplate);
+
+        $CNF = &$this->_oModule->_oConfig->CNF;
+        if (isset($CNF['OBJECT_CONNECTIONS'])){
+            $this->_aConnectionToFunctionCheck[$CNF['OBJECT_CONNECTIONS']] = array(
+                'add' => 'checkAllowedFanAdd', 
+                'remove' => 'checkAllowedFanRemove'
+            );
+
+            $this->_aConnectionToFunctionTitle[$CNF['OBJECT_CONNECTIONS']] = '_getMenuItemConnectionsTitle';
+        }
+    }
+
+    protected function _getMenuItemJoinPaid($aItem)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if(!isLogged() || $this->_oModule->checkAllowedFanAdd($this->_aContentInfo) !== CHECK_ACTION_RESULT_ALLOWED)
+            return false;
+
+        $oConnection = BxDolConnection::getObjectInstance($CNF['OBJECT_CONNECTIONS']);
+        if(!$oConnection)
+            return false;
+        
+        $sTitle = $this->_getMenuItemConnectionsTitle('add', $oConnection);
+        if(empty($sTitle))
+            return false;
+
+        return [
+            $this->getUnitMetaItemButton(_t('_bx_groups_menu_item_title_pay_and_join'), [
+                'href' => bx_absolute_url(BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_JOIN_ENTRY'], [
+                    'profile_id' => $this->_oContentProfile->id()
+                ]))
+            ]),
+            'bx-menu-item-button'
+        ];
+    }
+
+    protected function _getMenuItemJoin($aItem)
+    {
+        if (isset($this->_oModule->_oConfig->CNF['OBJECT_CONNECTIONS']))
+            return $this->_getMenuItemConnection($this->_oModule->_oConfig->CNF['OBJECT_CONNECTIONS'], 'add', $aItem);
+
+        return false;
+    }
+
+    protected function _getMenuItemLeave($aItem)
+    {
+        return $this->_getMenuItemConnection($this->_oModule->_oConfig->CNF['OBJECT_CONNECTIONS'], 'remove', $aItem);
+    }
+
+    protected function _getMenuItemPrivacy($aItem)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $sField = 'FIELD_ALLOW_VIEW_TO';
+        if(empty($CNF[$sField]) || empty($this->_aContentInfo[$CNF[$sField]]))
+            return false;
+
+        if($this->_aContentInfo[$CNF[$sField]] == BX_DOL_PG_ALL)
+            return false;
+
+        return $this->getUnitMetaItemText(_t($CNF['T']['txt_private_group']));
+    }
+
+    protected function _getMenuItemMembers($aItem)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if(!$this->_bContentPublic || !$this->_oContentProfile || empty($CNF['OBJECT_CONNECTIONS']))
+            return false;
+
+        $oConnection = BxDolConnection::getObjectInstance($CNF['OBJECT_CONNECTIONS']);
+        if(!$oConnection)
+            return false;
+
+        $iMembers = $oConnection->getConnectedInitiatorsCount($this->_oContentProfile->id(), true);
+        if(!$iMembers && !$this->_bShowZeros)
+            return false;
+
+        if($this->_bIsApi)
+            return $this->_getMenuItemAPI($aItem, 'text', [
+                'title' => _t('_sys_menu_item_title_sm_members', $iMembers)
+            ]);
+
+        $sIcon = BxTemplFunctions::getInstanceWithTemplate($this->_oTemplate)->getIconAsHtml(!empty($aItem['icon']) ? $aItem['icon'] : '');
+
+        return $this->getUnitMetaItemCustom($oConnection->getCounter($this->_oContentProfile->id(), true, [
+            'caption' => '_sys_menu_item_title_sm_members', 
+            'custom_icon' => $sIcon
+        ], BX_CONNECTIONS_CONTENT_TYPE_INITIATORS));
+    }
+
+    protected function _getMenuItemConnectionsTitle($sAction, &$oConnection)
+    {
+        $iProfile = bx_get_logged_profile_id();
+        $iContentProfile = $this->_oContentProfile->id();
+
+        $aResult = array();
+        if($oConnection->isConnectedNotMutual($iProfile, $iContentProfile))
+            $aResult = array(
+                'add' => '',
+                'remove' => _t('_sys_menu_item_title_sm_leave_cancel'),
+            );
+        else if($oConnection->isConnectedNotMutual($iContentProfile, $iProfile))
+            $aResult = array(
+                'add' => _t('_sys_menu_item_title_sm_join_confirm'),
+                'remove' => _t('_sys_menu_item_title_sm_leave_reject'),
+            );
+        else if($oConnection->isConnected($iProfile, $iContentProfile, true))
+            $aResult = array(
+                'add' => '',
+                'remove' => _t('_sys_menu_item_title_sm_leave'),
+            );
+        else
+            $aResult = array(
+                'add' => _t('_sys_menu_item_title_sm_join'),
+                'remove' => '',
+            );
+
+        return !empty($sAction) && isset($aResult[$sAction]) ? $aResult[$sAction] : $aResult;
+    }
+
+    protected function _getMenuItemCountry($aItem)
+    {
+        return $this->_getMenuItemLocation($aItem, true);
+    }
+
+    protected function _getMenuItemCountryCity($aItem)
+    {
+        return $this->_getMenuItemLocation($aItem, false);
+    }
+
+    protected function _getMenuItemLocation($aItem, $bCountryOnly = false)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if(!$this->_bContentPublic || !$this->_oContentProfile || empty($CNF['OBJECT_CONNECTIONS']))
+            return false;
+
+        $oMeta = BxDolMetatags::getObjectInstance($CNF['OBJECT_METATAGS']);
+        if(!$oMeta)
+            return false;
+
+        if (!($sLocation = $oMeta->locationsString($this->_iContentId, false, $bCountryOnly ? array('country_only' => 1) : array('country_city_only' => 1))))
+            return false;
+
+        return $this->getUnitMetaItemText($sLocation);
+    }
+}
+
+/** @} */

+ 286 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/modules/base/profile/classes/BxBaseModProfileSearchResult.php

@@ -0,0 +1,286 @@
+<?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 BxBaseModProfileSearchResult extends BxBaseModGeneralSearchResult
+{      
+    protected $bRecommendedView = false;
+    
+    public function __construct($sMode = '', $aParams = array())
+    {
+        parent::__construct($sMode, $aParams);
+
+        $this->aUnitViews = [
+            'unit' => 'unit_wo_cover.html', 
+            'unit_wo_info' => 'unit_wo_info.html', 
+            'gallery' => 'unit_with_cover.html', 
+            'showcase' => 'unit_with_cover_showcase.html', 
+            'showcase_wo_info' => 'unit_wo_info_showcase.html', 
+            'simple' => 'unit_wo_links.html'
+        ];
+        if (!empty($aParams['unit_views']) && is_array($aParams['unit_views']))
+            $this->aUnitViews = array_merge ($this->aUnitViews, $aParams['unit_views']);
+
+        if (!empty($aParams['unit_view']))
+            $this->sUnitViewDefault = $aParams['unit_view'];
+        
+        $this->sUnitTemplate = $this->aUnitViews[$this->sUnitViewDefault];
+        $this->sCenterContentUnitSelector = '.bx-base-pofile-unit';
+        $this->addContainerClass (array(
+            'bx-base-pofile-units-wrapper', 
+            'bx-base-puw-' . str_replace('_' ,  '-', $this->sUnitViewDefault),
+            'bx-def-margin-sec-neg'
+        ));
+
+        if(in_array($this->sUnitTemplate, ['unit_wo_cover.html', 'unit_wo_info.html', 'unit_wo_info_showcase.html']))
+            $this->removeContainerClass('bx-def-margin-sec-neg');
+
+        if(in_array($this->sUnitTemplate, ['unit_with_cover_showcase.html', 'unit_wo_info_showcase.html'])) {
+            $this->bShowcaseView = true;
+
+            if($this->sUnitTemplate == 'unit_wo_info_showcase.html')
+                $this->addContainerAttribute(array('bx-sc-group-cells' => 3));
+        }
+
+        if ($sMode == 'recommended')
+            $this->bRecommendedView=true;
+    }
+
+    function getRssUnitImage (&$a, $sField)
+    {
+        $CNF = &$this->oModule->_oConfig->CNF;
+
+        if(empty($sField) || empty($a[$sField]))
+            return '';
+
+        $sTranscoder = '';
+        if(!empty($CNF['OBJECT_IMAGES_TRANSCODER_PICTURE']))
+            $sTranscoder = $CNF['OBJECT_IMAGES_TRANSCODER_PICTURE'];
+        else if(!empty($CNF['OBJECT_IMAGES_TRANSCODER_GALLERY']))
+            $sTranscoder = $CNF['OBJECT_IMAGES_TRANSCODER_GALLERY'];
+        else 
+            return '';
+
+        $oTranscoder = BxDolTranscoderImage::getObjectInstance($sTranscoder);
+        if(!$oTranscoder)
+            return '';
+
+        return $oTranscoder->getFileUrl($a[$sField]);      
+    }
+
+    protected function _setAccountConfirmationConditions($aParams)
+    {
+        $sConfirmationType = getParam('sys_account_confirmation_type');
+        $bHideUnconfirmed = getParam('sys_account_hide_unconfirmed_accounts') == 'on';
+        if($sConfirmationType == BX_ACCOUNT_CONFIRMATION_NONE || !$bHideUnconfirmed) 
+            return;
+
+        $this->aCurrent['restriction'] = array_merge($this->aCurrent['restriction'], array(
+            'account_confirmed_email' => array('value' => '', 'field' => 'email_confirmed', 'operator' => '=', 'table' => 'sys_accounts'),
+            'account_confirmed_phone' => array('value' => '', 'field' => 'phone_confirmed', 'operator' => '=', 'table' => 'sys_accounts'),
+            'account_confirmed_email_and_phone' => array('value' => '', 'field' => 'phone_confirmed` | `sys_accounts`.`email_confirmed', 'operator' => '=', 'table' => 'sys_accounts'),
+        ));
+
+        switch($sConfirmationType) {
+            case BX_ACCOUNT_CONFIRMATION_EMAIL:
+                $this->aCurrent['restriction']['account_confirmed_email']['value'] = 1;
+                break;
+
+            case BX_ACCOUNT_CONFIRMATION_PHONE:
+                $this->aCurrent['restriction']['account_confirmed_phone']['value'] = 1;
+                break;
+
+            case BX_ACCOUNT_CONFIRMATION_EMAIL_PHONE:
+                $this->aCurrent['restriction']['account_confirmed_email']['value'] = 1;
+                $this->aCurrent['restriction']['account_confirmed_phone']['value'] = 1;
+                break;
+                
+            case BX_ACCOUNT_CONFIRMATION_EMAIL_OR_PHONE:
+                $this->aCurrent['restriction']['account_confirmed_email_and_phone']['value'] = 1;
+                break;
+        }
+    }
+
+    protected function _setConnectionsConditions ($aParams)
+    {
+        $oConnection = isset($aParams['object']) ? BxDolConnection::getObjectInstance($aParams['object']) : false;
+        if (!$oConnection || !isset($aParams['profile']) || !(int)$aParams['profile'])
+            return false;
+
+        $sContentType = isset($aParams['type']) ? $aParams['type'] : BX_CONNECTIONS_CONTENT_TYPE_CONTENT;
+        $isMutual = isset($aParams['mutual']) ? $aParams['mutual'] : false;
+        $a = $oConnection->getConnectionsAsCondition ($sContentType, 'id', (int)$aParams['profile'], (int)$aParams['profile2'], $isMutual);
+
+        $this->aCurrent['restriction'] = array_merge($this->aCurrent['restriction'], $a['restriction']);
+        $this->aCurrent['join'] = array_merge($this->aCurrent['join'], $a['join']);
+
+        return true;
+    }
+    
+    protected function _setFamiliarConnectionsConditions ($aParams)
+    {
+        $oConnection = isset($aParams['object']) ? BxDolConnection::getObjectInstance($aParams['object']) : false;
+        if (!$oConnection || !isset($aParams['profile']) || !(int)$aParams['profile'])
+            return false;
+
+        $iProfile = (int)$aParams['profile'];
+        $sContentType = isset($aParams['type']) ? $aParams['type'] : BX_CONNECTIONS_CONTENT_TYPE_CONTENT;
+        $isMutual = isset($aParams['mutual']) ? $aParams['mutual'] : false;
+
+        $aIds = $oConnection->getConnectionsAsArray($sContentType, $iProfile, false, $isMutual);
+
+        $aCondition = $oConnection->getConnectionsAsCondition ($sContentType, 'id', $aIds, false, $isMutual);
+
+        $this->aCurrent['distinct'] = true;
+        $this->aCurrent['join'] = array_merge($this->aCurrent['join'], $aCondition['join']);
+        $this->aCurrent['restriction'] = array_merge($this->aCurrent['restriction'], $aCondition['restriction'], [
+            'connected' => [
+                'value' => array_merge([$iProfile], $aIds),
+                'field' => 'id',
+                'operator' => 'not in',
+            ]
+        ]);
+
+        return true;
+    }
+    
+    protected function _setConditionsForRecommended ()
+    {
+        $oConnection = BxDolConnection::getObjectInstance('sys_profiles_subscriptions'); 
+        $a = $oConnection->getConnectedContentAsSQLPartsExt ($this->aCurrent['table'], '', '');
+        
+        $aTmp = array(
+            'recommended' => array(
+            'type' => 'LEFT',
+            'table' =>  $a['join']['table'],
+            'mainField' => 'id` AND ' . bx_get_logged_profile_id() . ' = `' . $a['join']['table'] . '`.`initiator',
+            'onField' => 'content',
+            'joinFields' => array(),
+            )
+        );
+        $this->aCurrent['join'] = array_merge($this->aCurrent['join'], $aTmp);
+        
+        if (isset($this->aCurrent['restriction_sql'])) 
+            $this->aCurrent['restriction_sql'] .= ' AND `' . $a['join']['table'] . '`.`initiator` IS NULL AND ' . bx_get_logged_profile_id() . ' <> `sys_profiles`.`id` ' ;
+        else
+            $this->aCurrent['restriction_sql'] = ' AND `' . $a['join']['table'] . '`.`initiator` IS NULL AND ' . bx_get_logged_profile_id() . ' <> `sys_profiles`.`id` ' ;
+    }
+
+	protected function _setAclConditions ($aParams)
+    {
+        $oAcl = BxDolAcl::getInstance();
+        if(!$oAcl || empty($aParams['level']))
+            return false;
+
+		if(!is_array($aParams['level']))
+			$aParams['level'] = array($aParams['level']);
+
+		$this->aCurrent['title'] = array();
+		foreach($aParams['level'] as $iLevelId) {
+			$aInfo = $oAcl->getMembershipInfo($iLevelId);
+			if(empty($aInfo) || !is_array($aInfo))
+				continue;
+
+			$this->aCurrent['title'][] = _t($aInfo['name']);
+		}
+
+        $aCondition = $oAcl->getContentByLevelAsCondition('id', $aParams['level']);        
+        $this->aCurrent['restriction_sql'] = (!empty($this->aCurrent['restriction_sql']) ? $this->aCurrent['restriction_sql'] : '') . $aCondition['restriction_sql'];
+        $this->aCurrent['restriction'] = array_merge($this->aCurrent['restriction'], $aCondition['restriction']);
+        $this->aCurrent['join'] = array_merge($this->aCurrent['join'], $aCondition['join']);
+
+        return true;
+    }
+
+    protected function _setFavoriteConditions($sMode, $aParams, &$oProfileAuthor)
+    {
+        $CNF = &$this->oModule->_oConfig->CNF;
+
+        $oProfileAuthor = BxDolProfile::getInstance((int)$aParams['user']);
+        if(!$oProfileAuthor) 
+            return false;
+
+        $iListId = 0;
+        if(isset($aParams['list_id']))
+            $iListId = (int)$aParams['list_id'];
+
+        $iProfileAuthor = $oProfileAuthor->id();
+        $oFavorite = $this->oModule->getObjectFavorite();
+        if(!$oFavorite->isPublic() && $iProfileAuthor != bx_get_logged_profile_id()) 
+            return false;
+
+        $aConditions = $oFavorite->getConditionsTrack($CNF['TABLE_ENTRIES'], 'id', $iProfileAuthor, $iListId);
+        if(!empty($aConditions) && is_array($aConditions)) {
+            if(empty($this->aCurrent['restriction']) || !is_array($this->aCurrent['restriction']))
+                $this->aCurrent['restriction'] = array();
+            $this->aCurrent['restriction'] = array_merge($this->aCurrent['restriction'], $aConditions['restriction']);
+
+            if(empty($this->aCurrent['join']) || !is_array($this->aCurrent['join']))
+                $this->aCurrent['join'] = array();
+            $this->aCurrent['join'] = array_merge($this->aCurrent['join'], $aConditions['join']);
+        }
+
+        $this->sBrowseUrl = 'page.php?i=' . $CNF['URI_VIEW_FAVORITES'] . '&profile_id={profile_id}';
+        $this->aCurrent['title'] = _t($CNF['T']['txt_browse_favorites']);
+        $this->aCurrent['rss']['link'] = 'modules/?r=' . $this->oModule->_oConfig->getUri() . '/rss/' . $sMode . '/' . $iProfileAuthor;
+
+        return true;
+    }
+    
+    function decodeData ($a)
+    {
+        $CNF = $this->oModule->_oConfig->CNF;
+
+        $oPrivacy = BxDolPrivacy::getObjectInstance($CNF['OBJECT_PRIVACY_VIEW']);
+        $bPrivacy = $oPrivacy !== false;
+
+        $oMetaMenu = !empty($CNF['OBJECT_MENU_SNIPPET_META']) ? BxDolMenu::getObjectInstance($CNF['OBJECT_MENU_SNIPPET_META'], $this->oModule->_oTemplate) : false;
+        $bMetaMenu = $oMetaMenu !== false;
+
+        $oContentInfo = $this->getContentInfoObject();
+
+        foreach ($a as $i => $r) {
+            $a[$i] = array_merge($a[$i], [
+                'url' => $this->decodeDataUrl($oContentInfo, $r),
+                'image' => $oContentInfo->getContentThumb($r['id']),
+                'cover' => $oContentInfo->getContentCover($r['id']),
+                'summary_plain' => $this->decodeDataSummaryPlain($oContentInfo, $r)
+            ]);
+
+            if($bMetaMenu) {
+                $bPublic = !$bPrivacy || $oPrivacy->check($r[$CNF['FIELD_ID']]) || $oPrivacy->isPartiallyVisible($r[$CNF['FIELD_ALLOW_VIEW_TO']]);
+
+                $oMetaMenu->setContentId($r['id']);
+                $oMetaMenu->setContentPublic($bPublic);
+                $a[$i]['meta'] = $oMetaMenu->getCodeAPI();
+            }
+        }
+
+        return $a;
+    }
+	
+	function getItemPerPageInShowCase ()
+    {
+        $iPerPageInShowCase = parent::getItemPerPageInShowCase();
+        $CNF = &$this->oModule->_oConfig->CNF;
+		if ($this->bRecommendedView && isset($CNF['PARAM_PER_PAGE_BROWSE_RECOMMENDED'])){
+			$iPerPageInShowCase =  getParam($CNF['PARAM_PER_PAGE_BROWSE_RECOMMENDED']);
+        }
+        return $iPerPageInShowCase;
+    }
+    
+    function displayResultBlock ()
+    {
+		$this->oModule->_oTemplate->addJs(array('modules/base/profile/js/|searchresult.js'));
+		return parent::displayResultBlock ();
+	}
+}
+
+/** @} */

+ 278 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/modules/base/text/classes/BxBaseModTextSearchResult.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    BaseText Base classes for text modules
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+class BxBaseModTextSearchResult extends BxBaseModGeneralSearchResult
+{
+    protected $sUnitViewParamName = 'unit_view';
+
+    function __construct($sMode = '', $aParams = array())
+    {
+        parent::__construct($sMode, $aParams);
+        $this->aUnitViews = array('extended' => 'unit.html', 'gallery' => 'unit_gallery.html', 'full' => 'unit_full.html', 'showcase' => 'unit_showcase.html');
+		if (!empty($aParams['unit_views']))
+            $this->aUnitViews = $aParams['unit_views'];
+        if (!empty($aParams['unit_view']))
+            $this->sUnitViewDefault = $aParams['unit_view'];
+        $this->aGetParams = array($this->sUnitViewParamName);
+        $this->sUnitTemplate = $this->aUnitViews[$this->sUnitViewDefault];
+        if (isset($this->aUnitViews[bx_get($this->sUnitViewParamName)]))
+            $this->sUnitTemplate = $this->aUnitViews[bx_get($this->sUnitViewParamName)];
+		$this->addContainerClass ('bx-def-margin-bottom-neg');
+        if ('unit_gallery.html' == $this->sUnitTemplate){
+			$this->removeContainerClass ('bx-def-margin-bottom-neg');		
+            $this->addContainerClass (array('bx-base-text-unit-gallery-wrapper', 'bx-def-margin-sec-neg'));
+		}
+		if ('unit_showcase.html' == $this->sUnitTemplate){
+			$this->bShowcaseView = true;
+		}
+    }
+
+    protected function checkRestrictionsForContext($sMode, $aParams, $oProfileAuthor){
+        if(bx_get('context_id')){
+            $aParams['context'] = bx_get('context_id');
+            $oProfileAuthor = null;
+            $this->_updateCurrentForContext($sMode, $aParams, $oProfileAuthor);
+        }
+    }
+    
+    function getAlterOrder()
+    {
+        $CNF = &$this->oModule->_oConfig->CNF;
+
+        $sTable = $this->aCurrent['table'];
+
+        $aSql = array();
+        switch ($this->aCurrent['sorting']) {
+            case 'last':
+                $aSql['order'] = ' ORDER BY `' . $sTable . '`.`' . $CNF['FIELD_ADDED'] . '` DESC';
+                break;
+
+            case 'updated':
+                $aSql['order'] = ' ORDER BY `' . $sTable . '`.`' . $CNF['FIELD_CHANGED'] . '` DESC';
+                break;
+
+            case 'featured':
+                $aSql['order'] = ' ORDER BY `' . $sTable . '`.`featured` DESC';
+                break;
+
+            case 'popular':
+                $aSql['order'] = ' ORDER BY `' . $sTable . '`.`' . $CNF['FIELD_VIEWS'] . '` DESC';
+                break;
+
+            case 'top':
+                $aSql['order'] = '';
+
+                $aPartsUp = $aPartsDown = array(0);
+                if(!empty($CNF['OBJECT_VOTES']) && ($oVote = BxDolVote::getObjectInstance($CNF['OBJECT_VOTES'], 0, false)) !== false && $oVote->isEnabled()) {
+                    $aVote = $oVote->getSystemInfo();
+                    if(!empty($aVote['trigger_table']) && !empty($aVote['trigger_field_count']))
+                        $aPartsUp[] = '`' . $aVote['trigger_table'] . '`.`' . $aVote['trigger_field_count'] . '`';
+                }
+
+                if(!empty($CNF['OBJECT_REACTIONS']) && ($oReaction = BxDolVote::getObjectInstance($CNF['OBJECT_REACTIONS'], 0, false)) !== false && $oReaction->isEnabled()) {
+                    $aReaction = $oReaction->getSystemInfo();
+                    if(!empty($aReaction['trigger_table']) && !empty($aReaction['trigger_field_count']))
+                        $aPartsUp[] = '`' . $aReaction['trigger_table'] . '`.`' . $aReaction['trigger_field_count'] . '`';
+                }
+
+                if(!empty($CNF['OBJECT_SCORES']) && ($oScore = BxDolScore::getObjectInstance($CNF['OBJECT_SCORES'], 0, false)) !== false && $oScore->isEnabled()) {
+                    $aScore = $oScore->getSystemInfo();
+                    if(!empty($aScore['trigger_table']) && !empty($aScore['trigger_field_cup']) && !empty($aScore['trigger_field_cdown'])) {
+                        $aPartsUp[] = '`' . $aScore['trigger_table'] . '`.`' . $aScore['trigger_field_cup'] . '`';
+                        $aPartsDown[] = '`' . $aScore['trigger_table'] . '`.`' . $aScore['trigger_field_cdown'] . '`';
+                    }
+                }
+
+                if(empty($aPartsUp) && empty($aPartsDown))
+                    break;
+
+                $aSql['order'] = ' ORDER BY ' . pow(10, 8) . ' * ((' . implode(' + ', $aPartsUp) . ') - (' . implode(' + ', $aPartsDown) . ')) / (UNIX_TIMESTAMP() - `' . $sTable . '`.`' . $CNF['FIELD_ADDED'] . '`) DESC';
+                break;
+        }
+
+        return $aSql;
+    }
+
+    function getRssUnitImage (&$a, $sField)
+    {
+        $CNF = &$this->oModule->_oConfig->CNF;
+
+        if(empty($sField) || empty($a[$sField]) || empty($CNF['OBJECT_IMAGES_TRANSCODER_GALLERY']))
+            return '';
+
+        $oTranscoder = BxDolTranscoderImage::getObjectInstance($CNF['OBJECT_IMAGES_TRANSCODER_GALLERY']);
+        if(!$oTranscoder)
+            return '';
+
+        return $oTranscoder->getFileUrl($a[$sField]);      
+    }
+
+    protected function processReplaceableMarkers($oProfileAuthor) 
+    {
+        if ($oProfileAuthor) {
+            $this->addMarkers($oProfileAuthor->getInfo()); // profile info is replaceable
+            $this->addMarkers(array('profile_id' => $oProfileAuthor->id())); // profile id is replaceable
+            $this->addMarkers(array('display_name' => $oProfileAuthor->getDisplayName())); // profile display name is replaceable
+        }
+
+        $this->sBrowseUrl = $this->_replaceMarkers($this->sBrowseUrl);
+        $this->aCurrent['title'] = $this->_replaceMarkers($this->aCurrent['title']);
+    }
+
+    protected function getCurrentOnclick($aAdditionalParams = array(), $bReplacePagesParams = true) 
+    {
+        // always add UnitView as additional param
+        $sUnitView = bx_process_input(bx_get($this->sUnitViewParamName));
+        if ($sUnitView && isset($this->aUnitViews[$sUnitView]))
+            $aAdditionalParams = array_merge(array($this->sUnitViewParamName => $sUnitView), $aAdditionalParams);
+
+        return parent::getCurrentOnclick($aAdditionalParams, $bReplacePagesParams);
+    }
+
+    protected function _updateCurrentForAuthor($sMode, $aParams, &$oProfileAuthor)
+    {
+        $CNF = &$this->oModule->_oConfig->CNF;
+
+        $oProfileAuthor = BxDolProfile::getInstance((int)$aParams['author']);
+        if (!$oProfileAuthor) 
+            return false;
+
+        $iProfileAuthor = $oProfileAuthor->id();
+        if (bx_get_logged_profile_id() == $iProfileAuthor || $this->oModule->_isModerator()) {
+            // for real owner and for moderators show anonymous posts
+            $this->aCurrent['restriction']['author']['operator'] = 'in';
+            $this->aCurrent['restriction']['author']['value'] = array($iProfileAuthor, -$iProfileAuthor);
+        } 
+        else {
+           $this->aCurrent['restriction']['author']['value'] = $iProfileAuthor;
+        }
+
+        if(!empty($aParams['except']))
+        	$this->aCurrent['restriction']['except']['value'] = is_array($aParams['except']) ? $aParams['except'] : array($aParams['except']); 
+
+        if(!empty($aParams['per_page']))
+        	$this->aCurrent['paginate']['perPage'] = is_numeric($aParams['per_page']) ? (int)$aParams['per_page'] : (int)getParam($aParams['per_page']);
+
+        $this->sBrowseUrl = 'page.php?i=' . $CNF['URI_AUTHOR_ENTRIES'] . '&profile_id={profile_id}';
+        $this->aCurrent['title'] = _t($CNF['T']['txt_all_entries_by_author']);
+        $this->aCurrent['rss']['link'] = 'modules/?r=' . $this->oModule->_oConfig->getUri() . '/rss/' . $sMode . '/' . $iProfileAuthor;
+
+        return true;
+    }
+
+    protected function _updateCurrentForContext($sMode, $aParams, &$oProfileContext)
+    {
+        $CNF = &$this->oModule->_oConfig->CNF;
+            
+        $oProfileContext = BxDolProfile::getInstance((int)$aParams['context']);
+        if (!$oProfileContext) 
+            return false;
+
+        $iProfileIdContext = $oProfileContext->id();
+        $this->aCurrent['restriction']['context'] = array(
+            'value' => -$iProfileIdContext,
+            'field' => $CNF['FIELD_ALLOW_VIEW_TO'],
+            'operator' => '=',
+        );
+
+        if(!empty($aParams['per_page']))
+        	$this->aCurrent['paginate']['perPage'] = is_numeric($aParams['per_page']) ? (int)$aParams['per_page'] : (int)getParam($aParams['per_page']);
+
+        $this->sBrowseUrl = 'page.php?i=' . $CNF['URI_ENTRIES_BY_CONTEXT'] . '&profile_id={profile_id}';
+        $this->aCurrent['title'] = _t($CNF['T']['txt_all_entries_by_context']);
+        $this->aCurrent['rss']['link'] = 'modules/?r=' . $this->oModule->_oConfig->getUri() . '/rss/' . $sMode . '/' . $iProfileIdContext;
+
+        return true;
+    }
+    
+    protected function _updateCurrentForFavorite($sMode, $aParams, &$oProfileAuthor)
+    {
+        $CNF = &$this->oModule->_oConfig->CNF;
+        
+        $sSystem = '';
+        $iListId = 0;
+        
+        if(!empty($aParams['system'])) {
+            $sSystem = $aParams['system'];
+            unset($aParams['system']);
+        }
+        
+        if(!empty($aParams['list_id'])) {
+            $iListId = $aParams['list_id'];
+            unset($aParams['list_id']);
+        }
+
+        $oProfileAuthor = BxDolProfile::getInstance((int)$aParams['user']);
+        if(!$oProfileAuthor) 
+            return false;
+
+        $iProfileAuthor = $oProfileAuthor->id();
+        $oFavorite = $this->oModule->getObjectFavorite($sSystem);
+        if(!$oFavorite->isPublic() && $iProfileAuthor != bx_get_logged_profile_id()) 
+            return false;
+
+        $aConditions = $oFavorite->getConditionsTrack($this->aCurrent['table'], 'id', $iProfileAuthor, $iListId);
+        if(!empty($aConditions) && is_array($aConditions)) {
+            if(empty($this->aCurrent['restriction']) || !is_array($this->aCurrent['restriction']))
+                $this->aCurrent['restriction'] = array();
+            $this->aCurrent['restriction'] = array_merge($this->aCurrent['restriction'], $aConditions['restriction']);
+
+            if(empty($this->aCurrent['join']) || !is_array($this->aCurrent['join']))
+                $this->aCurrent['join'] = array();
+            $this->aCurrent['join'] = array_merge($this->aCurrent['join'], $aConditions['join']);
+        }
+        
+        $this->sBrowseUrl = 'page.php?i=' . $CNF['URI_AUTHOR_ENTRIES'] . '&profile_id={profile_id}';
+        $this->aCurrent['title'] = _t($CNF['T']['txt_all_entries_by_author']);
+        $this->aCurrent['rss']['link'] = 'modules/?r=' . $this->oModule->_oConfig->getUri() . '/rss/' . $sMode . '/' . $iProfileAuthor;
+
+        return true;
+    }
+    
+    function decodeData ($a)
+    {
+        $CNF = &$this->oModule->_oConfig->CNF;
+
+        $oMetaMenu = !empty($CNF['OBJECT_MENU_SNIPPET_META']) ? BxDolMenu::getObjectInstance($CNF['OBJECT_MENU_SNIPPET_META'], $this->oModule->_oTemplate) : false;
+        $bMetaMenu = $oMetaMenu !== false;
+
+        $oContentInfo = $this->getContentInfoObject();
+
+        foreach ($a as $i => $r) {
+            $a[$i] = array_merge($a[$i], [
+                'url' => $this->decodeDataUrl($oContentInfo, $r),
+                'image' => $oContentInfo->getContentThumb($r['id']),
+                'summary_plain' => $this->decodeDataSummaryPlain($oContentInfo, $r)
+            ]);
+
+            if($bMetaMenu) {
+                $oMetaMenu->setContentId($r['id']);
+                $a[$i]['meta'] = $oMetaMenu->getCodeAPI();
+            }
+        }
+
+        return $a;
+    }
+
+    function _getPseud ()
+    {
+        return array(
+            'id' => 'id',
+            'title' => 'title',
+            'text' => 'text',
+            'added' => 'added',
+            'author' => 'author',
+            'photo' => 'photo',
+        );
+    }
+}
+
+/** @} */

+ 50 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/oembed.php

@@ -0,0 +1,50 @@
+<?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");
+
+$aLinks = bx_get('l');
+
+bx_import('BxDolEmbed');
+$oEmbed = BxDolEmbed::getObjectInstance('sys_embedly');
+
+if (bx_get('html')){
+    echo '<body style="margin:0"><style>iframe{width:100%} .embera-embed-responsive {
+    position: relative;
+    display: block;
+    width: 100%;
+    padding: 0;
+    overflow: hidden;
+    padding-bottom: 50%;
+}
+.embera-embed-responsive-item {
+    position: absolute;
+    top: 0;
+    bottom: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    border: 0;
+}
+.embedly-card{
+    width:600px;
+}
+.bx-base-attach-link-form-field .embera-embed-responsive {
+    padding-bottom: 0;
+}
+.bx-base-attach-link-form-field .embera-embed-responsive-item {
+    position: relative;
+    width: auto;
+    height: auto;
+}</style><script language="javascript" src="/plugins_public/jquery/jquery.min.js"></script>' . $oEmbed->addJsCss() . '<div style="max-width:900; margin:0px auto; justify-content: center; display:flex">' . $oEmbed->getLinkHTML($aLinks) .'</div>' . $oEmbed->addProcessLinkMethod() . '</body>';
+    exit();
+}
+
+echoJson($oEmbed->parseLinks($aLinks));

+ 7 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins/autoload.php

@@ -0,0 +1,7 @@
+<?php
+
+// autoload.php @generated by Composer
+
+require_once __DIR__ . '/composer/autoload_real.php';
+
+return ComposerAutoloaderInit357b0b491f24c4da5b09ff1058ce1aec::getLoader();

+ 15 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins/composer/autoload_files.php

@@ -0,0 +1,15 @@
+<?php
+
+// autoload_files.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = dirname($vendorDir);
+
+return array(
+    '7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php',
+    '6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php',
+    'c964ee0ededf28c96ebd9db5099ef910' => $vendorDir . '/guzzlehttp/promises/src/functions_include.php',
+    '37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php',
+    'c65d09b6820da036953a371c8c73a9b1' => $vendorDir . '/facebook/graph-sdk/src/Facebook/polyfills.php',
+    '2cffec82183ee1cea088009cef9a6fc3' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier.composer.php',
+);

+ 75 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins/composer/autoload_real.php

@@ -0,0 +1,75 @@
+<?php
+
+// autoload_real.php @generated by Composer
+
+class ComposerAutoloaderInit357b0b491f24c4da5b09ff1058ce1aec
+{
+    private static $loader;
+
+    public static function loadClassLoader($class)
+    {
+        if ('Composer\Autoload\ClassLoader' === $class) {
+            require __DIR__ . '/ClassLoader.php';
+        }
+    }
+
+    /**
+     * @return \Composer\Autoload\ClassLoader
+     */
+    public static function getLoader()
+    {
+        if (null !== self::$loader) {
+            return self::$loader;
+        }
+
+        require __DIR__ . '/platform_check.php';
+
+        spl_autoload_register(array('ComposerAutoloaderInit357b0b491f24c4da5b09ff1058ce1aec', 'loadClassLoader'), true, true);
+        self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
+        spl_autoload_unregister(array('ComposerAutoloaderInit357b0b491f24c4da5b09ff1058ce1aec', 'loadClassLoader'));
+
+        $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
+        if ($useStaticLoader) {
+            require __DIR__ . '/autoload_static.php';
+
+            call_user_func(\Composer\Autoload\ComposerStaticInit357b0b491f24c4da5b09ff1058ce1aec::getInitializer($loader));
+        } else {
+            $map = require __DIR__ . '/autoload_namespaces.php';
+            foreach ($map as $namespace => $path) {
+                $loader->set($namespace, $path);
+            }
+
+            $map = require __DIR__ . '/autoload_psr4.php';
+            foreach ($map as $namespace => $path) {
+                $loader->setPsr4($namespace, $path);
+            }
+
+            $classMap = require __DIR__ . '/autoload_classmap.php';
+            if ($classMap) {
+                $loader->addClassMap($classMap);
+            }
+        }
+
+        $loader->register(true);
+
+        if ($useStaticLoader) {
+            $includeFiles = Composer\Autoload\ComposerStaticInit357b0b491f24c4da5b09ff1058ce1aec::$files;
+        } else {
+            $includeFiles = require __DIR__ . '/autoload_files.php';
+        }
+        foreach ($includeFiles as $fileIdentifier => $file) {
+            composerRequire357b0b491f24c4da5b09ff1058ce1aec($fileIdentifier, $file);
+        }
+
+        return $loader;
+    }
+}
+
+function composerRequire357b0b491f24c4da5b09ff1058ce1aec($fileIdentifier, $file)
+{
+    if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
+        require $file;
+
+        $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
+    }
+}

+ 224 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins/composer/autoload_static.php

@@ -0,0 +1,224 @@
+<?php
+
+// autoload_static.php @generated by Composer
+
+namespace Composer\Autoload;
+
+class ComposerStaticInit357b0b491f24c4da5b09ff1058ce1aec
+{
+    public static $files = array (
+        '7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php',
+        '6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php',
+        'c964ee0ededf28c96ebd9db5099ef910' => __DIR__ . '/..' . '/guzzlehttp/promises/src/functions_include.php',
+        '37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php',
+        'c65d09b6820da036953a371c8c73a9b1' => __DIR__ . '/..' . '/facebook/graph-sdk/src/Facebook/polyfills.php',
+        '2cffec82183ee1cea088009cef9a6fc3' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier.composer.php',
+    );
+
+    public static $prefixLengthsPsr4 = array (
+        'T' => 
+        array (
+            'Twilio\\' => 7,
+        ),
+        'S' => 
+        array (
+            'Stripe\\' => 7,
+            'Spatie\\CalendarLinks\\' => 21,
+            'Snipe\\BanBuilder\\' => 17,
+            'Sample\\' => 7,
+        ),
+        'P' => 
+        array (
+            'Psr\\Http\\Message\\' => 17,
+            'Psr\\Http\\Client\\' => 16,
+            'PayPalHttp\\' => 11,
+            'PayPalCheckoutSdk\\' => 18,
+            'PHPMailer\\PHPMailer\\' => 20,
+        ),
+        'M' => 
+        array (
+            'MatthiasMullie\\PathConverter\\' => 29,
+            'MatthiasMullie\\Minify\\' => 22,
+        ),
+        'I' => 
+        array (
+            'Intervention\\Image\\' => 19,
+        ),
+        'G' => 
+        array (
+            'GuzzleHttp\\Psr7\\' => 16,
+            'GuzzleHttp\\Promise\\' => 19,
+            'GuzzleHttp\\' => 11,
+        ),
+        'F' => 
+        array (
+            'Facebook\\' => 9,
+        ),
+        'E' => 
+        array (
+            'Embera\\' => 7,
+        ),
+        'C' => 
+        array (
+            'ChargeBee\\' => 10,
+        ),
+        'A' => 
+        array (
+            'AvtoDev\\Composer\\Cleanup\\' => 25,
+            'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\' => 38,
+        ),
+    );
+
+    public static $prefixDirsPsr4 = array (
+        'Twilio\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/twilio/sdk/src/Twilio',
+        ),
+        'Stripe\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/stripe/stripe-php/lib',
+        ),
+        'Spatie\\CalendarLinks\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/spatie/calendar-links/src',
+        ),
+        'Snipe\\BanBuilder\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/snipe/banbuilder/src',
+        ),
+        'Sample\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/paypal/paypal-checkout-sdk/samples',
+        ),
+        'Psr\\Http\\Message\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/psr/http-message/src',
+            1 => __DIR__ . '/..' . '/psr/http-factory/src',
+        ),
+        'Psr\\Http\\Client\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/psr/http-client/src',
+        ),
+        'PayPalHttp\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/paypal/paypalhttp/lib/PayPalHttp',
+        ),
+        'PayPalCheckoutSdk\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/paypal/paypal-checkout-sdk/lib/PayPalCheckoutSdk',
+        ),
+        'PHPMailer\\PHPMailer\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/phpmailer/phpmailer/src',
+        ),
+        'MatthiasMullie\\PathConverter\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/matthiasmullie/path-converter/src',
+        ),
+        'MatthiasMullie\\Minify\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/matthiasmullie/minify/src',
+        ),
+        'Intervention\\Image\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image',
+        ),
+        'GuzzleHttp\\Psr7\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/guzzlehttp/psr7/src',
+        ),
+        'GuzzleHttp\\Promise\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/guzzlehttp/promises/src',
+        ),
+        'GuzzleHttp\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/guzzlehttp/guzzle/src',
+        ),
+        'Facebook\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/facebook/graph-sdk/src/Facebook',
+        ),
+        'Embera\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/mpratt/embera/src/Embera',
+        ),
+        'ChargeBee\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/chargebee/chargebee-php/lib',
+        ),
+        'AvtoDev\\Composer\\Cleanup\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/avto-dev/composer-cleanup-plugin/src',
+        ),
+        'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/akeeba/s3/src',
+        ),
+    );
+
+    public static $prefixesPsr0 = array (
+        'P' => 
+        array (
+            'Parsedown' => 
+            array (
+                0 => __DIR__ . '/..' . '/erusev/parsedown',
+            ),
+        ),
+        'O' => 
+        array (
+            'OAuth\\Unit' => 
+            array (
+                0 => __DIR__ . '/..' . '/lusitanian/oauth/tests',
+            ),
+            'OAuth2' => 
+            array (
+                0 => __DIR__ . '/..' . '/bshaffer/oauth2-server-php/src',
+            ),
+            'OAuth' => 
+            array (
+                0 => __DIR__ . '/..' . '/lusitanian/oauth/src',
+            ),
+        ),
+        'L' => 
+        array (
+            'Less' => 
+            array (
+                0 => __DIR__ . '/..' . '/wikimedia/less.php/lib',
+            ),
+        ),
+        'H' => 
+        array (
+            'HTMLPurifier' => 
+            array (
+                0 => __DIR__ . '/..' . '/ezyang/htmlpurifier/library',
+            ),
+        ),
+        'C' => 
+        array (
+            'ChargeBee' => 
+            array (
+                0 => __DIR__ . '/..' . '/chargebee/chargebee-php/lib',
+            ),
+        ),
+    );
+
+    public static $classMap = array (
+        'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
+        'S3v4\\S3' => __DIR__ . '/..' . '/tpyo/amazon-s3-php-class/S3.php',
+        'S3v4\\S3Exception' => __DIR__ . '/..' . '/tpyo/amazon-s3-php-class/S3.php',
+        'S3v4\\S3Request' => __DIR__ . '/..' . '/tpyo/amazon-s3-php-class/S3.php',
+        'lessc' => __DIR__ . '/..' . '/wikimedia/less.php/lessc.inc.php',
+    );
+
+    public static function getInitializer(ClassLoader $loader)
+    {
+        return \Closure::bind(function () use ($loader) {
+            $loader->prefixLengthsPsr4 = ComposerStaticInit357b0b491f24c4da5b09ff1058ce1aec::$prefixLengthsPsr4;
+            $loader->prefixDirsPsr4 = ComposerStaticInit357b0b491f24c4da5b09ff1058ce1aec::$prefixDirsPsr4;
+            $loader->prefixesPsr0 = ComposerStaticInit357b0b491f24c4da5b09ff1058ce1aec::$prefixesPsr0;
+            $loader->classMap = ComposerStaticInit357b0b491f24c4da5b09ff1058ce1aec::$classMap;
+
+        }, null, ClassLoader::class);
+    }
+}

+ 2018 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins/composer/installed.json

@@ -0,0 +1,2018 @@
+{
+    "packages": [
+        {
+            "name": "achingbrain/php5-akismet",
+            "version": "0.5",
+            "version_normalized": "0.5.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/achingbrain/php5-akismet",
+                "reference": "master"
+            },
+            "type": "library",
+            "installation-source": "source",
+            "install-path": "../achingbrain/php5-akismet"
+        },
+        {
+            "name": "akeeba/s3",
+            "version": "dev-development",
+            "version_normalized": "dev-development",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/akeeba/s3.git",
+                "reference": "6448ddf99541f3ba50f97e373ceeb5bfd42a0167"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/akeeba/s3/zipball/6448ddf99541f3ba50f97e373ceeb5bfd42a0167",
+                "reference": "6448ddf99541f3ba50f97e373ceeb5bfd42a0167",
+                "shasum": ""
+            },
+            "require": {
+                "ext-curl": "*",
+                "ext-simplexml": "*",
+                "php": ">=7.1.0 <8.1"
+            },
+            "time": "2022-07-28T17:03:08+00:00",
+            "default-branch": true,
+            "type": "library",
+            "installation-source": "dist",
+            "autoload": {
+                "psr-4": {
+                    "Akeeba\\Engine\\Postproc\\Connector\\S3v4\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "GPL-3.0+"
+            ],
+            "authors": [
+                {
+                    "name": "Nicholas K. Dionysopoulos",
+                    "email": "nicholas_NO_SPAM_PLEASE@akeeba.com",
+                    "homepage": "http://www.dionysopoulos.me",
+                    "role": "Lead Developer"
+                }
+            ],
+            "description": "A compact, dependency-less Amazon S3 API client implementing the most commonly used features",
+            "homepage": "https://github.com/akeeba/s3",
+            "keywords": [
+                "s3"
+            ],
+            "support": {
+                "issues": "https://github.com/akeeba/s3/issues",
+                "source": "https://github.com/akeeba/s3/tree/development"
+            },
+            "install-path": "../akeeba/s3"
+        },
+        {
+            "name": "avto-dev/composer-cleanup-plugin",
+            "version": "dev-master",
+            "version_normalized": "dev-master",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/unaio/composer-cleanup-plugin.git",
+                "reference": "75187ca4854265174021765e3b67cb973dc37723"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/unaio/composer-cleanup-plugin/zipball/75187ca4854265174021765e3b67cb973dc37723",
+                "reference": "75187ca4854265174021765e3b67cb973dc37723",
+                "shasum": ""
+            },
+            "require": {
+                "composer-plugin-api": "^1.0 || ^2.0",
+                "php": "^7.2 || ^8.0"
+            },
+            "require-dev": {
+                "composer/composer": "^1.8.6 || ^2.0",
+                "phpstan/phpstan": "~0.12.34",
+                "phpunit/phpunit": "^8.5.4 || ^9.3"
+            },
+            "time": "2021-09-04T13:12:22+00:00",
+            "default-branch": true,
+            "type": "composer-plugin",
+            "extra": {
+                "class": "AvtoDev\\Composer\\Cleanup\\Plugin"
+            },
+            "installation-source": "dist",
+            "autoload": {
+                "psr-4": {
+                    "AvtoDev\\Composer\\Cleanup\\": "src/"
+                }
+            },
+            "autoload-dev": {
+                "psr-4": {
+                    "AvtoDev\\Composer\\Cleanup\\Tests\\": "tests/"
+                }
+            },
+            "scripts": {
+                "post-update-cmd": [
+                    "AvtoDev\\Composer\\Cleanup\\Plugin::cleanupAllPackages"
+                ],
+                "post-install-cmd": [
+                    "AvtoDev\\Composer\\Cleanup\\Plugin::cleanupAllPackages"
+                ],
+                "phpunit": [
+                    "@php ./vendor/bin/phpunit --no-coverage --colors=always"
+                ],
+                "phpunit-cover": [
+                    "@php ./vendor/bin/phpunit --coverage-html='./coverage/html'"
+                ],
+                "phpstan": [
+                    "@php ./vendor/bin/phpstan analyze -c ./phpstan.neon.dist --no-progress --ansi"
+                ],
+                "test": [
+                    "@phpstan",
+                    "@phpunit"
+                ],
+                "test-cover": [
+                    "@phpstan",
+                    "@phpunit-cover"
+                ]
+            },
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "avto-dev",
+                    "homepage": "https://github.com/avto-dev"
+                },
+                {
+                    "name": "Barry vd. Heuvel",
+                    "email": "barryvdh@gmail.com"
+                }
+            ],
+            "description": "A composer cleanup plugin, to remove tests and documentation to save space",
+            "support": {
+                "issues": "https://github.com/avto-dev/composer-cleanup-plugin/issues",
+                "source": "https://github.com/avto-dev/composer-cleanup-plugin"
+            },
+            "install-path": "../avto-dev/composer-cleanup-plugin"
+        },
+        {
+            "name": "bshaffer/oauth2-server-php",
+            "version": "v1.12.1",
+            "version_normalized": "1.12.1.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/bshaffer/oauth2-server-php.git",
+                "reference": "2bfaf9d7bbebe2ba1c1deb48e756ba0b3af4e985"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/bshaffer/oauth2-server-php/zipball/2bfaf9d7bbebe2ba1c1deb48e756ba0b3af4e985",
+                "reference": "2bfaf9d7bbebe2ba1c1deb48e756ba0b3af4e985",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.1"
+            },
+            "require-dev": {
+                "aws/aws-sdk-php": "^2.8",
+                "firebase/php-jwt": "^2.2",
+                "mongodb/mongodb": "^1.1",
+                "phpunit/phpunit": "^7.5||^8.0",
+                "predis/predis": "^1.1",
+                "thobbs/phpcassa": "dev-master",
+                "yoast/phpunit-polyfills": "^1.0"
+            },
+            "suggest": {
+                "aws/aws-sdk-php": "~2.8 is required to use DynamoDB storage",
+                "firebase/php-jwt": "~2.2 is required to use JWT features",
+                "mongodb/mongodb": "^1.1 is required to use MongoDB storage",
+                "predis/predis": "Required to use Redis storage",
+                "thobbs/phpcassa": "Required to use Cassandra storage"
+            },
+            "time": "2022-05-31T16:12:58+00:00",
+            "type": "library",
+            "installation-source": "dist",
+            "autoload": {
+                "psr-0": {
+                    "OAuth2": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Brent Shaffer",
+                    "email": "bshafs@gmail.com",
+                    "homepage": "http://brentertainment.com"
+                }
+            ],
+            "description": "OAuth2 Server for PHP",
+            "homepage": "http://github.com/bshaffer/oauth2-server-php",
+            "keywords": [
+                "auth",
+                "oauth",
+                "oauth2"
+            ],
+            "support": {
+                "issues": "https://github.com/bshaffer/oauth2-server-php/issues",
+                "source": "https://github.com/bshaffer/oauth2-server-php/tree/v1.12.1"
+            },
+            "install-path": "../bshaffer/oauth2-server-php"
+        },
+        {
+            "name": "chargebee/chargebee-php",
+            "version": "v3.11.0",
+            "version_normalized": "3.11.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/chargebee/chargebee-php.git",
+                "reference": "2b74d71005693f8f11cc2ef1aea07c48a8f8e155"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/chargebee/chargebee-php/zipball/2b74d71005693f8f11cc2ef1aea07c48a8f8e155",
+                "reference": "2b74d71005693f8f11cc2ef1aea07c48a8f8e155",
+                "shasum": ""
+            },
+            "require": {
+                "ext-curl": "*",
+                "guzzlehttp/guzzle": ">=6.5",
+                "php": ">=5.6.0"
+            },
+            "require-dev": {
+                "simpletest/simpletest": "^1.1"
+            },
+            "time": "2022-09-20T13:55:16+00:00",
+            "type": "library",
+            "installation-source": "dist",
+            "autoload": {
+                "psr-0": {
+                    "ChargeBee": "lib/"
+                },
+                "psr-4": {
+                    "ChargeBee\\": "lib/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "description": "ChargeBee API client implementation for PHP",
+            "homepage": "https://github.com/chargebee/chargebee-php",
+            "keywords": [
+                "chargebee",
+                "payments",
+                "recurring billing",
+                "subscription billing"
+            ],
+            "support": {
+                "issues": "https://github.com/chargebee/chargebee-php/issues",
+                "source": "https://github.com/chargebee/chargebee-php/tree/v3.11.0"
+            },
+            "install-path": "../chargebee/chargebee-php"
+        },
+        {
+            "name": "curl/cacert",
+            "version": "0.0.1",
+            "version_normalized": "0.0.1.0",
+            "dist": {
+                "type": "file",
+                "url": "https://curl.se/ca/cacert.pem"
+            },
+            "type": "library",
+            "installation-source": "dist",
+            "install-path": "../curl/cacert"
+        },
+        {
+            "name": "erusev/parsedown",
+            "version": "dev-master",
+            "version_normalized": "dev-master",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/erusev/parsedown.git",
+                "reference": "6598f3860c2698fe2f0f1bc98212fc01d0a1893c"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/erusev/parsedown/zipball/6598f3860c2698fe2f0f1bc98212fc01d0a1893c",
+                "reference": "6598f3860c2698fe2f0f1bc98212fc01d0a1893c",
+                "shasum": ""
+            },
+            "require": {
+                "ext-mbstring": "*",
+                "php": ">=5.3.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^4.8.35"
+            },
+            "time": "2020-08-09T14:12:21+00:00",
+            "default-branch": true,
+            "type": "library",
+            "installation-source": "dist",
+            "autoload": {
+                "psr-0": {
+                    "Parsedown": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Emanuil Rusev",
+                    "email": "hello@erusev.com",
+                    "homepage": "http://erusev.com"
+                }
+            ],
+            "description": "Parser for Markdown.",
+            "homepage": "http://parsedown.org",
+            "keywords": [
+                "markdown",
+                "parser"
+            ],
+            "support": {
+                "issues": "https://github.com/erusev/parsedown/issues",
+                "source": "https://github.com/erusev/parsedown/tree/master"
+            },
+            "install-path": "../erusev/parsedown"
+        },
+        {
+            "name": "ezyang/htmlpurifier",
+            "version": "v4.16.0",
+            "version_normalized": "4.16.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/ezyang/htmlpurifier.git",
+                "reference": "523407fb06eb9e5f3d59889b3978d5bfe94299c8"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/523407fb06eb9e5f3d59889b3978d5bfe94299c8",
+                "reference": "523407fb06eb9e5f3d59889b3978d5bfe94299c8",
+                "shasum": ""
+            },
+            "require": {
+                "php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0"
+            },
+            "require-dev": {
+                "cerdic/css-tidy": "^1.7 || ^2.0",
+                "simpletest/simpletest": "dev-master"
+            },
+            "suggest": {
+                "cerdic/css-tidy": "If you want to use the filter 'Filter.ExtractStyleBlocks'.",
+                "ext-bcmath": "Used for unit conversion and imagecrash protection",
+                "ext-iconv": "Converts text to and from non-UTF-8 encodings",
+                "ext-tidy": "Used for pretty-printing HTML"
+            },
+            "time": "2022-09-18T07:06:19+00:00",
+            "type": "library",
+            "installation-source": "dist",
+            "autoload": {
+                "files": [
+                    "library/HTMLPurifier.composer.php"
+                ],
+                "psr-0": {
+                    "HTMLPurifier": "library/"
+                },
+                "exclude-from-classmap": [
+                    "/library/HTMLPurifier/Language/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "LGPL-2.1-or-later"
+            ],
+            "authors": [
+                {
+                    "name": "Edward Z. Yang",
+                    "email": "admin@htmlpurifier.org",
+                    "homepage": "http://ezyang.com"
+                }
+            ],
+            "description": "Standards compliant HTML filter written in PHP",
+            "homepage": "http://htmlpurifier.org/",
+            "keywords": [
+                "html"
+            ],
+            "support": {
+                "issues": "https://github.com/ezyang/htmlpurifier/issues",
+                "source": "https://github.com/ezyang/htmlpurifier/tree/v4.16.0"
+            },
+            "install-path": "../ezyang/htmlpurifier"
+        },
+        {
+            "name": "facebook/graph-sdk",
+            "version": "5.7.0",
+            "version_normalized": "5.7.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/facebook/php-graph-sdk.git",
+                "reference": "2d8250638b33d73e7a87add65f47fabf91f8ad9b"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/facebook/php-graph-sdk/zipball/2d8250638b33d73e7a87add65f47fabf91f8ad9b",
+                "reference": "2d8250638b33d73e7a87add65f47fabf91f8ad9b",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^5.4|^7.0"
+            },
+            "require-dev": {
+                "guzzlehttp/guzzle": "~5.0",
+                "mockery/mockery": "~0.8",
+                "phpunit/phpunit": "~4.0"
+            },
+            "suggest": {
+                "guzzlehttp/guzzle": "Allows for implementation of the Guzzle HTTP client",
+                "paragonie/random_compat": "Provides a better CSPRNG option in PHP 5"
+            },
+            "time": "2018-12-11T22:56:31+00:00",
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "5.x-dev"
+                }
+            },
+            "installation-source": "dist",
+            "autoload": {
+                "files": [
+                    "src/Facebook/polyfills.php"
+                ],
+                "psr-4": {
+                    "Facebook\\": "src/Facebook/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Facebook Platform"
+            ],
+            "authors": [
+                {
+                    "name": "Facebook",
+                    "homepage": "https://github.com/facebook/php-graph-sdk/contributors"
+                }
+            ],
+            "description": "Facebook SDK for PHP",
+            "homepage": "https://github.com/facebook/php-graph-sdk",
+            "keywords": [
+                "facebook",
+                "sdk"
+            ],
+            "support": {
+                "issues": "https://github.com/facebook/php-graph-sdk/issues",
+                "source": "https://github.com/facebook/php-graph-sdk/tree/5.7.0"
+            },
+            "abandoned": true,
+            "install-path": "../facebook/graph-sdk"
+        },
+        {
+            "name": "ffmpeg/ffmpeg",
+            "version": "4.4.0",
+            "version_normalized": "4.4.0.0",
+            "dist": {
+                "type": "file",
+                "url": "https://github.com/eugeneware/ffmpeg-static/releases/download/b4.4/linux-x64"
+            },
+            "type": "library",
+            "installation-source": "dist",
+            "install-path": "../ffmpeg/ffmpeg"
+        },
+        {
+            "name": "guzzlehttp/guzzle",
+            "version": "7.5.0",
+            "version_normalized": "7.5.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/guzzle/guzzle.git",
+                "reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b50a2a1251152e43f6a37f0fa053e730a67d25ba",
+                "reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba",
+                "shasum": ""
+            },
+            "require": {
+                "ext-json": "*",
+                "guzzlehttp/promises": "^1.5",
+                "guzzlehttp/psr7": "^1.9 || ^2.4",
+                "php": "^7.2.5 || ^8.0",
+                "psr/http-client": "^1.0",
+                "symfony/deprecation-contracts": "^2.2 || ^3.0"
+            },
+            "provide": {
+                "psr/http-client-implementation": "1.0"
+            },
+            "require-dev": {
+                "bamarni/composer-bin-plugin": "^1.8.1",
+                "ext-curl": "*",
+                "php-http/client-integration-tests": "^3.0",
+                "phpunit/phpunit": "^8.5.29 || ^9.5.23",
+                "psr/log": "^1.1 || ^2.0 || ^3.0"
+            },
+            "suggest": {
+                "ext-curl": "Required for CURL handler support",
+                "ext-intl": "Required for Internationalized Domain Name (IDN) support",
+                "psr/log": "Required for using the Log middleware"
+            },
+            "time": "2022-08-28T15:39:27+00:00",
+            "type": "library",
+            "extra": {
+                "bamarni-bin": {
+                    "bin-links": true,
+                    "forward-command": false
+                },
+                "branch-alias": {
+                    "dev-master": "7.5-dev"
+                }
+            },
+            "installation-source": "dist",
+            "autoload": {
+                "files": [
+                    "src/functions_include.php"
+                ],
+                "psr-4": {
+                    "GuzzleHttp\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Graham Campbell",
+                    "email": "hello@gjcampbell.co.uk",
+                    "homepage": "https://github.com/GrahamCampbell"
+                },
+                {
+                    "name": "Michael Dowling",
+                    "email": "mtdowling@gmail.com",
+                    "homepage": "https://github.com/mtdowling"
+                },
+                {
+                    "name": "Jeremy Lindblom",
+                    "email": "jeremeamia@gmail.com",
+                    "homepage": "https://github.com/jeremeamia"
+                },
+                {
+                    "name": "George Mponos",
+                    "email": "gmponos@gmail.com",
+                    "homepage": "https://github.com/gmponos"
+                },
+                {
+                    "name": "Tobias Nyholm",
+                    "email": "tobias.nyholm@gmail.com",
+                    "homepage": "https://github.com/Nyholm"
+                },
+                {
+                    "name": "Márk Sági-Kazár",
+                    "email": "mark.sagikazar@gmail.com",
+                    "homepage": "https://github.com/sagikazarmark"
+                },
+                {
+                    "name": "Tobias Schultze",
+                    "email": "webmaster@tubo-world.de",
+                    "homepage": "https://github.com/Tobion"
+                }
+            ],
+            "description": "Guzzle is a PHP HTTP client library",
+            "keywords": [
+                "client",
+                "curl",
+                "framework",
+                "http",
+                "http client",
+                "psr-18",
+                "psr-7",
+                "rest",
+                "web service"
+            ],
+            "support": {
+                "issues": "https://github.com/guzzle/guzzle/issues",
+                "source": "https://github.com/guzzle/guzzle/tree/7.5.0"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/GrahamCampbell",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/Nyholm",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle",
+                    "type": "tidelift"
+                }
+            ],
+            "install-path": "../guzzlehttp/guzzle"
+        },
+        {
+            "name": "guzzlehttp/promises",
+            "version": "1.5.2",
+            "version_normalized": "1.5.2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/guzzle/promises.git",
+                "reference": "b94b2807d85443f9719887892882d0329d1e2598"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/guzzle/promises/zipball/b94b2807d85443f9719887892882d0329d1e2598",
+                "reference": "b94b2807d85443f9719887892882d0329d1e2598",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.5"
+            },
+            "require-dev": {
+                "symfony/phpunit-bridge": "^4.4 || ^5.1"
+            },
+            "time": "2022-08-28T14:55:35+00:00",
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.5-dev"
+                }
+            },
+            "installation-source": "dist",
+            "autoload": {
+                "files": [
+                    "src/functions_include.php"
+                ],
+                "psr-4": {
+                    "GuzzleHttp\\Promise\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Graham Campbell",
+                    "email": "hello@gjcampbell.co.uk",
+                    "homepage": "https://github.com/GrahamCampbell"
+                },
+                {
+                    "name": "Michael Dowling",
+                    "email": "mtdowling@gmail.com",
+                    "homepage": "https://github.com/mtdowling"
+                },
+                {
+                    "name": "Tobias Nyholm",
+                    "email": "tobias.nyholm@gmail.com",
+                    "homepage": "https://github.com/Nyholm"
+                },
+                {
+                    "name": "Tobias Schultze",
+                    "email": "webmaster@tubo-world.de",
+                    "homepage": "https://github.com/Tobion"
+                }
+            ],
+            "description": "Guzzle promises library",
+            "keywords": [
+                "promise"
+            ],
+            "support": {
+                "issues": "https://github.com/guzzle/promises/issues",
+                "source": "https://github.com/guzzle/promises/tree/1.5.2"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/GrahamCampbell",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/Nyholm",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises",
+                    "type": "tidelift"
+                }
+            ],
+            "install-path": "../guzzlehttp/promises"
+        },
+        {
+            "name": "guzzlehttp/psr7",
+            "version": "2.5.0",
+            "version_normalized": "2.5.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/guzzle/psr7.git",
+                "reference": "b635f279edd83fc275f822a1188157ffea568ff6"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/guzzle/psr7/zipball/b635f279edd83fc275f822a1188157ffea568ff6",
+                "reference": "b635f279edd83fc275f822a1188157ffea568ff6",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.2.5 || ^8.0",
+                "psr/http-factory": "^1.0",
+                "psr/http-message": "^1.1 || ^2.0",
+                "ralouphie/getallheaders": "^3.0"
+            },
+            "provide": {
+                "psr/http-factory-implementation": "1.0",
+                "psr/http-message-implementation": "1.0"
+            },
+            "require-dev": {
+                "bamarni/composer-bin-plugin": "^1.8.1",
+                "http-interop/http-factory-tests": "^0.9",
+                "phpunit/phpunit": "^8.5.29 || ^9.5.23"
+            },
+            "suggest": {
+                "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
+            },
+            "time": "2023-04-17T16:11:26+00:00",
+            "type": "library",
+            "extra": {
+                "bamarni-bin": {
+                    "bin-links": true,
+                    "forward-command": false
+                }
+            },
+            "installation-source": "dist",
+            "autoload": {
+                "psr-4": {
+                    "GuzzleHttp\\Psr7\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Graham Campbell",
+                    "email": "hello@gjcampbell.co.uk",
+                    "homepage": "https://github.com/GrahamCampbell"
+                },
+                {
+                    "name": "Michael Dowling",
+                    "email": "mtdowling@gmail.com",
+                    "homepage": "https://github.com/mtdowling"
+                },
+                {
+                    "name": "George Mponos",
+                    "email": "gmponos@gmail.com",
+                    "homepage": "https://github.com/gmponos"
+                },
+                {
+                    "name": "Tobias Nyholm",
+                    "email": "tobias.nyholm@gmail.com",
+                    "homepage": "https://github.com/Nyholm"
+                },
+                {
+                    "name": "Márk Sági-Kazár",
+                    "email": "mark.sagikazar@gmail.com",
+                    "homepage": "https://github.com/sagikazarmark"
+                },
+                {
+                    "name": "Tobias Schultze",
+                    "email": "webmaster@tubo-world.de",
+                    "homepage": "https://github.com/Tobion"
+                },
+                {
+                    "name": "Márk Sági-Kazár",
+                    "email": "mark.sagikazar@gmail.com",
+                    "homepage": "https://sagikazarmark.hu"
+                }
+            ],
+            "description": "PSR-7 message implementation that also provides common utility methods",
+            "keywords": [
+                "http",
+                "message",
+                "psr-7",
+                "request",
+                "response",
+                "stream",
+                "uri",
+                "url"
+            ],
+            "support": {
+                "issues": "https://github.com/guzzle/psr7/issues",
+                "source": "https://github.com/guzzle/psr7/tree/2.5.0"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/GrahamCampbell",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/Nyholm",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7",
+                    "type": "tidelift"
+                }
+            ],
+            "install-path": "../guzzlehttp/psr7"
+        },
+        {
+            "name": "intervention/image",
+            "version": "2.7.2",
+            "version_normalized": "2.7.2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/Intervention/image.git",
+                "reference": "04be355f8d6734c826045d02a1079ad658322dad"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/Intervention/image/zipball/04be355f8d6734c826045d02a1079ad658322dad",
+                "reference": "04be355f8d6734c826045d02a1079ad658322dad",
+                "shasum": ""
+            },
+            "require": {
+                "ext-fileinfo": "*",
+                "guzzlehttp/psr7": "~1.1 || ^2.0",
+                "php": ">=5.4.0"
+            },
+            "require-dev": {
+                "mockery/mockery": "~0.9.2",
+                "phpunit/phpunit": "^4.8 || ^5.7 || ^7.5.15"
+            },
+            "suggest": {
+                "ext-gd": "to use GD library based image processing.",
+                "ext-imagick": "to use Imagick based image processing.",
+                "intervention/imagecache": "Caching extension for the Intervention Image library"
+            },
+            "time": "2022-05-21T17:30:32+00:00",
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.4-dev"
+                },
+                "laravel": {
+                    "providers": [
+                        "Intervention\\Image\\ImageServiceProvider"
+                    ],
+                    "aliases": {
+                        "Image": "Intervention\\Image\\Facades\\Image"
+                    }
+                }
+            },
+            "installation-source": "dist",
+            "autoload": {
+                "psr-4": {
+                    "Intervention\\Image\\": "src/Intervention/Image"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Oliver Vogel",
+                    "email": "oliver@intervention.io",
+                    "homepage": "https://intervention.io/"
+                }
+            ],
+            "description": "Image handling and manipulation library with support for Laravel integration",
+            "homepage": "http://image.intervention.io/",
+            "keywords": [
+                "gd",
+                "image",
+                "imagick",
+                "laravel",
+                "thumbnail",
+                "watermark"
+            ],
+            "support": {
+                "issues": "https://github.com/Intervention/image/issues",
+                "source": "https://github.com/Intervention/image/tree/2.7.2"
+            },
+            "funding": [
+                {
+                    "url": "https://paypal.me/interventionio",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/Intervention",
+                    "type": "github"
+                }
+            ],
+            "install-path": "../intervention/image"
+        },
+        {
+            "name": "lusitanian/oauth",
+            "version": "v0.8.11",
+            "version_normalized": "0.8.11.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/Lusitanian/PHPoAuthLib.git",
+                "reference": "fc11a53db4b66da555a6a11fce294f574a8374f9"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/Lusitanian/PHPoAuthLib/zipball/fc11a53db4b66da555a6a11fce294f574a8374f9",
+                "reference": "fc11a53db4b66da555a6a11fce294f574a8374f9",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "3.7.*",
+                "predis/predis": "0.8.*@dev",
+                "squizlabs/php_codesniffer": "2.*",
+                "symfony/http-foundation": "~2.1"
+            },
+            "suggest": {
+                "ext-openssl": "Allows for usage of secure connections with the stream-based HTTP client.",
+                "predis/predis": "Allows using the Redis storage backend.",
+                "symfony/http-foundation": "Allows using the Symfony Session storage backend."
+            },
+            "time": "2018-02-14T22:37:14+00:00",
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "0.1-dev"
+                }
+            },
+            "installation-source": "dist",
+            "autoload": {
+                "psr-0": {
+                    "OAuth": "src",
+                    "OAuth\\Unit": "tests"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "David Desberg",
+                    "email": "david@daviddesberg.com"
+                },
+                {
+                    "name": "Elliot Chance",
+                    "email": "elliotchance@gmail.com"
+                },
+                {
+                    "name": "Pieter Hordijk",
+                    "email": "info@pieterhordijk.com"
+                }
+            ],
+            "description": "PHP 5.3+ oAuth 1/2 Library",
+            "keywords": [
+                "Authentication",
+                "authorization",
+                "oauth",
+                "security"
+            ],
+            "support": {
+                "issues": "https://github.com/Lusitanian/PHPoAuthLib/issues",
+                "source": "https://github.com/Lusitanian/PHPoAuthLib/tree/master"
+            },
+            "install-path": "../lusitanian/oauth"
+        },
+        {
+            "name": "matthiasmullie/minify",
+            "version": "1.3.69",
+            "version_normalized": "1.3.69.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/matthiasmullie/minify.git",
+                "reference": "a61c949cccd086808063611ef9698eabe42ef22f"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/matthiasmullie/minify/zipball/a61c949cccd086808063611ef9698eabe42ef22f",
+                "reference": "a61c949cccd086808063611ef9698eabe42ef22f",
+                "shasum": ""
+            },
+            "require": {
+                "ext-pcre": "*",
+                "matthiasmullie/path-converter": "~1.1",
+                "php": ">=5.3.0"
+            },
+            "require-dev": {
+                "friendsofphp/php-cs-fixer": "~2.0",
+                "matthiasmullie/scrapbook": "dev-master",
+                "phpunit/phpunit": ">=4.8"
+            },
+            "suggest": {
+                "psr/cache-implementation": "Cache implementation to use with Minify::cache"
+            },
+            "time": "2022-08-01T09:00:18+00:00",
+            "bin": [
+                "bin/minifycss",
+                "bin/minifyjs"
+            ],
+            "type": "library",
+            "installation-source": "dist",
+            "autoload": {
+                "psr-4": {
+                    "MatthiasMullie\\Minify\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Matthias Mullie",
+                    "email": "minify@mullie.eu",
+                    "homepage": "http://www.mullie.eu",
+                    "role": "Developer"
+                }
+            ],
+            "description": "CSS & JavaScript minifier, in PHP. Removes whitespace, strips comments, combines files (incl. @import statements and small assets in CSS files), and optimizes/shortens a few common programming patterns.",
+            "homepage": "http://www.minifier.org",
+            "keywords": [
+                "JS",
+                "css",
+                "javascript",
+                "minifier",
+                "minify"
+            ],
+            "support": {
+                "issues": "https://github.com/matthiasmullie/minify/issues",
+                "source": "https://github.com/matthiasmullie/minify/tree/1.3.69"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/matthiasmullie",
+                    "type": "github"
+                }
+            ],
+            "install-path": "../matthiasmullie/minify"
+        },
+        {
+            "name": "matthiasmullie/path-converter",
+            "version": "1.1.3",
+            "version_normalized": "1.1.3.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/matthiasmullie/path-converter.git",
+                "reference": "e7d13b2c7e2f2268e1424aaed02085518afa02d9"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/matthiasmullie/path-converter/zipball/e7d13b2c7e2f2268e1424aaed02085518afa02d9",
+                "reference": "e7d13b2c7e2f2268e1424aaed02085518afa02d9",
+                "shasum": ""
+            },
+            "require": {
+                "ext-pcre": "*",
+                "php": ">=5.3.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.8"
+            },
+            "time": "2019-02-05T23:41:09+00:00",
+            "type": "library",
+            "installation-source": "dist",
+            "autoload": {
+                "psr-4": {
+                    "MatthiasMullie\\PathConverter\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Matthias Mullie",
+                    "email": "pathconverter@mullie.eu",
+                    "homepage": "http://www.mullie.eu",
+                    "role": "Developer"
+                }
+            ],
+            "description": "Relative path converter",
+            "homepage": "http://github.com/matthiasmullie/path-converter",
+            "keywords": [
+                "converter",
+                "path",
+                "paths",
+                "relative"
+            ],
+            "support": {
+                "issues": "https://github.com/matthiasmullie/path-converter/issues",
+                "source": "https://github.com/matthiasmullie/path-converter/tree/1.1.3"
+            },
+            "install-path": "../matthiasmullie/path-converter"
+        },
+        {
+            "name": "mpratt/embera",
+            "version": "2.0.27",
+            "version_normalized": "2.0.27.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/mpratt/Embera.git",
+                "reference": "9c16a9559fa75d9a9d5efddb17cd34555a6dff5e"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/mpratt/Embera/zipball/9c16a9559fa75d9a9d5efddb17cd34555a6dff5e",
+                "reference": "9c16a9559fa75d9a9d5efddb17cd34555a6dff5e",
+                "shasum": ""
+            },
+            "require": {
+                "ext-json": "*",
+                "php": ">=5.6"
+            },
+            "require-dev": {
+                "phpstan/phpstan": "^1.4",
+                "phpunit/phpunit": "^9.0"
+            },
+            "suggest": {
+                "ext-curl": "Fetch data using curl instead of using file_get_contents"
+            },
+            "time": "2022-07-15T01:01:46+00:00",
+            "type": "library",
+            "installation-source": "dist",
+            "autoload": {
+                "psr-4": {
+                    "Embera\\": "src/Embera"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Michael Pratt",
+                    "email": "yo@michael-pratt.com",
+                    "homepage": "http://www.michael-pratt.com",
+                    "role": "Author/Developer"
+                }
+            ],
+            "description": "Oembed consumer library. Converts urls into their html embed code. Supports 150+ sites, such as Youtube, Twitter, vimeo, Instagram etc.",
+            "homepage": "https://github.com/mpratt/Embera",
+            "keywords": [
+                "Auto embed",
+                "Embed Text",
+                "Responsive Embeds",
+                "Url Embed",
+                "embed",
+                "instagram",
+                "oembed",
+                "twitter",
+                "vimeo",
+                "vine",
+                "youtube"
+            ],
+            "support": {
+                "issues": "https://github.com/mpratt/Embera/issues",
+                "source": "https://github.com/mpratt/Embera/tree/2.0.27"
+            },
+            "funding": [
+                {
+                    "url": "https://paypal.me/mtpratt",
+                    "type": "paypal"
+                }
+            ],
+            "install-path": "../mpratt/embera"
+        },
+        {
+            "name": "paypal/paypal-checkout-sdk",
+            "version": "1.0.2",
+            "version_normalized": "1.0.2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/paypal/Checkout-PHP-SDK.git",
+                "reference": "19992ce7051ff9e47e643f28abb8cc1b3e5f1812"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/paypal/Checkout-PHP-SDK/zipball/19992ce7051ff9e47e643f28abb8cc1b3e5f1812",
+                "reference": "19992ce7051ff9e47e643f28abb8cc1b3e5f1812",
+                "shasum": ""
+            },
+            "require": {
+                "paypal/paypalhttp": "1.0.1"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^5.7"
+            },
+            "time": "2021-09-21T20:57:38+00:00",
+            "type": "library",
+            "installation-source": "dist",
+            "autoload": {
+                "psr-4": {
+                    "Sample\\": "samples/",
+                    "PayPalCheckoutSdk\\": "lib/PayPalCheckoutSdk"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "https://github.com/paypal/Checkout-PHP-SDK/blob/master/LICENSE"
+            ],
+            "authors": [
+                {
+                    "name": "PayPal",
+                    "homepage": "https://github.com/paypal/Checkout-PHP-SDK/contributors"
+                }
+            ],
+            "description": "PayPal's PHP SDK for Checkout REST APIs",
+            "homepage": "http://github.com/paypal/Checkout-PHP-SDK/",
+            "keywords": [
+                "checkout",
+                "orders",
+                "payments",
+                "paypal",
+                "rest",
+                "sdk"
+            ],
+            "support": {
+                "source": "https://github.com/paypal/Checkout-PHP-SDK/tree/1.0.2"
+            },
+            "abandoned": true,
+            "install-path": "../paypal/paypal-checkout-sdk"
+        },
+        {
+            "name": "paypal/paypalhttp",
+            "version": "1.0.1",
+            "version_normalized": "1.0.1.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/paypal/paypalhttp_php.git",
+                "reference": "7b09c89c80828e842c79230e7f156b61fbb68d25"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/paypal/paypalhttp_php/zipball/7b09c89c80828e842c79230e7f156b61fbb68d25",
+                "reference": "7b09c89c80828e842c79230e7f156b61fbb68d25",
+                "shasum": ""
+            },
+            "require": {
+                "ext-curl": "*"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^5.7",
+                "wiremock-php/wiremock-php": "1.43.2"
+            },
+            "time": "2021-09-14T21:35:26+00:00",
+            "type": "library",
+            "installation-source": "dist",
+            "autoload": {
+                "psr-4": {
+                    "PayPalHttp\\": "lib/PayPalHttp"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PayPal",
+                    "homepage": "https://github.com/paypal/paypalhttp_php/contributors"
+                }
+            ],
+            "support": {
+                "issues": "https://github.com/paypal/paypalhttp_php/issues",
+                "source": "https://github.com/paypal/paypalhttp_php/tree/1.0.1"
+            },
+            "install-path": "../paypal/paypalhttp"
+        },
+        {
+            "name": "phpmailer/phpmailer",
+            "version": "v6.6.4",
+            "version_normalized": "6.6.4.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/PHPMailer/PHPMailer.git",
+                "reference": "a94fdebaea6bd17f51be0c2373ab80d3d681269b"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/a94fdebaea6bd17f51be0c2373ab80d3d681269b",
+                "reference": "a94fdebaea6bd17f51be0c2373ab80d3d681269b",
+                "shasum": ""
+            },
+            "require": {
+                "ext-ctype": "*",
+                "ext-filter": "*",
+                "ext-hash": "*",
+                "php": ">=5.5.0"
+            },
+            "require-dev": {
+                "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0",
+                "doctrine/annotations": "^1.2",
+                "php-parallel-lint/php-console-highlighter": "^1.0.0",
+                "php-parallel-lint/php-parallel-lint": "^1.3.2",
+                "phpcompatibility/php-compatibility": "^9.3.5",
+                "roave/security-advisories": "dev-latest",
+                "squizlabs/php_codesniffer": "^3.6.2",
+                "yoast/phpunit-polyfills": "^1.0.0"
+            },
+            "suggest": {
+                "ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses",
+                "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication",
+                "league/oauth2-google": "Needed for Google XOAUTH2 authentication",
+                "psr/log": "For optional PSR-3 debug logging",
+                "stevenmaguire/oauth2-microsoft": "Needed for Microsoft XOAUTH2 authentication",
+                "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)"
+            },
+            "time": "2022-08-22T09:22:00+00:00",
+            "type": "library",
+            "installation-source": "dist",
+            "autoload": {
+                "psr-4": {
+                    "PHPMailer\\PHPMailer\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "LGPL-2.1-only"
+            ],
+            "authors": [
+                {
+                    "name": "Marcus Bointon",
+                    "email": "phpmailer@synchromedia.co.uk"
+                },
+                {
+                    "name": "Jim Jagielski",
+                    "email": "jimjag@gmail.com"
+                },
+                {
+                    "name": "Andy Prevost",
+                    "email": "codeworxtech@users.sourceforge.net"
+                },
+                {
+                    "name": "Brent R. Matzelle"
+                }
+            ],
+            "description": "PHPMailer is a full-featured email creation and transfer class for PHP",
+            "support": {
+                "issues": "https://github.com/PHPMailer/PHPMailer/issues",
+                "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.6.4"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/Synchro",
+                    "type": "github"
+                }
+            ],
+            "install-path": "../phpmailer/phpmailer"
+        },
+        {
+            "name": "psr/http-client",
+            "version": "1.0.1",
+            "version_normalized": "1.0.1.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/http-client.git",
+                "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621",
+                "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.0 || ^8.0",
+                "psr/http-message": "^1.0"
+            },
+            "time": "2020-06-29T06:28:15+00:00",
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "installation-source": "dist",
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Http\\Client\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interface for HTTP clients",
+            "homepage": "https://github.com/php-fig/http-client",
+            "keywords": [
+                "http",
+                "http-client",
+                "psr",
+                "psr-18"
+            ],
+            "support": {
+                "source": "https://github.com/php-fig/http-client/tree/master"
+            },
+            "install-path": "../psr/http-client"
+        },
+        {
+            "name": "psr/http-factory",
+            "version": "1.0.2",
+            "version_normalized": "1.0.2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/http-factory.git",
+                "reference": "e616d01114759c4c489f93b099585439f795fe35"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35",
+                "reference": "e616d01114759c4c489f93b099585439f795fe35",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.0.0",
+                "psr/http-message": "^1.0 || ^2.0"
+            },
+            "time": "2023-04-10T20:10:41+00:00",
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "installation-source": "dist",
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Http\\Message\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "https://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interfaces for PSR-7 HTTP message factories",
+            "keywords": [
+                "factory",
+                "http",
+                "message",
+                "psr",
+                "psr-17",
+                "psr-7",
+                "request",
+                "response"
+            ],
+            "support": {
+                "source": "https://github.com/php-fig/http-factory/tree/1.0.2"
+            },
+            "install-path": "../psr/http-factory"
+        },
+        {
+            "name": "psr/http-message",
+            "version": "1.1",
+            "version_normalized": "1.1.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/http-message.git",
+                "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/http-message/zipball/cb6ce4845ce34a8ad9e68117c10ee90a29919eba",
+                "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.2 || ^8.0"
+            },
+            "time": "2023-04-04T09:50:52+00:00",
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.1.x-dev"
+                }
+            },
+            "installation-source": "dist",
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Http\\Message\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interface for HTTP messages",
+            "homepage": "https://github.com/php-fig/http-message",
+            "keywords": [
+                "http",
+                "http-message",
+                "psr",
+                "psr-7",
+                "request",
+                "response"
+            ],
+            "support": {
+                "source": "https://github.com/php-fig/http-message/tree/1.1"
+            },
+            "install-path": "../psr/http-message"
+        },
+        {
+            "name": "ralouphie/getallheaders",
+            "version": "3.0.3",
+            "version_normalized": "3.0.3.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/ralouphie/getallheaders.git",
+                "reference": "120b605dfeb996808c31b6477290a714d356e822"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
+                "reference": "120b605dfeb996808c31b6477290a714d356e822",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.6"
+            },
+            "require-dev": {
+                "php-coveralls/php-coveralls": "^2.1",
+                "phpunit/phpunit": "^5 || ^6.5"
+            },
+            "time": "2019-03-08T08:55:37+00:00",
+            "type": "library",
+            "installation-source": "dist",
+            "autoload": {
+                "files": [
+                    "src/getallheaders.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Ralph Khattar",
+                    "email": "ralph.khattar@gmail.com"
+                }
+            ],
+            "description": "A polyfill for getallheaders.",
+            "support": {
+                "issues": "https://github.com/ralouphie/getallheaders/issues",
+                "source": "https://github.com/ralouphie/getallheaders/tree/develop"
+            },
+            "install-path": "../ralouphie/getallheaders"
+        },
+        {
+            "name": "snipe/banbuilder",
+            "version": "dev-master",
+            "version_normalized": "dev-master",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/snipe/banbuilder.git",
+                "reference": "621d6d857400983d0fd6cd291f105a835a317748"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/snipe/banbuilder/zipball/621d6d857400983d0fd6cd291f105a835a317748",
+                "reference": "621d6d857400983d0fd6cd291f105a835a317748",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.6.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^5.7.0"
+            },
+            "time": "2018-08-30T22:45:56+00:00",
+            "default-branch": true,
+            "type": "library",
+            "installation-source": "dist",
+            "autoload": {
+                "psr-4": {
+                    "Snipe\\BanBuilder\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "AGPL-3.0"
+            ],
+            "authors": [
+                {
+                    "name": "snipe",
+                    "email": "snipe@snipe.net",
+                    "role": "Creator & Developer"
+                }
+            ],
+            "description": "Library to easily handle profanity filtering in PHP projects.",
+            "homepage": "http://banbuilder.com",
+            "keywords": [
+                "filter",
+                "language",
+                "profanity",
+                "strings",
+                "swear"
+            ],
+            "support": {
+                "issues": "https://github.com/snipe/banbuilder/issues",
+                "source": "https://github.com/snipe/banbuilder/tree/2.3.0"
+            },
+            "install-path": "../snipe/banbuilder"
+        },
+        {
+            "name": "spatie/calendar-links",
+            "version": "1.8.0",
+            "version_normalized": "1.8.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/spatie/calendar-links.git",
+                "reference": "654fc0174e6279539b9bf0abdc3339e34c5286d7"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/spatie/calendar-links/zipball/654fc0174e6279539b9bf0abdc3339e34c5286d7",
+                "reference": "654fc0174e6279539b9bf0abdc3339e34c5286d7",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.4 || ^8.0"
+            },
+            "require-dev": {
+                "friendsofphp/php-cs-fixer": "^3.0",
+                "phpunit/phpunit": "^9.5",
+                "spatie/phpunit-snapshot-assertions": "^4.0",
+                "vimeo/psalm": "^4.20"
+            },
+            "time": "2022-08-20T12:59:17+00:00",
+            "type": "library",
+            "installation-source": "dist",
+            "autoload": {
+                "psr-4": {
+                    "Spatie\\CalendarLinks\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian De Deyne",
+                    "email": "sebastian@spatie.be",
+                    "homepage": "https://spatie.be",
+                    "role": "Developer"
+                }
+            ],
+            "description": "Generate add to calendar links for Google, iCal and other calendar systems",
+            "homepage": "https://github.com/spatie/calendar-links",
+            "keywords": [
+                "calendar-links",
+                "spatie"
+            ],
+            "support": {
+                "issues": "https://github.com/spatie/calendar-links/issues",
+                "source": "https://github.com/spatie/calendar-links/tree/1.8.0"
+            },
+            "funding": [
+                {
+                    "url": "https://spatie.be/open-source/support-us",
+                    "type": "custom"
+                }
+            ],
+            "install-path": "../spatie/calendar-links"
+        },
+        {
+            "name": "stripe/stripe-php",
+            "version": "v7.128.0",
+            "version_normalized": "7.128.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/stripe/stripe-php.git",
+                "reference": "c704949c49b72985c76cc61063aa26fefbd2724e"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/stripe/stripe-php/zipball/c704949c49b72985c76cc61063aa26fefbd2724e",
+                "reference": "c704949c49b72985c76cc61063aa26fefbd2724e",
+                "shasum": ""
+            },
+            "require": {
+                "ext-curl": "*",
+                "ext-json": "*",
+                "ext-mbstring": "*",
+                "php": ">=5.6.0"
+            },
+            "require-dev": {
+                "friendsofphp/php-cs-fixer": "3.5.0",
+                "phpstan/phpstan": "^1.2",
+                "phpunit/phpunit": "^5.7 || ^9.0",
+                "squizlabs/php_codesniffer": "^3.3"
+            },
+            "time": "2022-05-05T17:18:02+00:00",
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0-dev"
+                }
+            },
+            "installation-source": "dist",
+            "autoload": {
+                "psr-4": {
+                    "Stripe\\": "lib/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Stripe and contributors",
+                    "homepage": "https://github.com/stripe/stripe-php/contributors"
+                }
+            ],
+            "description": "Stripe PHP Library",
+            "homepage": "https://stripe.com/",
+            "keywords": [
+                "api",
+                "payment processing",
+                "stripe"
+            ],
+            "support": {
+                "issues": "https://github.com/stripe/stripe-php/issues",
+                "source": "https://github.com/stripe/stripe-php/tree/v7.128.0"
+            },
+            "install-path": "../stripe/stripe-php"
+        },
+        {
+            "name": "symfony/deprecation-contracts",
+            "version": "v2.5.2",
+            "version_normalized": "2.5.2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/deprecation-contracts.git",
+                "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e8b495ea28c1d97b5e0c121748d6f9b53d075c66",
+                "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.1"
+            },
+            "time": "2022-01-02T09:53:40+00:00",
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "2.5-dev"
+                },
+                "thanks": {
+                    "name": "symfony/contracts",
+                    "url": "https://github.com/symfony/contracts"
+                }
+            },
+            "installation-source": "dist",
+            "autoload": {
+                "files": [
+                    "function.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "A generic function and convention to trigger deprecation notices",
+            "homepage": "https://symfony.com",
+            "support": {
+                "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.2"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "install-path": "../symfony/deprecation-contracts"
+        },
+        {
+            "name": "tpyo/amazon-s3-php-class",
+            "version": "dev-master",
+            "version_normalized": "dev-master",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/unaio/amazon-s3-php-class.git",
+                "reference": "7c62b0e9b7d956a04e377ca236979a78dbac4e66"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/unaio/amazon-s3-php-class/zipball/7c62b0e9b7d956a04e377ca236979a78dbac4e66",
+                "reference": "7c62b0e9b7d956a04e377ca236979a78dbac4e66",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.2.0"
+            },
+            "time": "2022-06-30T03:20:34+00:00",
+            "default-branch": true,
+            "type": "library",
+            "installation-source": "dist",
+            "autoload": {
+                "classmap": [
+                    "S3.php"
+                ]
+            },
+            "license": [
+                "BSD-2-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Donovan Schönknecht",
+                    "email": "don@tpyo.net"
+                }
+            ],
+            "description": "A standalone Amazon S3 (REST) client for PHP 5.2.x using CURL that does not require PEAR.",
+            "homepage": "https://github.com/tpyo/amazon-s3-php-class",
+            "support": {
+                "source": "https://github.com/unaio/amazon-s3-php-class/tree/master"
+            },
+            "install-path": "../tpyo/amazon-s3-php-class"
+        },
+        {
+            "name": "twilio/sdk",
+            "version": "5.42.2",
+            "version_normalized": "5.42.2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/twilio/twilio-php.git",
+                "reference": "0cfcb871b18a9c427dd9e8f0ed7458d43009b48a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/twilio/twilio-php/zipball/0cfcb871b18a9c427dd9e8f0ed7458d43009b48a",
+                "reference": "0cfcb871b18a9c427dd9e8f0ed7458d43009b48a",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.5.0"
+            },
+            "require-dev": {
+                "apigen/apigen": "^4.1",
+                "guzzlehttp/guzzle": "^6.3",
+                "phpunit/phpunit": ">=4.5"
+            },
+            "suggest": {
+                "guzzlehttp/guzzle": "An HTTP client to execute the API requests"
+            },
+            "time": "2020-02-05T19:55:13+00:00",
+            "type": "library",
+            "installation-source": "dist",
+            "autoload": {
+                "psr-4": {
+                    "Twilio\\": "src/Twilio/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Twilio API Team",
+                    "email": "api@twilio.com"
+                }
+            ],
+            "description": "A PHP wrapper for Twilio's API",
+            "homepage": "http://github.com/twilio/twilio-php",
+            "keywords": [
+                "api",
+                "sms",
+                "twilio"
+            ],
+            "support": {
+                "issues": "https://github.com/twilio/twilio-php/issues",
+                "source": "https://github.com/twilio/twilio-php/tree/5.42.2"
+            },
+            "install-path": "../twilio/sdk"
+        },
+        {
+            "name": "unaio/amazon-s3-php-class-hmac-v2",
+            "version": "0.6",
+            "version_normalized": "0.6.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/unaio/amazon-s3-php-class-hmac-v2",
+                "reference": "3687dec0af7be2cb11f21828aa4b12d20c4c5935"
+            },
+            "type": "library",
+            "installation-source": "source",
+            "install-path": "../unaio/amazon-s3-php-class-hmac-v2"
+        },
+        {
+            "name": "wikimedia/less.php",
+            "version": "v3.1.0",
+            "version_normalized": "3.1.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/wikimedia/less.php.git",
+                "reference": "a486d78b9bd16b72f237fc6093aa56d69ce8bd13"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/wikimedia/less.php/zipball/a486d78b9bd16b72f237fc6093aa56d69ce8bd13",
+                "reference": "a486d78b9bd16b72f237fc6093aa56d69ce8bd13",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.2.9"
+            },
+            "require-dev": {
+                "mediawiki/mediawiki-codesniffer": "34.0.0",
+                "mediawiki/minus-x": "1.0.0",
+                "php-parallel-lint/php-console-highlighter": "0.5.0",
+                "php-parallel-lint/php-parallel-lint": "1.2.0",
+                "phpunit/phpunit": "^8.5"
+            },
+            "time": "2020-12-11T19:33:31+00:00",
+            "bin": [
+                "bin/lessc"
+            ],
+            "type": "library",
+            "installation-source": "dist",
+            "autoload": {
+                "psr-0": {
+                    "Less": "lib/"
+                },
+                "classmap": [
+                    "lessc.inc.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "Josh Schmidt",
+                    "homepage": "https://github.com/oyejorge"
+                },
+                {
+                    "name": "Matt Agar",
+                    "homepage": "https://github.com/agar"
+                },
+                {
+                    "name": "Martin Jantošovič",
+                    "homepage": "https://github.com/Mordred"
+                }
+            ],
+            "description": "PHP port of the Javascript version of LESS http://lesscss.org (Originally maintained by Josh Schmidt)",
+            "keywords": [
+                "css",
+                "less",
+                "less.js",
+                "lesscss",
+                "php",
+                "stylesheet"
+            ],
+            "support": {
+                "issues": "https://github.com/wikimedia/less.php/issues",
+                "source": "https://github.com/wikimedia/less.php/tree/v3.1.0"
+            },
+            "install-path": "../wikimedia/less.php"
+        }
+    ],
+    "dev": true,
+    "dev-package-names": []
+}

+ 348 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins/composer/installed.php

@@ -0,0 +1,348 @@
+<?php return array(
+    'root' => array(
+        'pretty_version' => 'dev-master',
+        'version' => 'dev-master',
+        'type' => 'library',
+        'install_path' => __DIR__ . '/../../',
+        'aliases' => array(),
+        'reference' => '6573d838c28a78276c0e4f7ba72ce20ffcd2bc1e',
+        'name' => 'unaio/una',
+        'dev' => true,
+    ),
+    'versions' => array(
+        'achingbrain/php5-akismet' => array(
+            'pretty_version' => '0.5',
+            'version' => '0.5.0.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../achingbrain/php5-akismet',
+            'aliases' => array(),
+            'reference' => 'master',
+            'dev_requirement' => false,
+        ),
+        'akeeba/s3' => array(
+            'pretty_version' => 'dev-development',
+            'version' => 'dev-development',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../akeeba/s3',
+            'aliases' => array(
+                0 => '9999999-dev',
+            ),
+            'reference' => '6448ddf99541f3ba50f97e373ceeb5bfd42a0167',
+            'dev_requirement' => false,
+        ),
+        'avto-dev/composer-cleanup-plugin' => array(
+            'pretty_version' => 'dev-master',
+            'version' => 'dev-master',
+            'type' => 'composer-plugin',
+            'install_path' => __DIR__ . '/../avto-dev/composer-cleanup-plugin',
+            'aliases' => array(
+                0 => '9999999-dev',
+            ),
+            'reference' => '75187ca4854265174021765e3b67cb973dc37723',
+            'dev_requirement' => false,
+        ),
+        'bshaffer/oauth2-server-php' => array(
+            'pretty_version' => 'v1.12.1',
+            'version' => '1.12.1.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../bshaffer/oauth2-server-php',
+            'aliases' => array(),
+            'reference' => '2bfaf9d7bbebe2ba1c1deb48e756ba0b3af4e985',
+            'dev_requirement' => false,
+        ),
+        'chargebee/chargebee-php' => array(
+            'pretty_version' => 'v3.11.0',
+            'version' => '3.11.0.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../chargebee/chargebee-php',
+            'aliases' => array(),
+            'reference' => '2b74d71005693f8f11cc2ef1aea07c48a8f8e155',
+            'dev_requirement' => false,
+        ),
+        'curl/cacert' => array(
+            'pretty_version' => '0.0.1',
+            'version' => '0.0.1.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../curl/cacert',
+            'aliases' => array(),
+            'reference' => NULL,
+            'dev_requirement' => false,
+        ),
+        'erusev/parsedown' => array(
+            'pretty_version' => 'dev-master',
+            'version' => 'dev-master',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../erusev/parsedown',
+            'aliases' => array(
+                0 => '9999999-dev',
+            ),
+            'reference' => '6598f3860c2698fe2f0f1bc98212fc01d0a1893c',
+            'dev_requirement' => false,
+        ),
+        'ezyang/htmlpurifier' => array(
+            'pretty_version' => 'v4.16.0',
+            'version' => '4.16.0.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../ezyang/htmlpurifier',
+            'aliases' => array(),
+            'reference' => '523407fb06eb9e5f3d59889b3978d5bfe94299c8',
+            'dev_requirement' => false,
+        ),
+        'facebook/graph-sdk' => array(
+            'pretty_version' => '5.7.0',
+            'version' => '5.7.0.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../facebook/graph-sdk',
+            'aliases' => array(),
+            'reference' => '2d8250638b33d73e7a87add65f47fabf91f8ad9b',
+            'dev_requirement' => false,
+        ),
+        'ffmpeg/ffmpeg' => array(
+            'pretty_version' => '4.4.0',
+            'version' => '4.4.0.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../ffmpeg/ffmpeg',
+            'aliases' => array(),
+            'reference' => NULL,
+            'dev_requirement' => false,
+        ),
+        'guzzlehttp/guzzle' => array(
+            'pretty_version' => '7.5.0',
+            'version' => '7.5.0.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../guzzlehttp/guzzle',
+            'aliases' => array(),
+            'reference' => 'b50a2a1251152e43f6a37f0fa053e730a67d25ba',
+            'dev_requirement' => false,
+        ),
+        'guzzlehttp/promises' => array(
+            'pretty_version' => '1.5.2',
+            'version' => '1.5.2.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../guzzlehttp/promises',
+            'aliases' => array(),
+            'reference' => 'b94b2807d85443f9719887892882d0329d1e2598',
+            'dev_requirement' => false,
+        ),
+        'guzzlehttp/psr7' => array(
+            'pretty_version' => '2.5.0',
+            'version' => '2.5.0.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../guzzlehttp/psr7',
+            'aliases' => array(),
+            'reference' => 'b635f279edd83fc275f822a1188157ffea568ff6',
+            'dev_requirement' => false,
+        ),
+        'intervention/image' => array(
+            'pretty_version' => '2.7.2',
+            'version' => '2.7.2.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../intervention/image',
+            'aliases' => array(),
+            'reference' => '04be355f8d6734c826045d02a1079ad658322dad',
+            'dev_requirement' => false,
+        ),
+        'lusitanian/oauth' => array(
+            'pretty_version' => 'v0.8.11',
+            'version' => '0.8.11.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../lusitanian/oauth',
+            'aliases' => array(),
+            'reference' => 'fc11a53db4b66da555a6a11fce294f574a8374f9',
+            'dev_requirement' => false,
+        ),
+        'matthiasmullie/minify' => array(
+            'pretty_version' => '1.3.69',
+            'version' => '1.3.69.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../matthiasmullie/minify',
+            'aliases' => array(),
+            'reference' => 'a61c949cccd086808063611ef9698eabe42ef22f',
+            'dev_requirement' => false,
+        ),
+        'matthiasmullie/path-converter' => array(
+            'pretty_version' => '1.1.3',
+            'version' => '1.1.3.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../matthiasmullie/path-converter',
+            'aliases' => array(),
+            'reference' => 'e7d13b2c7e2f2268e1424aaed02085518afa02d9',
+            'dev_requirement' => false,
+        ),
+        'mpratt/embera' => array(
+            'pretty_version' => '2.0.27',
+            'version' => '2.0.27.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../mpratt/embera',
+            'aliases' => array(),
+            'reference' => '9c16a9559fa75d9a9d5efddb17cd34555a6dff5e',
+            'dev_requirement' => false,
+        ),
+        'paypal/paypal-checkout-sdk' => array(
+            'pretty_version' => '1.0.2',
+            'version' => '1.0.2.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../paypal/paypal-checkout-sdk',
+            'aliases' => array(),
+            'reference' => '19992ce7051ff9e47e643f28abb8cc1b3e5f1812',
+            'dev_requirement' => false,
+        ),
+        'paypal/paypalhttp' => array(
+            'pretty_version' => '1.0.1',
+            'version' => '1.0.1.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../paypal/paypalhttp',
+            'aliases' => array(),
+            'reference' => '7b09c89c80828e842c79230e7f156b61fbb68d25',
+            'dev_requirement' => false,
+        ),
+        'phpmailer/phpmailer' => array(
+            'pretty_version' => 'v6.6.4',
+            'version' => '6.6.4.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../phpmailer/phpmailer',
+            'aliases' => array(),
+            'reference' => 'a94fdebaea6bd17f51be0c2373ab80d3d681269b',
+            'dev_requirement' => false,
+        ),
+        'psr/http-client' => array(
+            'pretty_version' => '1.0.1',
+            'version' => '1.0.1.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../psr/http-client',
+            'aliases' => array(),
+            'reference' => '2dfb5f6c5eff0e91e20e913f8c5452ed95b86621',
+            'dev_requirement' => false,
+        ),
+        'psr/http-client-implementation' => array(
+            'dev_requirement' => false,
+            'provided' => array(
+                0 => '1.0',
+            ),
+        ),
+        'psr/http-factory' => array(
+            'pretty_version' => '1.0.2',
+            'version' => '1.0.2.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../psr/http-factory',
+            'aliases' => array(),
+            'reference' => 'e616d01114759c4c489f93b099585439f795fe35',
+            'dev_requirement' => false,
+        ),
+        'psr/http-factory-implementation' => array(
+            'dev_requirement' => false,
+            'provided' => array(
+                0 => '1.0',
+            ),
+        ),
+        'psr/http-message' => array(
+            'pretty_version' => '1.1',
+            'version' => '1.1.0.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../psr/http-message',
+            'aliases' => array(),
+            'reference' => 'cb6ce4845ce34a8ad9e68117c10ee90a29919eba',
+            'dev_requirement' => false,
+        ),
+        'psr/http-message-implementation' => array(
+            'dev_requirement' => false,
+            'provided' => array(
+                0 => '1.0',
+            ),
+        ),
+        'ralouphie/getallheaders' => array(
+            'pretty_version' => '3.0.3',
+            'version' => '3.0.3.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../ralouphie/getallheaders',
+            'aliases' => array(),
+            'reference' => '120b605dfeb996808c31b6477290a714d356e822',
+            'dev_requirement' => false,
+        ),
+        'snipe/banbuilder' => array(
+            'pretty_version' => 'dev-master',
+            'version' => 'dev-master',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../snipe/banbuilder',
+            'aliases' => array(
+                0 => '9999999-dev',
+            ),
+            'reference' => '621d6d857400983d0fd6cd291f105a835a317748',
+            'dev_requirement' => false,
+        ),
+        'spatie/calendar-links' => array(
+            'pretty_version' => '1.8.0',
+            'version' => '1.8.0.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../spatie/calendar-links',
+            'aliases' => array(),
+            'reference' => '654fc0174e6279539b9bf0abdc3339e34c5286d7',
+            'dev_requirement' => false,
+        ),
+        'stripe/stripe-php' => array(
+            'pretty_version' => 'v7.128.0',
+            'version' => '7.128.0.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../stripe/stripe-php',
+            'aliases' => array(),
+            'reference' => 'c704949c49b72985c76cc61063aa26fefbd2724e',
+            'dev_requirement' => false,
+        ),
+        'symfony/deprecation-contracts' => array(
+            'pretty_version' => 'v2.5.2',
+            'version' => '2.5.2.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../symfony/deprecation-contracts',
+            'aliases' => array(),
+            'reference' => 'e8b495ea28c1d97b5e0c121748d6f9b53d075c66',
+            'dev_requirement' => false,
+        ),
+        'tpyo/amazon-s3-php-class' => array(
+            'pretty_version' => 'dev-master',
+            'version' => 'dev-master',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../tpyo/amazon-s3-php-class',
+            'aliases' => array(
+                0 => '9999999-dev',
+            ),
+            'reference' => '7c62b0e9b7d956a04e377ca236979a78dbac4e66',
+            'dev_requirement' => false,
+        ),
+        'twilio/sdk' => array(
+            'pretty_version' => '5.42.2',
+            'version' => '5.42.2.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../twilio/sdk',
+            'aliases' => array(),
+            'reference' => '0cfcb871b18a9c427dd9e8f0ed7458d43009b48a',
+            'dev_requirement' => false,
+        ),
+        'unaio/amazon-s3-php-class-hmac-v2' => array(
+            'pretty_version' => '0.6',
+            'version' => '0.6.0.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../unaio/amazon-s3-php-class-hmac-v2',
+            'aliases' => array(),
+            'reference' => '3687dec0af7be2cb11f21828aa4b12d20c4c5935',
+            'dev_requirement' => false,
+        ),
+        'unaio/una' => array(
+            'pretty_version' => 'dev-master',
+            'version' => 'dev-master',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../../',
+            'aliases' => array(),
+            'reference' => '6573d838c28a78276c0e4f7ba72ce20ffcd2bc1e',
+            'dev_requirement' => false,
+        ),
+        'wikimedia/less.php' => array(
+            'pretty_version' => 'v3.1.0',
+            'version' => '3.1.0.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../wikimedia/less.php',
+            'aliases' => array(),
+            'reference' => 'a486d78b9bd16b72f237fc6093aa56d69ce8bd13',
+            'dev_requirement' => false,
+        ),
+    ),
+);

+ 93 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins/guzzlehttp/psr7/composer.json

@@ -0,0 +1,93 @@
+{
+    "name": "guzzlehttp/psr7",
+    "description": "PSR-7 message implementation that also provides common utility methods",
+    "keywords": [
+        "request",
+        "response",
+        "message",
+        "stream",
+        "http",
+        "uri",
+        "url",
+        "psr-7"
+    ],
+    "license": "MIT",
+    "authors": [
+        {
+            "name": "Graham Campbell",
+            "email": "hello@gjcampbell.co.uk",
+            "homepage": "https://github.com/GrahamCampbell"
+        },
+        {
+            "name": "Michael Dowling",
+            "email": "mtdowling@gmail.com",
+            "homepage": "https://github.com/mtdowling"
+        },
+        {
+            "name": "George Mponos",
+            "email": "gmponos@gmail.com",
+            "homepage": "https://github.com/gmponos"
+        },
+        {
+            "name": "Tobias Nyholm",
+            "email": "tobias.nyholm@gmail.com",
+            "homepage": "https://github.com/Nyholm"
+        },
+        {
+            "name": "Márk Sági-Kazár",
+            "email": "mark.sagikazar@gmail.com",
+            "homepage": "https://github.com/sagikazarmark"
+        },
+        {
+            "name": "Tobias Schultze",
+            "email": "webmaster@tubo-world.de",
+            "homepage": "https://github.com/Tobion"
+        },
+        {
+            "name": "Márk Sági-Kazár",
+            "email": "mark.sagikazar@gmail.com",
+            "homepage": "https://sagikazarmark.hu"
+        }
+    ],
+    "require": {
+        "php": "^7.2.5 || ^8.0",
+        "psr/http-factory": "^1.0",
+        "psr/http-message": "^1.1 || ^2.0",
+        "ralouphie/getallheaders": "^3.0"
+    },
+    "provide": {
+        "psr/http-factory-implementation": "1.0",
+        "psr/http-message-implementation": "1.0"
+    },
+    "require-dev": {
+        "bamarni/composer-bin-plugin": "^1.8.1",
+        "http-interop/http-factory-tests": "^0.9",
+        "phpunit/phpunit": "^8.5.29 || ^9.5.23"
+    },
+    "suggest": {
+        "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
+    },
+    "autoload": {
+        "psr-4": {
+            "GuzzleHttp\\Psr7\\": "src/"
+        }
+    },
+    "autoload-dev": {
+        "psr-4": {
+            "GuzzleHttp\\Tests\\Psr7\\": "tests/"
+        }
+    },
+    "extra": {
+        "bamarni-bin": {
+            "bin-links": true,
+            "forward-command": false
+        }
+    },
+    "config": {
+        "allow-plugins": {
+            "bamarni/composer-bin-plugin": true
+        },
+        "preferred-install": "dist",
+        "sort-packages": true
+    }
+}

+ 134 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins/guzzlehttp/psr7/src/Header.php

@@ -0,0 +1,134 @@
+<?php
+
+declare(strict_types=1);
+
+namespace GuzzleHttp\Psr7;
+
+final class Header
+{
+    /**
+     * Parse an array of header values containing ";" separated data into an
+     * array of associative arrays representing the header key value pair data
+     * of the header. When a parameter does not contain a value, but just
+     * contains a key, this function will inject a key with a '' string value.
+     *
+     * @param string|array $header Header to parse into components.
+     */
+    public static function parse($header): array
+    {
+        static $trimmed = "\"'  \n\t\r";
+        $params = $matches = [];
+
+        foreach ((array) $header as $value) {
+            foreach (self::splitList($value) as $val) {
+                $part = [];
+                foreach (preg_split('/;(?=([^"]*"[^"]*")*[^"]*$)/', $val) as $kvp) {
+                    if (preg_match_all('/<[^>]+>|[^=]+/', $kvp, $matches)) {
+                        $m = $matches[0];
+                        if (isset($m[1])) {
+                            $part[trim($m[0], $trimmed)] = trim($m[1], $trimmed);
+                        } else {
+                            $part[] = trim($m[0], $trimmed);
+                        }
+                    }
+                }
+                if ($part) {
+                    $params[] = $part;
+                }
+            }
+        }
+
+        return $params;
+    }
+
+    /**
+     * Converts an array of header values that may contain comma separated
+     * headers into an array of headers with no comma separated values.
+     *
+     * @param string|array $header Header to normalize.
+     *
+     * @deprecated Use self::splitList() instead.
+     */
+    public static function normalize($header): array
+    {
+        $result = [];
+        foreach ((array) $header as $value) {
+            foreach (self::splitList($value) as $parsed) {
+                $result[] = $parsed;
+            }
+        }
+
+        return $result;
+    }
+
+    /**
+     * Splits a HTTP header defined to contain a comma-separated list into
+     * each individual value. Empty values will be removed.
+     *
+     * Example headers include 'accept', 'cache-control' and 'if-none-match'.
+     *
+     * This method must not be used to parse headers that are not defined as
+     * a list, such as 'user-agent' or 'set-cookie'.
+     *
+     * @param string|string[] $values Header value as returned by MessageInterface::getHeader()
+     *
+     * @return string[]
+     */
+    public static function splitList($values): array
+    {
+        if (!\is_array($values)) {
+            $values = [$values];
+        }
+
+        $result = [];
+        foreach ($values as $value) {
+            if (!\is_string($value)) {
+                throw new \TypeError('$header must either be a string or an array containing strings.');
+            }
+
+            $v = '';
+            $isQuoted = false;
+            $isEscaped = false;
+            for ($i = 0, $max = \strlen($value); $i < $max; $i++) {
+                if ($isEscaped) {
+                    $v .= $value[$i];
+                    $isEscaped = false;
+
+                    continue;
+                }
+
+                if (!$isQuoted && $value[$i] === ',') {
+                    $v = \trim($v);
+                    if ($v !== '') {
+                        $result[] = $v;
+                    }
+
+                    $v = '';
+                    continue;
+                }
+
+                if ($isQuoted && $value[$i] === '\\') {
+                    $isEscaped = true;
+                    $v .= $value[$i];
+
+                    continue;
+                }
+                if ($value[$i] === '"') {
+                    $isQuoted = !$isQuoted;
+                    $v .= $value[$i];
+
+                    continue;
+                }
+
+                $v .= $value[$i];
+            }
+
+            $v = \trim($v);
+            if ($v !== '') {
+                $result[] = $v;
+            }
+        }
+
+        return $result;
+    }
+}

+ 49 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins/guzzlehttp/psr7/src/LazyOpenStream.php

@@ -0,0 +1,49 @@
+<?php
+
+declare(strict_types=1);
+
+namespace GuzzleHttp\Psr7;
+
+use Psr\Http\Message\StreamInterface;
+
+/**
+ * Lazily reads or writes to a file that is opened only after an IO operation
+ * take place on the stream.
+ */
+final class LazyOpenStream implements StreamInterface
+{
+    use StreamDecoratorTrait;
+
+    /** @var string */
+    private $filename;
+
+    /** @var string */
+    private $mode;
+
+    /**
+     * @var StreamInterface
+     */
+    private $stream;
+
+    /**
+     * @param string $filename File to lazily open
+     * @param string $mode     fopen mode to use when opening the stream
+     */
+    public function __construct(string $filename, string $mode)
+    {
+        $this->filename = $filename;
+        $this->mode = $mode;
+
+        // unsetting the property forces the first access to go through
+        // __get().
+        unset($this->stream);
+    }
+
+    /**
+     * Creates the underlying stream lazily when required.
+     */
+    protected function createStream(): StreamInterface
+    {
+        return Utils::streamFor(Utils::tryFopen($this->filename, $this->mode));
+    }
+}

+ 246 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins/guzzlehttp/psr7/src/Message.php

@@ -0,0 +1,246 @@
+<?php
+
+declare(strict_types=1);
+
+namespace GuzzleHttp\Psr7;
+
+use Psr\Http\Message\MessageInterface;
+use Psr\Http\Message\RequestInterface;
+use Psr\Http\Message\ResponseInterface;
+
+final class Message
+{
+    /**
+     * Returns the string representation of an HTTP message.
+     *
+     * @param MessageInterface $message Message to convert to a string.
+     */
+    public static function toString(MessageInterface $message): string
+    {
+        if ($message instanceof RequestInterface) {
+            $msg = trim($message->getMethod() . ' '
+                    . $message->getRequestTarget())
+                . ' HTTP/' . $message->getProtocolVersion();
+            if (!$message->hasHeader('host')) {
+                $msg .= "\r\nHost: " . $message->getUri()->getHost();
+            }
+        } elseif ($message instanceof ResponseInterface) {
+            $msg = 'HTTP/' . $message->getProtocolVersion() . ' '
+                . $message->getStatusCode() . ' '
+                . $message->getReasonPhrase();
+        } else {
+            throw new \InvalidArgumentException('Unknown message type');
+        }
+
+        foreach ($message->getHeaders() as $name => $values) {
+            if (strtolower($name) === 'set-cookie') {
+                foreach ($values as $value) {
+                    $msg .= "\r\n{$name}: " . $value;
+                }
+            } else {
+                $msg .= "\r\n{$name}: " . implode(', ', $values);
+            }
+        }
+
+        return "{$msg}\r\n\r\n" . $message->getBody();
+    }
+
+    /**
+     * Get a short summary of the message body.
+     *
+     * Will return `null` if the response is not printable.
+     *
+     * @param MessageInterface $message    The message to get the body summary
+     * @param int              $truncateAt The maximum allowed size of the summary
+     */
+    public static function bodySummary(MessageInterface $message, int $truncateAt = 120): ?string
+    {
+        $body = $message->getBody();
+
+        if (!$body->isSeekable() || !$body->isReadable()) {
+            return null;
+        }
+
+        $size = $body->getSize();
+
+        if ($size === 0) {
+            return null;
+        }
+
+        $body->rewind();
+        $summary = $body->read($truncateAt);
+        $body->rewind();
+
+        if ($size > $truncateAt) {
+            $summary .= ' (truncated...)';
+        }
+
+        // Matches any printable character, including unicode characters:
+        // letters, marks, numbers, punctuation, spacing, and separators.
+        if (preg_match('/[^\pL\pM\pN\pP\pS\pZ\n\r\t]/u', $summary) !== 0) {
+            return null;
+        }
+
+        return $summary;
+    }
+
+    /**
+     * Attempts to rewind a message body and throws an exception on failure.
+     *
+     * The body of the message will only be rewound if a call to `tell()`
+     * returns a value other than `0`.
+     *
+     * @param MessageInterface $message Message to rewind
+     *
+     * @throws \RuntimeException
+     */
+    public static function rewindBody(MessageInterface $message): void
+    {
+        $body = $message->getBody();
+
+        if ($body->tell()) {
+            $body->rewind();
+        }
+    }
+
+    /**
+     * Parses an HTTP message into an associative array.
+     *
+     * The array contains the "start-line" key containing the start line of
+     * the message, "headers" key containing an associative array of header
+     * array values, and a "body" key containing the body of the message.
+     *
+     * @param string $message HTTP request or response to parse.
+     */
+    public static function parseMessage(string $message): array
+    {
+        if (!$message) {
+            throw new \InvalidArgumentException('Invalid message');
+        }
+
+        $message = ltrim($message, "\r\n");
+
+        $messageParts = preg_split("/\r?\n\r?\n/", $message, 2);
+
+        if ($messageParts === false || count($messageParts) !== 2) {
+            throw new \InvalidArgumentException('Invalid message: Missing header delimiter');
+        }
+
+        [$rawHeaders, $body] = $messageParts;
+        $rawHeaders .= "\r\n"; // Put back the delimiter we split previously
+        $headerParts = preg_split("/\r?\n/", $rawHeaders, 2);
+
+        if ($headerParts === false || count($headerParts) !== 2) {
+            throw new \InvalidArgumentException('Invalid message: Missing status line');
+        }
+
+        [$startLine, $rawHeaders] = $headerParts;
+
+        if (preg_match("/(?:^HTTP\/|^[A-Z]+ \S+ HTTP\/)(\d+(?:\.\d+)?)/i", $startLine, $matches) && $matches[1] === '1.0') {
+            // Header folding is deprecated for HTTP/1.1, but allowed in HTTP/1.0
+            $rawHeaders = preg_replace(Rfc7230::HEADER_FOLD_REGEX, ' ', $rawHeaders);
+        }
+
+        /** @var array[] $headerLines */
+        $count = preg_match_all(Rfc7230::HEADER_REGEX, $rawHeaders, $headerLines, PREG_SET_ORDER);
+
+        // If these aren't the same, then one line didn't match and there's an invalid header.
+        if ($count !== substr_count($rawHeaders, "\n")) {
+            // Folding is deprecated, see https://tools.ietf.org/html/rfc7230#section-3.2.4
+            if (preg_match(Rfc7230::HEADER_FOLD_REGEX, $rawHeaders)) {
+                throw new \InvalidArgumentException('Invalid header syntax: Obsolete line folding');
+            }
+
+            throw new \InvalidArgumentException('Invalid header syntax');
+        }
+
+        $headers = [];
+
+        foreach ($headerLines as $headerLine) {
+            $headers[$headerLine[1]][] = $headerLine[2];
+        }
+
+        return [
+            'start-line' => $startLine,
+            'headers' => $headers,
+            'body' => $body,
+        ];
+    }
+
+    /**
+     * Constructs a URI for an HTTP request message.
+     *
+     * @param string $path    Path from the start-line
+     * @param array  $headers Array of headers (each value an array).
+     */
+    public static function parseRequestUri(string $path, array $headers): string
+    {
+        $hostKey = array_filter(array_keys($headers), function ($k) {
+            // Numeric array keys are converted to int by PHP.
+            $k = (string) $k;
+
+            return strtolower($k) === 'host';
+        });
+
+        // If no host is found, then a full URI cannot be constructed.
+        if (!$hostKey) {
+            return $path;
+        }
+
+        $host = $headers[reset($hostKey)][0];
+        $scheme = substr($host, -4) === ':443' ? 'https' : 'http';
+
+        return $scheme . '://' . $host . '/' . ltrim($path, '/');
+    }
+
+    /**
+     * Parses a request message string into a request object.
+     *
+     * @param string $message Request message string.
+     */
+    public static function parseRequest(string $message): RequestInterface
+    {
+        $data = self::parseMessage($message);
+        $matches = [];
+        if (!preg_match('/^[\S]+\s+([a-zA-Z]+:\/\/|\/).*/', $data['start-line'], $matches)) {
+            throw new \InvalidArgumentException('Invalid request string');
+        }
+        $parts = explode(' ', $data['start-line'], 3);
+        $version = isset($parts[2]) ? explode('/', $parts[2])[1] : '1.1';
+
+        $request = new Request(
+            $parts[0],
+            $matches[1] === '/' ? self::parseRequestUri($parts[1], $data['headers']) : $parts[1],
+            $data['headers'],
+            $data['body'],
+            $version
+        );
+
+        return $matches[1] === '/' ? $request : $request->withRequestTarget($parts[1]);
+    }
+
+    /**
+     * Parses a response message string into a response object.
+     *
+     * @param string $message Response message string.
+     */
+    public static function parseResponse(string $message): ResponseInterface
+    {
+        $data = self::parseMessage($message);
+        // According to https://tools.ietf.org/html/rfc7230#section-3.1.2 the space
+        // between status-code and reason-phrase is required. But browsers accept
+        // responses without space and reason as well.
+        if (!preg_match('/^HTTP\/.* [0-9]{3}( .*|$)/', $data['start-line'])) {
+            throw new \InvalidArgumentException('Invalid response string: ' . $data['start-line']);
+        }
+        $parts = explode(' ', $data['start-line'], 3);
+
+        return new Response(
+            (int) $parts[1],
+            $data['headers'],
+            $data['body'],
+            explode('/', $parts[0])[1],
+            $parts[2] ?? null
+        );
+    }
+}

+ 263 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins/guzzlehttp/psr7/src/MessageTrait.php

@@ -0,0 +1,263 @@
+<?php
+
+declare(strict_types=1);
+
+namespace GuzzleHttp\Psr7;
+
+use Psr\Http\Message\MessageInterface;
+use Psr\Http\Message\StreamInterface;
+
+/**
+ * Trait implementing functionality common to requests and responses.
+ */
+trait MessageTrait
+{
+    /** @var array<string, string[]> Map of all registered headers, as original name => array of values */
+    private $headers = [];
+
+    /** @var array<string, string> Map of lowercase header name => original name at registration */
+    private $headerNames  = [];
+
+    /** @var string */
+    private $protocol = '1.1';
+
+    /** @var StreamInterface|null */
+    private $stream;
+
+    public function getProtocolVersion(): string
+    {
+        return $this->protocol;
+    }
+
+    public function withProtocolVersion($version): MessageInterface
+    {
+        if ($this->protocol === $version) {
+            return $this;
+        }
+
+        $new = clone $this;
+        $new->protocol = $version;
+        return $new;
+    }
+
+    public function getHeaders(): array
+    {
+        return $this->headers;
+    }
+
+    public function hasHeader($header): bool
+    {
+        return isset($this->headerNames[strtolower($header)]);
+    }
+
+    public function getHeader($header): array
+    {
+        $header = strtolower($header);
+
+        if (!isset($this->headerNames[$header])) {
+            return [];
+        }
+
+        $header = $this->headerNames[$header];
+
+        return $this->headers[$header];
+    }
+
+    public function getHeaderLine($header): string
+    {
+        return implode(', ', $this->getHeader($header));
+    }
+
+    public function withHeader($header, $value): MessageInterface
+    {
+        $this->assertHeader($header);
+        $value = $this->normalizeHeaderValue($value);
+        $normalized = strtolower($header);
+
+        $new = clone $this;
+        if (isset($new->headerNames[$normalized])) {
+            unset($new->headers[$new->headerNames[$normalized]]);
+        }
+        $new->headerNames[$normalized] = $header;
+        $new->headers[$header] = $value;
+
+        return $new;
+    }
+
+    public function withAddedHeader($header, $value): MessageInterface
+    {
+        $this->assertHeader($header);
+        $value = $this->normalizeHeaderValue($value);
+        $normalized = strtolower($header);
+
+        $new = clone $this;
+        if (isset($new->headerNames[$normalized])) {
+            $header = $this->headerNames[$normalized];
+            $new->headers[$header] = array_merge($this->headers[$header], $value);
+        } else {
+            $new->headerNames[$normalized] = $header;
+            $new->headers[$header] = $value;
+        }
+
+        return $new;
+    }
+
+    public function withoutHeader($header): MessageInterface
+    {
+        $normalized = strtolower($header);
+
+        if (!isset($this->headerNames[$normalized])) {
+            return $this;
+        }
+
+        $header = $this->headerNames[$normalized];
+
+        $new = clone $this;
+        unset($new->headers[$header], $new->headerNames[$normalized]);
+
+        return $new;
+    }
+
+    public function getBody(): StreamInterface
+    {
+        if (!$this->stream) {
+            $this->stream = Utils::streamFor('');
+        }
+
+        return $this->stream;
+    }
+
+    public function withBody(StreamInterface $body): MessageInterface
+    {
+        if ($body === $this->stream) {
+            return $this;
+        }
+
+        $new = clone $this;
+        $new->stream = $body;
+        return $new;
+    }
+
+    /**
+     * @param array<string|int, string|string[]> $headers
+     */
+    private function setHeaders(array $headers): void
+    {
+        $this->headerNames = $this->headers = [];
+        foreach ($headers as $header => $value) {
+            // Numeric array keys are converted to int by PHP.
+            $header = (string) $header;
+
+            $this->assertHeader($header);
+            $value = $this->normalizeHeaderValue($value);
+            $normalized = strtolower($header);
+            if (isset($this->headerNames[$normalized])) {
+                $header = $this->headerNames[$normalized];
+                $this->headers[$header] = array_merge($this->headers[$header], $value);
+            } else {
+                $this->headerNames[$normalized] = $header;
+                $this->headers[$header] = $value;
+            }
+        }
+    }
+
+    /**
+     * @param mixed $value
+     *
+     * @return string[]
+     */
+    private function normalizeHeaderValue($value): array
+    {
+        if (!is_array($value)) {
+            return $this->trimAndValidateHeaderValues([$value]);
+        }
+
+        if (count($value) === 0) {
+            throw new \InvalidArgumentException('Header value can not be an empty array.');
+        }
+
+        return $this->trimAndValidateHeaderValues($value);
+    }
+
+    /**
+     * Trims whitespace from the header values.
+     *
+     * Spaces and tabs ought to be excluded by parsers when extracting the field value from a header field.
+     *
+     * header-field = field-name ":" OWS field-value OWS
+     * OWS          = *( SP / HTAB )
+     *
+     * @param mixed[] $values Header values
+     *
+     * @return string[] Trimmed header values
+     *
+     * @see https://tools.ietf.org/html/rfc7230#section-3.2.4
+     */
+    private function trimAndValidateHeaderValues(array $values): array
+    {
+        return array_map(function ($value) {
+            if (!is_scalar($value) && null !== $value) {
+                throw new \InvalidArgumentException(sprintf(
+                    'Header value must be scalar or null but %s provided.',
+                    is_object($value) ? get_class($value) : gettype($value)
+                ));
+            }
+
+            $trimmed = trim((string) $value, " \t");
+            $this->assertValue($trimmed);
+
+            return $trimmed;
+        }, array_values($values));
+    }
+
+    /**
+     * @see https://tools.ietf.org/html/rfc7230#section-3.2
+     *
+     * @param mixed $header
+     */
+    private function assertHeader($header): void
+    {
+        if (!is_string($header)) {
+            throw new \InvalidArgumentException(sprintf(
+                'Header name must be a string but %s provided.',
+                is_object($header) ? get_class($header) : gettype($header)
+            ));
+        }
+
+        if (! preg_match('/^[a-zA-Z0-9\'`#$%&*+.^_|~!-]+$/D', $header)) {
+            throw new \InvalidArgumentException(
+                sprintf('"%s" is not valid header name.', $header)
+            );
+        }
+    }
+
+    /**
+     * @see https://tools.ietf.org/html/rfc7230#section-3.2
+     *
+     * field-value    = *( field-content / obs-fold )
+     * field-content  = field-vchar [ 1*( SP / HTAB ) field-vchar ]
+     * field-vchar    = VCHAR / obs-text
+     * VCHAR          = %x21-7E
+     * obs-text       = %x80-FF
+     * obs-fold       = CRLF 1*( SP / HTAB )
+     */
+    private function assertValue(string $value): void
+    {
+        // The regular expression intentionally does not support the obs-fold production, because as
+        // per RFC 7230#3.2.4:
+        //
+        // A sender MUST NOT generate a message that includes
+        // line folding (i.e., that has any field-value that contains a match to
+        // the obs-fold rule) unless the message is intended for packaging
+        // within the message/http media type.
+        //
+        // Clients must not send a request with line folding and a server sending folded headers is
+        // likely very rare. Line folding is a fairly obscure feature of HTTP/1.1 and thus not accepting
+        // folding is not likely to break any legitimate use case.
+        if (! preg_match('/^[\x20\x09\x21-\x7E\x80-\xFF]*$/D', $value)) {
+            throw new \InvalidArgumentException(
+                sprintf('"%s" is not valid header value.', $value)
+            );
+        }
+    }
+}

+ 159 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins/guzzlehttp/psr7/src/MultipartStream.php

@@ -0,0 +1,159 @@
+<?php
+
+declare(strict_types=1);
+
+namespace GuzzleHttp\Psr7;
+
+use Psr\Http\Message\StreamInterface;
+
+/**
+ * Stream that when read returns bytes for a streaming multipart or
+ * multipart/form-data stream.
+ */
+final class MultipartStream implements StreamInterface
+{
+    use StreamDecoratorTrait;
+
+    /** @var string */
+    private $boundary;
+
+    /** @var StreamInterface */
+    private $stream;
+
+    /**
+     * @param array  $elements Array of associative arrays, each containing a
+     *                         required "name" key mapping to the form field,
+     *                         name, a required "contents" key mapping to a
+     *                         StreamInterface/resource/string, an optional
+     *                         "headers" associative array of custom headers,
+     *                         and an optional "filename" key mapping to a
+     *                         string to send as the filename in the part.
+     * @param string $boundary You can optionally provide a specific boundary
+     *
+     * @throws \InvalidArgumentException
+     */
+    public function __construct(array $elements = [], string $boundary = null)
+    {
+        $this->boundary = $boundary ?: bin2hex(random_bytes(20));
+        $this->stream = $this->createStream($elements);
+    }
+
+    public function getBoundary(): string
+    {
+        return $this->boundary;
+    }
+
+    public function isWritable(): bool
+    {
+        return false;
+    }
+
+    /**
+     * Get the headers needed before transferring the content of a POST file
+     *
+     * @param array<string, string> $headers
+     */
+    private function getHeaders(array $headers): string
+    {
+        $str = '';
+        foreach ($headers as $key => $value) {
+            $str .= "{$key}: {$value}\r\n";
+        }
+
+        return "--{$this->boundary}\r\n" . trim($str) . "\r\n\r\n";
+    }
+
+    /**
+     * Create the aggregate stream that will be used to upload the POST data
+     */
+    protected function createStream(array $elements = []): StreamInterface
+    {
+        $stream = new AppendStream();
+
+        foreach ($elements as $element) {
+            if (!is_array($element)) {
+                throw new \UnexpectedValueException("An array is expected");
+            }
+            $this->addElement($stream, $element);
+        }
+
+        // Add the trailing boundary with CRLF
+        $stream->addStream(Utils::streamFor("--{$this->boundary}--\r\n"));
+
+        return $stream;
+    }
+
+    private function addElement(AppendStream $stream, array $element): void
+    {
+        foreach (['contents', 'name'] as $key) {
+            if (!array_key_exists($key, $element)) {
+                throw new \InvalidArgumentException("A '{$key}' key is required");
+            }
+        }
+
+        $element['contents'] = Utils::streamFor($element['contents']);
+
+        if (empty($element['filename'])) {
+            $uri = $element['contents']->getMetadata('uri');
+            if ($uri && \is_string($uri) && \substr($uri, 0, 6) !== 'php://' && \substr($uri, 0, 7) !== 'data://') {
+                $element['filename'] = $uri;
+            }
+        }
+
+        [$body, $headers] = $this->createElement(
+            $element['name'],
+            $element['contents'],
+            $element['filename'] ?? null,
+            $element['headers'] ?? []
+        );
+
+        $stream->addStream(Utils::streamFor($this->getHeaders($headers)));
+        $stream->addStream($body);
+        $stream->addStream(Utils::streamFor("\r\n"));
+    }
+
+    private function createElement(string $name, StreamInterface $stream, ?string $filename, array $headers): array
+    {
+        // Set a default content-disposition header if one was no provided
+        $disposition = $this->getHeader($headers, 'content-disposition');
+        if (!$disposition) {
+            $headers['Content-Disposition'] = ($filename === '0' || $filename)
+                ? sprintf(
+                    'form-data; name="%s"; filename="%s"',
+                    $name,
+                    basename($filename)
+                )
+                : "form-data; name=\"{$name}\"";
+        }
+
+        // Set a default content-length header if one was no provided
+        $length = $this->getHeader($headers, 'content-length');
+        if (!$length) {
+            if ($length = $stream->getSize()) {
+                $headers['Content-Length'] = (string) $length;
+            }
+        }
+
+        // Set a default Content-Type if one was not supplied
+        $type = $this->getHeader($headers, 'content-type');
+        if (!$type && ($filename === '0' || $filename)) {
+            if ($type = MimeType::fromFilename($filename)) {
+                $headers['Content-Type'] = $type;
+            }
+        }
+
+        return [$stream, $headers];
+    }
+
+    private function getHeader(array $headers, string $key)
+    {
+        $lowercaseHeader = strtolower($key);
+        foreach ($headers as $k => $v) {
+            if (strtolower($k) === $lowercaseHeader) {
+                return $v;
+            }
+        }
+
+        return null;
+    }
+}

+ 344 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins/guzzlehttp/psr7/src/ServerRequest.php

@@ -0,0 +1,344 @@
+<?php
+
+declare(strict_types=1);
+
+namespace GuzzleHttp\Psr7;
+
+use InvalidArgumentException;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Message\StreamInterface;
+use Psr\Http\Message\UploadedFileInterface;
+use Psr\Http\Message\UriInterface;
+
+/**
+ * Server-side HTTP request
+ *
+ * Extends the Request definition to add methods for accessing incoming data,
+ * specifically server parameters, cookies, matched path parameters, query
+ * string arguments, body parameters, and upload file information.
+ *
+ * "Attributes" are discovered via decomposing the request (and usually
+ * specifically the URI path), and typically will be injected by the application.
+ *
+ * Requests are considered immutable; all methods that might change state are
+ * implemented such that they retain the internal state of the current
+ * message and return a new instance that contains the changed state.
+ */
+class ServerRequest extends Request implements ServerRequestInterface
+{
+    /**
+     * @var array
+     */
+    private $attributes = [];
+
+    /**
+     * @var array
+     */
+    private $cookieParams = [];
+
+    /**
+     * @var array|object|null
+     */
+    private $parsedBody;
+
+    /**
+     * @var array
+     */
+    private $queryParams = [];
+
+    /**
+     * @var array
+     */
+    private $serverParams;
+
+    /**
+     * @var array
+     */
+    private $uploadedFiles = [];
+
+    /**
+     * @param string                               $method       HTTP method
+     * @param string|UriInterface                  $uri          URI
+     * @param array<string, string|string[]>       $headers      Request headers
+     * @param string|resource|StreamInterface|null $body         Request body
+     * @param string                               $version      Protocol version
+     * @param array                                $serverParams Typically the $_SERVER superglobal
+     */
+    public function __construct(
+        string $method,
+        $uri,
+        array $headers = [],
+        $body = null,
+        string $version = '1.1',
+        array $serverParams = []
+    ) {
+        $this->serverParams = $serverParams;
+
+        parent::__construct($method, $uri, $headers, $body, $version);
+    }
+
+    /**
+     * Return an UploadedFile instance array.
+     *
+     * @param array $files An array which respect $_FILES structure
+     *
+     * @throws InvalidArgumentException for unrecognized values
+     */
+    public static function normalizeFiles(array $files): array
+    {
+        $normalized = [];
+
+        foreach ($files as $key => $value) {
+            if ($value instanceof UploadedFileInterface) {
+                $normalized[$key] = $value;
+            } elseif (is_array($value) && isset($value['tmp_name'])) {
+                $normalized[$key] = self::createUploadedFileFromSpec($value);
+            } elseif (is_array($value)) {
+                $normalized[$key] = self::normalizeFiles($value);
+                continue;
+            } else {
+                throw new InvalidArgumentException('Invalid value in files specification');
+            }
+        }
+
+        return $normalized;
+    }
+
+    /**
+     * Create and return an UploadedFile instance from a $_FILES specification.
+     *
+     * If the specification represents an array of values, this method will
+     * delegate to normalizeNestedFileSpec() and return that return value.
+     *
+     * @param array $value $_FILES struct
+     *
+     * @return UploadedFileInterface|UploadedFileInterface[]
+     */
+    private static function createUploadedFileFromSpec(array $value)
+    {
+        if (is_array($value['tmp_name'])) {
+            return self::normalizeNestedFileSpec($value);
+        }
+
+        return new UploadedFile(
+            $value['tmp_name'],
+            (int) $value['size'],
+            (int) $value['error'],
+            $value['name'],
+            $value['type']
+        );
+    }
+
+    /**
+     * Normalize an array of file specifications.
+     *
+     * Loops through all nested files and returns a normalized array of
+     * UploadedFileInterface instances.
+     *
+     * @return UploadedFileInterface[]
+     */
+    private static function normalizeNestedFileSpec(array $files = []): array
+    {
+        $normalizedFiles = [];
+
+        foreach (array_keys($files['tmp_name']) as $key) {
+            $spec = [
+                'tmp_name' => $files['tmp_name'][$key],
+                'size'     => $files['size'][$key] ?? null,
+                'error'    => $files['error'][$key] ?? null,
+                'name'     => $files['name'][$key] ?? null,
+                'type'     => $files['type'][$key] ?? null,
+            ];
+            $normalizedFiles[$key] = self::createUploadedFileFromSpec($spec);
+        }
+
+        return $normalizedFiles;
+    }
+
+    /**
+     * Return a ServerRequest populated with superglobals:
+     * $_GET
+     * $_POST
+     * $_COOKIE
+     * $_FILES
+     * $_SERVER
+     */
+    public static function fromGlobals(): ServerRequestInterface
+    {
+        $method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
+        $headers = getallheaders();
+        $uri = self::getUriFromGlobals();
+        $body = new CachingStream(new LazyOpenStream('php://input', 'r+'));
+        $protocol = isset($_SERVER['SERVER_PROTOCOL']) ? str_replace('HTTP/', '', $_SERVER['SERVER_PROTOCOL']) : '1.1';
+
+        $serverRequest = new ServerRequest($method, $uri, $headers, $body, $protocol, $_SERVER);
+
+        return $serverRequest
+            ->withCookieParams($_COOKIE)
+            ->withQueryParams($_GET)
+            ->withParsedBody($_POST)
+            ->withUploadedFiles(self::normalizeFiles($_FILES));
+    }
+
+    private static function extractHostAndPortFromAuthority(string $authority): array
+    {
+        $uri = 'http://' . $authority;
+        $parts = parse_url($uri);
+        if (false === $parts) {
+            return [null, null];
+        }
+
+        $host = $parts['host'] ?? null;
+        $port = $parts['port'] ?? null;
+
+        return [$host, $port];
+    }
+
+    /**
+     * Get a Uri populated with values from $_SERVER.
+     */
+    public static function getUriFromGlobals(): UriInterface
+    {
+        $uri = new Uri('');
+
+        $uri = $uri->withScheme(!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https' : 'http');
+
+        $hasPort = false;
+        if (isset($_SERVER['HTTP_HOST'])) {
+            [$host, $port] = self::extractHostAndPortFromAuthority($_SERVER['HTTP_HOST']);
+            if ($host !== null) {
+                $uri = $uri->withHost($host);
+            }
+
+            if ($port !== null) {
+                $hasPort = true;
+                $uri = $uri->withPort($port);
+            }
+        } elseif (isset($_SERVER['SERVER_NAME'])) {
+            $uri = $uri->withHost($_SERVER['SERVER_NAME']);
+        } elseif (isset($_SERVER['SERVER_ADDR'])) {
+            $uri = $uri->withHost($_SERVER['SERVER_ADDR']);
+        }
+
+        if (!$hasPort && isset($_SERVER['SERVER_PORT'])) {
+            $uri = $uri->withPort($_SERVER['SERVER_PORT']);
+        }
+
+        $hasQuery = false;
+        if (isset($_SERVER['REQUEST_URI'])) {
+            $requestUriParts = explode('?', $_SERVER['REQUEST_URI'], 2);
+            $uri = $uri->withPath($requestUriParts[0]);
+            if (isset($requestUriParts[1])) {
+                $hasQuery = true;
+                $uri = $uri->withQuery($requestUriParts[1]);
+            }
+        }
+
+        if (!$hasQuery && isset($_SERVER['QUERY_STRING'])) {
+            $uri = $uri->withQuery($_SERVER['QUERY_STRING']);
+        }
+
+        return $uri;
+    }
+
+    public function getServerParams(): array
+    {
+        return $this->serverParams;
+    }
+
+    public function getUploadedFiles(): array
+    {
+        return $this->uploadedFiles;
+    }
+
+    public function withUploadedFiles(array $uploadedFiles): ServerRequestInterface
+    {
+        $new = clone $this;
+        $new->uploadedFiles = $uploadedFiles;
+
+        return $new;
+    }
+
+    public function getCookieParams(): array
+    {
+        return $this->cookieParams;
+    }
+
+    public function withCookieParams(array $cookies): ServerRequestInterface
+    {
+        $new = clone $this;
+        $new->cookieParams = $cookies;
+
+        return $new;
+    }
+
+    public function getQueryParams(): array
+    {
+        return $this->queryParams;
+    }
+
+    public function withQueryParams(array $query): ServerRequestInterface
+    {
+        $new = clone $this;
+        $new->queryParams = $query;
+
+        return $new;
+    }
+
+    /**
+     * {@inheritdoc}
+     *
+     * @return array|object|null
+     */
+    public function getParsedBody()
+    {
+        return $this->parsedBody;
+    }
+
+    public function withParsedBody($data): ServerRequestInterface
+    {
+        $new = clone $this;
+        $new->parsedBody = $data;
+
+        return $new;
+    }
+
+    public function getAttributes(): array
+    {
+        return $this->attributes;
+    }
+
+    /**
+     * {@inheritdoc}
+     *
+     * @return mixed
+     */
+    public function getAttribute($attribute, $default = null)
+    {
+        if (false === array_key_exists($attribute, $this->attributes)) {
+            return $default;
+        }
+
+        return $this->attributes[$attribute];
+    }
+
+    public function withAttribute($attribute, $value): ServerRequestInterface
+    {
+        $new = clone $this;
+        $new->attributes[$attribute] = $value;
+
+        return $new;
+    }
+
+    public function withoutAttribute($attribute): ServerRequestInterface
+    {
+        if (false === array_key_exists($attribute, $this->attributes)) {
+            return $this;
+        }
+
+        $new = clone $this;
+        unset($new->attributes[$attribute]);
+
+        return $new;
+    }
+}

+ 740 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins/guzzlehttp/psr7/src/Uri.php

@@ -0,0 +1,740 @@
+<?php
+
+declare(strict_types=1);
+
+namespace GuzzleHttp\Psr7;
+
+use GuzzleHttp\Psr7\Exception\MalformedUriException;
+use Psr\Http\Message\UriInterface;
+
+/**
+ * PSR-7 URI implementation.
+ *
+ * @author Michael Dowling
+ * @author Tobias Schultze
+ * @author Matthew Weier O'Phinney
+ */
+class Uri implements UriInterface, \JsonSerializable
+{
+    /**
+     * Absolute http and https URIs require a host per RFC 7230 Section 2.7
+     * but in generic URIs the host can be empty. So for http(s) URIs
+     * we apply this default host when no host is given yet to form a
+     * valid URI.
+     */
+    private const HTTP_DEFAULT_HOST = 'localhost';
+
+    private const DEFAULT_PORTS = [
+        'http'  => 80,
+        'https' => 443,
+        'ftp' => 21,
+        'gopher' => 70,
+        'nntp' => 119,
+        'news' => 119,
+        'telnet' => 23,
+        'tn3270' => 23,
+        'imap' => 143,
+        'pop' => 110,
+        'ldap' => 389,
+    ];
+
+    /**
+     * Unreserved characters for use in a regex.
+     *
+     * @link https://tools.ietf.org/html/rfc3986#section-2.3
+     */
+    private const CHAR_UNRESERVED = 'a-zA-Z0-9_\-\.~';
+
+    /**
+     * Sub-delims for use in a regex.
+     *
+     * @link https://tools.ietf.org/html/rfc3986#section-2.2
+     */
+    private const CHAR_SUB_DELIMS = '!\$&\'\(\)\*\+,;=';
+    private const QUERY_SEPARATORS_REPLACEMENT = ['=' => '%3D', '&' => '%26'];
+
+    /** @var string Uri scheme. */
+    private $scheme = '';
+
+    /** @var string Uri user info. */
+    private $userInfo = '';
+
+    /** @var string Uri host. */
+    private $host = '';
+
+    /** @var int|null Uri port. */
+    private $port;
+
+    /** @var string Uri path. */
+    private $path = '';
+
+    /** @var string Uri query string. */
+    private $query = '';
+
+    /** @var string Uri fragment. */
+    private $fragment = '';
+
+    /** @var string|null String representation */
+    private $composedComponents;
+
+    public function __construct(string $uri = '')
+    {
+        if ($uri !== '') {
+            $parts = self::parse($uri);
+            if ($parts === false) {
+                throw new MalformedUriException("Unable to parse URI: $uri");
+            }
+            $this->applyParts($parts);
+        }
+    }
+    /**
+     * UTF-8 aware \parse_url() replacement.
+     *
+     * The internal function produces broken output for non ASCII domain names
+     * (IDN) when used with locales other than "C".
+     *
+     * On the other hand, cURL understands IDN correctly only when UTF-8 locale
+     * is configured ("C.UTF-8", "en_US.UTF-8", etc.).
+     *
+     * @see https://bugs.php.net/bug.php?id=52923
+     * @see https://www.php.net/manual/en/function.parse-url.php#114817
+     * @see https://curl.haxx.se/libcurl/c/CURLOPT_URL.html#ENCODING
+     *
+     * @return array|false
+     */
+    private static function parse(string $url)
+    {
+        // If IPv6
+        $prefix = '';
+        if (preg_match('%^(.*://\[[0-9:a-f]+\])(.*?)$%', $url, $matches)) {
+            /** @var array{0:string, 1:string, 2:string} $matches */
+            $prefix = $matches[1];
+            $url = $matches[2];
+        }
+
+        /** @var string */
+        $encodedUrl = preg_replace_callback(
+            '%[^:/@?&=#]+%usD',
+            static function ($matches) {
+                return urlencode($matches[0]);
+            },
+            $url
+        );
+
+        $result = parse_url($prefix . $encodedUrl);
+
+        if ($result === false) {
+            return false;
+        }
+
+        return array_map('urldecode', $result);
+    }
+
+    public function __toString(): string
+    {
+        if ($this->composedComponents === null) {
+            $this->composedComponents = self::composeComponents(
+                $this->scheme,
+                $this->getAuthority(),
+                $this->path,
+                $this->query,
+                $this->fragment
+            );
+        }
+
+        return $this->composedComponents;
+    }
+
+    /**
+     * Composes a URI reference string from its various components.
+     *
+     * Usually this method does not need to be called manually but instead is used indirectly via
+     * `Psr\Http\Message\UriInterface::__toString`.
+     *
+     * PSR-7 UriInterface treats an empty component the same as a missing component as
+     * getQuery(), getFragment() etc. always return a string. This explains the slight
+     * difference to RFC 3986 Section 5.3.
+     *
+     * Another adjustment is that the authority separator is added even when the authority is missing/empty
+     * for the "file" scheme. This is because PHP stream functions like `file_get_contents` only work with
+     * `file:///myfile` but not with `file:/myfile` although they are equivalent according to RFC 3986. But
+     * `file:///` is the more common syntax for the file scheme anyway (Chrome for example redirects to
+     * that format).
+     *
+     * @link https://tools.ietf.org/html/rfc3986#section-5.3
+     */
+    public static function composeComponents(?string $scheme, ?string $authority, string $path, ?string $query, ?string $fragment): string
+    {
+        $uri = '';
+
+        // weak type checks to also accept null until we can add scalar type hints
+        if ($scheme != '') {
+            $uri .= $scheme . ':';
+        }
+
+        if ($authority != '' || $scheme === 'file') {
+            $uri .= '//' . $authority;
+        }
+
+        if ($authority != '' && $path != '' && $path[0] != '/') {
+            $path = '/' . $path;
+        }
+
+        $uri .= $path;
+
+        if ($query != '') {
+            $uri .= '?' . $query;
+        }
+
+        if ($fragment != '') {
+            $uri .= '#' . $fragment;
+        }
+
+        return $uri;
+    }
+
+    /**
+     * Whether the URI has the default port of the current scheme.
+     *
+     * `Psr\Http\Message\UriInterface::getPort` may return null or the standard port. This method can be used
+     * independently of the implementation.
+     */
+    public static function isDefaultPort(UriInterface $uri): bool
+    {
+        return $uri->getPort() === null
+            || (isset(self::DEFAULT_PORTS[$uri->getScheme()]) && $uri->getPort() === self::DEFAULT_PORTS[$uri->getScheme()]);
+    }
+
+    /**
+     * Whether the URI is absolute, i.e. it has a scheme.
+     *
+     * An instance of UriInterface can either be an absolute URI or a relative reference. This method returns true
+     * if it is the former. An absolute URI has a scheme. A relative reference is used to express a URI relative
+     * to another URI, the base URI. Relative references can be divided into several forms:
+     * - network-path references, e.g. '//example.com/path'
+     * - absolute-path references, e.g. '/path'
+     * - relative-path references, e.g. 'subpath'
+     *
+     * @see Uri::isNetworkPathReference
+     * @see Uri::isAbsolutePathReference
+     * @see Uri::isRelativePathReference
+     * @link https://tools.ietf.org/html/rfc3986#section-4
+     */
+    public static function isAbsolute(UriInterface $uri): bool
+    {
+        return $uri->getScheme() !== '';
+    }
+
+    /**
+     * Whether the URI is a network-path reference.
+     *
+     * A relative reference that begins with two slash characters is termed an network-path reference.
+     *
+     * @link https://tools.ietf.org/html/rfc3986#section-4.2
+     */
+    public static function isNetworkPathReference(UriInterface $uri): bool
+    {
+        return $uri->getScheme() === '' && $uri->getAuthority() !== '';
+    }
+
+    /**
+     * Whether the URI is a absolute-path reference.
+     *
+     * A relative reference that begins with a single slash character is termed an absolute-path reference.
+     *
+     * @link https://tools.ietf.org/html/rfc3986#section-4.2
+     */
+    public static function isAbsolutePathReference(UriInterface $uri): bool
+    {
+        return $uri->getScheme() === ''
+            && $uri->getAuthority() === ''
+            && isset($uri->getPath()[0])
+            && $uri->getPath()[0] === '/';
+    }
+
+    /**
+     * Whether the URI is a relative-path reference.
+     *
+     * A relative reference that does not begin with a slash character is termed a relative-path reference.
+     *
+     * @link https://tools.ietf.org/html/rfc3986#section-4.2
+     */
+    public static function isRelativePathReference(UriInterface $uri): bool
+    {
+        return $uri->getScheme() === ''
+            && $uri->getAuthority() === ''
+            && (!isset($uri->getPath()[0]) || $uri->getPath()[0] !== '/');
+    }
+
+    /**
+     * Whether the URI is a same-document reference.
+     *
+     * A same-document reference refers to a URI that is, aside from its fragment
+     * component, identical to the base URI. When no base URI is given, only an empty
+     * URI reference (apart from its fragment) is considered a same-document reference.
+     *
+     * @param UriInterface      $uri  The URI to check
+     * @param UriInterface|null $base An optional base URI to compare against
+     *
+     * @link https://tools.ietf.org/html/rfc3986#section-4.4
+     */
+    public static function isSameDocumentReference(UriInterface $uri, UriInterface $base = null): bool
+    {
+        if ($base !== null) {
+            $uri = UriResolver::resolve($base, $uri);
+
+            return ($uri->getScheme() === $base->getScheme())
+                && ($uri->getAuthority() === $base->getAuthority())
+                && ($uri->getPath() === $base->getPath())
+                && ($uri->getQuery() === $base->getQuery());
+        }
+
+        return $uri->getScheme() === '' && $uri->getAuthority() === '' && $uri->getPath() === '' && $uri->getQuery() === '';
+    }
+
+    /**
+     * Creates a new URI with a specific query string value removed.
+     *
+     * Any existing query string values that exactly match the provided key are
+     * removed.
+     *
+     * @param UriInterface $uri URI to use as a base.
+     * @param string       $key Query string key to remove.
+     */
+    public static function withoutQueryValue(UriInterface $uri, string $key): UriInterface
+    {
+        $result = self::getFilteredQueryString($uri, [$key]);
+
+        return $uri->withQuery(implode('&', $result));
+    }
+
+    /**
+     * Creates a new URI with a specific query string value.
+     *
+     * Any existing query string values that exactly match the provided key are
+     * removed and replaced with the given key value pair.
+     *
+     * A value of null will set the query string key without a value, e.g. "key"
+     * instead of "key=value".
+     *
+     * @param UriInterface $uri   URI to use as a base.
+     * @param string       $key   Key to set.
+     * @param string|null  $value Value to set
+     */
+    public static function withQueryValue(UriInterface $uri, string $key, ?string $value): UriInterface
+    {
+        $result = self::getFilteredQueryString($uri, [$key]);
+
+        $result[] = self::generateQueryString($key, $value);
+
+        return $uri->withQuery(implode('&', $result));
+    }
+
+    /**
+     * Creates a new URI with multiple specific query string values.
+     *
+     * It has the same behavior as withQueryValue() but for an associative array of key => value.
+     *
+     * @param UriInterface               $uri           URI to use as a base.
+     * @param array<string, string|null> $keyValueArray Associative array of key and values
+     */
+    public static function withQueryValues(UriInterface $uri, array $keyValueArray): UriInterface
+    {
+        $result = self::getFilteredQueryString($uri, array_keys($keyValueArray));
+
+        foreach ($keyValueArray as $key => $value) {
+            $result[] = self::generateQueryString((string) $key, $value !== null ? (string) $value : null);
+        }
+
+        return $uri->withQuery(implode('&', $result));
+    }
+
+    /**
+     * Creates a URI from a hash of `parse_url` components.
+     *
+     * @link http://php.net/manual/en/function.parse-url.php
+     *
+     * @throws MalformedUriException If the components do not form a valid URI.
+     */
+    public static function fromParts(array $parts): UriInterface
+    {
+        $uri = new self();
+        $uri->applyParts($parts);
+        $uri->validateState();
+
+        return $uri;
+    }
+
+    public function getScheme(): string
+    {
+        return $this->scheme;
+    }
+
+    public function getAuthority(): string
+    {
+        $authority = $this->host;
+        if ($this->userInfo !== '') {
+            $authority = $this->userInfo . '@' . $authority;
+        }
+
+        if ($this->port !== null) {
+            $authority .= ':' . $this->port;
+        }
+
+        return $authority;
+    }
+
+    public function getUserInfo(): string
+    {
+        return $this->userInfo;
+    }
+
+    public function getHost(): string
+    {
+        return $this->host;
+    }
+
+    public function getPort(): ?int
+    {
+        return $this->port;
+    }
+
+    public function getPath(): string
+    {
+        return $this->path;
+    }
+
+    public function getQuery(): string
+    {
+        return $this->query;
+    }
+
+    public function getFragment(): string
+    {
+        return $this->fragment;
+    }
+
+    public function withScheme($scheme): UriInterface
+    {
+        $scheme = $this->filterScheme($scheme);
+
+        if ($this->scheme === $scheme) {
+            return $this;
+        }
+
+        $new = clone $this;
+        $new->scheme = $scheme;
+        $new->composedComponents = null;
+        $new->removeDefaultPort();
+        $new->validateState();
+
+        return $new;
+    }
+
+    public function withUserInfo($user, $password = null): UriInterface
+    {
+        $info = $this->filterUserInfoComponent($user);
+        if ($password !== null) {
+            $info .= ':' . $this->filterUserInfoComponent($password);
+        }
+
+        if ($this->userInfo === $info) {
+            return $this;
+        }
+
+        $new = clone $this;
+        $new->userInfo = $info;
+        $new->composedComponents = null;
+        $new->validateState();
+
+        return $new;
+    }
+
+    public function withHost($host): UriInterface
+    {
+        $host = $this->filterHost($host);
+
+        if ($this->host === $host) {
+            return $this;
+        }
+
+        $new = clone $this;
+        $new->host = $host;
+        $new->composedComponents = null;
+        $new->validateState();
+
+        return $new;
+    }
+
+    public function withPort($port): UriInterface
+    {
+        $port = $this->filterPort($port);
+
+        if ($this->port === $port) {
+            return $this;
+        }
+
+        $new = clone $this;
+        $new->port = $port;
+        $new->composedComponents = null;
+        $new->removeDefaultPort();
+        $new->validateState();
+
+        return $new;
+    }
+
+    public function withPath($path): UriInterface
+    {
+        $path = $this->filterPath($path);
+
+        if ($this->path === $path) {
+            return $this;
+        }
+
+        $new = clone $this;
+        $new->path = $path;
+        $new->composedComponents = null;
+        $new->validateState();
+
+        return $new;
+    }
+
+    public function withQuery($query): UriInterface
+    {
+        $query = $this->filterQueryAndFragment($query);
+
+        if ($this->query === $query) {
+            return $this;
+        }
+
+        $new = clone $this;
+        $new->query = $query;
+        $new->composedComponents = null;
+
+        return $new;
+    }
+
+    public function withFragment($fragment): UriInterface
+    {
+        $fragment = $this->filterQueryAndFragment($fragment);
+
+        if ($this->fragment === $fragment) {
+            return $this;
+        }
+
+        $new = clone $this;
+        $new->fragment = $fragment;
+        $new->composedComponents = null;
+
+        return $new;
+    }
+
+    public function jsonSerialize(): string
+    {
+        return $this->__toString();
+    }
+
+    /**
+     * Apply parse_url parts to a URI.
+     *
+     * @param array $parts Array of parse_url parts to apply.
+     */
+    private function applyParts(array $parts): void
+    {
+        $this->scheme = isset($parts['scheme'])
+            ? $this->filterScheme($parts['scheme'])
+            : '';
+        $this->userInfo = isset($parts['user'])
+            ? $this->filterUserInfoComponent($parts['user'])
+            : '';
+        $this->host = isset($parts['host'])
+            ? $this->filterHost($parts['host'])
+            : '';
+        $this->port = isset($parts['port'])
+            ? $this->filterPort($parts['port'])
+            : null;
+        $this->path = isset($parts['path'])
+            ? $this->filterPath($parts['path'])
+            : '';
+        $this->query = isset($parts['query'])
+            ? $this->filterQueryAndFragment($parts['query'])
+            : '';
+        $this->fragment = isset($parts['fragment'])
+            ? $this->filterQueryAndFragment($parts['fragment'])
+            : '';
+        if (isset($parts['pass'])) {
+            $this->userInfo .= ':' . $this->filterUserInfoComponent($parts['pass']);
+        }
+
+        $this->removeDefaultPort();
+    }
+
+    /**
+     * @param mixed $scheme
+     *
+     * @throws \InvalidArgumentException If the scheme is invalid.
+     */
+    private function filterScheme($scheme): string
+    {
+        if (!is_string($scheme)) {
+            throw new \InvalidArgumentException('Scheme must be a string');
+        }
+
+        return \strtr($scheme, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz');
+    }
+
+    /**
+     * @param mixed $component
+     *
+     * @throws \InvalidArgumentException If the user info is invalid.
+     */
+    private function filterUserInfoComponent($component): string
+    {
+        if (!is_string($component)) {
+            throw new \InvalidArgumentException('User info must be a string');
+        }
+
+        return preg_replace_callback(
+            '/(?:[^%' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . ']+|%(?![A-Fa-f0-9]{2}))/',
+            [$this, 'rawurlencodeMatchZero'],
+            $component
+        );
+    }
+
+    /**
+     * @param mixed $host
+     *
+     * @throws \InvalidArgumentException If the host is invalid.
+     */
+    private function filterHost($host): string
+    {
+        if (!is_string($host)) {
+            throw new \InvalidArgumentException('Host must be a string');
+        }
+
+        return \strtr($host, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz');
+    }
+
+    /**
+     * @param mixed $port
+     *
+     * @throws \InvalidArgumentException If the port is invalid.
+     */
+    private function filterPort($port): ?int
+    {
+        if ($port === null) {
+            return null;
+        }
+
+        $port = (int) $port;
+        if (0 > $port || 0xffff < $port) {
+            throw new \InvalidArgumentException(
+                sprintf('Invalid port: %d. Must be between 0 and 65535', $port)
+            );
+        }
+
+        return $port;
+    }
+
+    /**
+     * @param string[] $keys
+     *
+     * @return string[]
+     */
+    private static function getFilteredQueryString(UriInterface $uri, array $keys): array
+    {
+        $current = $uri->getQuery();
+
+        if ($current === '') {
+            return [];
+        }
+
+        $decodedKeys = array_map('rawurldecode', $keys);
+
+        return array_filter(explode('&', $current), function ($part) use ($decodedKeys) {
+            return !in_array(rawurldecode(explode('=', $part)[0]), $decodedKeys, true);
+        });
+    }
+
+    private static function generateQueryString(string $key, ?string $value): string
+    {
+        // Query string separators ("=", "&") within the key or value need to be encoded
+        // (while preventing double-encoding) before setting the query string. All other
+        // chars that need percent-encoding will be encoded by withQuery().
+        $queryString = strtr($key, self::QUERY_SEPARATORS_REPLACEMENT);
+
+        if ($value !== null) {
+            $queryString .= '=' . strtr($value, self::QUERY_SEPARATORS_REPLACEMENT);
+        }
+
+        return $queryString;
+    }
+
+    private function removeDefaultPort(): void
+    {
+        if ($this->port !== null && self::isDefaultPort($this)) {
+            $this->port = null;
+        }
+    }
+
+    /**
+     * Filters the path of a URI
+     *
+     * @param mixed $path
+     *
+     * @throws \InvalidArgumentException If the path is invalid.
+     */
+    private function filterPath($path): string
+    {
+        if (!is_string($path)) {
+            throw new \InvalidArgumentException('Path must be a string');
+        }
+
+        return preg_replace_callback(
+            '/(?:[^' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . '%:@\/]++|%(?![A-Fa-f0-9]{2}))/',
+            [$this, 'rawurlencodeMatchZero'],
+            $path
+        );
+    }
+
+    /**
+     * Filters the query string or fragment of a URI.
+     *
+     * @param mixed $str
+     *
+     * @throws \InvalidArgumentException If the query or fragment is invalid.
+     */
+    private function filterQueryAndFragment($str): string
+    {
+        if (!is_string($str)) {
+            throw new \InvalidArgumentException('Query and fragment must be a string');
+        }
+
+        return preg_replace_callback(
+            '/(?:[^' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . '%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/',
+            [$this, 'rawurlencodeMatchZero'],
+            $str
+        );
+    }
+
+    private function rawurlencodeMatchZero(array $match): string
+    {
+        return rawurlencode($match[0]);
+    }
+
+    private function validateState(): void
+    {
+        if ($this->host === '' && ($this->scheme === 'http' || $this->scheme === 'https')) {
+            $this->host = self::HTTP_DEFAULT_HOST;
+        }
+
+        if ($this->getAuthority() === '') {
+            if (0 === strpos($this->path, '//')) {
+                throw new MalformedUriException('The path of a URI without an authority must not start with two slashes "//"');
+            }
+            if ($this->scheme === '' && false !== strpos(explode('/', $this->path, 2)[0], ':')) {
+                throw new MalformedUriException('A relative URI must not have a path beginning with a segment containing a colon');
+            }
+        }
+    }
+}

+ 35 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins/psr/http-factory/composer.json

@@ -0,0 +1,35 @@
+{
+    "name": "psr/http-factory",
+    "description": "Common interfaces for PSR-7 HTTP message factories",
+    "keywords": [
+        "psr",
+        "psr-7",
+        "psr-17",
+        "http",
+        "factory",
+        "message",
+        "request",
+        "response"
+    ],
+    "license": "MIT",
+    "authors": [
+        {
+            "name": "PHP-FIG",
+            "homepage": "https://www.php-fig.org/"
+        }
+    ],
+    "require": {
+        "php": ">=7.0.0",
+        "psr/http-message": "^1.0 || ^2.0"
+    },
+    "autoload": {
+        "psr-4": {
+            "Psr\\Http\\Message\\": "src/"
+        }
+    },
+    "extra": {
+        "branch-alias": {
+            "dev-master": "1.0.x-dev"
+        }
+    }
+}

+ 26 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins/psr/http-message/composer.json

@@ -0,0 +1,26 @@
+{
+    "name": "psr/http-message",
+    "description": "Common interface for HTTP messages",
+    "keywords": ["psr", "psr-7", "http", "http-message", "request", "response"],
+    "homepage": "https://github.com/php-fig/http-message",
+    "license": "MIT",
+    "authors": [
+        {
+            "name": "PHP-FIG",
+            "homepage": "http://www.php-fig.org/"
+        }
+    ],
+    "require": {
+        "php": "^7.2 || ^8.0"
+    },
+    "autoload": {
+        "psr-4": {
+            "Psr\\Http\\Message\\": "src/"
+        }
+    },
+    "extra": {
+        "branch-alias": {
+            "dev-master": "1.1.x-dev"
+        }
+    }
+}

+ 130 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins/psr/http-message/docs/PSR7-Interfaces.md

@@ -0,0 +1,130 @@
+# Interfaces
+
+The purpose of this list is to help in finding the methods when working with PSR-7. This can be considered as a cheatsheet for PSR-7 interfaces.
+
+The interfaces defined in PSR-7 are the following:
+
+| Class Name | Description |
+|---|---|
+| [Psr\Http\Message\MessageInterface](http://www.php-fig.org/psr/psr-7/#psrhttpmessagemessageinterface) | Representation of a HTTP message |
+| [Psr\Http\Message\RequestInterface](http://www.php-fig.org/psr/psr-7/#psrhttpmessagerequestinterface) | Representation of an outgoing, client-side request. |
+| [Psr\Http\Message\ServerRequestInterface](http://www.php-fig.org/psr/psr-7/#psrhttpmessageserverrequestinterface) | Representation of an incoming, server-side HTTP request. | 
+| [Psr\Http\Message\ResponseInterface](http://www.php-fig.org/psr/psr-7/#psrhttpmessageresponseinterface) | Representation of an outgoing, server-side response. |
+| [Psr\Http\Message\StreamInterface](http://www.php-fig.org/psr/psr-7/#psrhttpmessagestreaminterface) | Describes a data stream |
+| [Psr\Http\Message\UriInterface](http://www.php-fig.org/psr/psr-7/#psrhttpmessageuriinterface) | Value object representing a URI. |
+| [Psr\Http\Message\UploadedFileInterface](http://www.php-fig.org/psr/psr-7/#psrhttpmessageuploadedfileinterface) | Value object representing a file uploaded through an HTTP request. |
+
+## `Psr\Http\Message\MessageInterface` Methods
+
+| Method Name                        | Description | Notes |
+|------------------------------------| ----------- | ----- |
+| `getProtocolVersion()`             | Retrieve HTTP protocol version          |  1.0 or 1.1 |
+| `withProtocolVersion($version)`    | Returns new message instance with given HTTP protocol version          |      |
+| `getHeaders()`                     | Retrieve all HTTP Headers               | [Request Header List](https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Request_fields), [Response Header List](https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Response_fields)      |
+| `hasHeader($name)`                 | Checks if HTTP Header with given name exists  | |
+| `getHeader($name)`                 | Retrieves a array with the values for a single header | |
+| `getHeaderLine($name)`             | Retrieves a comma-separated string of the values for a single header |  |
+| `withHeader($name, $value)`        | Returns new message instance with given HTTP Header | if the header existed in the original instance, replaces the header value from the original message with the value provided when creating the new instance. |
+| `withAddedHeader($name, $value)`   | Returns new message instance with appended value to given header | If header already exists value will be appended, if not a new header will be created |
+| `withoutHeader($name)`             | Removes HTTP Header with given name| |
+| `getBody()`                        | Retrieves the HTTP Message Body | Returns object implementing `StreamInterface`|
+| `withBody(StreamInterface $body)`  | Returns new message instance with given HTTP Message Body | |
+
+
+## `Psr\Http\Message\RequestInterface` Methods
+
+Same methods as `Psr\Http\Message\MessageInterface`  + the following methods:
+
+| Method Name                        | Description | Notes |
+|------------------------------------| ----------- | ----- |
+| `getRequestTarget()`                | Retrieves the message's request target              | origin-form, absolute-form, authority-form, asterisk-form ([RFC7230](https://www.rfc-editor.org/rfc/rfc7230.txt)) |
+| `withRequestTarget($requestTarget)` | Return a new message instance with the specific request-target |      |
+| `getMethod()`                       | Retrieves the HTTP method of the request.  |  GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE (defined in [RFC7231](https://tools.ietf.org/html/rfc7231)), PATCH (defined in [RFC5789](https://tools.ietf.org/html/rfc5789)) |
+| `withMethod($method)`               | Returns a new message instance with the provided HTTP method  | |
+| `getUri()`                 | Retrieves the URI instance | |
+| `withUri(UriInterface $uri, $preserveHost = false)` | Returns a new message instance with the provided URI |  |
+
+
+## `Psr\Http\Message\ServerRequestInterface` Methods
+
+Same methods as `Psr\Http\Message\RequestInterface`  + the following methods:
+
+| Method Name                        | Description | Notes |
+|------------------------------------| ----------- | ----- |
+| `getServerParams() `               | Retrieve server parameters  | Typically derived from `$_SERVER`  |
+| `getCookieParams()`                | Retrieves cookies sent by the client to the server. | Typically derived from `$_COOKIES` |
+| `withCookieParams(array $cookies)` |  Returns a new request instance with the specified cookies      |   | 
+| `withQueryParams(array $query)` | Returns a new request instance with the specified query string arguments  |  |
+| `getUploadedFiles()` | Retrieve normalized file upload data  |  |
+| `withUploadedFiles(array $uploadedFiles)` | Returns a new request instance with the specified uploaded files  |  |
+| `getParsedBody()` | Retrieve any parameters provided in the request body  |  |
+| `withParsedBody($data)` | Returns a new request instance with the specified body parameters  |  |
+| `getAttributes()` | Retrieve attributes derived from the request  |  |
+| `getAttribute($name, $default = null)` | Retrieve a single derived request attribute  |  |
+| `withAttribute($name, $value)` | Returns a new request instance with the specified derived request attribute  |  |
+| `withoutAttribute($name)` | Returns a new request instance that without the specified derived request attribute  |  |
+
+## `Psr\Http\Message\ResponseInterface` Methods:
+
+Same methods as `Psr\Http\Message\MessageInterface`  + the following methods:
+
+| Method Name                        | Description | Notes |
+|------------------------------------| ----------- | ----- |
+| `getStatusCode()` | Gets the response status code. | |
+| `withStatus($code, $reasonPhrase = '')` | Returns a new response instance with the specified status code and, optionally, reason phrase. | |
+| `getReasonPhrase()` | Gets the response reason phrase associated with the status code. | |
+
+##  `Psr\Http\Message\StreamInterface` Methods
+
+| Method Name                        | Description | Notes |
+|------------------------------------| ----------- | ----- |
+| `__toString()` | Reads all data from the stream into a string, from the beginning to end. | |
+| `close()` | Closes the stream and any underlying resources. | |
+| `detach()` | Separates any underlying resources from the stream. | |
+| `getSize()` | Get the size of the stream if known. | |
+| `eof()` | Returns true if the stream is at the end of the stream.| |
+| `isSeekable()` |  Returns whether or not the stream is seekable. | |
+| `seek($offset, $whence = SEEK_SET)` | Seek to a position in the stream. | |
+| `rewind()` | Seek to the beginning of the stream. | |
+| `isWritable()` | Returns whether or not the stream is writable. | |
+| `write($string)` | Write data to the stream. | |
+| `isReadable()` | Returns whether or not the stream is readable. | |
+| `read($length)` | Read data from the stream. | |
+| `getContents()` | Returns the remaining contents in a string | |
+| `getMetadata($key = null)()` | Get stream metadata as an associative array or retrieve a specific key. | |
+
+## `Psr\Http\Message\UriInterface` Methods
+
+| Method Name                        | Description | Notes |
+|------------------------------------| ----------- | ----- |
+| `getScheme()` | Retrieve the scheme component of the URI. | |
+| `getAuthority()` | Retrieve the authority component of the URI. | |
+| `getUserInfo()` | Retrieve the user information component of the URI. | |
+| `getHost()` | Retrieve the host component of the URI. | |
+| `getPort()` | Retrieve the port component of the URI. | |
+| `getPath()` | Retrieve the path component of the URI. | |
+| `getQuery()` | Retrieve the query string of the URI. | |
+| `getFragment()` | Retrieve the fragment component of the URI. | |
+| `withScheme($scheme)` | Return an instance with the specified scheme. | |
+| `withUserInfo($user, $password = null)` | Return an instance with the specified user information. | |
+| `withHost($host)` | Return an instance with the specified host. | |
+| `withPort($port)` | Return an instance with the specified port. | |
+| `withPath($path)` | Return an instance with the specified path. | |
+| `withQuery($query)` | Return an instance with the specified query string. | |
+| `withFragment($fragment)` | Return an instance with the specified URI fragment. | |
+| `__toString()` | Return the string representation as a URI reference. | |
+
+## `Psr\Http\Message\UploadedFileInterface` Methods
+
+| Method Name                        | Description | Notes |
+|------------------------------------| ----------- | ----- |
+| `getStream()` | Retrieve a stream representing the uploaded file. | |
+| `moveTo($targetPath)` | Move the uploaded file to a new location. | |
+| `getSize()` | Retrieve the file size. | |
+| `getError()` | Retrieve the error associated with the uploaded file. | |
+| `getClientFilename()` | Retrieve the filename sent by the client. | |
+| `getClientMediaType()` | Retrieve the media type sent by the client. | |
+
+> `RequestInterface`, `ServerRequestInterface`, `ResponseInterface` extend `MessageInterface`  because the `Request` and the `Response` are `HTTP Messages`.
+> When using `ServerRequestInterface`, both `RequestInterface` and `Psr\Http\Message\MessageInterface` methods are considered.
+

+ 159 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins/psr/http-message/docs/PSR7-Usage.md

@@ -0,0 +1,159 @@
+### PSR-7 Usage
+
+All PSR-7 applications comply with these interfaces 
+They were created to establish a standard between middleware implementations.
+
+> `RequestInterface`, `ServerRequestInterface`, `ResponseInterface` extend `MessageInterface`  because the `Request` and the `Response` are `HTTP Messages`.
+> When using `ServerRequestInterface`, both `RequestInterface` and `Psr\Http\Message\MessageInterface` methods are considered.
+
+
+The following examples will illustrate how basic operations are done in PSR-7.
+
+##### Examples
+
+
+For this examples to work (at least) a PSR-7 implementation package is required. (eg: zendframework/zend-diactoros, guzzlehttp/psr7, slim/slim, etc)
+All PSR-7 implementations should have the same behaviour.
+
+The following will be assumed: 
+`$request` is an object of `Psr\Http\Message\RequestInterface` and
+
+`$response` is an object implementing `Psr\Http\Message\RequestInterface`
+
+
+### Working with HTTP Headers
+
+#### Adding headers to response:
+
+```php
+$response->withHeader('My-Custom-Header', 'My Custom Message');
+```
+
+#### Appending values to headers
+
+```php
+$response->withAddedHeader('My-Custom-Header', 'The second message');
+```
+
+#### Checking if header exists:
+
+```php
+$request->hasHeader('My-Custom-Header'); // will return false
+$response->hasHeader('My-Custom-Header'); // will return true
+```
+
+> Note: My-Custom-Header was only added in the Response
+
+#### Getting comma-separated values from a header (also applies to request)
+
+```php
+// getting value from request headers
+$request->getHeaderLine('Content-Type'); // will return: "text/html; charset=UTF-8"
+// getting value from response headers
+$response->getHeaderLine('My-Custom-Header'); // will return:  "My Custom Message; The second message"
+```
+
+#### Getting array of value from a header (also applies to request)
+```php
+// getting value from request headers
+$request->getHeader('Content-Type'); // will return: ["text/html", "charset=UTF-8"]
+// getting value from response headers
+$response->getHeader('My-Custom-Header'); // will return:  ["My Custom Message",  "The second message"]
+```
+
+#### Removing headers from HTTP Messages
+```php
+// removing a header from Request, removing deprecated "Content-MD5" header
+$request->withoutHeader('Content-MD5'); 
+
+// removing a header from Response
+// effect: the browser won't know the size of the stream
+// the browser will download the stream till it ends
+$response->withoutHeader('Content-Length');
+```
+
+### Working with HTTP Message Body
+
+When working with the PSR-7 there are two methods of implementation:
+#### 1. Getting the body separately
+
+> This method makes the body handling easier to understand and is useful when repeatedly calling body methods. (You only call `getBody()` once). Using this method mistakes like `$response->write()` are also prevented.
+
+```php
+$body = $response->getBody();
+// operations on body, eg. read, write, seek
+// ...
+// replacing the old body
+$response->withBody($body); 
+// this last statement is optional as we working with objects
+// in this case the "new" body is same with the "old" one
+// the $body variable has the same value as the one in $request, only the reference is passed
+```
+
+#### 2. Working directly on response
+
+> This method is useful when only performing few operations as the `$request->getBody()` statement fragment is required
+
+```php
+$response->getBody()->write('hello');
+```
+
+### Getting the body contents
+
+The following snippet gets the contents of a stream contents.
+> Note: Streams must be rewinded, if content was written into streams, it will be ignored when calling `getContents()` because the stream pointer is set to the last character, which is `\0` - meaning end of stream.
+```php 
+$body = $response->getBody();
+$body->rewind(); // or $body->seek(0);
+$bodyText = $body->getContents();
+```
+> Note: If `$body->seek(1)` is called before `$body->getContents()`, the first character will be ommited as the starting pointer is set to `1`, not `0`. This is why using `$body->rewind()` is recommended.
+
+### Append to body
+
+```php
+$response->getBody()->write('Hello'); // writing directly
+$body = $request->getBody(); // which is a `StreamInterface`
+$body->write('xxxxx');
+```
+
+### Prepend to body
+Prepending is different when it comes to streams. The content must be copied before writing the content to be prepended.
+The following example will explain the behaviour of streams.
+
+```php
+// assuming our response is initially empty
+$body = $repsonse->getBody();
+// writing the string "abcd"
+$body->write('abcd');
+
+// seeking to start of stream
+$body->seek(0);
+// writing 'ef'
+$body->write('ef'); // at this point the stream contains "efcd"
+```
+
+#### Prepending by rewriting separately
+
+```php
+// assuming our response body stream only contains: "abcd"
+$body = $response->getBody();
+$body->rewind();
+$contents = $body->getContents(); // abcd
+// seeking the stream to beginning
+$body->rewind();
+$body->write('ef'); // stream contains "efcd"
+$body->write($contents); // stream contains "efabcd"
+```
+
+> Note: `getContents()` seeks the stream while reading it, therefore if the second `rewind()` method call was not present the stream would have resulted in `abcdefabcd` because the `write()` method appends to stream if not preceeded by `rewind()` or `seek(0)`.
+
+#### Prepending by using contents as a string
+```php
+$body = $response->getBody();
+$body->rewind();
+$contents = $body->getContents(); // efabcd
+$contents = 'ef'.$contents;
+$body->rewind();
+$body->write($contents);
+```

+ 189 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins/psr/http-message/src/MessageInterface.php

@@ -0,0 +1,189 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Psr\Http\Message;
+
+/**
+ * HTTP messages consist of requests from a client to a server and responses
+ * from a server to a client. This interface defines the methods common to
+ * each.
+ *
+ * Messages are considered immutable; all methods that might change state MUST
+ * be implemented such that they retain the internal state of the current
+ * message and return an instance that contains the changed state.
+ *
+ * @link http://www.ietf.org/rfc/rfc7230.txt
+ * @link http://www.ietf.org/rfc/rfc7231.txt
+ */
+interface MessageInterface
+{
+    /**
+     * Retrieves the HTTP protocol version as a string.
+     *
+     * The string MUST contain only the HTTP version number (e.g., "1.1", "1.0").
+     *
+     * @return string HTTP protocol version.
+     */
+    public function getProtocolVersion();
+
+    /**
+     * Return an instance with the specified HTTP protocol version.
+     *
+     * The version string MUST contain only the HTTP version number (e.g.,
+     * "1.1", "1.0").
+     *
+     * This method MUST be implemented in such a way as to retain the
+     * immutability of the message, and MUST return an instance that has the
+     * new protocol version.
+     *
+     * @param string $version HTTP protocol version
+     * @return static
+     */
+    public function withProtocolVersion(string $version);
+
+    /**
+     * Retrieves all message header values.
+     *
+     * The keys represent the header name as it will be sent over the wire, and
+     * each value is an array of strings associated with the header.
+     *
+     *     // Represent the headers as a string
+     *     foreach ($message->getHeaders() as $name => $values) {
+     *         echo $name . ": " . implode(", ", $values);
+     *     }
+     *
+     *     // Emit headers iteratively:
+     *     foreach ($message->getHeaders() as $name => $values) {
+     *         foreach ($values as $value) {
+     *             header(sprintf('%s: %s', $name, $value), false);
+     *         }
+     *     }
+     *
+     * While header names are not case-sensitive, getHeaders() will preserve the
+     * exact case in which headers were originally specified.
+     *
+     * @return string[][] Returns an associative array of the message's headers. Each
+     *     key MUST be a header name, and each value MUST be an array of strings
+     *     for that header.
+     */
+    public function getHeaders();
+
+    /**
+     * Checks if a header exists by the given case-insensitive name.
+     *
+     * @param string $name Case-insensitive header field name.
+     * @return bool Returns true if any header names match the given header
+     *     name using a case-insensitive string comparison. Returns false if
+     *     no matching header name is found in the message.
+     */
+    public function hasHeader(string $name);
+
+    /**
+     * Retrieves a message header value by the given case-insensitive name.
+     *
+     * This method returns an array of all the header values of the given
+     * case-insensitive header name.
+     *
+     * If the header does not appear in the message, this method MUST return an
+     * empty array.
+     *
+     * @param string $name Case-insensitive header field name.
+     * @return string[] An array of string values as provided for the given
+     *    header. If the header does not appear in the message, this method MUST
+     *    return an empty array.
+     */
+    public function getHeader(string $name);
+
+    /**
+     * Retrieves a comma-separated string of the values for a single header.
+     *
+     * This method returns all of the header values of the given
+     * case-insensitive header name as a string concatenated together using
+     * a comma.
+     *
+     * NOTE: Not all header values may be appropriately represented using
+     * comma concatenation. For such headers, use getHeader() instead
+     * and supply your own delimiter when concatenating.
+     *
+     * If the header does not appear in the message, this method MUST return
+     * an empty string.
+     *
+     * @param string $name Case-insensitive header field name.
+     * @return string A string of values as provided for the given header
+     *    concatenated together using a comma. If the header does not appear in
+     *    the message, this method MUST return an empty string.
+     */
+    public function getHeaderLine(string $name);
+
+    /**
+     * Return an instance with the provided value replacing the specified header.
+     *
+     * While header names are case-insensitive, the casing of the header will
+     * be preserved by this function, and returned from getHeaders().
+     *
+     * This method MUST be implemented in such a way as to retain the
+     * immutability of the message, and MUST return an instance that has the
+     * new and/or updated header and value.
+     *
+     * @param string $name Case-insensitive header field name.
+     * @param string|string[] $value Header value(s).
+     * @return static
+     * @throws \InvalidArgumentException for invalid header names or values.
+     */
+    public function withHeader(string $name, $value);
+
+    /**
+     * Return an instance with the specified header appended with the given value.
+     *
+     * Existing values for the specified header will be maintained. The new
+     * value(s) will be appended to the existing list. If the header did not
+     * exist previously, it will be added.
+     *
+     * This method MUST be implemented in such a way as to retain the
+     * immutability of the message, and MUST return an instance that has the
+     * new header and/or value.
+     *
+     * @param string $name Case-insensitive header field name to add.
+     * @param string|string[] $value Header value(s).
+     * @return static
+     * @throws \InvalidArgumentException for invalid header names or values.
+     */
+    public function withAddedHeader(string $name, $value);
+
+    /**
+     * Return an instance without the specified header.
+     *
+     * Header resolution MUST be done without case-sensitivity.
+     *
+     * This method MUST be implemented in such a way as to retain the
+     * immutability of the message, and MUST return an instance that removes
+     * the named header.
+     *
+     * @param string $name Case-insensitive header field name to remove.
+     * @return static
+     */
+    public function withoutHeader(string $name);
+
+    /**
+     * Gets the body of the message.
+     *
+     * @return StreamInterface Returns the body as a stream.
+     */
+    public function getBody();
+
+    /**
+     * Return an instance with the specified message body.
+     *
+     * The body MUST be a StreamInterface object.
+     *
+     * This method MUST be implemented in such a way as to retain the
+     * immutability of the message, and MUST return a new instance that has the
+     * new body stream.
+     *
+     * @param StreamInterface $body Body.
+     * @return static
+     * @throws \InvalidArgumentException When the body is not valid.
+     */
+    public function withBody(StreamInterface $body);
+}

+ 131 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins/psr/http-message/src/RequestInterface.php

@@ -0,0 +1,131 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Psr\Http\Message;
+
+/**
+ * Representation of an outgoing, client-side request.
+ *
+ * Per the HTTP specification, this interface includes properties for
+ * each of the following:
+ *
+ * - Protocol version
+ * - HTTP method
+ * - URI
+ * - Headers
+ * - Message body
+ *
+ * During construction, implementations MUST attempt to set the Host header from
+ * a provided URI if no Host header is provided.
+ *
+ * Requests are considered immutable; all methods that might change state MUST
+ * be implemented such that they retain the internal state of the current
+ * message and return an instance that contains the changed state.
+ */
+interface RequestInterface extends MessageInterface
+{
+    /**
+     * Retrieves the message's request target.
+     *
+     * Retrieves the message's request-target either as it will appear (for
+     * clients), as it appeared at request (for servers), or as it was
+     * specified for the instance (see withRequestTarget()).
+     *
+     * In most cases, this will be the origin-form of the composed URI,
+     * unless a value was provided to the concrete implementation (see
+     * withRequestTarget() below).
+     *
+     * If no URI is available, and no request-target has been specifically
+     * provided, this method MUST return the string "/".
+     *
+     * @return string
+     */
+    public function getRequestTarget();
+
+    /**
+     * Return an instance with the specific request-target.
+     *
+     * If the request needs a non-origin-form request-target — e.g., for
+     * specifying an absolute-form, authority-form, or asterisk-form —
+     * this method may be used to create an instance with the specified
+     * request-target, verbatim.
+     *
+     * This method MUST be implemented in such a way as to retain the
+     * immutability of the message, and MUST return an instance that has the
+     * changed request target.
+     *
+     * @link http://tools.ietf.org/html/rfc7230#section-5.3 (for the various
+     *     request-target forms allowed in request messages)
+     * @param string $requestTarget
+     * @return static
+     */
+    public function withRequestTarget(string $requestTarget);
+
+    /**
+     * Retrieves the HTTP method of the request.
+     *
+     * @return string Returns the request method.
+     */
+    public function getMethod();
+
+    /**
+     * Return an instance with the provided HTTP method.
+     *
+     * While HTTP method names are typically all uppercase characters, HTTP
+     * method names are case-sensitive and thus implementations SHOULD NOT
+     * modify the given string.
+     *
+     * This method MUST be implemented in such a way as to retain the
+     * immutability of the message, and MUST return an instance that has the
+     * changed request method.
+     *
+     * @param string $method Case-sensitive method.
+     * @return static
+     * @throws \InvalidArgumentException for invalid HTTP methods.
+     */
+    public function withMethod(string $method);
+
+    /**
+     * Retrieves the URI instance.
+     *
+     * This method MUST return a UriInterface instance.
+     *
+     * @link http://tools.ietf.org/html/rfc3986#section-4.3
+     * @return UriInterface Returns a UriInterface instance
+     *     representing the URI of the request.
+     */
+    public function getUri();
+
+    /**
+     * Returns an instance with the provided URI.
+     *
+     * This method MUST update the Host header of the returned request by
+     * default if the URI contains a host component. If the URI does not
+     * contain a host component, any pre-existing Host header MUST be carried
+     * over to the returned request.
+     *
+     * You can opt-in to preserving the original state of the Host header by
+     * setting `$preserveHost` to `true`. When `$preserveHost` is set to
+     * `true`, this method interacts with the Host header in the following ways:
+     *
+     * - If the Host header is missing or empty, and the new URI contains
+     *   a host component, this method MUST update the Host header in the returned
+     *   request.
+     * - If the Host header is missing or empty, and the new URI does not contain a
+     *   host component, this method MUST NOT update the Host header in the returned
+     *   request.
+     * - If a Host header is present and non-empty, this method MUST NOT update
+     *   the Host header in the returned request.
+     *
+     * This method MUST be implemented in such a way as to retain the
+     * immutability of the message, and MUST return an instance that has the
+     * new UriInterface instance.
+     *
+     * @link http://tools.ietf.org/html/rfc3986#section-4.3
+     * @param UriInterface $uri New request URI to use.
+     * @param bool $preserveHost Preserve the original state of the Host header.
+     * @return static
+     */
+    public function withUri(UriInterface $uri, bool $preserveHost = false);
+}

+ 70 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins/psr/http-message/src/ResponseInterface.php

@@ -0,0 +1,70 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Psr\Http\Message;
+
+/**
+ * Representation of an outgoing, server-side response.
+ *
+ * Per the HTTP specification, this interface includes properties for
+ * each of the following:
+ *
+ * - Protocol version
+ * - Status code and reason phrase
+ * - Headers
+ * - Message body
+ *
+ * Responses are considered immutable; all methods that might change state MUST
+ * be implemented such that they retain the internal state of the current
+ * message and return an instance that contains the changed state.
+ */
+interface ResponseInterface extends MessageInterface
+{
+    /**
+     * Gets the response status code.
+     *
+     * The status code is a 3-digit integer result code of the server's attempt
+     * to understand and satisfy the request.
+     *
+     * @return int Status code.
+     */
+    public function getStatusCode();
+
+    /**
+     * Return an instance with the specified status code and, optionally, reason phrase.
+     *
+     * If no reason phrase is specified, implementations MAY choose to default
+     * to the RFC 7231 or IANA recommended reason phrase for the response's
+     * status code.
+     *
+     * This method MUST be implemented in such a way as to retain the
+     * immutability of the message, and MUST return an instance that has the
+     * updated status and reason phrase.
+     *
+     * @link http://tools.ietf.org/html/rfc7231#section-6
+     * @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
+     * @param int $code The 3-digit integer result code to set.
+     * @param string $reasonPhrase The reason phrase to use with the
+     *     provided status code; if none is provided, implementations MAY
+     *     use the defaults as suggested in the HTTP specification.
+     * @return static
+     * @throws \InvalidArgumentException For invalid status code arguments.
+     */
+    public function withStatus(int $code, string $reasonPhrase = '');
+
+    /**
+     * Gets the response reason phrase associated with the status code.
+     *
+     * Because a reason phrase is not a required element in a response
+     * status line, the reason phrase value MAY be null. Implementations MAY
+     * choose to return the default RFC 7231 recommended reason phrase (or those
+     * listed in the IANA HTTP Status Code Registry) for the response's
+     * status code.
+     *
+     * @link http://tools.ietf.org/html/rfc7231#section-6
+     * @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
+     * @return string Reason phrase; must return an empty string if none present.
+     */
+    public function getReasonPhrase();
+}

+ 263 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins/psr/http-message/src/ServerRequestInterface.php

@@ -0,0 +1,263 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Psr\Http\Message;
+
+/**
+ * Representation of an incoming, server-side HTTP request.
+ *
+ * Per the HTTP specification, this interface includes properties for
+ * each of the following:
+ *
+ * - Protocol version
+ * - HTTP method
+ * - URI
+ * - Headers
+ * - Message body
+ *
+ * Additionally, it encapsulates all data as it has arrived to the
+ * application from the CGI and/or PHP environment, including:
+ *
+ * - The values represented in $_SERVER.
+ * - Any cookies provided (generally via $_COOKIE)
+ * - Query string arguments (generally via $_GET, or as parsed via parse_str())
+ * - Upload files, if any (as represented by $_FILES)
+ * - Deserialized body parameters (generally from $_POST)
+ *
+ * $_SERVER values MUST be treated as immutable, as they represent application
+ * state at the time of request; as such, no methods are provided to allow
+ * modification of those values. The other values provide such methods, as they
+ * can be restored from $_SERVER or the request body, and may need treatment
+ * during the application (e.g., body parameters may be deserialized based on
+ * content type).
+ *
+ * Additionally, this interface recognizes the utility of introspecting a
+ * request to derive and match additional parameters (e.g., via URI path
+ * matching, decrypting cookie values, deserializing non-form-encoded body
+ * content, matching authorization headers to users, etc). These parameters
+ * are stored in an "attributes" property.
+ *
+ * Requests are considered immutable; all methods that might change state MUST
+ * be implemented such that they retain the internal state of the current
+ * message and return an instance that contains the changed state.
+ */
+interface ServerRequestInterface extends RequestInterface
+{
+    /**
+     * Retrieve server parameters.
+     *
+     * Retrieves data related to the incoming request environment,
+     * typically derived from PHP's $_SERVER superglobal. The data IS NOT
+     * REQUIRED to originate from $_SERVER.
+     *
+     * @return array
+     */
+    public function getServerParams();
+
+    /**
+     * Retrieve cookies.
+     *
+     * Retrieves cookies sent by the client to the server.
+     *
+     * The data MUST be compatible with the structure of the $_COOKIE
+     * superglobal.
+     *
+     * @return array
+     */
+    public function getCookieParams();
+
+    /**
+     * Return an instance with the specified cookies.
+     *
+     * The data IS NOT REQUIRED to come from the $_COOKIE superglobal, but MUST
+     * be compatible with the structure of $_COOKIE. Typically, this data will
+     * be injected at instantiation.
+     *
+     * This method MUST NOT update the related Cookie header of the request
+     * instance, nor related values in the server params.
+     *
+     * This method MUST be implemented in such a way as to retain the
+     * immutability of the message, and MUST return an instance that has the
+     * updated cookie values.
+     *
+     * @param array $cookies Array of key/value pairs representing cookies.
+     * @return static
+     */
+    public function withCookieParams(array $cookies);
+
+    /**
+     * Retrieve query string arguments.
+     *
+     * Retrieves the deserialized query string arguments, if any.
+     *
+     * Note: the query params might not be in sync with the URI or server
+     * params. If you need to ensure you are only getting the original
+     * values, you may need to parse the query string from `getUri()->getQuery()`
+     * or from the `QUERY_STRING` server param.
+     *
+     * @return array
+     */
+    public function getQueryParams();
+
+    /**
+     * Return an instance with the specified query string arguments.
+     *
+     * These values SHOULD remain immutable over the course of the incoming
+     * request. They MAY be injected during instantiation, such as from PHP's
+     * $_GET superglobal, or MAY be derived from some other value such as the
+     * URI. In cases where the arguments are parsed from the URI, the data
+     * MUST be compatible with what PHP's parse_str() would return for
+     * purposes of how duplicate query parameters are handled, and how nested
+     * sets are handled.
+     *
+     * Setting query string arguments MUST NOT change the URI stored by the
+     * request, nor the values in the server params.
+     *
+     * This method MUST be implemented in such a way as to retain the
+     * immutability of the message, and MUST return an instance that has the
+     * updated query string arguments.
+     *
+     * @param array $query Array of query string arguments, typically from
+     *     $_GET.
+     * @return static
+     */
+    public function withQueryParams(array $query);
+
+    /**
+     * Retrieve normalized file upload data.
+     *
+     * This method returns upload metadata in a normalized tree, with each leaf
+     * an instance of Psr\Http\Message\UploadedFileInterface.
+     *
+     * These values MAY be prepared from $_FILES or the message body during
+     * instantiation, or MAY be injected via withUploadedFiles().
+     *
+     * @return array An array tree of UploadedFileInterface instances; an empty
+     *     array MUST be returned if no data is present.
+     */
+    public function getUploadedFiles();
+
+    /**
+     * Create a new instance with the specified uploaded files.
+     *
+     * This method MUST be implemented in such a way as to retain the
+     * immutability of the message, and MUST return an instance that has the
+     * updated body parameters.
+     *
+     * @param array $uploadedFiles An array tree of UploadedFileInterface instances.
+     * @return static
+     * @throws \InvalidArgumentException if an invalid structure is provided.
+     */
+    public function withUploadedFiles(array $uploadedFiles);
+
+    /**
+     * Retrieve any parameters provided in the request body.
+     *
+     * If the request Content-Type is either application/x-www-form-urlencoded
+     * or multipart/form-data, and the request method is POST, this method MUST
+     * return the contents of $_POST.
+     *
+     * Otherwise, this method may return any results of deserializing
+     * the request body content; as parsing returns structured content, the
+     * potential types MUST be arrays or objects only. A null value indicates
+     * the absence of body content.
+     *
+     * @return null|array|object The deserialized body parameters, if any.
+     *     These will typically be an array or object.
+     */
+    public function getParsedBody();
+
+    /**
+     * Return an instance with the specified body parameters.
+     *
+     * These MAY be injected during instantiation.
+     *
+     * If the request Content-Type is either application/x-www-form-urlencoded
+     * or multipart/form-data, and the request method is POST, use this method
+     * ONLY to inject the contents of $_POST.
+     *
+     * The data IS NOT REQUIRED to come from $_POST, but MUST be the results of
+     * deserializing the request body content. Deserialization/parsing returns
+     * structured data, and, as such, this method ONLY accepts arrays or objects,
+     * or a null value if nothing was available to parse.
+     *
+     * As an example, if content negotiation determines that the request data
+     * is a JSON payload, this method could be used to create a request
+     * instance with the deserialized parameters.
+     *
+     * This method MUST be implemented in such a way as to retain the
+     * immutability of the message, and MUST return an instance that has the
+     * updated body parameters.
+     *
+     * @param null|array|object $data The deserialized body data. This will
+     *     typically be in an array or object.
+     * @return static
+     * @throws \InvalidArgumentException if an unsupported argument type is
+     *     provided.
+     */
+    public function withParsedBody($data);
+
+    /**
+     * Retrieve attributes derived from the request.
+     *
+     * The request "attributes" may be used to allow injection of any
+     * parameters derived from the request: e.g., the results of path
+     * match operations; the results of decrypting cookies; the results of
+     * deserializing non-form-encoded message bodies; etc. Attributes
+     * will be application and request specific, and CAN be mutable.
+     *
+     * @return array Attributes derived from the request.
+     */
+    public function getAttributes();
+
+    /**
+     * Retrieve a single derived request attribute.
+     *
+     * Retrieves a single derived request attribute as described in
+     * getAttributes(). If the attribute has not been previously set, returns
+     * the default value as provided.
+     *
+     * This method obviates the need for a hasAttribute() method, as it allows
+     * specifying a default value to return if the attribute is not found.
+     *
+     * @see getAttributes()
+     * @param string $name The attribute name.
+     * @param mixed $default Default value to return if the attribute does not exist.
+     * @return mixed
+     */
+    public function getAttribute(string $name, $default = null);
+
+    /**
+     * Return an instance with the specified derived request attribute.
+     *
+     * This method allows setting a single derived request attribute as
+     * described in getAttributes().
+     *
+     * This method MUST be implemented in such a way as to retain the
+     * immutability of the message, and MUST return an instance that has the
+     * updated attribute.
+     *
+     * @see getAttributes()
+     * @param string $name The attribute name.
+     * @param mixed $value The value of the attribute.
+     * @return static
+     */
+    public function withAttribute(string $name, $value);
+
+    /**
+     * Return an instance that removes the specified derived request attribute.
+     *
+     * This method allows removing a single derived request attribute as
+     * described in getAttributes().
+     *
+     * This method MUST be implemented in such a way as to retain the
+     * immutability of the message, and MUST return an instance that removes
+     * the attribute.
+     *
+     * @see getAttributes()
+     * @param string $name The attribute name.
+     * @return static
+     */
+    public function withoutAttribute(string $name);
+}

+ 160 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins/psr/http-message/src/StreamInterface.php

@@ -0,0 +1,160 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Psr\Http\Message;
+
+/**
+ * Describes a data stream.
+ *
+ * Typically, an instance will wrap a PHP stream; this interface provides
+ * a wrapper around the most common operations, including serialization of
+ * the entire stream to a string.
+ */
+interface StreamInterface
+{
+    /**
+     * Reads all data from the stream into a string, from the beginning to end.
+     *
+     * This method MUST attempt to seek to the beginning of the stream before
+     * reading data and read the stream until the end is reached.
+     *
+     * Warning: This could attempt to load a large amount of data into memory.
+     *
+     * This method MUST NOT raise an exception in order to conform with PHP's
+     * string casting operations.
+     *
+     * @see http://php.net/manual/en/language.oop5.magic.php#object.tostring
+     * @return string
+     */
+    public function __toString();
+
+    /**
+     * Closes the stream and any underlying resources.
+     *
+     * @return void
+     */
+    public function close();
+
+    /**
+     * Separates any underlying resources from the stream.
+     *
+     * After the stream has been detached, the stream is in an unusable state.
+     *
+     * @return resource|null Underlying PHP stream, if any
+     */
+    public function detach();
+
+    /**
+     * Get the size of the stream if known.
+     *
+     * @return int|null Returns the size in bytes if known, or null if unknown.
+     */
+    public function getSize();
+
+    /**
+     * Returns the current position of the file read/write pointer
+     *
+     * @return int Position of the file pointer
+     * @throws \RuntimeException on error.
+     */
+    public function tell();
+
+    /**
+     * Returns true if the stream is at the end of the stream.
+     *
+     * @return bool
+     */
+    public function eof();
+
+    /**
+     * Returns whether or not the stream is seekable.
+     *
+     * @return bool
+     */
+    public function isSeekable();
+
+    /**
+     * Seek to a position in the stream.
+     *
+     * @link http://www.php.net/manual/en/function.fseek.php
+     * @param int $offset Stream offset
+     * @param int $whence Specifies how the cursor position will be calculated
+     *     based on the seek offset. Valid values are identical to the built-in
+     *     PHP $whence values for `fseek()`.  SEEK_SET: Set position equal to
+     *     offset bytes SEEK_CUR: Set position to current location plus offset
+     *     SEEK_END: Set position to end-of-stream plus offset.
+     * @throws \RuntimeException on failure.
+     */
+    public function seek(int $offset, int $whence = SEEK_SET);
+
+    /**
+     * Seek to the beginning of the stream.
+     *
+     * If the stream is not seekable, this method will raise an exception;
+     * otherwise, it will perform a seek(0).
+     *
+     * @see seek()
+     * @link http://www.php.net/manual/en/function.fseek.php
+     * @throws \RuntimeException on failure.
+     */
+    public function rewind();
+
+    /**
+     * Returns whether or not the stream is writable.
+     *
+     * @return bool
+     */
+    public function isWritable();
+
+    /**
+     * Write data to the stream.
+     *
+     * @param string $string The string that is to be written.
+     * @return int Returns the number of bytes written to the stream.
+     * @throws \RuntimeException on failure.
+     */
+    public function write(string $string);
+
+    /**
+     * Returns whether or not the stream is readable.
+     *
+     * @return bool
+     */
+    public function isReadable();
+
+    /**
+     * Read data from the stream.
+     *
+     * @param int $length Read up to $length bytes from the object and return
+     *     them. Fewer than $length bytes may be returned if underlying stream
+     *     call returns fewer bytes.
+     * @return string Returns the data read from the stream, or an empty string
+     *     if no bytes are available.
+     * @throws \RuntimeException if an error occurs.
+     */
+    public function read(int $length);
+
+    /**
+     * Returns the remaining contents in a string
+     *
+     * @return string
+     * @throws \RuntimeException if unable to read or an error occurs while
+     *     reading.
+     */
+    public function getContents();
+
+    /**
+     * Get stream metadata as an associative array or retrieve a specific key.
+     *
+     * The keys returned are identical to the keys returned from PHP's
+     * stream_get_meta_data() function.
+     *
+     * @link http://php.net/manual/en/function.stream-get-meta-data.php
+     * @param string|null $key Specific metadata to retrieve.
+     * @return array|mixed|null Returns an associative array if no key is
+     *     provided. Returns a specific key value if a key is provided and the
+     *     value is found, or null if the key is not found.
+     */
+    public function getMetadata(?string $key = null);
+}

+ 125 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins/psr/http-message/src/UploadedFileInterface.php

@@ -0,0 +1,125 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Psr\Http\Message;
+
+/**
+ * Value object representing a file uploaded through an HTTP request.
+ *
+ * Instances of this interface are considered immutable; all methods that
+ * might change state MUST be implemented such that they retain the internal
+ * state of the current instance and return an instance that contains the
+ * changed state.
+ */
+interface UploadedFileInterface
+{
+    /**
+     * Retrieve a stream representing the uploaded file.
+     *
+     * This method MUST return a StreamInterface instance, representing the
+     * uploaded file. The purpose of this method is to allow utilizing native PHP
+     * stream functionality to manipulate the file upload, such as
+     * stream_copy_to_stream() (though the result will need to be decorated in a
+     * native PHP stream wrapper to work with such functions).
+     *
+     * If the moveTo() method has been called previously, this method MUST raise
+     * an exception.
+     *
+     * @return StreamInterface Stream representation of the uploaded file.
+     * @throws \RuntimeException in cases when no stream is available or can be
+     *     created.
+     */
+    public function getStream();
+
+    /**
+     * Move the uploaded file to a new location.
+     *
+     * Use this method as an alternative to move_uploaded_file(). This method is
+     * guaranteed to work in both SAPI and non-SAPI environments.
+     * Implementations must determine which environment they are in, and use the
+     * appropriate method (move_uploaded_file(), rename(), or a stream
+     * operation) to perform the operation.
+     *
+     * $targetPath may be an absolute path, or a relative path. If it is a
+     * relative path, resolution should be the same as used by PHP's rename()
+     * function.
+     *
+     * The original file or stream MUST be removed on completion.
+     *
+     * If this method is called more than once, any subsequent calls MUST raise
+     * an exception.
+     *
+     * When used in an SAPI environment where $_FILES is populated, when writing
+     * files via moveTo(), is_uploaded_file() and move_uploaded_file() SHOULD be
+     * used to ensure permissions and upload status are verified correctly.
+     *
+     * If you wish to move to a stream, use getStream(), as SAPI operations
+     * cannot guarantee writing to stream destinations.
+     *
+     * @see http://php.net/is_uploaded_file
+     * @see http://php.net/move_uploaded_file
+     * @param string $targetPath Path to which to move the uploaded file.
+     * @throws \InvalidArgumentException if the $targetPath specified is invalid.
+     * @throws \RuntimeException on any error during the move operation, or on
+     *     the second or subsequent call to the method.
+     */
+    public function moveTo(string $targetPath);
+    
+    /**
+     * Retrieve the file size.
+     *
+     * Implementations SHOULD return the value stored in the "size" key of
+     * the file in the $_FILES array if available, as PHP calculates this based
+     * on the actual size transmitted.
+     *
+     * @return int|null The file size in bytes or null if unknown.
+     */
+    public function getSize();
+    
+    /**
+     * Retrieve the error associated with the uploaded file.
+     *
+     * The return value MUST be one of PHP's UPLOAD_ERR_XXX constants.
+     *
+     * If the file was uploaded successfully, this method MUST return
+     * UPLOAD_ERR_OK.
+     *
+     * Implementations SHOULD return the value stored in the "error" key of
+     * the file in the $_FILES array.
+     *
+     * @see http://php.net/manual/en/features.file-upload.errors.php
+     * @return int One of PHP's UPLOAD_ERR_XXX constants.
+     */
+    public function getError();
+    
+    /**
+     * Retrieve the filename sent by the client.
+     *
+     * Do not trust the value returned by this method. A client could send
+     * a malicious filename with the intention to corrupt or hack your
+     * application.
+     *
+     * Implementations SHOULD return the value stored in the "name" key of
+     * the file in the $_FILES array.
+     *
+     * @return string|null The filename sent by the client or null if none
+     *     was provided.
+     */
+    public function getClientFilename();
+    
+    /**
+     * Retrieve the media type sent by the client.
+     *
+     * Do not trust the value returned by this method. A client could send
+     * a malicious media type with the intention to corrupt or hack your
+     * application.
+     *
+     * Implementations SHOULD return the value stored in the "type" key of
+     * the file in the $_FILES array.
+     *
+     * @return string|null The media type sent by the client or null if none
+     *     was provided.
+     */
+    public function getClientMediaType();
+}

+ 326 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins/psr/http-message/src/UriInterface.php

@@ -0,0 +1,326 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Psr\Http\Message;
+
+/**
+ * Value object representing a URI.
+ *
+ * This interface is meant to represent URIs according to RFC 3986 and to
+ * provide methods for most common operations. Additional functionality for
+ * working with URIs can be provided on top of the interface or externally.
+ * Its primary use is for HTTP requests, but may also be used in other
+ * contexts.
+ *
+ * Instances of this interface are considered immutable; all methods that
+ * might change state MUST be implemented such that they retain the internal
+ * state of the current instance and return an instance that contains the
+ * changed state.
+ *
+ * Typically the Host header will be also be present in the request message.
+ * For server-side requests, the scheme will typically be discoverable in the
+ * server parameters.
+ *
+ * @link http://tools.ietf.org/html/rfc3986 (the URI specification)
+ */
+interface UriInterface
+{
+    /**
+     * Retrieve the scheme component of the URI.
+     *
+     * If no scheme is present, this method MUST return an empty string.
+     *
+     * The value returned MUST be normalized to lowercase, per RFC 3986
+     * Section 3.1.
+     *
+     * The trailing ":" character is not part of the scheme and MUST NOT be
+     * added.
+     *
+     * @see https://tools.ietf.org/html/rfc3986#section-3.1
+     * @return string The URI scheme.
+     */
+    public function getScheme();
+
+    /**
+     * Retrieve the authority component of the URI.
+     *
+     * If no authority information is present, this method MUST return an empty
+     * string.
+     *
+     * The authority syntax of the URI is:
+     *
+     * <pre>
+     * [user-info@]host[:port]
+     * </pre>
+     *
+     * If the port component is not set or is the standard port for the current
+     * scheme, it SHOULD NOT be included.
+     *
+     * @see https://tools.ietf.org/html/rfc3986#section-3.2
+     * @return string The URI authority, in "[user-info@]host[:port]" format.
+     */
+    public function getAuthority();
+
+    /**
+     * Retrieve the user information component of the URI.
+     *
+     * If no user information is present, this method MUST return an empty
+     * string.
+     *
+     * If a user is present in the URI, this will return that value;
+     * additionally, if the password is also present, it will be appended to the
+     * user value, with a colon (":") separating the values.
+     *
+     * The trailing "@" character is not part of the user information and MUST
+     * NOT be added.
+     *
+     * @return string The URI user information, in "username[:password]" format.
+     */
+    public function getUserInfo();
+
+    /**
+     * Retrieve the host component of the URI.
+     *
+     * If no host is present, this method MUST return an empty string.
+     *
+     * The value returned MUST be normalized to lowercase, per RFC 3986
+     * Section 3.2.2.
+     *
+     * @see http://tools.ietf.org/html/rfc3986#section-3.2.2
+     * @return string The URI host.
+     */
+    public function getHost();
+
+    /**
+     * Retrieve the port component of the URI.
+     *
+     * If a port is present, and it is non-standard for the current scheme,
+     * this method MUST return it as an integer. If the port is the standard port
+     * used with the current scheme, this method SHOULD return null.
+     *
+     * If no port is present, and no scheme is present, this method MUST return
+     * a null value.
+     *
+     * If no port is present, but a scheme is present, this method MAY return
+     * the standard port for that scheme, but SHOULD return null.
+     *
+     * @return null|int The URI port.
+     */
+    public function getPort();
+
+    /**
+     * Retrieve the path component of the URI.
+     *
+     * The path can either be empty or absolute (starting with a slash) or
+     * rootless (not starting with a slash). Implementations MUST support all
+     * three syntaxes.
+     *
+     * Normally, the empty path "" and absolute path "/" are considered equal as
+     * defined in RFC 7230 Section 2.7.3. But this method MUST NOT automatically
+     * do this normalization because in contexts with a trimmed base path, e.g.
+     * the front controller, this difference becomes significant. It's the task
+     * of the user to handle both "" and "/".
+     *
+     * The value returned MUST be percent-encoded, but MUST NOT double-encode
+     * any characters. To determine what characters to encode, please refer to
+     * RFC 3986, Sections 2 and 3.3.
+     *
+     * As an example, if the value should include a slash ("/") not intended as
+     * delimiter between path segments, that value MUST be passed in encoded
+     * form (e.g., "%2F") to the instance.
+     *
+     * @see https://tools.ietf.org/html/rfc3986#section-2
+     * @see https://tools.ietf.org/html/rfc3986#section-3.3
+     * @return string The URI path.
+     */
+    public function getPath();
+
+    /**
+     * Retrieve the query string of the URI.
+     *
+     * If no query string is present, this method MUST return an empty string.
+     *
+     * The leading "?" character is not part of the query and MUST NOT be
+     * added.
+     *
+     * The value returned MUST be percent-encoded, but MUST NOT double-encode
+     * any characters. To determine what characters to encode, please refer to
+     * RFC 3986, Sections 2 and 3.4.
+     *
+     * As an example, if a value in a key/value pair of the query string should
+     * include an ampersand ("&") not intended as a delimiter between values,
+     * that value MUST be passed in encoded form (e.g., "%26") to the instance.
+     *
+     * @see https://tools.ietf.org/html/rfc3986#section-2
+     * @see https://tools.ietf.org/html/rfc3986#section-3.4
+     * @return string The URI query string.
+     */
+    public function getQuery();
+
+    /**
+     * Retrieve the fragment component of the URI.
+     *
+     * If no fragment is present, this method MUST return an empty string.
+     *
+     * The leading "#" character is not part of the fragment and MUST NOT be
+     * added.
+     *
+     * The value returned MUST be percent-encoded, but MUST NOT double-encode
+     * any characters. To determine what characters to encode, please refer to
+     * RFC 3986, Sections 2 and 3.5.
+     *
+     * @see https://tools.ietf.org/html/rfc3986#section-2
+     * @see https://tools.ietf.org/html/rfc3986#section-3.5
+     * @return string The URI fragment.
+     */
+    public function getFragment();
+
+    /**
+     * Return an instance with the specified scheme.
+     *
+     * This method MUST retain the state of the current instance, and return
+     * an instance that contains the specified scheme.
+     *
+     * Implementations MUST support the schemes "http" and "https" case
+     * insensitively, and MAY accommodate other schemes if required.
+     *
+     * An empty scheme is equivalent to removing the scheme.
+     *
+     * @param string $scheme The scheme to use with the new instance.
+     * @return static A new instance with the specified scheme.
+     * @throws \InvalidArgumentException for invalid or unsupported schemes.
+     */
+    public function withScheme(string $scheme);
+
+    /**
+     * Return an instance with the specified user information.
+     *
+     * This method MUST retain the state of the current instance, and return
+     * an instance that contains the specified user information.
+     *
+     * Password is optional, but the user information MUST include the
+     * user; an empty string for the user is equivalent to removing user
+     * information.
+     *
+     * @param string $user The user name to use for authority.
+     * @param null|string $password The password associated with $user.
+     * @return static A new instance with the specified user information.
+     */
+    public function withUserInfo(string $user, ?string $password = null);
+
+    /**
+     * Return an instance with the specified host.
+     *
+     * This method MUST retain the state of the current instance, and return
+     * an instance that contains the specified host.
+     *
+     * An empty host value is equivalent to removing the host.
+     *
+     * @param string $host The hostname to use with the new instance.
+     * @return static A new instance with the specified host.
+     * @throws \InvalidArgumentException for invalid hostnames.
+     */
+    public function withHost(string $host);
+
+    /**
+     * Return an instance with the specified port.
+     *
+     * This method MUST retain the state of the current instance, and return
+     * an instance that contains the specified port.
+     *
+     * Implementations MUST raise an exception for ports outside the
+     * established TCP and UDP port ranges.
+     *
+     * A null value provided for the port is equivalent to removing the port
+     * information.
+     *
+     * @param null|int $port The port to use with the new instance; a null value
+     *     removes the port information.
+     * @return static A new instance with the specified port.
+     * @throws \InvalidArgumentException for invalid ports.
+     */
+    public function withPort(?int $port);
+
+    /**
+     * Return an instance with the specified path.
+     *
+     * This method MUST retain the state of the current instance, and return
+     * an instance that contains the specified path.
+     *
+     * The path can either be empty or absolute (starting with a slash) or
+     * rootless (not starting with a slash). Implementations MUST support all
+     * three syntaxes.
+     *
+     * If the path is intended to be domain-relative rather than path relative then
+     * it must begin with a slash ("/"). Paths not starting with a slash ("/")
+     * are assumed to be relative to some base path known to the application or
+     * consumer.
+     *
+     * Users can provide both encoded and decoded path characters.
+     * Implementations ensure the correct encoding as outlined in getPath().
+     *
+     * @param string $path The path to use with the new instance.
+     * @return static A new instance with the specified path.
+     * @throws \InvalidArgumentException for invalid paths.
+     */
+    public function withPath(string $path);
+
+    /**
+     * Return an instance with the specified query string.
+     *
+     * This method MUST retain the state of the current instance, and return
+     * an instance that contains the specified query string.
+     *
+     * Users can provide both encoded and decoded query characters.
+     * Implementations ensure the correct encoding as outlined in getQuery().
+     *
+     * An empty query string value is equivalent to removing the query string.
+     *
+     * @param string $query The query string to use with the new instance.
+     * @return static A new instance with the specified query string.
+     * @throws \InvalidArgumentException for invalid query strings.
+     */
+    public function withQuery(string $query);
+
+    /**
+     * Return an instance with the specified URI fragment.
+     *
+     * This method MUST retain the state of the current instance, and return
+     * an instance that contains the specified URI fragment.
+     *
+     * Users can provide both encoded and decoded fragment characters.
+     * Implementations ensure the correct encoding as outlined in getFragment().
+     *
+     * An empty fragment value is equivalent to removing the fragment.
+     *
+     * @param string $fragment The fragment to use with the new instance.
+     * @return static A new instance with the specified fragment.
+     */
+    public function withFragment(string $fragment);
+
+    /**
+     * Return the string representation as a URI reference.
+     *
+     * Depending on which components of the URI are present, the resulting
+     * string is either a full URI or relative reference according to RFC 3986,
+     * Section 4.1. The method concatenates the various components of the URI,
+     * using the appropriate delimiters:
+     *
+     * - If a scheme is present, it MUST be suffixed by ":".
+     * - If an authority is present, it MUST be prefixed by "//".
+     * - The path can be concatenated without delimiters. But there are two
+     *   cases where the path has to be adjusted to make the URI reference
+     *   valid as PHP does not allow to throw an exception in __toString():
+     *     - If the path is rootless and an authority is present, the path MUST
+     *       be prefixed by "/".
+     *     - If the path is starting with more than one "/" and no authority is
+     *       present, the starting slashes MUST be reduced to one.
+     * - If a query is present, it MUST be prefixed by "?".
+     * - If a fragment is present, it MUST be prefixed by "#".
+     *
+     * @see http://tools.ietf.org/html/rfc3986#section-4.1
+     * @return string
+     */
+    public function __toString();
+}

+ 39 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/ar-ar.js

@@ -0,0 +1,39 @@
+glFilepondLocale = {
+    labelIdle: 'اسحب و ادرج ملفاتك أو <span class="filepond--label-action"> تصفح </span>',
+    labelInvalidField: 'الحقل يحتوي على ملفات غير صالحة',
+    labelFileWaitingForSize: 'بانتظار الحجم',
+    labelFileSizeNotAvailable: 'الحجم غير متاح',
+    labelFileLoading: 'بالإنتظار',
+    labelFileLoadError: 'حدث خطأ أثناء التحميل',
+    labelFileProcessing: 'يتم الرفع',
+    labelFileProcessingComplete: 'تم الرفع',
+    labelFileProcessingAborted: 'تم إلغاء الرفع',
+    labelFileProcessingError: 'حدث خطأ أثناء الرفع',
+    labelFileProcessingRevertError: 'حدث خطأ أثناء التراجع',
+    labelFileRemoveError: 'حدث خطأ أثناء الحذف',
+    labelTapToCancel: 'انقر للإلغاء',
+    labelTapToRetry: 'انقر لإعادة المحاولة',
+    labelTapToUndo: 'انقر للتراجع',
+    labelButtonRemoveItem: 'مسح',
+    labelButtonAbortItemLoad: 'إلغاء',
+    labelButtonRetryItemLoad: 'إعادة',
+    labelButtonAbortItemProcessing: 'إلغاء',
+    labelButtonUndoItemProcessing: 'تراجع',
+    labelButtonRetryItemProcessing: 'إعادة',
+    labelButtonProcessItem: 'رفع',
+    labelMaxFileSizeExceeded: 'الملف كبير جدا',
+    labelMaxFileSize: 'حجم الملف الأقصى: {filesize}',
+    labelMaxTotalFileSizeExceeded: 'تم تجاوز الحد الأقصى للحجم الإجمالي',
+    labelMaxTotalFileSize: 'الحد الأقصى لحجم الملف: {filesize}',
+    labelFileTypeNotAllowed: 'ملف من نوع غير صالح',
+    fileValidateTypeLabelExpectedTypes: 'تتوقع {allButLastType} من {lastType}',
+    imageValidateSizeLabelFormatError: 'نوع الصورة غير مدعوم',
+    imageValidateSizeLabelImageSizeTooSmall: 'الصورة صغير جدا',
+    imageValidateSizeLabelImageSizeTooBig: 'الصورة كبيرة جدا',
+    imageValidateSizeLabelExpectedMinSize: 'الحد الأدنى للأبعاد هو: {minWidth} × {minHeight}',
+    imageValidateSizeLabelExpectedMaxSize: 'الحد الأقصى للأبعاد هو: {maxWidth} × {maxHeight}',
+    imageValidateSizeLabelImageResolutionTooLow: 'الدقة ضعيفة جدا',
+    imageValidateSizeLabelImageResolutionTooHigh: 'الدقة مرتفعة جدا',
+    imageValidateSizeLabelExpectedMinResolution: 'أقل دقة: {minResolution}',
+    imageValidateSizeLabelExpectedMaxResolution: 'أقصى دقة: {maxResolution}'
+};

+ 8 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/cmd.sh

@@ -0,0 +1,8 @@
+# change files Linux
+# find ./ -type f -exec sed -i 's/glFilepondLocale =/glFilepondLocale =/' {} \;
+
+# change files Mac OSX
+# find ./ -type f -exec sed -i '' -e 's/glFilepondLocale =/glFilepondLocale =/' {} \;
+
+# get list of langs
+# ls -la | grep "\.js$" | awk '{print $9}' | awk -F '.' '{printf("\047%s\047 => 1, ", $1); }'

+ 39 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/cs-cz.js

@@ -0,0 +1,39 @@
+glFilepondLocale = {
+    labelIdle: 'Přetáhněte soubor sem (drag&drop) nebo <span class="filepond--label-action"> Vyhledat </span>',
+    labelInvalidField: 'Pole obsahuje chybné soubory',
+    labelFileWaitingForSize: 'Zjišťuje se velikost',
+    labelFileSizeNotAvailable: 'Velikost není známá',
+    labelFileLoading: 'Přenáší se',
+    labelFileLoadError: 'Chyba při přenosu',
+    labelFileProcessing: 'Probíhá upload',
+    labelFileProcessingComplete: 'Upload dokončen',
+    labelFileProcessingAborted: 'Upload stornován',
+    labelFileProcessingError: 'Chyba při uploadu',
+    labelFileProcessingRevertError: 'Chyba při obnově',
+    labelFileRemoveError: 'Chyba při odstranění',
+    labelTapToCancel: 'klepněte pro storno',
+    labelTapToRetry: 'klepněte pro opakování',
+    labelTapToUndo: 'klepněte pro vrácení',
+    labelButtonRemoveItem: 'Odstranit',
+    labelButtonAbortItemLoad: 'Storno',
+    labelButtonRetryItemLoad: 'Opakovat',
+    labelButtonAbortItemProcessing: 'Zpět',
+    labelButtonUndoItemProcessing: 'Vrátit',
+    labelButtonRetryItemProcessing: 'Opakovat',
+    labelButtonProcessItem: 'Upload',
+    labelMaxFileSizeExceeded: 'Soubor je příliš velký',
+    labelMaxFileSize: 'Největší velikost souboru je {filesize}',
+    labelMaxTotalFileSizeExceeded: 'Překročena maximální celková velikost souboru',
+    labelMaxTotalFileSize: 'Maximální celková velikost souboru je {filesize}',
+    labelFileTypeNotAllowed: 'Soubor je nesprávného typu',
+    fileValidateTypeLabelExpectedTypes: 'Očekává se {allButLastType} nebo {lastType}',
+    imageValidateSizeLabelFormatError: 'Obrázek tohoto typu není podporován',
+    imageValidateSizeLabelImageSizeTooSmall: 'Obrázek je příliš malý',
+    imageValidateSizeLabelImageSizeTooBig: 'Obrázek je příliš velký',
+    imageValidateSizeLabelExpectedMinSize: 'Minimální rozměr je {minWidth} × {minHeight}',
+    imageValidateSizeLabelExpectedMaxSize: 'Maximální rozměr je {maxWidth} × {maxHeight}',
+    imageValidateSizeLabelImageResolutionTooLow: 'Rozlišení je příliš malé',
+    imageValidateSizeLabelImageResolutionTooHigh: 'Rozlišení je příliš velké',
+    imageValidateSizeLabelExpectedMinResolution: 'Minimální rozlišení je {minResolution}',
+    imageValidateSizeLabelExpectedMaxResolution: 'Maximální rozlišení je {maxResolution}'
+};

+ 40 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/da-dk.js

@@ -0,0 +1,40 @@
+glFilepondLocale = {
+    labelIdle:
+        'Træk & slip filer eller <span class = "filepond - label-action"> Gennemse </span>',
+    labelInvalidField: 'Felt indeholder ugyldige filer',
+    labelFileWaitingForSize: 'Venter på størrelse',
+    labelFileSizeNotAvailable: 'Størrelse ikke tilgængelig',
+    labelFileLoading: 'Loader',
+    labelFileLoadError: 'Load fejlede',
+    labelFileProcessing: 'Uploader',
+    labelFileProcessingComplete: 'Upload færdig',
+    labelFileProcessingAborted: 'Upload annulleret',
+    labelFileProcessingError: 'Upload fejlede',
+    labelFileProcessingRevertError: 'Fortryd fejlede',
+    labelFileRemoveError: 'Fjern fejlede',
+    labelTapToCancel: 'tryk for at annullere',
+    labelTapToRetry: 'tryk for at prøve igen',
+    labelTapToUndo: 'tryk for at fortryde',
+    labelButtonRemoveItem: 'Fjern',
+    labelButtonAbortItemLoad: 'Annuller',
+    labelButtonRetryItemLoad: 'Forsøg igen',
+    labelButtonAbortItemProcessing: 'Annuller',
+    labelButtonUndoItemProcessing: 'Fortryd',
+    labelButtonRetryItemProcessing: 'Prøv igen',
+    labelButtonProcessItem: 'Upload',
+    labelMaxFileSizeExceeded: 'Filen er for stor',
+    labelMaxFileSize: 'Maksimal filstørrelse er {filesize}',
+    labelMaxTotalFileSizeExceeded: 'Maksimal totalstørrelse overskredet',
+    labelMaxTotalFileSize: 'Maksimal total filstørrelse er {filesize}',
+    labelFileTypeNotAllowed: 'Ugyldig filtype',
+    fileValidateTypeLabelExpectedTypes: 'Forventer {allButLastType} eller {lastType}',
+    imageValidateSizeLabelFormatError: 'Ugyldigt format',
+    imageValidateSizeLabelImageSizeTooSmall: 'Billedet er for lille',
+    imageValidateSizeLabelImageSizeTooBig: 'Billedet er for stort',
+    imageValidateSizeLabelExpectedMinSize: 'Minimum størrelse er {minBredde} × {minHøjde}',
+    imageValidateSizeLabelExpectedMaxSize: 'Maksimal størrelse er {maxWidth} × {maxHeight}',
+    imageValidateSizeLabelImageResolutionTooLow: 'For lav opløsning',
+    imageValidateSizeLabelImageResolutionTooHigh: 'For høj opløsning',
+    imageValidateSizeLabelExpectedMinResolution: 'Minimum opløsning er {minResolution}',
+    imageValidateSizeLabelExpectedMaxResolution: 'Maksimal opløsning er {maxResolution}',
+};

+ 39 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/de-de.js

@@ -0,0 +1,39 @@
+glFilepondLocale = {
+    labelIdle: 'Dateien ablegen oder <span class="filepond--label-action"> auswählen </span>',
+    labelInvalidField: 'Feld beinhaltet ungültige Dateien',
+    labelFileWaitingForSize: 'Dateigröße berechnen',
+    labelFileSizeNotAvailable: 'Dateigröße nicht verfügbar',
+    labelFileLoading: 'Laden',
+    labelFileLoadError: 'Fehler beim Laden',
+    labelFileProcessing: 'Upload läuft',
+    labelFileProcessingComplete: 'Upload abgeschlossen',
+    labelFileProcessingAborted: 'Upload abgebrochen',
+    labelFileProcessingError: 'Fehler beim Upload',
+    labelFileProcessingRevertError: 'Fehler beim Wiederherstellen',
+    labelFileRemoveError: 'Fehler beim Löschen',
+    labelTapToCancel: 'abbrechen',
+    labelTapToRetry: 'erneut versuchen',
+    labelTapToUndo: 'rückgängig',
+    labelButtonRemoveItem: 'Entfernen',
+    labelButtonAbortItemLoad: 'Verwerfen',
+    labelButtonRetryItemLoad: 'Erneut versuchen',
+    labelButtonAbortItemProcessing: 'Abbrechen',
+    labelButtonUndoItemProcessing: 'Rückgängig',
+    labelButtonRetryItemProcessing: 'Erneut versuchen',
+    labelButtonProcessItem: 'Upload',
+    labelMaxFileSizeExceeded: 'Datei ist zu groß',
+    labelMaxFileSize: 'Maximale Dateigröße: {filesize}',
+    labelMaxTotalFileSizeExceeded: 'Maximale gesamte Dateigröße überschritten',
+    labelMaxTotalFileSize: 'Maximale gesamte Dateigröße: {filesize}',
+    labelFileTypeNotAllowed: 'Dateityp ungültig',
+    fileValidateTypeLabelExpectedTypes: 'Erwartet {allButLastType} oder {lastType}',
+    imageValidateSizeLabelFormatError: 'Bildtyp nicht unterstützt',
+    imageValidateSizeLabelImageSizeTooSmall: 'Bild ist zu klein',
+    imageValidateSizeLabelImageSizeTooBig: 'Bild ist zu groß',
+    imageValidateSizeLabelExpectedMinSize: 'Mindestgröße: {minWidth} × {minHeight}',
+    imageValidateSizeLabelExpectedMaxSize: 'Maximale Größe: {maxWidth} × {maxHeight}',
+    imageValidateSizeLabelImageResolutionTooLow: 'Auflösung ist zu niedrig',
+    imageValidateSizeLabelImageResolutionTooHigh: 'Auflösung ist zu hoch',
+    imageValidateSizeLabelExpectedMinResolution: 'Mindestauflösung: {minResolution}',
+    imageValidateSizeLabelExpectedMaxResolution: 'Maximale Auflösung: {maxResolution}'
+  };

+ 39 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/el-el.js

@@ -0,0 +1,39 @@
+glFilepondLocale = {
+    labelIdle: 'Σύρετε τα αρχεία σας στο πλαίσιο ή <span class="filepond--label-action"> Επιλέξτε </span>',
+    labelInvalidField: 'Το πεδίο περιέχει μη έγκυρα αρχεία',
+    labelFileWaitingForSize: 'Σε αναμονή για το μέγεθος',
+    labelFileSizeNotAvailable: 'Μέγεθος μη διαθέσιμο',
+    labelFileLoading: 'Φόρτωση σε εξέλιξη',
+    labelFileLoadError: 'Σφάλμα κατά τη φόρτωση',
+    labelFileProcessing: 'Επεξεργασία',
+    labelFileProcessingComplete: 'Η επεξεργασία ολοκληρώθηκε',
+    labelFileProcessingAborted: 'Η επεξεργασία ακυρώθηκε',
+    labelFileProcessingError: 'Σφάλμα κατά την επεξεργασία',
+    labelFileProcessingRevertError: 'Σφάλμα κατά την επαναφορά',
+    labelFileRemoveError: 'Σφάλμα κατά την διαγραφή',
+    labelTapToCancel: 'πατήστε για ακύρωση',
+    labelTapToRetry: 'πατήστε για επανάληψη',
+    labelTapToUndo: 'πατήστε για αναίρεση',
+    labelButtonRemoveItem: 'Αφαίρεση',
+    labelButtonAbortItemLoad: 'Ακύρωση',
+    labelButtonRetryItemLoad: 'Επανάληψη',
+    labelButtonAbortItemProcessing: 'Ακύρωση',
+    labelButtonUndoItemProcessing: 'Αναίρεση',
+    labelButtonRetryItemProcessing: 'Επανάληψη',
+    labelButtonProcessItem: 'Μεταφόρτωση',
+    labelMaxFileSizeExceeded: 'Το αρχείο είναι πολύ μεγάλο',
+    labelMaxFileSize: 'Το μέγιστο μέγεθος αρχείου είναι {filesize}',
+    labelMaxTotalFileSizeExceeded: 'Υπέρβαση του μέγιστου συνολικού μεγέθους',
+    labelMaxTotalFileSize: 'Το μέγιστο συνολικό μέγεθος αρχείων είναι {filesize}',
+    labelFileTypeNotAllowed: 'Μη έγκυρος τύπος αρχείου',
+    fileValidateTypeLabelExpectedTypes: 'Τα αποδεκτά αρχεία είναι {allButLastType} ή {lastType}',
+    imageValidateSizeLabelFormatError: 'Ο τύπος της εικόνας δεν υποστηρίζεται',
+    imageValidateSizeLabelImageSizeTooSmall: 'Η εικόνα είναι πολύ μικρή',
+    imageValidateSizeLabelImageSizeTooBig: 'Η εικόνα είναι πολύ μεγάλη',
+    imageValidateSizeLabelExpectedMinSize: 'Το ελάχιστο αποδεκτό μέγεθος είναι {minWidth} × {minHeight}',
+    imageValidateSizeLabelExpectedMaxSize: 'Το μέγιστο αποδεκτό μέγεθος είναι {maxWidth} × {maxHeight}',
+    imageValidateSizeLabelImageResolutionTooLow: 'Η ανάλυση της εικόνας είναι πολύ χαμηλή',
+    imageValidateSizeLabelImageResolutionTooHigh: 'Η ανάλυση της εικόνας είναι πολύ υψηλή',
+    imageValidateSizeLabelExpectedMinResolution: 'Η ελάχιστη αποδεκτή ανάλυση είναι {minResolution}',
+    imageValidateSizeLabelExpectedMaxResolution: 'Η μέγιστη αποδεκτή ανάλυση είναι {maxResolution}'
+};

+ 39 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/en-en.js

@@ -0,0 +1,39 @@
+glFilepondLocale = {
+    labelIdle: 'Drag & Drop your files or <span class="filepond--label-action"> Browse </span>',
+    labelInvalidField: 'Field contains invalid files',
+    labelFileWaitingForSize: 'Waiting for size',
+    labelFileSizeNotAvailable: 'Size not available',
+    labelFileLoading: 'Loading',
+    labelFileLoadError: 'Error during load',
+    labelFileProcessing: 'Uploading',
+    labelFileProcessingComplete: 'Upload complete',
+    labelFileProcessingAborted: 'Upload cancelled',
+    labelFileProcessingError: 'Error during upload',
+    labelFileProcessingRevertError: 'Error during revert',
+    labelFileRemoveError: 'Error during remove',
+    labelTapToCancel: 'tap to cancel',
+    labelTapToRetry: 'tap to retry',
+    labelTapToUndo: 'tap to undo',
+    labelButtonRemoveItem: 'Remove',
+    labelButtonAbortItemLoad: 'Abort',
+    labelButtonRetryItemLoad: 'Retry',
+    labelButtonAbortItemProcessing: 'Cancel',
+    labelButtonUndoItemProcessing: 'Undo',
+    labelButtonRetryItemProcessing: 'Retry',
+    labelButtonProcessItem: 'Upload',
+    labelMaxFileSizeExceeded: 'File is too large',
+    labelMaxFileSize: 'Maximum file size is {filesize}',
+    labelMaxTotalFileSizeExceeded: 'Maximum total size exceeded',
+    labelMaxTotalFileSize: 'Maximum total file size is {filesize}',
+    labelFileTypeNotAllowed: 'File of invalid type',
+    fileValidateTypeLabelExpectedTypes: 'Expects {allButLastType} or {lastType}',
+    imageValidateSizeLabelFormatError: 'Image type not supported',
+    imageValidateSizeLabelImageSizeTooSmall: 'Image is too small',
+    imageValidateSizeLabelImageSizeTooBig: 'Image is too big',
+    imageValidateSizeLabelExpectedMinSize: 'Minimum size is {minWidth} × {minHeight}',
+    imageValidateSizeLabelExpectedMaxSize: 'Maximum size is {maxWidth} × {maxHeight}',
+    imageValidateSizeLabelImageResolutionTooLow: 'Resolution is too low',
+    imageValidateSizeLabelImageResolutionTooHigh: 'Resolution is too high',
+    imageValidateSizeLabelExpectedMinResolution: 'Minimum resolution is {minResolution}',
+    imageValidateSizeLabelExpectedMaxResolution: 'Maximum resolution is {maxResolution}'
+};

+ 39 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/es-es.js

@@ -0,0 +1,39 @@
+glFilepondLocale = {
+    labelIdle: 'Arrastra y suelta tus archivos o <span class = "filepond--label-action"> Examinar <span>',
+    labelInvalidField: "El campo contiene archivos inválidos",
+    labelFileWaitingForSize: "Esperando tamaño",
+    labelFileSizeNotAvailable: "Tamaño no disponible",
+    labelFileLoading: "Cargando",
+    labelFileLoadError: "Error durante la carga",
+    labelFileProcessing: "Cargando",
+    labelFileProcessingComplete: "Carga completa",
+    labelFileProcessingAborted: "Carga cancelada",
+    labelFileProcessingError: "Error durante la carga",
+    labelFileProcessingRevertError: "Error durante la reversión",
+    labelFileRemoveError: "Error durante la eliminación",
+    labelTapToCancel: "toca para cancelar",
+    labelTapToRetry: "tocar para volver a intentar",
+    labelTapToUndo: "tocar para deshacer",
+    labelButtonRemoveItem: "Eliminar",
+    labelButtonAbortItemLoad: "Abortar",
+    labelButtonRetryItemLoad: "Reintentar",
+    labelButtonAbortItemProcessing: "Cancelar",
+    labelButtonUndoItemProcessing: "Deshacer",
+    labelButtonRetryItemProcessing: "Reintentar",
+    labelButtonProcessItem: "Cargar",
+    labelMaxFileSizeExceeded: "El archivo es demasiado grande",
+    labelMaxFileSize: "El tamaño máximo del archivo es {filesize}",
+    labelMaxTotalFileSizeExceeded: "Tamaño total máximo excedido",
+    labelMaxTotalFileSize: "El tamaño total máximo del archivo es {filesize}",
+    labelFileTypeNotAllowed: "Archivo de tipo no válido",
+    fileValidateTypeLabelExpectedTypes: "Espera {allButLastType} o {lastType}",
+    imageValidateSizeLabelFormatError: "Tipo de imagen no compatible",
+    imageValidateSizeLabelImageSizeTooSmall: "La imagen es demasiado pequeña",
+    imageValidateSizeLabelImageSizeTooBig: "La imagen es demasiado grande",
+    imageValidateSizeLabelExpectedMinSize: "El tamaño mínimo es {minWidth} × {minHeight}",
+    imageValidateSizeLabelExpectedMaxSize: "El tamaño máximo es {maxWidth} × {maxHeight}",
+    imageValidateSizeLabelImageResolutionTooLow: "La resolución es demasiado baja",
+    imageValidateSizeLabelImageResolutionTooHigh: "La resolución es demasiado alta",
+    imageValidateSizeLabelExpectedMinResolution: "La resolución mínima es {minResolution}",
+    imageValidateSizeLabelExpectedMaxResolution: "La resolución máxima es {maxResolution}",
+};

+ 39 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/fa-ir.js

@@ -0,0 +1,39 @@
+glFilepondLocale = {
+    labelIdle: 'فایل را اینجا بکشید و رها کنید، یا <span class="filepond--label-action"> جستجو کنید </span>',
+    labelInvalidField: 'فیلد دارای فایل های نامعتبر است',
+    labelFileWaitingForSize: 'Waiting for size',
+    labelFileSizeNotAvailable: 'حجم فایل مجاز نیست',
+    labelFileLoading: 'درحال بارگذاری',
+    labelFileLoadError: 'خطا در زمان اجرا',
+    labelFileProcessing: 'درحال بارگذاری',
+    labelFileProcessingComplete: 'بارگذاری کامل شد',
+    labelFileProcessingAborted: 'بارگذاری لغو شد',
+    labelFileProcessingError: 'خطا در زمان بارگذاری',
+    labelFileProcessingRevertError: 'خطا در زمان حذف',
+    labelFileRemoveError: 'خطا در زمان حذف',
+    labelTapToCancel: 'برای لغو ضربه بزنید',
+    labelTapToRetry: 'برای تکرار کلیک کنید',
+    labelTapToUndo: 'برای برگشت کلیک کنید',
+    labelButtonRemoveItem: 'حذف',
+    labelButtonAbortItemLoad: 'لغو',
+    labelButtonRetryItemLoad: 'تکرار',
+    labelButtonAbortItemProcessing: 'لغو',
+    labelButtonUndoItemProcessing: 'برگشت',
+    labelButtonRetryItemProcessing: 'تکرار',
+    labelButtonProcessItem: 'بارگذاری',
+    labelMaxFileSizeExceeded: 'فایل بسیار حجیم است',
+    labelMaxFileSize: 'حداکثر مجاز فایل {filesize} است',
+    labelMaxTotalFileSizeExceeded: 'از حداکثر حجم فایل بیشتر شد',
+    labelMaxTotalFileSize: 'حداکثر حجم فایل {filesize} است',
+    labelFileTypeNotAllowed: 'نوع فایل نامعتبر است',
+    fileValidateTypeLabelExpectedTypes: 'در انتظار {allButLastType} یا {lastType}',
+    imageValidateSizeLabelFormatError: 'فرمت تصویر پشتیبانی نمی شود',
+    imageValidateSizeLabelImageSizeTooSmall: 'تصویر بسیار کوچک است',
+    imageValidateSizeLabelImageSizeTooBig: 'تصویر بسیار بزرگ است',
+    imageValidateSizeLabelExpectedMinSize: 'حداقل اندازه {minWidth} × {minHeight} است',
+    imageValidateSizeLabelExpectedMaxSize: 'حداکثر اندازه {maxWidth} × {maxHeight} است',
+    imageValidateSizeLabelImageResolutionTooLow: 'وضوح تصویر بسیار کم است',
+    imageValidateSizeLabelImageResolutionTooHigh: 'وضوع تصویر بسیار زیاد است',
+    imageValidateSizeLabelExpectedMinResolution: 'حداقل وضوح تصویر {minResolution} است',
+    imageValidateSizeLabelExpectedMaxResolution: 'حداکثر وضوح تصویر {maxResolution} است'
+};

+ 39 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/fi-fi.js

@@ -0,0 +1,39 @@
+glFilepondLocale = {
+    labelIdle: 'Vedä ja pudota tiedostoja tai <span class="filepond--label-action"> Selaa </span>',
+    labelInvalidField: 'Kentässä on virheellisiä tiedostoja',
+    labelFileWaitingForSize: 'Odotetaan kokoa',
+    labelFileSizeNotAvailable: 'Kokoa ei saatavilla',
+    labelFileLoading: 'Ladataan',
+    labelFileLoadError: 'Virhe latauksessa',
+    labelFileProcessing: 'Lähetetään',
+    labelFileProcessingComplete: 'Lähetys valmis',
+    labelFileProcessingAborted: 'Lähetys peruttu',
+    labelFileProcessingError: 'Virhe lähetyksessä',
+    labelFileProcessingRevertError: 'Virhe palautuksessa',
+    labelFileRemoveError: 'Virhe poistamisessa',
+    labelTapToCancel: 'peruuta napauttamalla',
+    labelTapToRetry: 'yritä uudelleen napauttamalla',
+    labelTapToUndo: 'kumoa napauttamalla',
+    labelButtonRemoveItem: 'Poista',
+    labelButtonAbortItemLoad: 'Keskeytä',
+    labelButtonRetryItemLoad: 'Yritä uudelleen',
+    labelButtonAbortItemProcessing: 'Peruuta',
+    labelButtonUndoItemProcessing: 'Kumoa',
+    labelButtonRetryItemProcessing: 'Yritä uudelleen',
+    labelButtonProcessItem: 'Lähetä',
+    labelMaxFileSizeExceeded: 'Tiedoston koko on liian suuri',
+    labelMaxFileSize: 'Tiedoston maksimikoko on {filesize}',
+    labelMaxTotalFileSizeExceeded: 'Tiedostojen yhdistetty maksimikoko ylitetty',
+    labelMaxTotalFileSize: 'Tiedostojen yhdistetty maksimikoko on {filesize}',
+    labelFileTypeNotAllowed: 'Tiedostotyyppiä ei sallita',
+    fileValidateTypeLabelExpectedTypes: 'Sallitaan {allButLastType} tai {lastType}',
+    imageValidateSizeLabelFormatError: 'Kuvatyyppiä ei tueta',
+    imageValidateSizeLabelImageSizeTooSmall: 'Kuva on liian pieni',
+    imageValidateSizeLabelImageSizeTooBig: 'Kuva on liian suuri',
+    imageValidateSizeLabelExpectedMinSize: 'Minimikoko on {minWidth} × {minHeight}',
+    imageValidateSizeLabelExpectedMaxSize: 'Maksimikoko on {maxWidth} × {maxHeight}',
+    imageValidateSizeLabelImageResolutionTooLow: 'Resoluutio on liian pieni',
+    imageValidateSizeLabelImageResolutionTooHigh: 'Resoluutio on liian suuri',
+    imageValidateSizeLabelExpectedMinResolution: 'Minimiresoluutio on {minResolution}',
+    imageValidateSizeLabelExpectedMaxResolution: 'Maksimiresoluutio on {maxResolution}'
+};

+ 39 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/fr-fr.js

@@ -0,0 +1,39 @@
+glFilepondLocale = {
+    labelIdle: 'Faites glisser vos fichiers ou <span class = "filepond--label-action"> Parcourir <span>',
+    labelInvalidField: "Le champ contient des fichiers invalides",
+    labelFileWaitingForSize: "En attente de taille",
+    labelFileSizeNotAvailable: "Taille non disponible",
+    labelFileLoading: "Chargement",
+    labelFileLoadError: "Erreur durant le chargement",
+    labelFileProcessing: "Traitement",
+    labelFileProcessingComplete: "Traitement effectué",
+    labelFileProcessingAborted: "Traitement interrompu",
+    labelFileProcessingError: "Erreur durant le traitement",
+    labelFileProcessingRevertError: "Erreur durant la restauration",
+    labelFileRemoveError: "Erreur durant la suppression",
+    labelTapToCancel: "appuyer pour annuler",
+    labelTapToRetry: "appuyer pour réessayer",
+    labelTapToUndo: "appuyer pour revenir en arrière",
+    labelButtonRemoveItem: "Retirer",
+    labelButtonAbortItemLoad: "Annuler",
+    labelButtonRetryItemLoad: "Recommencer",
+    labelButtonAbortItemProcessing: "Annuler",
+    labelButtonUndoItemProcessing: "Revenir en arrière",
+    labelButtonRetryItemProcessing: "Recommencer",
+    labelButtonProcessItem: "Transférer",
+    labelMaxFileSizeExceeded: "Le fichier est trop volumineux",
+    labelMaxFileSize: "La taille maximale de fichier est {filesize}",
+    labelMaxTotalFileSizeExceeded: "Taille totale maximale dépassée",
+    labelMaxTotalFileSize: "La taille totale maximale des fichiers est {filesize}",
+    labelFileTypeNotAllowed: "Fichier non valide",
+    fileValidateTypeLabelExpectedTypes: "Attendu {allButLastType} ou {lastType}",
+    imageValidateSizeLabelFormatError: "Type d'image non pris en charge",
+    imageValidateSizeLabelImageSizeTooSmall: "L'image est trop petite",
+    imageValidateSizeLabelImageSizeTooBig: "L'image est trop grande",
+    imageValidateSizeLabelExpectedMinSize: "La taille minimale est {minWidth} × {minHeight}",
+    imageValidateSizeLabelExpectedMaxSize: "La taille maximale est {maxWidth} × {maxHeight}",
+    imageValidateSizeLabelImageResolutionTooLow: "La résolution est trop faible",
+    imageValidateSizeLabelImageResolutionTooHigh: "La résolution est trop élevée",
+    imageValidateSizeLabelExpectedMinResolution: "La résolution minimale est {minResolution}",
+    imageValidateSizeLabelExpectedMaxResolution: "La résolution maximale est {maxResolution}",
+};

+ 39 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/he-he.js

@@ -0,0 +1,39 @@
+glFilepondLocale = {
+    labelIdle: 'גרור ושחרר את הקבצים כאן או <span class="filepond--label-action"> לחץ כאן לבחירה </span>',
+    labelInvalidField: 'קובץ לא חוקי',
+    labelFileWaitingForSize: 'מחשב את גודל הקבצים',
+    labelFileSizeNotAvailable: 'לא ניתן לקבוע את גודל הקבצים',
+    labelFileLoading: 'טוען...',
+    labelFileLoadError: 'שגיאה ארעה בעת טעינת הקבצים',
+    labelFileProcessing: 'מעלה את הקבצים',
+    labelFileProcessingComplete: 'העלאת הקבצים הסתיימה',
+    labelFileProcessingAborted: 'העלאת הקבצים בוטלה',
+    labelFileProcessingError: 'שגיאה ארעה בעת העלאת הקבצים',
+    labelFileProcessingRevertError: 'שגיאה ארעה בעת שחזור הקבצים',
+    labelFileRemoveError: 'שגיאה ארעה בעת הסרת הקובץ',
+    labelTapToCancel: 'הקלק לביטול',
+    labelTapToRetry: 'הקלק לנסות שנית',
+    labelTapToUndo: 'הקלק לשחזר',
+    labelButtonRemoveItem: 'הסר',
+    labelButtonAbortItemLoad: 'בטל',
+    labelButtonRetryItemLoad: 'טען שנית',
+    labelButtonAbortItemProcessing: 'בטל',
+    labelButtonUndoItemProcessing: 'שחזר',
+    labelButtonRetryItemProcessing: 'נסה שנית',
+    labelButtonProcessItem: 'העלה קובץ',
+    labelMaxFileSizeExceeded: 'הקובץ גדול מדי',
+    labelMaxFileSize: 'גודל המירבי המותר הוא: {filesize}',
+    labelMaxTotalFileSizeExceeded: 'גודל הקבצים חורג מהכמות המותרת',
+    labelMaxTotalFileSize: 'הגודל המירבי של סך הקבצים: {filesize}',
+    labelFileTypeNotAllowed: 'קובץ מסוג זה אינו מותר',
+    fileValidateTypeLabelExpectedTypes: 'הקבצים המותרים הם {allButLastType} או {lastType}',
+    imageValidateSizeLabelFormatError: 'תמונה בפורמט זה אינה נתמכת',
+    imageValidateSizeLabelImageSizeTooSmall: 'תמונה זו קטנה מדי',
+    imageValidateSizeLabelImageSizeTooBig: 'תמונה זו גדולה מדי',
+    imageValidateSizeLabelExpectedMinSize: 'הגודל צריך להיות לפחות: {minWidth} × {minHeight}',
+    imageValidateSizeLabelExpectedMaxSize: 'הגודל המרבי המותר: {maxWidth} × {maxHeight}',
+    imageValidateSizeLabelImageResolutionTooLow: 'הרזולוציה של תמונה זו נמוכה מדי',
+    imageValidateSizeLabelImageResolutionTooHigh: 'הרזולוציה של תמונה זו גבוהה מדי',
+    imageValidateSizeLabelExpectedMinResolution: 'הרזולוציה צריכה להיות לפחות: {minResolution}',
+    imageValidateSizeLabelExpectedMaxResolution: 'הרזולוציה המירבית המותרת היא: {maxResolution}',
+};

+ 39 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/hr-hr.js

@@ -0,0 +1,39 @@
+glFilepondLocale = {
+    labelIdle: 'Ovdje "ispusti" datoteku ili <span class="filepond--label-action"> Pretraži </span>',
+    labelInvalidField: 'Polje sadrži neispravne datoteke',
+    labelFileWaitingForSize: 'Čekanje na veličinu datoteke',
+    labelFileSizeNotAvailable: 'Veličina datoteke nije dostupna',
+    labelFileLoading: 'Učitavanje',
+    labelFileLoadError: 'Greška tijekom učitavanja',
+    labelFileProcessing: 'Prijenos',
+    labelFileProcessingComplete: 'Prijenos završen',
+    labelFileProcessingAborted: 'Prijenos otkazan',
+    labelFileProcessingError: 'Greška tijekom prijenosa',
+    labelFileProcessingRevertError: 'Greška tijekom vraćanja',
+    labelFileRemoveError: 'Greška tijekom uklananja datoteke',
+    labelTapToCancel: 'Dodirni za prekid',
+    labelTapToRetry: 'Dodirni za ponovno',
+    labelTapToUndo: 'Dodirni za vraćanje',
+    labelButtonRemoveItem: 'Ukloni',
+    labelButtonAbortItemLoad: 'Odbaci',
+    labelButtonRetryItemLoad: 'Ponovi',
+    labelButtonAbortItemProcessing: 'Prekini',
+    labelButtonUndoItemProcessing: 'Vrati',
+    labelButtonRetryItemProcessing: 'Ponovi',
+    labelButtonProcessItem: 'Prijenos',
+    labelMaxFileSizeExceeded: 'Datoteka je prevelika',
+    labelMaxFileSize: 'Maksimalna veličina datoteke je {filesize}',
+    labelMaxTotalFileSizeExceeded: 'Maksimalna ukupna veličina datoteke prekoračena',
+    labelMaxTotalFileSize: 'Maksimalna ukupna veličina datoteke je {filesize}',
+    labelFileTypeNotAllowed: 'Tip datoteke nije podržan',
+    fileValidateTypeLabelExpectedTypes: 'Očekivan {allButLastType} ili {lastType}',
+    imageValidateSizeLabelFormatError: 'Tip slike nije podržan',
+    imageValidateSizeLabelImageSizeTooSmall: 'Slika je premala',
+    imageValidateSizeLabelImageSizeTooBig: 'Slika je prevelika',
+    imageValidateSizeLabelExpectedMinSize: 'Minimalna veličina je {minWidth} × {minHeight}',
+    imageValidateSizeLabelExpectedMaxSize: 'Maksimalna veličina je {maxWidth} × {maxHeight}',
+    imageValidateSizeLabelImageResolutionTooLow: 'Rezolucija je preniska',
+    imageValidateSizeLabelImageResolutionTooHigh: 'Rezolucija je previsoka',
+    imageValidateSizeLabelExpectedMinResolution: 'Minimalna rezolucija je {minResolution}',
+    imageValidateSizeLabelExpectedMaxResolution: 'Maksimalna rezolucija je {maxResolution}'
+};

+ 39 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/hu-hu.js

@@ -0,0 +1,39 @@
+glFilepondLocale = {
+    labelIdle: 'Mozgasd ide a fájlt a feltöltéshez, vagy <span class="filepond--label-action"> tallózás </span>',
+    labelInvalidField: 'A mező érvénytelen fájlokat tartalmaz',
+    labelFileWaitingForSize: 'Fáljméret kiszámolása',
+    labelFileSizeNotAvailable: 'A fájlméret nem elérhető',
+    labelFileLoading: 'Töltés',
+    labelFileLoadError: 'Hiba a betöltés során',
+    labelFileProcessing: 'Feltöltés',
+    labelFileProcessingComplete: 'Sikeres feltöltés',
+    labelFileProcessingAborted: 'A feltöltés megszakítva',
+    labelFileProcessingError: 'Hiba történt a feltöltés során',
+    labelFileProcessingRevertError: 'Hiba a visszaállítás során',
+    labelFileRemoveError: 'Hiba történt az eltávolítás során',
+    labelTapToCancel: 'koppints a törléshez',
+    labelTapToRetry: 'koppints az újrakezdéshez',
+    labelTapToUndo: 'koppints a visszavonáshoz',
+    labelButtonRemoveItem: 'Eltávolítás',
+    labelButtonAbortItemLoad: 'Megszakítás',
+    labelButtonRetryItemLoad: 'Újrapróbálkozás',
+    labelButtonAbortItemProcessing: 'Megszakítás',
+    labelButtonUndoItemProcessing: 'Visszavonás',
+    labelButtonRetryItemProcessing: 'Újrapróbálkozás',
+    labelButtonProcessItem: 'Feltöltés',
+    labelMaxFileSizeExceeded: 'A fájl túllépte a maximális méretet',
+    labelMaxFileSize: 'Maximális fájlméret: {filesize}',
+    labelMaxTotalFileSizeExceeded: 'Túllépte a maximális teljes méretet',
+    labelMaxTotalFileSize: 'A maximáis teljes fájlméret: {filesize}',
+    labelFileTypeNotAllowed: 'Érvénytelen típusú fájl',
+    fileValidateTypeLabelExpectedTypes: 'Engedélyezett típusok {allButLastType} vagy {lastType}',
+    imageValidateSizeLabelFormatError: 'A képtípus nem támogatott',
+    imageValidateSizeLabelImageSizeTooSmall: 'A kép túl kicsi',
+    imageValidateSizeLabelImageSizeTooBig: 'A kép túl nagy',
+    imageValidateSizeLabelExpectedMinSize: 'Minimum méret: {minWidth} × {minHeight}',
+    imageValidateSizeLabelExpectedMaxSize: 'Maximum méret: {maxWidth} × {maxHeight}',
+    imageValidateSizeLabelImageResolutionTooLow: 'A felbontás túl alacsony',
+    imageValidateSizeLabelImageResolutionTooHigh: 'A felbontás túl magas',
+    imageValidateSizeLabelExpectedMinResolution: 'Minimáis felbontás: {minResolution}',
+    imageValidateSizeLabelExpectedMaxResolution: 'Maximális felbontás: {maxResolution}'
+  };

+ 39 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/id-id.js

@@ -0,0 +1,39 @@
+glFilepondLocale = {
+    labelIdle: 'Seret dan Jatuhkan file Anda atau <span class="filepond--label-action">Jelajahi</span>',
+    labelInvalidField: 'Field berisi file tidak valid',
+    labelFileWaitingForSize: 'Menunggu ukuran',
+    labelFileSizeNotAvailable: 'Ukuran tidak tersedia',
+    labelFileLoading: 'Memuat',
+    labelFileLoadError: 'Kesalahan saat memuat',
+    labelFileProcessing: 'Mengunggah',
+    labelFileProcessingComplete: 'Unggahan selesai',
+    labelFileProcessingAborted: 'Unggahan dibatalkan',
+    labelFileProcessingError: 'Kesalahan saat mengunggah',
+    labelFileProcessingRevertError: 'Kesalahan saat pengembalian',
+    labelFileRemoveError: 'Kesalahan saat menghapus',
+    labelTapToCancel: 'ketuk untuk membatalkan',
+    labelTapToRetry: 'ketuk untuk mencoba lagi',
+    labelTapToUndo: 'ketuk untuk mengurungkan',
+    labelButtonRemoveItem: 'Hapus',
+    labelButtonAbortItemLoad: 'Batal',
+    labelButtonRetryItemLoad: 'Coba Kembali',
+    labelButtonAbortItemProcessing: 'Batal',
+    labelButtonUndoItemProcessing: 'Batal',
+    labelButtonRetryItemProcessing: 'Coba Kembali',
+    labelButtonProcessItem: 'Unggah',
+    labelMaxFileSizeExceeded: 'File terlalu besar',
+    labelMaxFileSize: 'Ukuran file maksimum adalah {filesize}',
+    labelMaxTotalFileSizeExceeded: 'Jumlah file maksimum terlampaui',
+    labelMaxTotalFileSize: 'Jumlah file maksimum adalah {filesize}',
+    labelFileTypeNotAllowed: 'Jenis file tidak valid',
+    fileValidateTypeLabelExpectedTypes: 'Mengharapkan {allButLastType} atau {lastType}',
+    imageValidateSizeLabelFormatError: 'Jenis gambar tidak didukung',
+    imageValidateSizeLabelImageSizeTooSmall: 'Gambar terlalu kecil',
+    imageValidateSizeLabelImageSizeTooBig: 'Gambar terlalu besar',
+    imageValidateSizeLabelExpectedMinSize: 'Ukuran minimum adalah {minWidth} × {minHeight}',
+    imageValidateSizeLabelExpectedMaxSize: 'Ukuran maksimum adalah {minWidth} × {minHeight}',
+    imageValidateSizeLabelImageResolutionTooLow: 'Resolusi terlalu rendah',
+    imageValidateSizeLabelImageResolutionTooHigh: 'Resolusi terlalu tinggi',
+    imageValidateSizeLabelExpectedMinResolution: 'Resolusi minimum adalah {minResolution}',
+    imageValidateSizeLabelExpectedMaxResolution: 'Resolusi maksimum adalah {maxResolution}'
+  };

+ 39 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/it-it.js

@@ -0,0 +1,39 @@
+glFilepondLocale = {
+    labelIdle: 'Trascina e rilascia i tuoi file oppure <span class = "filepond--label-action"> Carica <span>',
+    labelInvalidField: "Il campo contiene dei file non validi",
+    labelFileWaitingForSize: "Aspettando le dimensioni",
+    labelFileSizeNotAvailable: "Dimensioni non disponibili",
+    labelFileLoading: "Caricamento",
+    labelFileLoadError: "Errore durante il caricamento",
+    labelFileProcessing: "Caricamento",
+    labelFileProcessingComplete: "Caricamento completato",
+    labelFileProcessingAborted: "Caricamento cancellato",
+    labelFileProcessingError: "Errore durante il caricamento",
+    labelFileProcessingRevertError: "Errore durante il ripristino",
+    labelFileRemoveError: "Errore durante l'eliminazione",
+    labelTapToCancel: "tocca per cancellare",
+    labelTapToRetry: "tocca per riprovare",
+    labelTapToUndo: "tocca per ripristinare",
+    labelButtonRemoveItem: "Elimina",
+    labelButtonAbortItemLoad: "Cancella",
+    labelButtonRetryItemLoad: "Ritenta",
+    labelButtonAbortItemProcessing: "Camcella",
+    labelButtonUndoItemProcessing: "Indietro",
+    labelButtonRetryItemProcessing: "Ritenta",
+    labelButtonProcessItem: "Carica",
+    labelMaxFileSizeExceeded: "Il peso del file è eccessivo",
+    labelMaxFileSize: "Il peso massimo del file è {filesize}",
+    labelMaxTotalFileSizeExceeded: "Dimensione totale massima superata",
+    labelMaxTotalFileSize: "La dimensione massima totale del file è {filesize}",
+    labelFileTypeNotAllowed: "File non supportato",
+    fileValidateTypeLabelExpectedTypes: "Aspetta {allButLastType} o {lastType}",
+    imageValidateSizeLabelFormatError: "Tipo di immagine non compatibile",
+    imageValidateSizeLabelImageSizeTooSmall: "L'immagine è troppo piccola",
+    imageValidateSizeLabelImageSizeTooBig: "L'immagine è troppo grande",
+    imageValidateSizeLabelExpectedMinSize: "La dimensione minima è {minWidth} × {minHeight}",
+    imageValidateSizeLabelExpectedMaxSize: "La dimensione massima è {maxWidth} × {maxHeight}",
+    imageValidateSizeLabelImageResolutionTooLow: "La risoluzione è troppo bassa",
+    imageValidateSizeLabelImageResolutionTooHigh: "La risoluzione è troppo alta",
+    imageValidateSizeLabelExpectedMinResolution: "La risoluzione minima è {minResolution}",
+    imageValidateSizeLabelExpectedMaxResolution: "La risoluzione massima è {maxResolution}",
+};

+ 39 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/ja-ja.js

@@ -0,0 +1,39 @@
+glFilepondLocale = {
+    labelIdle: 'ファイルをドラッグ&ドロップ又は<span class="filepond--label-action">ファイル選択</span>',
+    labelInvalidField: "アップロードできないファイルが含まれています",
+    labelFileWaitingForSize: "ファイルサイズを待っています",
+    labelFileSizeNotAvailable: "ファイルサイズがみつかりません",
+    labelFileLoading: "読込中...",
+    labelFileLoadError: "読込中にエラーが発生",
+    labelFileProcessing: "読込中...",
+    labelFileProcessingComplete: "アップロード完了",                                                                                          
+    labelFileProcessingAborted: "アップロードがキャンセルされました",                                                                           
+    labelFileProcessingError: "アップロード中にエラーが発生",                                                                                 
+    labelFileProcessingRevertError: "ロールバック中にエラーが発生",                                                                           
+    labelFileRemoveError: "削除中にエラーが発生",                                                                                             
+    labelTapToCancel: "クリックしてキャンセル",                                                                                               
+    labelTapToRetry: "クリックしてもう一度お試し下さい",                                                                                      
+    labelTapToUndo: "元に戻すにはタップします",                                                                                               
+    labelButtonRemoveItem: "削除",                                                                                                            
+    labelButtonAbortItemLoad: "中断",                                                                                                         
+    labelButtonRetryItemLoad: "もう一度実行",                                                                                                 
+    labelButtonAbortItemProcessing: "キャンセル",                                                                                             
+    labelButtonUndoItemProcessing: "元に戻す",                                                                                                
+    labelButtonRetryItemProcessing: "もう一度実行",                                                                                           
+    labelButtonProcessItem: "アップロード",                                                                                                   
+    labelMaxFileSizeExceeded: "ファイルサイズが大きすぎます",                                                                                 
+    labelMaxFileSize: "最大ファイルサイズは {filesize} です",                                                                                 
+    labelMaxTotalFileSizeExceeded: "最大合計サイズを超えました",                                                                              
+    labelMaxTotalFileSize: "最大合計ファイルサイズは {filesize} です",                                                                        
+    labelFileTypeNotAllowed: "無効なファイルです",                                                                                            
+    fileValidateTypeLabelExpectedTypes: "サポートしているファイルは {allButLastType} 又は {lastType} です",                                   
+    imageValidateSizeLabelFormatError: "サポートしていない画像です",                                                                          
+    imageValidateSizeLabelImageSizeTooSmall: "画像が小さすぎます",                                                                            
+    imageValidateSizeLabelImageSizeTooBig: "画像が大きすぎます",                                                                              
+    imageValidateSizeLabelExpectedMinSize: "画像の最小サイズは{minWidth}×{minHeight}です",                                                   
+    imageValidateSizeLabelExpectedMaxSize: "画像の最大サイズは{maxWidth} × {maxHeight}です",                                                 
+    imageValidateSizeLabelImageResolutionTooLow: "画像の解像度が低すぎます",                                                                  
+    imageValidateSizeLabelImageResolutionTooHigh: "画像の解像度が高すぎます",                                                                 
+    imageValidateSizeLabelExpectedMinResolution: "画像の最小解像度は{minResolution}です",                                                     
+    imageValidateSizeLabelExpectedMaxResolution: "画像の最大解像度は{maxResolution}です",                                                     
+};

+ 39 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/lt-lt.js

@@ -0,0 +1,39 @@
+glFilepondLocale = {
+    labelIdle: 'Įdėkite failus čia arba <span class="filepond--label-action"> Ieškokite </span>',
+    labelInvalidField: 'Laukelis talpina netinkamus failus',
+    labelFileWaitingForSize: 'Laukiama dydžio',
+    labelFileSizeNotAvailable: 'Dydis nežinomas',
+    labelFileLoading: 'Kraunama',
+    labelFileLoadError: 'Klaida įkeliant',
+    labelFileProcessing: 'Įkeliama',
+    labelFileProcessingComplete: 'Įkėlimas sėkmingas',
+    labelFileProcessingAborted: 'Įkėlimas atšauktas',
+    labelFileProcessingError: 'Įkeliant įvyko klaida',
+    labelFileProcessingRevertError: 'Atšaukiant įvyko klaida',
+    labelFileRemoveError: 'Ištrinant įvyko klaida',
+    labelTapToCancel: 'Palieskite norėdami atšaukti',
+    labelTapToRetry: 'Palieskite norėdami pakartoti',
+    labelTapToUndo: 'Palieskite norėdami atšaukti',
+    labelButtonRemoveItem: 'Ištrinti',
+    labelButtonAbortItemLoad: 'Sustabdyti',
+    labelButtonRetryItemLoad: 'Pakartoti',
+    labelButtonAbortItemProcessing: 'Atšaukti',
+    labelButtonUndoItemProcessing: 'Atšaukti',
+    labelButtonRetryItemProcessing: 'Pakartoti',
+    labelButtonProcessItem: 'Įkelti',
+    labelMaxFileSizeExceeded: 'Failas per didelis',
+    labelMaxFileSize: 'Maksimalus failo dydis yra {filesize}',
+    labelMaxTotalFileSizeExceeded: 'Viršijote maksimalų leistiną dydį',
+    labelMaxTotalFileSize: 'Maksimalus leistinas dydis yra {filesize}',
+    labelFileTypeNotAllowed: 'Netinkamas failas',
+    fileValidateTypeLabelExpectedTypes: 'Tikisi {allButLastType} arba {lastType}',
+    imageValidateSizeLabelFormatError: 'Nuotraukos formatas nepalaikomas',
+    imageValidateSizeLabelImageSizeTooSmall: 'Nuotrauka per maža',
+    imageValidateSizeLabelImageSizeTooBig: 'Nuotrauka per didelė',
+    imageValidateSizeLabelExpectedMinSize: 'Minimalus dydis yra {minWidth} × {minHeight}',
+    imageValidateSizeLabelExpectedMaxSize: 'Maksimalus dydis yra {maxWidth} × {maxHeight}',
+    imageValidateSizeLabelImageResolutionTooLow: 'Rezoliucija per maža',
+    imageValidateSizeLabelImageResolutionTooHigh: 'Rezoliucija per didelė',
+    imageValidateSizeLabelExpectedMinResolution: 'Minimali rezoliucija yra {minResolution}',
+    imageValidateSizeLabelExpectedMaxResolution: 'Maksimali rezoliucija yra {maxResolution}'
+};

+ 39 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/nl-nl.js

@@ -0,0 +1,39 @@
+glFilepondLocale = {
+    labelIdle: 'Drag & Drop je bestanden of <span class="filepond--label-action"> Bladeren </span>',
+    labelInvalidField: 'Veld bevat ongeldige bestanden',
+    labelFileWaitingForSize: 'Wachten op grootte',
+    labelFileSizeNotAvailable: 'Grootte niet beschikbaar',
+    labelFileLoading: 'Laden',
+    labelFileLoadError: 'Fout tijdens laden',
+    labelFileProcessing: 'Uploaden',
+    labelFileProcessingComplete: 'Upload afgerond',
+    labelFileProcessingAborted: 'Upload geannuleerd',
+    labelFileProcessingError: 'Fout tijdens upload',
+    labelFileProcessingRevertError: 'Fout bij herstellen',
+    labelFileRemoveError: 'Fout bij verwijderen',
+    labelTapToCancel: 'tik om te annuleren',
+    labelTapToRetry: 'tik om opnieuw te proberen',
+    labelTapToUndo: 'tik om ongedaan te maken',
+    labelButtonRemoveItem: 'Verwijderen',
+    labelButtonAbortItemLoad: 'Afbreken',
+    labelButtonRetryItemLoad: 'Opnieuw proberen',
+    labelButtonAbortItemProcessing: 'Annuleren',
+    labelButtonUndoItemProcessing: 'Ongedaan maken',
+    labelButtonRetryItemProcessing: 'Opnieuw proberen',
+    labelButtonProcessItem: 'Upload',
+    labelMaxFileSizeExceeded: 'Bestand is te groot',
+    labelMaxFileSize: 'Maximale bestandsgrootte is {filesize}',
+    labelMaxTotalFileSizeExceeded: 'Maximale totale grootte overschreden',
+    labelMaxTotalFileSize: 'Maximale totale bestandsgrootte is {filesize}',
+    labelFileTypeNotAllowed: 'Ongeldig bestandstype',
+    fileValidateTypeLabelExpectedTypes: 'Verwacht {allButLastType} of {lastType}',
+    imageValidateSizeLabelFormatError: 'Afbeeldingstype niet ondersteund',
+    imageValidateSizeLabelImageSizeTooSmall: 'Afbeelding is te klein',
+    imageValidateSizeLabelImageSizeTooBig: 'Afbeelding is te groot',
+    imageValidateSizeLabelExpectedMinSize: 'Minimale afmeting is {minWidth} × {minHeight}',
+    imageValidateSizeLabelExpectedMaxSize: 'Maximale afmeting is {maxWidth} × {maxHeight}',
+    imageValidateSizeLabelImageResolutionTooLow: 'Resolutie is te laag',
+    imageValidateSizeLabelImageResolutionTooHigh: 'Resolution is too high',
+    imageValidateSizeLabelExpectedMinResolution: 'Minimale resolutie is {minResolution}',
+    imageValidateSizeLabelExpectedMaxResolution: 'Maximale resolutie is {maxResolution}'
+};

+ 39 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/no-nb.js

@@ -0,0 +1,39 @@
+glFilepondLocale = {
+    labelIdle: 'Dra og slipp filene dine, eller <span class="filepond--label-action"> Bla gjennom... </span>',
+    labelInvalidField: 'Feltet inneholder ugyldige filer',
+    labelFileWaitingForSize: 'Venter på størrelse',
+    labelFileSizeNotAvailable: 'Størrelse ikke tilgjengelig',
+    labelFileLoading: 'Laster',
+    labelFileLoadError: 'Feil under lasting',
+    labelFileProcessing: 'Laster opp',
+    labelFileProcessingComplete: 'Opplasting ferdig',
+    labelFileProcessingAborted: 'Opplasting avbrutt',
+    labelFileProcessingError: 'Feil under opplasting',
+    labelFileProcessingRevertError: 'Feil under reversering',
+    labelFileRemoveError: 'Feil under flytting',
+    labelTapToCancel: 'klikk for å avbryte',
+    labelTapToRetry: 'klikk for å prøve på nytt',
+    labelTapToUndo: 'klikk for å angre',
+    labelButtonRemoveItem: 'Fjern',
+    labelButtonAbortItemLoad: 'Avbryt',
+    labelButtonRetryItemLoad: 'Prøv på nytt',
+    labelButtonAbortItemProcessing: 'Avbryt',
+    labelButtonUndoItemProcessing: 'Angre',
+    labelButtonRetryItemProcessing: 'Prøv på nytt',
+    labelButtonProcessItem: 'Last opp',
+    labelMaxFileSizeExceeded: 'Filen er for stor',
+    labelMaxFileSize: 'Maksimal filstørrelse er {filesize}',
+    labelMaxTotalFileSizeExceeded: 'Maksimal total størrelse oversteget',
+    labelMaxTotalFileSize: 'Maksimal total størrelse er {filesize}',
+    labelFileTypeNotAllowed: 'Ugyldig filtype',
+    fileValidateTypeLabelExpectedTypes: 'Forventer {allButLastType} eller {lastType}',
+    imageValidateSizeLabelFormatError: 'Bildeformat ikke støttet',
+    imageValidateSizeLabelImageSizeTooSmall: 'Bildet er for lite',
+    imageValidateSizeLabelImageSizeTooBig: 'Bildet er for stort',
+    imageValidateSizeLabelExpectedMinSize: 'Minimumsstørrelse er {minWidth} × {minHeight}',
+    imageValidateSizeLabelExpectedMaxSize: 'Maksimumsstørrelse er {maxWidth} × {maxHeight}',
+    imageValidateSizeLabelImageResolutionTooLow: 'Oppløsningen er for lav',
+    imageValidateSizeLabelImageResolutionTooHigh: 'Oppløsningen er for høy',
+    imageValidateSizeLabelExpectedMinResolution: 'Minimum oppløsning er {minResolution}',
+    imageValidateSizeLabelExpectedMaxResolution: 'Maksimal oppløsning er {maxResolution}'
+};

+ 39 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/pl-pl.js

@@ -0,0 +1,39 @@
+glFilepondLocale = {
+    labelIdle: 'Przeciągnij i upuść lub <span class="filepond--label-action">wybierz</span> pliki',
+    labelInvalidField: 'Nieprawidłowe pliki',
+    labelFileWaitingForSize: 'Pobieranie rozmiaru',
+    labelFileSizeNotAvailable: 'Nieznany rozmiar',
+    labelFileLoading: 'Wczytywanie',
+    labelFileLoadError: 'Błąd wczytywania',
+    labelFileProcessing: 'Przesyłanie',
+    labelFileProcessingComplete: 'Przesłano',
+    labelFileProcessingAborted: 'Przerwano',
+    labelFileProcessingError: 'Przesyłanie nie powiodło się',
+    labelFileProcessingRevertError: 'Coś poszło nie tak',
+    labelFileRemoveError: 'Nieudane usunięcie',
+    labelTapToCancel: 'Anuluj',
+    labelTapToRetry: 'Ponów',
+    labelTapToUndo: 'Cofnij',
+    labelButtonRemoveItem: 'Usuń',
+    labelButtonAbortItemLoad: 'Przerwij',
+    labelButtonRetryItemLoad: 'Ponów',
+    labelButtonAbortItemProcessing: 'Anuluj',
+    labelButtonUndoItemProcessing: 'Cofnij',
+    labelButtonRetryItemProcessing: 'Ponów',
+    labelButtonProcessItem: 'Prześlij',
+    labelMaxFileSizeExceeded: 'Plik jest zbyt duży',
+    labelMaxFileSize: 'Dopuszczalna wielkość pliku to {filesize}',
+    labelMaxTotalFileSizeExceeded: 'Przekroczono łączny rozmiar plików',
+    labelMaxTotalFileSize: 'Łączny rozmiar plików nie może przekroczyć {filesize}',
+    labelFileTypeNotAllowed: 'Niedozwolony rodzaj pliku',
+    fileValidateTypeLabelExpectedTypes: 'Oczekiwano {allButLastType} lub {lastType}',
+    imageValidateSizeLabelFormatError: 'Nieobsługiwany format obrazu',
+    imageValidateSizeLabelImageSizeTooSmall: 'Obraz jest zbyt mały',
+    imageValidateSizeLabelImageSizeTooBig: 'Obraz jest zbyt duży',
+    imageValidateSizeLabelExpectedMinSize: 'Minimalne wymiary obrazu to {minWidth}×{minHeight}',
+    imageValidateSizeLabelExpectedMaxSize: 'Maksymalna wymiary obrazu to {maxWidth}×{maxHeight}',
+    imageValidateSizeLabelImageResolutionTooLow: 'Rozdzielczość jest zbyt niska',
+    imageValidateSizeLabelImageResolutionTooHigh: 'Rozdzielczość jest zbyt wysoka',
+    imageValidateSizeLabelExpectedMinResolution: 'Minimalna rozdzielczość to {minResolution}',
+    imageValidateSizeLabelExpectedMaxResolution: 'Maksymalna rozdzielczość to {maxResolution}'
+};

+ 39 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/pt-br.js

@@ -0,0 +1,39 @@
+glFilepondLocale = {
+    labelIdle: 'Arraste e solte os arquivos ou <span class="filepond--label-action"> Clique aqui </span>',
+    labelInvalidField: 'Arquivos inválidos',
+    labelFileWaitingForSize: 'Calculando o tamanho do arquivo',
+    labelFileSizeNotAvailable: 'Tamanho do arquivo indisponível',
+    labelFileLoading: 'Carregando',
+    labelFileLoadError: 'Erro durante o carregamento',
+    labelFileProcessing: 'Enviando',
+    labelFileProcessingComplete: 'Envio finalizado',
+    labelFileProcessingAborted: 'Envio cancelado',
+    labelFileProcessingError: 'Erro durante o envio',
+    labelFileProcessingRevertError: 'Erro ao reverter o envio',
+    labelFileRemoveError: 'Erro ao remover o arquivo',
+    labelTapToCancel: 'clique para cancelar',
+    labelTapToRetry: 'clique para reenviar',
+    labelTapToUndo: 'clique para desfazer',
+    labelButtonRemoveItem: 'Remover',
+    labelButtonAbortItemLoad: 'Abortar',
+    labelButtonRetryItemLoad: 'Reenviar',
+    labelButtonAbortItemProcessing: 'Cancelar',
+    labelButtonUndoItemProcessing: 'Desfazer',
+    labelButtonRetryItemProcessing: 'Reenviar',
+    labelButtonProcessItem: 'Enviar',
+    labelMaxFileSizeExceeded: 'Arquivo é muito grande',
+    labelMaxFileSize: 'O tamanho máximo permitido: {filesize}',
+    labelMaxTotalFileSizeExceeded: 'Tamanho total dos arquivos excedido',
+    labelMaxTotalFileSize: 'Tamanho total permitido: {filesize}',
+    labelFileTypeNotAllowed: 'Tipo de arquivo inválido',
+    fileValidateTypeLabelExpectedTypes: 'Tipos de arquivo suportados são {allButLastType} ou {lastType}',
+    imageValidateSizeLabelFormatError: 'Tipo de imagem inválida',
+    imageValidateSizeLabelImageSizeTooSmall: 'Imagem muito pequena',
+    imageValidateSizeLabelImageSizeTooBig: 'Imagem muito grande',
+    imageValidateSizeLabelExpectedMinSize: 'Tamanho mínimo permitida: {minWidth} × {minHeight}',
+    imageValidateSizeLabelExpectedMaxSize: 'Tamanho máximo permitido: {maxWidth} × {maxHeight}',
+    imageValidateSizeLabelImageResolutionTooLow: 'Resolução muito baixa',
+    imageValidateSizeLabelImageResolutionTooHigh: 'Resolução muito alta',
+    imageValidateSizeLabelExpectedMinResolution: 'Resolução mínima permitida: {minResolution}',
+    imageValidateSizeLabelExpectedMaxResolution: 'Resolução máxima permitida: {maxResolution}',
+};

+ 39 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/ro-ro.js

@@ -0,0 +1,39 @@
+glFilepondLocale = {
+    labelIdle: 'Trage și plasează fișiere sau <span class="filepond--label-action"> Caută-le </span>',
+    labelInvalidField: 'Câmpul conține fișiere care nu sunt valide',
+    labelFileWaitingForSize: 'În așteptarea dimensiunii',
+    labelFileSizeNotAvailable: 'Dimensiunea nu este diponibilă',
+    labelFileLoading: 'Se încarcă',
+    labelFileLoadError: 'Eroare la încărcare',
+    labelFileProcessing: 'Se încarcă',
+    labelFileProcessingComplete: 'Încărcare finalizată',
+    labelFileProcessingAborted: 'Încărcare anulată',
+    labelFileProcessingError: 'Eroare la încărcare',
+    labelFileProcessingRevertError: 'Eroare la anulare',
+    labelFileRemoveError: 'Eroare la ştergere',
+    labelTapToCancel: 'apasă pentru a anula',
+    labelTapToRetry: 'apasă pentru a reîncerca',
+    labelTapToUndo: 'apasă pentru a anula',
+    labelButtonRemoveItem: 'Şterge',
+    labelButtonAbortItemLoad: 'Anulează',
+    labelButtonRetryItemLoad: 'Reîncearcă',
+    labelButtonAbortItemProcessing: 'Anulează',
+    labelButtonUndoItemProcessing: 'Anulează',
+    labelButtonRetryItemProcessing: 'Reîncearcă',
+    labelButtonProcessItem: 'Încarcă',
+    labelMaxFileSizeExceeded: 'Fișierul este prea mare',
+    labelMaxFileSize: 'Dimensiunea maximă a unui fișier este de {filesize}',
+    labelMaxTotalFileSizeExceeded: 'Dimensiunea totală maximă a fost depășită',
+    labelMaxTotalFileSize: 'Dimensiunea totală maximă a fișierelor este de {filesize}',
+    labelFileTypeNotAllowed: 'Tipul fișierului nu este valid',
+    fileValidateTypeLabelExpectedTypes: 'Se așteaptă {allButLastType} sau {lastType}',
+    imageValidateSizeLabelFormatError: 'Formatul imaginii nu este acceptat',
+    imageValidateSizeLabelImageSizeTooSmall: 'Imaginea este prea mică',
+    imageValidateSizeLabelImageSizeTooBig: 'Imaginea este prea mare',
+    imageValidateSizeLabelExpectedMinSize: 'Mărimea minimă este de {maxWidth} x {maxHeight}',
+    imageValidateSizeLabelExpectedMaxSize: 'Mărimea maximă este de {maxWidth} x {maxHeight}',
+    imageValidateSizeLabelImageResolutionTooLow: 'Rezoluția este prea mică',
+    imageValidateSizeLabelImageResolutionTooHigh: 'Rezoluția este prea mare',
+    imageValidateSizeLabelExpectedMinResolution: 'Rezoluția minimă este de {minResolution}',
+    imageValidateSizeLabelExpectedMaxResolution: 'Rezoluția maximă este de {maxResolution}'
+};

+ 39 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/ru-ru.js

@@ -0,0 +1,39 @@
+glFilepondLocale = {
+    labelIdle: 'Перетащите файлы или <span class="filepond--label-action"> выберите </span>',
+    labelInvalidField: 'Поле содержит недопустимые файлы',
+    labelFileWaitingForSize: 'Укажите размер',
+    labelFileSizeNotAvailable: 'Размер не поддерживается',
+    labelFileLoading: 'Ожидание',
+    labelFileLoadError: 'Ошибка при ожидании',
+    labelFileProcessing: 'Загрузка',
+    labelFileProcessingComplete: 'Загрузка завершена',
+    labelFileProcessingAborted: 'Загрузка отменена',
+    labelFileProcessingError: 'Ошибка при загрузке',
+    labelFileProcessingRevertError: 'Ошибка при возврате',
+    labelFileRemoveError: 'Ошибка при удалении',
+    labelTapToCancel: 'нажмите для отмены',
+    labelTapToRetry: 'нажмите, чтобы повторить попытку',
+    labelTapToUndo: 'нажмите для отмены последнего действия',
+    labelButtonRemoveItem: 'Удалить',
+    labelButtonAbortItemLoad: 'Прекращено',
+    labelButtonRetryItemLoad: 'Повторите попытку',
+    labelButtonAbortItemProcessing: 'Отмена',
+    labelButtonUndoItemProcessing: 'Отмена последнего действия',
+    labelButtonRetryItemProcessing: 'Повторите попытку',
+    labelButtonProcessItem: 'Загрузка',
+    labelMaxFileSizeExceeded: 'Файл слишком большой',
+    labelMaxFileSize: 'Максимальный размер файла: {filesize}',
+    labelMaxTotalFileSizeExceeded: 'Превышен максимальный размер',
+    labelMaxTotalFileSize: 'Максимальный размер файла: {filesize}',
+    labelFileTypeNotAllowed: 'Файл неверного типа',
+    fileValidateTypeLabelExpectedTypes: 'Ожидается {allButLastType} или {lastType}',    
+    imageValidateSizeLabelFormatError: 'Тип изображения не поддерживается',
+    imageValidateSizeLabelImageSizeTooSmall: 'Изображение слишком маленькое',
+    imageValidateSizeLabelImageSizeTooBig: 'Изображение слишком большое',
+    imageValidateSizeLabelExpectedMinSize: 'Минимальный размер: {minWidth} × {minHeight}',
+    imageValidateSizeLabelExpectedMaxSize: 'Максимальный размер: {maxWidth} × {maxHeight}',
+    imageValidateSizeLabelImageResolutionTooLow: 'Разрешение слишком низкое',
+    imageValidateSizeLabelImageResolutionTooHigh: 'Разрешение слишком высокое',
+    imageValidateSizeLabelExpectedMinResolution: 'Минимальное разрешение: {minResolution}',
+    imageValidateSizeLabelExpectedMaxResolution: 'Максимальное разрешение: {maxResolution}'
+};

+ 39 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/sk-sk.js

@@ -0,0 +1,39 @@
+glFilepondLocale = {
+    labelIdle: 'Natiahnúť súbor (drag&drop) alebo <span class="filepond--label-action"> Vyhľadať </span>',
+    labelInvalidField: 'Pole obsahuje chybné súbory',
+    labelFileWaitingForSize: 'Zisťuje sa veľkosť',
+    labelFileSizeNotAvailable: 'Neznáma veľkosť',
+    labelFileLoading: 'Prenáša sa',
+    labelFileLoadError: 'Chyba pri prenose',
+    labelFileProcessing: 'Prebieha upload',
+    labelFileProcessingComplete: 'Upload dokončený',
+    labelFileProcessingAborted: 'Upload stornovaný',
+    labelFileProcessingError: 'Chyba pri uploade',
+    labelFileProcessingRevertError: 'Chyba pri obnove',
+    labelFileRemoveError: 'Chyba pri odstránení',
+    labelTapToCancel: 'Kliknite pre storno',
+    labelTapToRetry: 'Kliknite pre opakovanie',
+    labelTapToUndo: 'Kliknite pre vrátenie',
+    labelButtonRemoveItem: 'Odstrániť',
+    labelButtonAbortItemLoad: 'Storno',
+    labelButtonRetryItemLoad: 'Opakovať',
+    labelButtonAbortItemProcessing: 'Späť',
+    labelButtonUndoItemProcessing: 'Vrátiť',
+    labelButtonRetryItemProcessing: 'Opakovať',
+    labelButtonProcessItem: 'Upload',
+    labelMaxFileSizeExceeded: 'Súbor je príliš veľký',
+    labelMaxFileSize: 'Najväčšia veľkosť súboru je {filesize}',
+    labelMaxTotalFileSizeExceeded: 'Prekročená maximálna celková veľkosť súboru',
+    labelMaxTotalFileSize: 'Maximálna celková veľkosť súboru je {filesize}',
+    labelFileTypeNotAllowed: 'Súbor je nesprávneho typu',
+    fileValidateTypeLabelExpectedTypes: 'Očakáva sa {allButLastType} alebo {lastType}',
+    imageValidateSizeLabelFormatError: 'Obrázok tohto typu nie je podporovaný',
+    imageValidateSizeLabelImageSizeTooSmall: 'Obrázok je príliš malý',
+    imageValidateSizeLabelImageSizeTooBig: 'Obrázok je príliš veľký',
+    imageValidateSizeLabelExpectedMinSize: 'Minimálny rozmer je {minWidth} × {minHeight}',
+    imageValidateSizeLabelExpectedMaxSize: 'Maximálny rozmer je {maxWidth} × {maxHeight}',
+    imageValidateSizeLabelImageResolutionTooLow: 'Rozlíšenie je príliš malé',
+    imageValidateSizeLabelImageResolutionTooHigh: 'Rozlišenie je príliš veľké',
+    imageValidateSizeLabelExpectedMinResolution: 'Minimálne rozlíšenie je {minResolution}',
+    imageValidateSizeLabelExpectedMaxResolution: 'Maximálne rozlíšenie je {maxResolution}'
+};

+ 39 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/sv-se.js

@@ -0,0 +1,39 @@
+glFilepondLocale = {
+    labelIdle: 'Drag och släpp dina filer eller <span class="filepond--label-action"> Bläddra </span>',
+    labelInvalidField: 'Fältet innehåller felaktiga filer',
+    labelFileWaitingForSize: 'Väntar på storlek',
+    labelFileSizeNotAvailable: 'Storleken finns inte tillgänglig',
+    labelFileLoading: 'Laddar',
+    labelFileLoadError: 'Fel under laddning',
+    labelFileProcessing: 'Laddar upp',
+    labelFileProcessingComplete: 'Uppladdning klar',
+    labelFileProcessingAborted: 'Uppladdning avbruten',
+    labelFileProcessingError: 'Fel under uppladdning',
+    labelFileProcessingRevertError: 'Fel under återställning',
+    labelFileRemoveError: 'Fel under borttagning',
+    labelTapToCancel: 'tryck för att avbryta',
+    labelTapToRetry: 'tryck för att försöka igen',
+    labelTapToUndo: 'tryck för att ångra',
+    labelButtonRemoveItem: 'Tabort',
+    labelButtonAbortItemLoad: 'Avbryt',
+    labelButtonRetryItemLoad: 'Försök igen',
+    labelButtonAbortItemProcessing: 'Avbryt',
+    labelButtonUndoItemProcessing: 'Ångra',
+    labelButtonRetryItemProcessing: 'Försök igen',
+    labelButtonProcessItem: 'Ladda upp',
+    labelMaxFileSizeExceeded: 'Filen är för stor',
+    labelMaxFileSize: 'Största tillåtna filstorlek är {filesize}',
+    labelMaxTotalFileSizeExceeded: 'Maximal uppladdningsstorlek uppnåd',
+    labelMaxTotalFileSize: 'Maximal uppladdningsstorlek är {filesize}',
+    labelFileTypeNotAllowed: 'Felaktig filtyp',
+    fileValidateTypeLabelExpectedTypes: 'Godkända filtyper {allButLastType} eller {lastType}',
+    imageValidateSizeLabelFormatError: 'Bildtypen saknar stöd',
+    imageValidateSizeLabelImageSizeTooSmall: 'Bilden är för liten',
+    imageValidateSizeLabelImageSizeTooBig: 'Bilden är för stor',
+    imageValidateSizeLabelExpectedMinSize: 'Minimal storlek är {minWidth} × {minHeight}',
+    imageValidateSizeLabelExpectedMaxSize: 'Maximal storlek är {maxWidth} × {maxHeight}',
+    imageValidateSizeLabelImageResolutionTooLow: 'Upplösningen är för låg',
+    imageValidateSizeLabelImageResolutionTooHigh: 'Upplösningen är för hög',
+    imageValidateSizeLabelExpectedMinResolution: 'Minsta tillåtna upplösning är {minResolution}',
+    imageValidateSizeLabelExpectedMaxResolution: 'Högsta tillåtna upplösning är {maxResolution}'
+};

+ 39 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/tr-tr.js

@@ -0,0 +1,39 @@
+glFilepondLocale = {
+    labelIdle: 'Dosyanızı Sürükleyin & Bırakın ya da <span class="filepond--label-action"> Seçin </span>',
+    labelInvalidField: 'Alan geçersiz dosyalar içeriyor',
+    labelFileWaitingForSize: 'Boyut hesaplanıyor',
+    labelFileSizeNotAvailable: 'Boyut mevcut değil',
+    labelFileLoading: 'Yükleniyor',
+    labelFileLoadError: 'Yükleme sırasında hata oluştu',
+    labelFileProcessing: 'Yükleniyor',
+    labelFileProcessingComplete: 'Yükleme tamamlandı',
+    labelFileProcessingAborted: 'Yükleme iptal edildi',
+    labelFileProcessingError: 'Yüklerken hata oluştu',
+    labelFileProcessingRevertError: 'Geri çekerken hata oluştu',
+    labelFileRemoveError: 'Kaldırırken hata oluştu',
+    labelTapToCancel: 'İptal etmek için tıklayın',
+    labelTapToRetry: 'Tekrar denemek için tıklayın',
+    labelTapToUndo: 'Geri almak için tıklayın',
+    labelButtonRemoveItem: 'Kaldır',
+    labelButtonAbortItemLoad: 'İptal Et',
+    labelButtonRetryItemLoad: 'Tekrar dene',
+    labelButtonAbortItemProcessing: 'İptal et',
+    labelButtonUndoItemProcessing: 'Geri Al',
+    labelButtonRetryItemProcessing: 'Tekrar dene',
+    labelButtonProcessItem: 'Yükle',
+    labelMaxFileSizeExceeded: 'Dosya çok büyük',
+    labelMaxFileSize: 'En fazla dosya boyutu: {filesize}',
+    labelMaxTotalFileSizeExceeded: 'Maximum boyut aşıldı',
+    labelMaxTotalFileSize: 'Maximum dosya boyutu :{filesize}',
+    labelFileTypeNotAllowed: 'Geçersiz dosya tipi',
+    fileValidateTypeLabelExpectedTypes: 'Şu {allButLastType} ya da şu dosya olması gerekir: {lastType}',
+    imageValidateSizeLabelFormatError: 'Resim tipi desteklenmiyor',
+    imageValidateSizeLabelImageSizeTooSmall: 'Resim çok küçük',
+    imageValidateSizeLabelImageSizeTooBig: 'Resim çok büyük',
+    imageValidateSizeLabelExpectedMinSize: 'Minimum boyut {minWidth} × {minHeight}',
+    imageValidateSizeLabelExpectedMaxSize: 'Maximum boyut {maxWidth} × {maxHeight}',
+    imageValidateSizeLabelImageResolutionTooLow: 'Çözünürlük çok düşük',
+    imageValidateSizeLabelImageResolutionTooHigh: 'Çözünürlük çok yüksek',
+    imageValidateSizeLabelExpectedMinResolution: 'Minimum çözünürlük {minResolution}',
+    imageValidateSizeLabelExpectedMaxResolution: 'Maximum çözünürlük {maxResolution}'
+};

+ 39 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/uk-ua.js

@@ -0,0 +1,39 @@
+glFilepondLocale = {
+    labelIdle: 'Перетягніть файли або <span class="filepond--label-action"> виберіть </span>',
+    labelInvalidField: 'Поле містить недопустимі файли',
+    labelFileWaitingForSize: 'Вкажіть розмір',
+    labelFileSizeNotAvailable: 'Розмір не доступний',
+    labelFileLoading: 'Очікування',
+    labelFileLoadError: 'Помилка при очікуванні',
+    labelFileProcessing: 'Завантаження',
+    labelFileProcessingComplete: 'Завантаження завершено',
+    labelFileProcessingAborted: 'Завантаження скасовано',
+    labelFileProcessingError: 'Помилка при завантаженні',
+    labelFileProcessingRevertError: 'Помилка при відновленні',
+    labelFileRemoveError: 'Помилка при видаленні',
+    labelTapToCancel: 'Відмінити',
+    labelTapToRetry: 'Натисніть, щоб повторити спробу',
+    labelTapToUndo: 'Натисніть, щоб відмінити останню дію',
+    labelButtonRemoveItem: 'Видалити',
+    labelButtonAbortItemLoad: 'Відмінити',
+    labelButtonRetryItemLoad: 'Повторити спробу',
+    labelButtonAbortItemProcessing: 'Відмінити',
+    labelButtonUndoItemProcessing: 'Відмінити останню дію',
+    labelButtonRetryItemProcessing: 'Повторити спробу',
+    labelButtonProcessItem: 'Завантаження',
+    labelMaxFileSizeExceeded: 'Файл занадто великий',
+    labelMaxFileSize: 'Максимальний розмір файлу: {filesize}',
+    labelMaxTotalFileSizeExceeded: 'Перевищено максимальний загальний розмір',
+    labelMaxTotalFileSize: 'Максимальний загальний розмір: {filesize}',
+    labelFileTypeNotAllowed: 'Формат файлу не підтримується',
+    fileValidateTypeLabelExpectedTypes: 'Очікується {allButLastType} або {lastType}',
+    imageValidateSizeLabelFormatError: 'Формат зображення не підтримується',
+    imageValidateSizeLabelImageSizeTooSmall: 'Зображення занадто маленьке',
+    imageValidateSizeLabelImageSizeTooBig: 'Зображення занадто велике',
+    imageValidateSizeLabelExpectedMinSize: 'Мінімальний розмір: {minWidth} × {minHeight}',
+    imageValidateSizeLabelExpectedMaxSize: 'Максимальний розмір: {maxWidth} × {maxHeight}',
+    imageValidateSizeLabelImageResolutionTooLow: 'Розміри зображення занадто маленькі',
+    imageValidateSizeLabelImageResolutionTooHigh: 'Розміри зображення занадто великі',
+    imageValidateSizeLabelExpectedMinResolution: 'Мінімальні розміри: {minResolution}',
+    imageValidateSizeLabelExpectedMaxResolution: 'Максимальні розміри: {maxResolution}'
+};

+ 39 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/vi-vi.js

@@ -0,0 +1,39 @@
+glFilepondLocale = {
+    labelIdle: 'Kéo thả tệp của bạn hoặc <span class="filepond--label-action"> Tìm kiếm </span>',
+    labelInvalidField: 'Trường chứa các tệp không hợp lệ',
+    labelFileWaitingForSize: 'Đang chờ kích thước',
+    labelFileSizeNotAvailable: 'Kích thước không có sẵn',
+    labelFileLoading: 'Đang tải',
+    labelFileLoadError: 'Lỗi khi tải',
+    labelFileProcessing: 'Đang tải lên',
+    labelFileProcessingComplete: 'Tải lên thành công',
+    labelFileProcessingAborted: 'Đã huỷ tải lên',
+    labelFileProcessingError: 'Lỗi khi tải lên',
+    labelFileProcessingRevertError: 'Lỗi khi hoàn nguyên',
+    labelFileRemoveError: 'Lỗi khi xóa',
+    labelTapToCancel: 'nhấn để hủy',
+    labelTapToRetry: 'nhấn để thử lại',
+    labelTapToUndo: 'nhấn để hoàn tác',
+    labelButtonRemoveItem: 'Xoá',
+    labelButtonAbortItemLoad: 'Huỷ bỏ',
+    labelButtonRetryItemLoad: 'Thử lại',
+    labelButtonAbortItemProcessing: 'Hủy bỏ',
+    labelButtonUndoItemProcessing: 'Hoàn tác',
+    labelButtonRetryItemProcessing: 'Thử lại',
+    labelButtonProcessItem: 'Tải lên',
+    labelMaxFileSizeExceeded: 'Tập tin quá lớn',
+    labelMaxFileSize: 'Kích thước tệp tối đa là {filesize}',
+    labelMaxTotalFileSizeExceeded: 'Đã vượt quá tổng kích thước tối đa',
+    labelMaxTotalFileSize: 'Tổng kích thước tệp tối đa là {filesize}',
+    labelFileTypeNotAllowed: 'Tệp thuộc loại không hợp lệ',
+    fileValidateTypeLabelExpectedTypes: 'Kiểu tệp hợp lệ là {allButLastType} hoặc {lastType}',
+    imageValidateSizeLabelFormatError: 'Loại hình ảnh không được hỗ trợ',
+    imageValidateSizeLabelImageSizeTooSmall: 'Hình ảnh quá nhỏ',
+    imageValidateSizeLabelImageSizeTooBig: 'Hình ảnh quá lớn',
+    imageValidateSizeLabelExpectedMinSize: 'Kích thước tối thiểu là {minWidth} × {minHeight}',
+    imageValidateSizeLabelExpectedMaxSize: 'Kích thước tối đa là {maxWidth} × {maxHeight}',
+    imageValidateSizeLabelImageResolutionTooLow: 'Độ phân giải quá thấp',
+    imageValidateSizeLabelImageResolutionTooHigh: 'Độ phân giải quá cao',
+    imageValidateSizeLabelExpectedMinResolution: 'Độ phân giải tối thiểu là {minResolution}',
+    imageValidateSizeLabelExpectedMaxResolution: 'Độ phân giải tối đa là {maxResolution}'
+};

+ 39 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/zh-cn.js

@@ -0,0 +1,39 @@
+glFilepondLocale = {
+    labelIdle: '拖放文件,或者 <span class="filepond--label-action"> 浏览 </span>',
+    labelInvalidField: '字段包含无效文件',
+    labelFileWaitingForSize: '计算文件大小',
+    labelFileSizeNotAvailable: '文件大小不可用',
+    labelFileLoading: '加载',
+    labelFileLoadError: '加载错误',
+    labelFileProcessing: '上传',
+    labelFileProcessingComplete: '已上传',
+    labelFileProcessingAborted: '上传已取消',
+    labelFileProcessingError: '上传出错',
+    labelFileProcessingRevertError: '还原出错',
+    labelFileRemoveError: '删除出错',
+    labelTapToCancel: '点击取消',
+    labelTapToRetry: '点击重试',
+    labelTapToUndo: '点击撤消',
+    labelButtonRemoveItem: '删除',
+    labelButtonAbortItemLoad: '中止',
+    labelButtonRetryItemLoad: '重试',
+    labelButtonAbortItemProcessing: '取消',
+    labelButtonUndoItemProcessing: '撤消',
+    labelButtonRetryItemProcessing: '重试',
+    labelButtonProcessItem: '上传',
+    labelMaxFileSizeExceeded: '文件太大',
+    labelMaxFileSize: '最大值: {filesize}',
+    labelMaxTotalFileSizeExceeded: '超过最大文件大小',
+    labelMaxTotalFileSize: '最大文件大小:{filesize}',
+    labelFileTypeNotAllowed: '文件类型无效',
+    fileValidateTypeLabelExpectedTypes: '应为 {allButLastType} 或 {lastType}',
+    imageValidateSizeLabelFormatError: '不支持图像类型',
+    imageValidateSizeLabelImageSizeTooSmall: '图像太小',
+    imageValidateSizeLabelImageSizeTooBig: '图像太大',
+    imageValidateSizeLabelExpectedMinSize: '最小值: {minWidth} × {minHeight}',
+    imageValidateSizeLabelExpectedMaxSize: '最大值: {maxWidth} × {maxHeight}',
+    imageValidateSizeLabelImageResolutionTooLow: '分辨率太低',
+    imageValidateSizeLabelImageResolutionTooHigh: '分辨率太高',
+    imageValidateSizeLabelExpectedMinResolution: '最小分辨率:{minResolution}',
+    imageValidateSizeLabelExpectedMaxResolution: '最大分辨率:{maxResolution}'
+  };

+ 39 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/plugins_public/filepond/locale/zh-tw.js

@@ -0,0 +1,39 @@
+glFilepondLocale = {
+    labelIdle: '拖放檔案,或者 <span class="filepond--label-action"> 瀏覽 </span>',
+    labelInvalidField: '不支援此檔案',
+    labelFileWaitingForSize: '正在計算檔案大小',
+    labelFileSizeNotAvailable: '檔案大小不符',
+    labelFileLoading: '讀取中',
+    labelFileLoadError: '讀取錯誤',
+    labelFileProcessing: '上傳',
+    labelFileProcessingComplete: '已上傳',
+    labelFileProcessingAborted: '上傳已取消',
+    labelFileProcessingError: '上傳發生錯誤',
+    labelFileProcessingRevertError: '還原錯誤',
+    labelFileRemoveError: '刪除錯誤',
+    labelTapToCancel: '點擊取消',
+    labelTapToRetry: '點擊重試',
+    labelTapToUndo: '點擊還原',
+    labelButtonRemoveItem: '刪除',
+    labelButtonAbortItemLoad: '停止',
+    labelButtonRetryItemLoad: '重試',
+    labelButtonAbortItemProcessing: '取消',
+    labelButtonUndoItemProcessing: '取消',
+    labelButtonRetryItemProcessing: '重試',
+    labelButtonProcessItem: '上傳',
+    labelMaxFileSizeExceeded: '檔案過大',
+    labelMaxFileSize: '最大值:{filesize}',
+    labelMaxTotalFileSizeExceeded: '超過最大可上傳大小',
+    labelMaxTotalFileSize: '最大可上傳大小:{filesize}',
+    labelFileTypeNotAllowed: '不支援此類型檔案',
+    fileValidateTypeLabelExpectedTypes: '應為 {allButLastType} 或 {lastType}',
+    imageValidateSizeLabelFormatError: '不支持此類圖片類型',
+    imageValidateSizeLabelImageSizeTooSmall: '圖片過小',
+    imageValidateSizeLabelImageSizeTooBig: '圖片過大',
+    imageValidateSizeLabelExpectedMinSize: '最小尺寸:{minWidth} × {minHeight}',
+    imageValidateSizeLabelExpectedMaxSize: '最大尺寸:{maxWidth} × {maxHeight}',
+    imageValidateSizeLabelImageResolutionTooLow: '解析度過低',
+    imageValidateSizeLabelImageResolutionTooHigh: '解析度過高',
+    imageValidateSizeLabelExpectedMinResolution: '最低解析度:{minResolution}',
+    imageValidateSizeLabelExpectedMaxResolution: '最高解析度:{maxResolution}'
+  };

+ 44 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/studio/polyglot.php

@@ -0,0 +1,44 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaEndAdmin UNA Studio End Admin Pages
+ * @ingroup     UnaStudio
+ * @{
+ */
+
+require_once('./../inc/header.inc.php');
+require_once(BX_DIRECTORY_PATH_INC . 'design.inc.php');
+require_once(BX_DOL_DIR_STUDIO_INC . 'utils.inc.php');
+
+bx_import('BxDolLanguages');
+
+bx_require_authentication(true);
+
+$sPage = bx_get('page');
+$sPage = $sPage !== false ? bx_process_input($sPage) : '';
+
+$oPage = new BxTemplStudioPolyglot($sPage);
+
+if(($mixedResult = $oPage->checkAction()) !== false) {
+    echoJson($mixedResult);
+    exit;
+}
+
+$oTemplate = BxDolStudioTemplate::getInstance();
+
+$sPageCode = $oPage->getPageCode();
+if($sPageCode === false)
+    $oTemplate->displayMsg(($sError = $oPage->getError(false)) !== false ? $sError : '_sys_txt_error_occured', true);
+
+$oTemplate->setPageNameIndex($oPage->getPageIndex());
+$oTemplate->setPageHeader($oPage->getPageHeader());
+$oTemplate->setPageContent('page_caption_code', $oPage->getPageCaption());
+$oTemplate->setPageContent('page_attributes', $oPage->getPageAttributes());
+$oTemplate->setPageContent('page_menu_code', $oPage->getPageMenu());
+$oTemplate->setPageContent('page_main_code', $sPageCode . $oPage->getPageJsCode());
+$oTemplate->addCss($oPage->getPageCss());
+$oTemplate->addJs($oPage->getPageJs());
+$oTemplate->getPageCode();
+/** @} */

+ 33 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/studio/template/bp_blocks.html

@@ -0,0 +1,33 @@
+<bx_repeat:blocks>
+    <div id="__html_id__" class="adm-bp-block<bx_if:is_inactive> adm-bpb-inactive</bx_if:is_inactive> bx-def-border">
+        <div class="adm-bpb-content">
+            <bx_if:show_drag_handle>
+                <div class="adm-bpb-drag-handle bx-def-margin-thd-right">
+                    <i class="sys-icon align-justify"></i>
+                </div>
+            </bx_if:show_drag_handle>
+            <div class="adm-bpb-icon bx-def-margin-thd-right">
+            	<bx_if:icon>
+                    <div class="adm-bpb-icon-bg"></div>
+                    <i class="sys-icon __icon__"></i>
+                </bx_if:icon>
+                <bx_if:image><img src="__icon_url__" /></bx_if:image>
+            </div>
+            <div class="adm-bpb-info">
+                <div class="adm-bpb-title">
+                    <bx_if:show_link>
+                        <a href="javascript:void(0)" onclick="javascript:__onclick__">__title__</a>
+                    </bx_if:show_link>
+                    <bx_if:show_text>__title__</bx_if:show_text>
+                    <bx_if:show_api>
+                        <img class="bx-std-api" src="<bx_icon_url:api.svg />" title="<bx_text_attribute:_adm_txt_active_api />" />
+                    </bx_if:show_api>
+                </div>
+                <div class="adm-bpb-description bx-def-font-grayed">__module__ &bull; __visible_for__</div>
+            </div>
+            <bx_if:show_checkbox>
+                <div class="adm-bpb-checkbox">__content__</div>
+            </bx_if:show_checkbox>
+        </div>
+    </div>
+</bx_repeat:blocks>

+ 255 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/studio/template/css/builder_page.css

@@ -0,0 +1,255 @@
+/*--- Panel Top style ---*/
+div.bx-std-pt-pages {
+	position: relative;
+    float: left;
+}
+div.bx-std-pt-pages div.bx-form-element-wrapper {
+	width: 165px;
+}
+div.bx-std-pt-buttons {
+	float: right;
+}
+
+/*--- Main section style ---*/
+div.adm-bp-cell {
+    background-color: rgba(255,255,255,0.8);
+}
+div.adm-bp-cell-cnt {
+    padding: 3px 6px 50px 6px;
+}
+div.adm-bp-blocks {
+    position: relative;
+}
+div.adm-bp-block {
+    position: relative;
+    overflow: hidden;
+    margin: 3px 0px;
+
+    border-style: solid;
+    border-width: 1px;
+    border-radius: 5px 5px 5px 5px;
+
+    box-shadow: 0 0 1px rgba(51, 51, 51, 0.3);
+
+    background-color: #ececec;
+    background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#fff), to(#ececec));
+    background-image: -webkit-linear-gradient(top, #fff, #ececec); 
+    background-image: -moz-linear-gradient(top, #fff, #ececec); 
+    background-image: -ms-linear-gradient(top, #fff, #ececec); 
+    background-image: -o-linear-gradient(top, #fff, #ececec); 
+    background-image: linear-gradient(top, #fff, #ececec);	
+}
+div.adm-bpb-inactive {
+    opacity: 0.5;
+}
+div.adm-bp-block-empty {
+    height: 42px;
+    border-style: dashed;
+    box-shadow: none;
+}
+div.adm-bp-block-dragging {}
+div.adm-bpb-content {
+    position: relative;
+    display: -webkit-flex; 
+    display: flex;
+
+    -webkit-justify-content: flex-start;
+    justify-content: flex-start;
+
+    -webkit-align-items: center;
+    align-items: center;
+
+    -webkit-flex-flow: row nowrap;
+    flex-flow: row nowrap;
+    
+    padding: 0.5rem;
+}
+
+div.adm-bpb-icon,
+div.adm-bpb-icon-bg,
+div.adm-bpb-icon img,
+div.adm-bpb-icon i.sys-icon {
+    width: 2rem;
+    height: 2rem;
+}
+div.adm-bpb-icon {
+    position: relative;
+}
+div.adm-bpb-icon-bg {
+    background-color: #323c46;
+    border-radius: 0.5rem;
+}
+div.adm-bpb-icon i.sys-icon {
+    position: absolute;
+    top: 0px;
+    left: 0px;
+
+    color:white;
+    font-size:20px;
+    text-align:center;
+    text-shadow:none;
+    line-height: 2rem;
+}
+div.adm-bpb-info {
+    -webkit-flex-grow: 2;
+    flex-grow: 2;
+
+    min-height: 2rem;
+}
+div.adm-bpb-title {
+    display: -webkit-flex; 
+    display: flex;
+    -webkit-align-items: center;
+    align-items: center;
+
+    padding-right: 1rem;
+
+    white-space: nowrap;
+    text-overflow: ellipsis;
+    overflow: hidden;
+
+    font-weight: bold;
+}
+div.adm-bpb-title .bx-std-api {
+    margin-left: 0.25rem;
+}
+div.adm-bpb-description {
+    font-size: 0.75rem;
+}
+div.adm-bpb-drag-handle {
+    text-align: center;
+    line-height: 2rem;
+    cursor: move;
+    color: #666666;
+    font-size: 0.875rem;
+}
+
+/*--- Add Block popup style ---*/
+div#adm-bp-create-block-popup div.bx-popup-content {
+	padding-left: 0px;
+	padding-right: 0px; 
+}
+
+div.adm-bp-create-block-form {}
+
+div.adm-bp-create-block-form-cnt {}
+
+div.adm-bp-create-block-form fieldset.bx-form-section {
+	padding-bottom: 0px;
+}
+div.adm-bp-create-block-form div.adm-bp-block:first-child {
+	margin-top: 0px;
+}
+div.adm-bp-cbf-left {
+	position: absolute;
+        top: 0px;
+        left: 0px;
+	width: 179px;
+	height: 300px;
+	border-style: solid;
+	border-width: 1px 1px 0px 0px;
+	overflow-y: auto; 
+}
+div.adm-bp-cbf-left-cnt {}
+div.adm-bp-cbf-right {
+	position: relative;
+	width: 620px;
+	height: 300px;
+	margin-left: 180px;
+	border-style: solid;
+	border-width: 1px 0px 0px 0px;
+	overflow-y: auto;
+}
+div.adm-bp-cbf-right-cnt {}
+div.adm-bp-cbf-controls {
+	border-style: solid;
+	border-width: 1px 0px 0px 0px;
+}
+div.adm-bp-cbf-counter {
+	position: relative;
+	float: right;
+	line-height: 38px;
+	color: #b5b5b5;
+}
+
+/*--- Edit Block popup style ---*/
+div.adm-bp-edit-block-form {
+	margin-top: 0px;
+}
+
+/*--- Uploader Ghost in Popup style ---*/
+div.bx-popup .bx-uploader-ghost .bx-ug-content input.bx-ug-url.bx-form-input-text {
+    width: 18.0rem;
+}
+div.bx-popup .bx-uploader-ghost .bx-ug-content input.bx-ug-dimension.bx-form-input-text {
+	width: 4.0rem;
+}
+
+/*--- Page Settings popups style ---*/
+div#adm-bp-add-popup div.bx-popup-content,
+div#adm-bp-edit-popup div.bx-popup-content {
+    padding-left: 0px;
+    padding-right: 0px; 
+}
+
+div.adm-bp-add-form {}
+div.adm-bp-add-form-cnt {}
+
+div.adm-bp-edit-form {}
+div.adm-bp-edit-form-cnt {}
+
+div.adm-bp-add-form fieldset.bx-form-section,
+div.adm-bp-edit-form fieldset.bx-form-section {
+    padding-bottom: 0px;
+}
+div.adm-bp-add-form div.bx-std-pmen-item-cnt, 
+div.adm-bp-edit-form div.bx-std-pmen-item-cnt {
+	padding-left: 20px;
+}
+div.adm-bp-add-form div.bx-form-input-wrapper-custom, 
+div.adm-bp-edit-form div.bx-form-input-wrapper-custom {
+	position: relative;
+}
+div.adm-bp-epf-left {
+    position: absolute;
+    top: 0px;
+    left: 0px;
+    width: 179px;
+    height: 310px;
+    border-style: solid;
+    border-width: 1px 1px 0px 0px;
+    overflow-y: auto; 
+}
+div.adm-bp-epf-left-cnt {}
+div.adm-bp-epf-right {
+    position: relative;
+    width: 620px;
+    height: 310px;
+    margin-left: 180px;
+    border-style: solid;
+    border-width: 1px 0px 0px 0px;
+    overflow-y: auto;
+}
+div.adm-bp-epf-right-cnt {}
+div.adm-bp-epf-controls {
+    border-style: solid;
+    border-width: 1px 0px 0px 0px;
+}
+div#adm-bp-settings-group-layout {
+    margin: 0px -10px;
+}
+div.adm-bp-layout {
+	position: relative;
+	float: left;
+	width: 66px;
+	height: 66px;
+	margin-left: 10px;
+	margin-right: 10px; 
+	border: 2px solid transparent;
+}
+div.adm-bp-layout-active {
+	border-color: #9999cc;
+}
+div.adm-bp-layout-cnt {
+	padding: 1px;
+}

+ 221 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/studio/template/css/general.css

@@ -0,0 +1,221 @@
+html,
+body {
+    min-height: 100%;
+    min-height: 100vh;
+}
+iframe.bx-std-hidden {
+    display: none;
+}
+div.bx-std-cb {
+    clear: both;
+    font-size: 1px;
+}
+
+/**
+ * Page
+ */
+
+/**
+ * Page Content
+ */
+div.bx-std-page-bg {
+    position:fixed; 
+    width:100%; 
+    height:100%; 
+
+    background:url(../images/bx-std-spacer.gif) center center no-repeat;
+    /* background:url(../images/bx-std-bg-main-image.jpg) center center no-repeat; */
+}
+div.bx-std-page {
+    position: relative;
+    position: relative;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-flex-direction: column;
+    flex-direction: column;
+    width: 100%;
+    min-height: 100vh;
+}
+div.bx-std-menu-top-wrp {
+    -webkit-flex-grow: 0;
+    flex-grow: 0;
+    -webkit-flex-shrink: 0;
+    flex-shrink: 0;
+}
+div.bx-std-content {
+    position: relative;
+    -webkit-flex-grow: 2;
+    flex-grow: 2;
+
+    width: 100%;
+}
+div.bx-std-content-wrp,
+div.bx-std-page-columns,
+div.bx-std-page-columns-cnt {
+    position: relative;
+    width: 100%;
+}
+
+/**
+ * Dual Column page
+ */
+div.bx-std-page-caption {
+    position: relative;
+    height: 70px;
+
+    background-color: #334455;
+}
+div.bx-std-pcap-icon {}
+div.bx-std-pcap-text {
+	position: relative;
+	line-height: 70px;
+	text-align: center;
+	font-size: 2rem;
+}
+div.bx-std-pcap-menu-popup {
+	position: absolute;
+	z-index: 1;
+}
+div.bx-std-pcap-mp-right {
+	right: 10px;
+}
+
+div.bx-std-pcap-help .sys-rss-feed-wrapper {
+	padding-top: 0px;
+}
+div.bx-std-pcap-help .bx-loading-ajax {
+	margin-top: 0px;
+	margin-bottom: 0px;
+}
+
+div.bx-std-pcap-action {
+	position: relative;
+	float: left;
+	width: 36px;
+	height: 36px;
+}
+div.bx-std-pcap-action:first-child {
+	margin-left: 0px;
+}
+div#bx-std-pca-activate {
+	width: 82px;
+}
+div.bx-std-pcap-mp-right div.bx-std-pcap-action {
+    float: right;
+}
+
+/**
+ * Page Menu (Left Side)
+ */
+div.bx-std-pmen-item {
+    position: relative;
+    height: 2.5rem;
+
+    line-height: 2.5rem;
+    font-size: 0.875rem;
+    font-weight: 400;
+
+    overflow: hidden;
+}
+
+div.bx-std-pmen-item-cnt {
+    padding-left: 3rem;    
+
+    white-space: nowrap;
+    text-overflow: ellipsis;
+    overflow: hidden;
+}
+
+span.bx-std-pmen-item-counter {}
+span.bx-std-pmen-ic-num {}
+div.bx-std-pmen-item img,
+div.bx-std-pmen-item svg {
+    position: absolute;
+
+    top: 0.5rem;
+    left: 1rem;
+    width: 1.5rem;
+    height: 1.5rem;
+}
+
+div.bx-std-pmen-item a, 
+div.bx-std-pmen-item .sys-icon {
+    color: #595959;
+}
+
+div.bx-std-pmen-item a {
+    position: relative;
+    display: block;
+    width: 100%;
+    height: 100%;
+
+    text-decoration: none;
+}
+
+div.bx-std-pmen-item .sys-icon {
+    position: absolute;
+    left: 1.0rem;
+    width: 1.5rem;
+
+    font-size: 20px;
+    text-align: center;
+}
+
+div.bx-std-pmen-item svg g {
+    opacity: 0.7;
+}
+
+div.bx-std-pmen-item.bx-menu-tab-active {
+    background-color: rgba(24, 144, 255, 0.2);
+    box-shadow: inset -3px 0px 0px rgba(24, 144, 255, 1.0);
+
+    font-weight: 600;
+    
+}
+
+div.bx-std-pmen-item:hover a,
+div.bx-std-pmen-item:hover .sys-icon,
+div.bx-std-pmen-item.bx-menu-tab-active a,
+div.bx-std-pmen-item.bx-menu-tab-active .sys-icon {
+    color: #1890ff;
+}
+
+div.bx-std-pmen-item:hover svg g,
+div.bx-std-pmen-item.bx-menu-tab-active svg g {
+    opacity: 0.85;
+}
+/*--- Icons ---*/
+img.bx-item-icon,
+svg.bx-item-icon{
+    width: 32px;
+    height: 32px;
+}
+div.bx-item-icon-preview {}
+div.bx-item-icon-preview img,
+div.bx-item-icon-preview svg{
+	max-width:128px; 
+    max-height:128px;
+}
+div.adm-item-icon-preview-empty {}
+
+/**
+ * Footer
+ */
+div.bx-std-bx-footer {
+	margin-top: 60px;
+	border: 0px;
+	background-color: transparent;
+    -moz-shadow: none;
+    -webkit-shadow: none;
+	box-shadow: none;
+}
+
+
+/**
+ * API
+ */
+img.bx-std-api {
+    display: inline-block;
+    width: 1rem;
+    height: 1rem;
+}

+ 1 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/studio/template/images/icons/api.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#10b981" viewBox="0 0 256 256"><path d="M229.67,133.62l-96,96a7.94,7.94,0,0,1-11.24,0l-96-96a7.94,7.94,0,0,1,0-11.24l96.05-96a7.94,7.94,0,0,1,11.24,0l96,96.05A7.94,7.94,0,0,1,229.67,133.62Z" opacity="0.2"></path><path d="M235.33,116.72,139.28,20.66a16,16,0,0,0-22.56,0l-96,96.06a16,16,0,0,0,0,22.56l96.05,96.06h0a16,16,0,0,0,22.56,0l96.05-96.06a16,16,0,0,0,0-22.56ZM128,224h0L32,128,128,32,224,128Z"></path></svg>

+ 954 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/studio/template/scripts/BxBaseStudioNavigationItems.php

@@ -0,0 +1,954 @@
+<?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 BxBaseStudioNavigationItems extends BxDolStudioNavigationItems
+{
+    protected $sUrlPage;
+    protected $sUrlViewItems;
+
+    function __construct($aOptions, $oTemplate = false)
+    {
+        parent::__construct($aOptions, $oTemplate);
+
+        $this->_aOptions['actions_single']['edit']['attr']['title'] = _t('_adm_nav_btn_items_edit');
+        $this->_aOptions['actions_single']['delete']['attr']['title'] = _t('_adm_nav_btn_items_delete');
+
+        $this->sUrlPage = BX_DOL_URL_STUDIO . 'builder_menu.php?page=items';
+        $this->sUrlViewItems = $this->sUrlPage . '&module=%s&set=%s';
+    }
+
+    public function performActionImport()
+    {
+        $oGrid = BxDolGrid::getObjectInstance('sys_studio_nav_import');
+        if(!$oGrid)
+            return '';
+
+        $sContent = BxTemplStudioFunctions::getInstance()->popupBox('adm-nav-item-import-popup', _t('_adm_nav_txt_items_import_popup'), $this->_oTemplate->parseHtmlByName('nav_import_item.html', array(
+            'grid' => $oGrid->getCode()
+        )));
+
+        echoJson(array('popup' => array('html' => $sContent, 'options' => array('closeOnOuterClick' => false))));
+    }
+
+    public function performActionAdd()
+    {
+        $sAction = 'add';
+
+        $oForm = $this->_getFormObject($sAction);
+        $oForm->initChecker();
+
+        if($oForm->isSubmittedAndValid()) {
+            if(!empty($_FILES['icon_image']['tmp_name'])) {
+                $oStorage = BxDolStorage::getObjectInstance(BX_DOL_STORAGE_OBJ_IMAGES);
+
+                $mixedIcon = $oStorage->storeFileFromForm($_FILES['icon_image'], false, 0);
+                if($mixedIcon === false) {
+                    echoJson(array('msg' => _t('_adm_nav_err_items_icon_image') . $oStorage->getErrorString()));
+                    return;
+                }
+
+                $oStorage->afterUploadCleanup($mixedIcon, 0);
+                BxDolForm::setSubmittedValue('icon', $mixedIcon, $oForm->aFormAttrs['method']);
+            }
+
+            $oPermalinks = BxDolPermalinks::getInstance();
+
+            $sLink = $oForm->getCleanValue('link');
+            $sLink = $oPermalinks->unpermalink($sLink);
+            BxDolForm::setSubmittedValue('link', $sLink, $oForm->aFormAttrs['method']);
+
+            $sName = $oPermalinks->getPageNameFromLink($sLink);
+            if($sName == '') {
+                $sLanguage = BxDolStudioLanguagesUtils::getInstance()->getCurrentLangName(false);
+
+                $sName = BxDolForm::getSubmittedValue('title-' . $sLanguage, $oForm->aFormAttrs['method']);
+                if(empty($sName))
+                    $sName = BxDolForm::getSubmittedValue('title_system-' . $sLanguage, $oForm->aFormAttrs['method']);
+
+                $sName = uriGenerate($sName, 'sys_menu_items', 'name', ['empty' => 'item']);
+            }
+
+            bx_import('BxDolStudioUtils');
+            $iId = (int)$oForm->insert(array('module' => BX_DOL_STUDIO_MODULE_CUSTOM, 'name' => $sName, 'active' => 1, 'order' => $this->oDb->getItemOrderMax($this->sSet) + 1));
+            if($iId != 0)
+                $aRes = array('grid' => $this->getCode(false), 'blink' => $iId);
+            else
+                $aRes = array('msg' => _t('_adm_nav_err_items_create'));
+
+            echoJson($aRes);
+        }
+        else {
+            $sContent = BxTemplStudioFunctions::getInstance()->popupBox('adm-nav-item-create-popup', _t('_adm_nav_txt_items_create_popup'), $this->_oTemplate->parseHtmlByName('nav_add_item.html', array(
+                'form_id' => $oForm->aFormAttrs['id'],
+                'form' => $oForm->getCode(true),
+                'object' => $this->_sObject,
+                'action' => $sAction
+            )));
+
+            echoJson(array('popup' => array('html' => $sContent, 'options' => array('closeOnOuterClick' => false))));
+        }
+    }
+
+    public function performActionEdit($bUpdateGrid = false)
+    {
+        $sAction = 'edit';
+
+        $aItem = $this->_getItem('getItems');
+        if($aItem === false) {
+            echoJson(array());
+            exit;
+        }
+
+        $oForm = $this->_getFormObject($sAction, $aItem);
+        $oForm->initChecker();
+
+        if($oForm->isSubmittedAndValid()) {
+            $bIconImageCur = is_numeric($aItem['icon']) && (int)$aItem['icon'] != 0;
+            $bIconImageNew = !empty($_FILES['icon_image']['tmp_name']);
+
+            $sIconFont = $oForm->getCleanValue('icon');
+            $bIconFont = !empty($sIconFont);
+
+            if($bIconImageCur && ($bIconImageNew || $bIconFont)) {
+                $oStorage = BxDolStorage::getObjectInstance(BX_DOL_STORAGE_OBJ_IMAGES);
+                if(!$oStorage->deleteFile((int)$aItem['icon'], 0)) {
+                    echoJson(array('msg' => _t('_adm_nav_err_items_icon_image_remove')));
+                    return;
+                }
+            }
+
+            $sIcon = $sIconFont;
+            if($bIconImageNew) {
+                $oStorage = BxDolStorage::getObjectInstance(BX_DOL_STORAGE_OBJ_IMAGES);
+                $sIcon = $oStorage->storeFileFromForm($_FILES['icon_image'], false, 0);
+                if($sIcon === false) {
+                    echoJson(array('msg' => _t('_adm_nav_err_items_icon_image') . $oStorage->getErrorString()));
+                    return;
+                }
+
+                $oStorage->afterUploadCleanup($sIcon, 0);
+            } else if($bIconImageCur && !$bIconFont)
+                $sIcon = $aItem['icon'];
+
+            BxDolForm::setSubmittedValue('icon', $sIcon, $oForm->aFormAttrs['method']);
+
+            $sLink = $oForm->getCleanValue('link');
+            $sLink = BxDolPermalinks::getInstance()->unpermalink($sLink);
+            BxDolForm::setSubmittedValue('link', $sLink, $oForm->aFormAttrs['method']);
+
+            $sSubmenu = $oForm->getCleanValue('submenu_object');
+            if(empty($sSubmenu))
+            	BxDolForm::setSubmittedValue('submenu_popup', 0, $oForm->aFormAttrs['method']);
+
+            $sTarget = $oForm->getCleanValue('target');
+            if($sTarget === false && !in_array($aItem['target'], array('', '_blank')))
+                unset($oForm->aInputs['target']);
+
+            if($oForm->update($aItem['id']) !== false)
+                $aRes = array('grid' => $this->getCode(false), 'blink' => $aItem['id']);
+            else
+                $aRes = array('msg' => _t('_adm_nav_err_items_edit'));
+
+            echoJson($aRes);
+        }
+        else {
+            $sTitle = _t($aItem['title']);
+            $sContent = BxTemplStudioFunctions::getInstance()->popupBox('adm-nav-item-edit-popup', _t('_adm_nav_txt_items_edit_popup', ($sTitle != "" ? '"' . $sTitle . '"' : '')), $this->_oTemplate->parseHtmlByName('nav_add_item.html', array(
+                'form_id' => $oForm->aFormAttrs['id'],
+                'form' => $oForm->getCode(true),
+                'object' => $this->_sObject,
+                'action' => $sAction
+            )));
+
+            $aRes = array('popup' => array('html' => $sContent, 'options' => array('closeOnOuterClick' => false)));
+            if($bUpdateGrid)
+                $aRes = array_merge($aRes, array('grid' => $this->getCode(false), 'blink' => $aItem['id']));
+
+            echoJson($aRes);
+        }
+    }
+
+    public function performActionDelete()
+    {
+        $iAffected = 0;
+        $aIds = bx_get('ids');
+        if(!$aIds || !is_array($aIds)) {
+            echoJson(array());
+            exit;
+        }
+
+        $aIdsAffected = array ();
+        foreach($aIds as $iId) {
+            if(!$this->deleteById($iId))
+                continue;
+
+            $aIdsAffected[] = $iId;
+            $iAffected++;
+        }
+
+        echoJson($iAffected ? array('grid' => $this->getCode(false), 'blink' => $aIdsAffected) : array('msg' => _t('_adm_nav_err_items_delete')));
+    }
+
+    public function performActionShowTo()
+    {
+        $sAction = 'show_to';
+
+        $aItem = $this->_getItem('getItems');
+        if($aItem === false) {
+            echoJson(array());
+            exit;
+        }
+
+		bx_import('BxDolStudioUtils');
+        $aForm = array(
+            'form_attrs' => array(
+                'id' => 'adm-nav-item-create',
+                'action' => BX_DOL_URL_ROOT . 'grid.php?o=' . $this->_sObject . '&a=' . $sAction . '&set=' . $this->sSet,
+                'method' => 'post'
+            ),
+            'params' => array (
+                'db' => array(
+                    'table' => 'sys_menu_items',
+                    'key' => 'id',
+                    'uri' => '',
+                    'uri_title' => '',
+                    'submit_name' => 'do_submit'
+                ),
+            ),
+            'inputs' => array (
+                'id' => array(
+                    'type' => 'hidden',
+                    'name' => 'id',
+                    'value' => $aItem['id'],
+                    'db' => array (
+                        'pass' => 'Int',
+                    ),
+                ),
+                'visible_for' => array(
+                    'type' => 'select',
+                    'name' => 'visible_for',
+                    'caption' => _t('_adm_nav_txt_items_visible_for'),
+                    'info' => '',
+                    'value' => $aItem['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_nav_txt_items_visible_for_all')),
+                        array('key' => BX_DOL_STUDIO_VISIBLE_SELECTED, 'value' => _t('_adm_nav_txt_items_visible_for_selected')),
+                    ),
+                    'required' => '0',
+                    'attrs' => array(
+                        'onchange' => $this->getJsObject() . '.onChangeVisibleFor(this)'
+                    ),
+                    'db' => array (
+                        'pass' => 'Xss',
+                    )
+                ),
+                'visible_for_levels' => array(
+                    'type' => 'checkbox_set',
+                    'name' => 'visible_for_levels',
+                    'caption' => _t('_adm_nav_txt_items_visible_for_levels'),
+                    'info' => _t('_adm_nav_dsc_items_visible_for_levels'),
+                    'value' => '',
+                    'values' => array(),
+                    'tr_attrs' => array(
+                        'style' => $aItem['visible_for_levels'] == BX_DOL_INT_MAX ? 'display:none' : ''
+                    ),
+                    'db' => array (
+                        'pass' => 'Int',
+                    ),
+                ),
+                'controls' => array(
+                    'name' => 'controls',
+                    'type' => 'input_set',
+                    array(
+                        'type' => 'submit',
+                        'name' => 'do_submit',
+                        'value' => _t('_adm_nav_btn_items_save'),
+                    ),
+                    array (
+                        'type' => 'reset',
+                        'name' => 'close',
+                        'value' => _t('_adm_nav_btn_items_cancel'),
+                        'attrs' => array(
+                            'onclick' => "$('.bx-popup-applied:visible').dolPopupHide()",
+                            'class' => 'bx-def-margin-sec-left',
+                        ),
+                    )
+                )
+            )
+        );
+
+        BxDolStudioUtils::getVisibilityValues($aItem['visible_for_levels'], $aForm['inputs']['visible_for_levels']['values'], $aForm['inputs']['visible_for_levels']['value']);
+
+        $oForm = new BxTemplStudioFormView($aForm);
+        $oForm->initChecker();
+
+        if($oForm->isSubmittedAndValid()) {
+            if($oForm->updateWithVisibility($aItem['id']) !== false)
+                $aRes = array('grid' => $this->getCode(false), 'blink' => $aItem['id']);
+            else
+                $aRes = array('msg' => _t('_adm_nav_err_items_show_to'));
+
+            echoJson($aRes);
+        }
+        else {
+            $sContent = BxTemplStudioFunctions::getInstance()->popupBox('adm-nav-item-hide-from-popup', _t('_adm_nav_txt_items_show_to_popup', _t($aItem['title'])), $this->_oTemplate->parseHtmlByName('nav_add_item.html', array(
+                'form_id' => $aForm['form_attrs']['id'],
+                'form' => $oForm->getCode(true),
+                'object' => $this->_sObject,
+                'action' => $sAction
+            )));
+
+            echoJson(array('popup' => $sContent));
+        }
+    }
+
+    public function performActionDeleteIcon()
+    {
+        $sAction = 'delete_icon';
+
+        $aIds = bx_get('ids');
+        if(empty($aIds[0])) {
+            echoJson(array());
+            exit;
+        }
+
+        $iId = (int)$aIds[0];
+
+        $aItem = array();
+        $iItem = $this->oDb->getItems(array('type' => 'by_id', 'value' => $iId), $aItem);
+        if($iItem != 1 || empty($aItem)){
+            echoJson(array());
+            exit;
+        }
+
+        if(is_numeric($aItem['icon']) && (int)$aItem['icon'] != 0)
+            if(!BxDolStorage::getObjectInstance(BX_DOL_STORAGE_OBJ_IMAGES)->deleteFile((int)$aItem['icon'], 0)) {
+                echoJson(array());
+                exit;
+            }
+
+        if($this->oDb->updateItem($aItem['id'], array('icon' => '')) !== false)
+            echoJson(array('grid' => $this->getCode(false), 'blink' => $iId, 'preview' => $this->_getIconPreview($aItem['id']), 'eval' => $this->getJsObject() . ".onDeleteIcon(oData)"));
+    }
+
+    public function getJsObject()
+    {
+        return 'oBxDolStudioNavigationItems';
+    }
+
+    public function getSetsSelector($sModule = '')
+    {
+        $oForm = new BxTemplStudioFormView(array());
+
+        $aInputSets = array(
+            'type' => 'select',
+            'name' => 'set',
+            'attrs' => array(
+                'id' => 'bx-grid-set-' . $this->_sObject,
+                'onChange' => 'javascript:' . $this->getJsObject() . '.onChangeSet()'
+            ),
+            'value' => $this->sSet,
+            'values' => array()
+        );
+
+        $aSets = array();
+        if(!empty($sModule))
+            $this->oDb->getSets(array('type' => 'by_module', 'value' => $sModule), $aSets, false);
+        else
+            $aInputSets['attrs']['disabled'] = 'disabled';
+
+        if(!empty($aSets)) {
+            $aCounter = array();
+            $this->oDb->getItems(array('type' => 'counter_by_sets'), $aCounter, false);
+            foreach($aSets as $aSet)
+                $aInputSets['values'][$aSet['name']] = _t($aSet['title']) . " (" . (isset($aCounter[$aSet['name']]) ? $aCounter[$aSet['name']] : "0") . ")";
+
+            asort($aInputSets['values']);
+        }
+        $aInputSets['values'] = array_merge(array('' => _t('_adm_nav_txt_select_set')), $aInputSets['values']);
+
+        return $oForm->genRow($aInputSets);
+    }
+
+    public function getCode($isDisplayHeader = true)
+    {
+        return $this->_oTemplate->parseHtmlByName('nav_items.html', array(
+            'content' => parent::getCode($isDisplayHeader),
+            'js_object' => $this->getJsObject(),
+            'page_url' => $this->sUrlPage,
+            'grid_object' => $this->_sObject,
+            'params_divider' => $this->sParamsDivider
+        ));
+    }
+
+    protected function _addJsCss()
+    {
+        parent::_addJsCss();
+        $this->_oTemplate->addJs(array('jquery.form.min.js', 'navigation_items.js', 'navigation_import.js'));
+
+        $oForm = new BxTemplStudioFormView(array());
+        $oForm->addCssJs();
+    }
+
+    protected function _getCellSwitcher ($mixedValue, $sKey, $aField, $aRow)
+    {
+        if(!$this->_isEditable($aRow))
+            return parent::_getCellDefault('', $sKey, $aField, $aRow);
+
+        return parent::_getCellSwitcher($mixedValue, $sKey, $aField, $aRow);
+    }
+
+    protected function _getCellIcon ($mixedValue, $sKey, $aField, $aRow)
+    {
+        $mixedValue = $this->_oTemplate->getIcon($mixedValue, array('class' => 'bx-item-icon'));
+        return parent::_getCellDefault($mixedValue, $sKey, $aField, $aRow);
+    }
+
+    protected function _getCellTitleSystem ($mixedValue, $sKey, $aField, $aRow)
+    {
+        if(empty($mixedValue))
+            $mixedValue = _t($aRow['title']);
+
+        if((int)$aRow['active_api'] != 0)
+            $mixedValue .= ' ' . $this->_oTemplate->parseImage($this->_oTemplate->getIconUrl('api.svg'), [
+                'class' => 'bx-std-api',
+                'title' => bx_html_attribute(_t('_adm_txt_active_api'))
+            ]);
+
+        return parent::_getCellDefault($mixedValue, $sKey, $aField, $aRow);
+    }
+
+    protected function _getCellLink ($mixedValue, $sKey, $aField, $aRow)
+    {
+        $aSubitems = array();
+        $this->oDb->getItems(array('type' => 'by_parent_id', 'value' => $aRow['id']), $aSubitems, false);
+
+        if(!empty($aSubitems) && is_array($aSubitems)) {
+            $sPrefix = _t('_adm_nav_txt_items_gl_link_subitems');
+
+            $aField['chars_limit'] -= strlen($sPrefix);
+
+            $aTitles = array();
+            foreach($aSubitems as $aSubitem)
+                $aTitles[] = _t(!empty($aSubitem['title_system']) ? $aSubitem['title_system'] : $aSubitem['title']);
+
+            $aValue = $this->_limitMaxLength(implode(', ', $aTitles), $sKey, $aField, $aRow, $this->_isDisplayPopupOnTextOverflow, false);
+
+            $mixedValue = $sPrefix . ' ' . $aValue[0] . (isset($aValue[1]) ? $aValue[1] : '');
+        }
+        else if($aRow['submenu_object'] != "") {
+            $aMenu = array();
+            $this->oDb->getMenus(array('type' => 'by_object', 'value' => $aRow['submenu_object']), $aMenu, false);
+
+            $sPrefix = _t('_adm_nav_txt_items_gl_link_menu');
+            if(!empty($aMenu) && is_array($aMenu)) {
+                $aField['chars_limit'] -= strlen($sPrefix);
+
+                $aValue = $this->_limitMaxLength(_t($aMenu['title']), $sKey, $aField, $aRow, $this->_isDisplayPopupOnTextOverflow, false);
+
+                $sLink = sprintf($this->sUrlViewItems, $aMenu['module'], $aMenu['set_name']);
+                $mixedValue = $sPrefix . ' ' . $this->_oTemplate->parseLink($sLink, $aValue[0], array(
+                    'title' => _t('_adm_nav_txt_manage_items') 
+                )) . (isset($aValue[1]) ? $aValue[1] : '');
+            }
+            else 
+                $mixedValue = $sPrefix . ' ' . _t('_undefined');
+        } 
+        else if($aRow['submenu_object'] == "" && $aRow['onclick'] != "")
+            $mixedValue = $this->_limitMaxLength(_t('_adm_nav_txt_items_gl_link_custom'), $sKey, $aField, $aRow, $this->_isDisplayPopupOnTextOverflow);
+        else
+            $mixedValue = $this->_limitMaxLength(BxDolPermalinks::getInstance()->permalink($aRow['link']), $sKey, $aField, $aRow, $this->_isDisplayPopupOnTextOverflow);
+
+        return parent::_getCellDefault($mixedValue, $sKey, $aField, $aRow);
+    }
+
+    protected function _getCellModule($mixedValue, $sKey, $aField, $aRow)
+    {
+        $mixedValue = $this->_limitMaxLength($this->getModuleTitle($aRow['module']), $sKey, $aField, $aRow, $this->_isDisplayPopupOnTextOverflow);
+        return parent::_getCellDefault($mixedValue, $sKey, $aField, $aRow);
+    }
+
+    protected function _getCellVisibleForLevels ($mixedValue, $sKey, $aField, $aRow)
+    {
+        if(!$this->_isEditable($aRow))
+            return parent::_getCellDefault('', $sKey, $aField, $aRow);
+
+        $mixedValue = $this->_oTemplate->parseLink('javascript:void(0)', BxDolStudioUtils::getVisibilityTitle($aRow['visible_for_levels']), array(
+            'title' => _t('_adm_nav_txt_manage_visibility'),
+        	'bx_grid_action_single' => 'show_to',
+        	'bx_grid_action_data' => $aRow['id']
+        ));
+
+        return parent::_getCellDefault ($mixedValue, $sKey, $aField, $aRow);
+    }
+
+    protected function _getActionImport ($sType, $sKey, $a, $isSmall = false, $isDisabled = false, $aRow = array())
+    {
+        if($this->sSet == '')
+            $isDisabled = true;
+
+        return  parent::_getActionDefault($sType, $sKey, $a, false, $isDisabled, $aRow);
+    }
+
+    protected function _getActionAdd ($sType, $sKey, $a, $isSmall = false, $isDisabled = false, $aRow = array())
+    {
+        if($this->sSet == '')
+            $isDisabled = true;
+
+        return  parent::_getActionDefault($sType, $sKey, $a, false, $isDisabled, $aRow);
+    }
+
+	protected function _getActionEdit ($sType, $sKey, $a, $isSmall = false, $isDisabled = false, $aRow = array())
+    {
+        if($sType == 'single' && !$this->_isEditable($aRow))
+            return '';
+
+        return  parent::_getActionDefault($sType, $sKey, $a, false, $isDisabled, $aRow);
+    }
+
+    protected function _getActionDelete ($sType, $sKey, $a, $isSmall = false, $isDisabled = false, $aRow = array())
+    {
+        if ($sType == 'single'  && !$this->_isDeletable($aRow))
+            return '';
+
+        return  parent::_getActionDefault($sType, $sKey, $a, false, $isDisabled, $aRow);
+    }
+
+    protected function _getActionShowTo ($sType, $sKey, $a, $isSmall = false, $isDisabled = false, $aRow = array())
+    {
+        return '';
+    }
+
+    protected function _getActionDeleteIcon ($sType, $sKey, $a, $isSmall = false, $isDisabled = false, $aRow = array())
+    {
+        return '';
+    }
+
+    protected function _getActionsDisabledBehavior($aRow)
+    {
+        return false;
+    }
+
+    protected function _getFilterControls ()
+    {
+        parent::_getFilterControls();
+
+        $sContent = $this->getModulesSelectOne('getItems') . $this->getSetsSelector($this->sModule);
+
+        $oForm = new BxTemplStudioFormView(array());
+
+        $aInputSearch = array(
+            'type' => 'text',
+            'name' => 'keyword',
+            'attrs' => array(
+                'id' => 'bx-grid-search-' . $this->_sObject,
+            ),
+            'tr_attrs' => array(
+                'style' => 'display:none;'
+            )
+        );
+        $sContent .= $oForm->genRow($aInputSearch);
+
+        return $sContent;
+    }
+
+    protected function _getFormObject($sAction, $aItem = array())
+    {
+        bx_import('BxTemplStudioFormView');
+
+        $aForm = array(
+            'form_attrs' => array(
+                'id' => 'adm-nav-item-',
+                'action' => BX_DOL_URL_ROOT . 'grid.php?o=' . $this->_sObject . '&a=' . $sAction . '&set=' . $this->sSet,
+                'method' => BX_DOL_STUDIO_METHOD_DEFAULT,
+                'enctype' => 'multipart/form-data',
+            ),
+            'params' => array (
+                'db' => array(
+                    'table' => 'sys_menu_items',
+                    'key' => 'id',
+                    'uri' => '',
+                    'uri_title' => '',
+                    'submit_name' => 'do_submit'
+                ),
+            ),
+            'inputs' => array (
+                'id' => array(
+                    'type' => 'hidden',
+                    'name' => 'id',
+                    'value' => isset($aItem['id']) ? (int)$aItem['id'] : 0,
+                    'db' => array (
+                        'pass' => 'Int',
+                    ),
+                ),
+                'set_name' => array(
+                    'type' => 'hidden',
+                    'name' => 'set_name',
+                    'value' => $this->sSet,
+                    'db' => array (
+                        'pass' => 'Xss',
+                    ),
+                ),
+                'title_system' => array(
+                    'type' => 'text_translatable',
+                    'name' => 'title_system',
+                    'caption' => _t('_adm_nav_txt_items_title_system'),
+                    'info' => _t('_adm_nav_dsc_items_title_system'),
+                    'value' => isset($aItem['title_system']) ? $aItem['title_system'] : '_adm_nav_txt_item',
+                    'required' => '0',
+                    'db' => array (
+                        'pass' => 'Xss',
+                    ),
+                ),
+                'title' => array(
+                    'type' => 'text_translatable',
+                    'name' => 'title',
+                    'caption' => _t('_adm_nav_txt_items_title'),
+                    'info' => _t('_adm_nav_dsc_items_title'),
+                    'value' => isset($aItem['title']) ? $aItem['title'] : '_adm_nav_txt_item',
+                    'required' => '0',
+                    'db' => array (
+                        'pass' => 'Xss',
+                    ),
+                ),
+                'parent_id' => array(
+                    'type' => 'select',
+                    'name' => 'parent_id',
+                    'caption' => _t('_adm_nav_txt_items_parent_id'),
+                    'info' => _t('_adm_nav_dsc_items_parent_id'),
+                    'value' => isset($aItem['parent_id']) ? $aItem['parent_id'] : '',
+                    'values' => array(
+                        0 => _t('_adm_nav_txt_items_parent_id_empty')
+                    ),
+                    'required' => '0',
+                    'attrs' => array(),
+                    'db' => array (
+                        'pass' => 'Int',
+                    ),
+                ),
+                'submenu_object' => array(
+                    'type' => 'select',
+                    'name' => 'submenu_object',
+                    'caption' => _t('_adm_nav_txt_items_submenu'),
+                    'info' => _t('_adm_nav_dsc_items_submenu'),
+                    'value' => isset($aItem['submenu_object']) ? $aItem['submenu_object'] : '',
+                    'values' => array(),
+                    'required' => '0',
+                    'attrs' => array(
+                        'onChange' => 'javascript:' . $this->getJsObject() . '.onChangeSubmenu(this)'
+                    ),
+                    'db' => array (
+                        'pass' => 'Xss',
+                    ),
+                ),
+                'submenu_popup' => array(
+                    'type' => 'switcher',
+                    'name' => 'submenu_popup',
+                    'caption' => _t('_adm_nav_txt_items_submenu_popup'),
+                    'info' => '',
+                    'value' => '1',
+                    'checked' => isset($aItem['submenu_popup']) && $aItem['submenu_popup'] == '1',
+                    'attrs' => array(
+                        'onChange' => 'javascript:' . $this->getJsObject() . '.onChangeSubmenu(this)'
+                    ),
+                    'db' => array (
+                        'pass' => 'Int',
+                    )
+                ),
+                'link' => array(
+                    'type' => 'text',
+                    'name' => 'link',
+                    'caption' => _t('_adm_nav_txt_items_link'),
+                    'info' => _t('_adm_nav_dsc_items_link'),
+                    'value' => isset($aItem['link']) ? $aItem['link'] : '',
+                    'required' => '0',
+                    'db' => array (
+                        'pass' => 'Xss',
+                    ),
+                    'checker' => array (
+                        'func' => '',
+                        'params' => array(),
+                        'error' => _t('_adm_nav_err_items_link'),
+                    ),
+                ),
+                'target' => array(
+                    'type' => 'select',
+                    'name' => 'target',
+                    'caption' => _t('_adm_nav_txt_items_target'),
+                    'info' => _t('_adm_nav_dsc_items_target'),
+                    'value' => isset($aItem['target']) ? $aItem['target'] : '_self',
+                    'values' => array(
+                        array('key' => '', 'value' => _t('_adm_nav_txt_items_target_self')),
+                        array('key' => '_blank', 'value' => _t('_adm_nav_txt_items_target_blank'))
+                    ),
+                    'required' => '0',
+                    'db' => array (
+                        'pass' => 'Xss',
+                    )
+                ),
+                'onclick' => array(
+                    'type' => 'text',
+                    'name' => 'onclick',
+                	'caption' => _t('_adm_nav_txt_items_onclick'),
+                	'info' => _t('_adm_nav_dsc_items_onclick'),
+                    'value' => isset($aItem['onclick']) ? $aItem['onclick'] : '',
+                	'required' => '0',
+                    'db' => array (
+                        'pass' => 'Xss',
+                    ),
+                    'checker' => array (
+                        'func' => '',
+                        'params' => array(),
+                        'error' => _t('_adm_nav_err_items_onclick'),
+                    ),
+                ),
+                'hidden_on' => array(
+                    'type' => 'select_multiple',
+                    'name' => 'hidden_on',
+                    'caption' => _t('_adm_nav_txt_block_hidden_on'),
+                    'info' => '',
+                    'value' => isset($aItem['hidden_on']) ? (int)$aItem['hidden_on'] : '',
+                    'values' => array(
+                        BX_DB_HIDDEN_PHONE => _t('_adm_nav_txt_block_hidden_on_phone'),
+                        BX_DB_HIDDEN_TABLET => _t('_adm_nav_txt_block_hidden_on_tablet'),
+                        BX_DB_HIDDEN_DESKTOP => _t('_adm_nav_txt_block_hidden_on_desktop'),
+                        BX_DB_HIDDEN_MOBILE => _t('_adm_nav_txt_block_hidden_on_mobile')
+                    ),
+                    'db' => array (
+                        'pass' => 'Set',
+                    )
+                ),
+                'hidden_on_pt' => array(
+                    'type' => 'select_multiple',
+                    'name' => 'hidden_on_pt',
+                    'caption' => _t('_adm_nav_txt_block_hidden_on_pt'),
+                    'info' => '',
+                    'value' => isset($aItem['hidden_on_pt']) ? (int)$aItem['hidden_on_pt'] : '',
+                    'values' => array(),
+                    'db' => array (
+                        'pass' => 'Set',
+                    )
+                ),
+                'hidden_on_col' => array(
+                    'type' => 'select_multiple',
+                    'name' => 'hidden_on_col',
+                    'caption' => _t('_adm_nav_txt_block_hidden_on_col'),
+                    'info' => '',
+                    'value' => isset($aItem['hidden_on_col']) ? (int)$aItem['hidden_on_col'] : '',
+                    'values' => array(),
+                    'db' => array (
+                        'pass' => 'Set',
+                    )
+                ),
+                'icon' => array(
+                    'type' => 'textarea',
+                    'name' => 'icon',
+                    'caption' => _t('_adm_nav_txt_items_icon'),
+                    'info' => _t('_adm_nav_dsc_items_icon'),
+                    'value' => '',
+					'code' => 1,
+                    'required' => '0',
+                    'db' => array (
+                        'pass' => 'Xss',
+                    ),
+                    'checker' => array (
+                        'func' => '',
+                        'params' => array(),
+                        'error' => _t('_adm_nav_err_items_icon'),
+                    ),
+					'attrs' => array('class' => 'bx-form-input-textarea-small'),
+                ),
+                'icon_image' => array(
+                    'type' => 'file',
+                    'name' => 'icon_image',
+                    'caption' => _t('_adm_nav_txt_items_icon_image'),
+                    'info' => _t('_adm_nav_dsc_items_icon_image'),
+                    'value' => '',
+                    'checker' => array (
+                        'func' => '',
+                        'params' => '',
+                        'error' => _t('_adm_nav_err_items_icon_image'),
+                    ),
+                ),
+                'icon_preview' => array(
+                    'type' => 'custom',
+                    'name' => 'icon_preview',
+                    'caption' => _t('_adm_nav_txt_items_icon_image_old'),
+                    'content' => ''
+                ),
+                'primary' => array(
+                    'type' => 'switcher',
+                    'name' => 'primary',
+                    'caption' => _t('_adm_nav_txt_items_primary'),
+                    'info' => '',
+                    'value' => '1',
+                    'checked' => isset($aItem['primary']) && (int)$aItem['primary'] == 1,
+                    'db' => array (
+                        'pass' => 'Int',
+                    )
+                ),
+                'collapsed' => array(
+                    'type' => 'switcher',
+                    'name' => 'collapsed',
+                    'caption' => _t('_adm_nav_txt_items_collapsed'),
+                    'info' => '',
+                    'value' => '1',
+                    'checked' => isset($aItem['collapsed']) && (int)$aItem['collapsed'] == 1,
+                    'db' => array (
+                        'pass' => 'Int',
+                    )
+                ),
+                'active_api' => array(
+                    'type' => 'switcher',
+                    'name' => 'active_api',
+                    'caption' => _t('_adm_nav_txt_items_active_api'),
+                    'info' => '',
+                    'value' => '1',
+                    'checked' => isset($aItem['active_api']) && (int)$aItem['active_api'] == 1,
+                    'db' => array (
+                        'pass' => 'Int',
+                    )
+                ),
+                'controls' => array(
+                    'name' => 'controls',
+                    'type' => 'input_set',
+                    array(
+                        'type' => 'submit',
+                        'name' => 'do_submit',
+                        'value' => _t('_adm_nav_btn_items_add'),
+                    ),
+                    array (
+                        'type' => 'reset',
+                        'name' => 'close',
+                        'value' => _t('_adm_nav_btn_items_cancel'),
+                        'attrs' => array(
+                            'onclick' => "$('.bx-popup-applied:visible').dolPopupHide()",
+                            'class' => 'bx-def-margin-sec-left',
+                        ),
+                    )
+                )
+            )
+        );
+        
+        $aSetItems = array();
+        $this->oDb->getItems(array('type' => 'by_set_name', 'value' => $this->sSet), $aSetItems, false);
+        foreach($aSetItems as $aSetItem)
+            $aForm['inputs']['parent_id']['values'][$aSetItem['id']] = _t(!empty($aSetItem['title_system']) ? $aSetItem['title_system'] : $aSetItem['title']);
+
+        $aMenus = array();
+        $this->oDb->getMenus(array('type' => 'all'), $aMenus, false);
+        foreach($aMenus as $aMenu)
+            $aForm['inputs']['submenu_object']['values'][$aMenu['object']] = _t($aMenu['title']);
+
+        asort($aForm['inputs']['submenu_object']['values']);
+        $aForm['inputs']['submenu_object']['values'] = array_merge(array('' => _t('_adm_nav_txt_items_submenu_empty')), $aForm['inputs']['submenu_object']['values']);
+
+        $aPageTypes = BxDolPageQuery::getPageTypes();
+        foreach($aPageTypes as $aPageType) {
+            $iPageType = (int)$aPageType['id'];
+            if($iPageType == 1)
+                continue;
+
+            $aForm['inputs']['hidden_on_pt']['values'][$iPageType - 1] = _t($aPageType['title']);
+        }
+        
+        $aForm['inputs']['hidden_on_col']['values'][1] = _t('_adm_nav_txt_block_hidden_on_col_thin');
+        $aForm['inputs']['hidden_on_col']['values'][2] = _t('_adm_nav_txt_block_hidden_on_col_half');
+        $aForm['inputs']['hidden_on_col']['values'][3] = _t('_adm_nav_txt_block_hidden_on_col_wide');
+        $aForm['inputs']['hidden_on_col']['values'][4] = _t('_adm_nav_txt_block_hidden_on_col_full');
+
+        switch($sAction) {
+            case 'add':
+                unset($aForm['inputs']['id']);
+                unset($aForm['inputs']['icon_preview']);
+
+                $aForm['form_attrs']['id'] .= 'create';
+                break;
+
+            case 'edit':
+                unset($aForm['inputs']['set_name']);
+
+                $aForm['form_attrs']['id'] .= 'edit';
+                $aForm['inputs']['icon_image']['caption'] = _t('_adm_nav_txt_items_icon_image_new');
+                $aForm['inputs']['controls'][0]['value'] = _t('_adm_nav_btn_items_save');
+
+                $bSubmenu = !empty($aItem['submenu_object']);
+                if($bSubmenu !== true)
+                	$aForm['inputs']['submenu_popup']['tr_attrs']['style'] = 'display:none;';
+
+                if(!$bSubmenu && ($aItem['onclick'] != "" || !in_array($aItem['target'], array('', '_blank')))) {
+                    $aForm['inputs']['submenu_object']['tr_attrs']['style'] = 'display:none;';
+                    $aForm['inputs']['link']['tr_attrs']['style'] = 'display:none;';
+                    $aForm['inputs']['target']['tr_attrs']['style'] = 'display:none;';
+                }
+
+                $sIconImage = $sIconFont = "";
+                if(!empty($aItem['icon'])) {
+                    if(is_numeric($aItem['icon']) && (int)$aItem['icon'] != 0) {
+                        $oStorage = BxDolStorage::getObjectInstance(BX_DOL_STORAGE_OBJ_IMAGES);
+
+                        $sIconImage = $oStorage->getFileUrlById((int)$aItem['icon']);
+                    }
+                    else {
+                        $sIconFont = $aItem['icon'];
+                        $aForm['inputs']['icon']['value'] = $sIconFont;
+                    }
+                }
+
+                $aForm['inputs']['icon_preview']['content'] = $this->_getIconPreview($aItem['id'], $sIconImage, $sIconFont);
+                break;
+        }
+
+        return  new BxTemplStudioFormView($aForm);
+    }
+
+    protected function _getIconPreview($iId, $sIconImage = '', $sIcon = '')
+    {
+        $bIconImage = !empty($sIconImage);
+		
+        $aIcons = BxTemplFunctions::getInstanceWithTemplate($this->_oTemplate)->getIcon($sIcon);
+        $sIconHtml = $aIcons[2] . $aIcons[3] . $aIcons[4];
+		$bIconHtml = !empty($sIconHtml) && !$bIconImage;
+		
+        return $this->_oTemplate->parseHtmlByName('item_icon_preview.html', array(
+            'id' => $iId,
+            'bx_if:show_icon_empty' => array(
+                'condition' => !$bIconImage && !$bIconHtml,
+                'content' => array()
+            ),
+            'bx_if:show_icon_image' => array(
+                'condition' => $bIconImage,
+                'content' => array(
+                    'js_object' => $this->getJsObject(),
+                    'url' => $sIconImage,
+                    'id' => $iId
+                )
+            ),
+            'bx_if:show_icon_html' => array(
+                'condition' => $bIconHtml,
+                'content' => array(
+                    'icon' => $sIconHtml
+                )
+            )
+        ));
+    }
+
+    protected function _isEditable(&$aRow)
+    {
+    	return (int)$aRow['editable'] != 0;
+    }
+
+	protected function _isDeletable(&$aRow)
+    {
+    	return $aRow['module'] != BX_DOL_STUDIO_MODULE_SYSTEM;
+    }
+}
+
+/** @} */

+ 1614 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/template/scripts/BxBaseCmts.php

@@ -0,0 +1,1614 @@
+<?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
+ * @{
+ */
+
+/**
+ * @see BxDolCmts
+ */
+class BxBaseCmts extends BxDolCmts
+{
+    protected static $_sTmplContentElementBlock;
+    protected static $_sTmplContentElementInline;
+    protected static $_sTmplContentDoCommentLabel;
+    protected static $_sTmplContentCounter;
+
+    protected $_sTmplNameItem;
+    protected $_sTmplNameItemContent;
+
+    protected $_sJsObjClass;
+    protected $_sJsObjName;
+    protected $_sStylePrefix;
+
+    protected $_aHtmlIds;
+
+    protected $_aElementDefaults;
+    
+    protected $_aAclId2Name;
+
+    function __construct( $sSystem, $iId, $iInit = true, $oTemplate = false)
+    {
+        parent::__construct($sSystem, $iId, $iInit, $oTemplate);
+        if (empty($sSystem))
+            return;
+
+        $this->_sTmplNameItem = 'comment.html';
+        $this->_sTmplNameItemContent = 'comment_content.html';
+
+        $this->_sJsObjClass = 'BxDolCmts';
+        $this->_sJsObjName = 'oCmts' . bx_gen_method_name($sSystem, array('_' , '-')) . '_' .str_replace('-', 'n', $iId);
+        $this->_sStylePrefix = isset($this->_aSystem['root_style_prefix']) ? $this->_aSystem['root_style_prefix'] : 'cmt';
+
+        $sHtmlId = str_replace(array('_' , ' '), array('-', '-'), $sSystem) . '-' . $iId;
+
+        $this->_aHtmlIds = array(
+            'main' => 'bx-cmt-' . $sHtmlId,
+            'counter' => 'bx-cmt-counter-' . $sHtmlId
+        );
+
+        $this->_aElementDefaults = array(
+            'show_do_comment_as_button' => false,
+            'show_do_comment_as_button_small' => false,
+            'show_do_comment_icon' => true,
+            'show_do_comment_label' => false,
+            'show_counter' => true,
+            'show_counter_only' => true,
+            'show_counter_empty' => false,
+            'show_counter_reversed' => false,
+            'recalculate_counter' => false
+        );
+
+        $this->_aAclId2Name = array();
+        $aAclLevels = BxDolAcl::getInstance()->getMemberships(false, false, false);
+        foreach($aAclLevels as $iAclId => $sAclName)
+            $this->_aAclId2Name[$iAclId] = str_replace('_', '-', str_replace('_adm_prm_txt_level_', '', $sAclName));
+
+        if(empty(self::$_sTmplContentElementBlock))
+            self::$_sTmplContentElementBlock = $this->_oTemplate->getHtml('comment_element_block.html');
+
+        if(empty(self::$_sTmplContentElementInline))
+            self::$_sTmplContentElementInline = $this->_oTemplate->getHtml('comment_element_inline.html');
+
+        if(empty(self::$_sTmplContentDoCommentLabel))
+            self::$_sTmplContentDoCommentLabel = $this->_oTemplate->getHtml('comment_do_comment_label.html');
+
+        if(empty(self::$_sTmplContentCounter))
+            self::$_sTmplContentCounter = $this->_oTemplate->getHtml('comment_counter.html');
+
+        $this->_oTemplate->addJsTranslation('_sys_txt_cmt_loading');
+    }
+
+    /**
+     * Add comments CSS/JS
+     */
+    public function addCssJs ()
+    {
+        $oForm = BxDolForm::getObjectInstance($this->_sFormObject, $this->_sFormDisplayPost);
+        $oForm->addCssJs();
+
+        $this->_oTemplate->addCss(array(
+            BX_DIRECTORY_PATH_PLUGINS_PUBLIC . 'photo-swipe/|photoswipe.css',
+            BX_DIRECTORY_PATH_PLUGINS_PUBLIC . 'photo-swipe/default-skin/|default-skin.css',
+        ));
+
+        $this->_oTemplate->addJs(array(
+            'photo-swipe/photoswipe.min.js',
+            'photo-swipe/photoswipe-ui-default.min.js',
+        ));
+    }
+
+    public function getJsObjectName()
+    {
+        return $this->_sJsObjName;
+    }
+
+    /**
+     * Get initialization section of comments box
+     *
+     * @return string
+     */
+    public function getJsScript($aBp = array(), $aDp = array())
+    {
+        $bMinPostForm = isset($aDp['min_post_form']) ? $aDp['min_post_form'] : $this->_bMinPostForm;
+
+        $aParams = array(
+            'sObjName' => $this->_sJsObjName,
+            'sRootUrl' => BX_DOL_URL_ROOT,
+            'sSystem' => $this->getSystemName(),
+            'iAuthorId' => $this->_getAuthorId(),
+            'iObjId' => $this->getId(),
+            'sBaseUrl' => $this->getBaseUrl(),
+            'sPostFormPosition' => $this->_aSystem['post_form_position'],
+            'sBrowseType' => $this->_sBrowseType,
+            'sDisplayType' => $this->_sDisplayType,
+            'iDisplayStructure' => isset($aDp['structure']) && !empty($aDp['structure']) ? 1 : 0,
+            'iMinPostForm' => $bMinPostForm ? 1 : 0,
+            'sStylePrefix' => $this->_sStylePrefix,
+            'aHtmlIds' => $this->_aHtmlIds,
+        );
+
+        $this->addCssJs();
+        return $this->_oTemplate->_wrapInTagJsCode("if(window['" . $this->_sJsObjName . "'] == undefined) var " . $this->_sJsObjName . " = new " . $this->_sJsObjClass . "(" . json_encode($aParams) . "); " . $this->_sJsObjName . ".cmtInit();");
+    }
+
+    function getCommentsBlockAPI($aParams, $aBp = [], $aDp = ['in_designbox' => false, 'show_empty' => false])
+    {
+
+        $mixedResult = $this->isViewAllowed();
+        if($mixedResult !== CHECK_ACTION_RESULT_ALLOWED)
+            return $mixedResult; // TODO: error checking
+
+        $aBp['type'] = 'head';
+        $this->_getParams($aBp, $aDp);
+        $this->_prepareParams($aBp, $aDp);
+        
+        $aBp['order']['way'] = 'desc';
+        $aBp['order_way'] =  'desc';
+        $aBp['start'] = 0 ; 
+        $aPp = $aBp['per_view'];
+        $aBp['per_view'] =  $aBp['per_view'] + 1; 
+
+        $aCmts = [];
+
+        if (!isset($aParams['comment_id']))
+            $aCmts = $this->getCommentsArray($aBp['vparent_id'], $aBp['filter'], $aBp['order'], $aBp['start'], $aBp[($aBp['init_view'] != -1 ? 'init' : 'per') . '_view']);
+        else
+            $aCmts = [['cmt_id' => $aParams['comment_id']]];
+        
+        $aParams['start_from'] = 0;
+        if (count($aCmts) == $aBp['per_view']){
+            $aBp['per_view'] = $aPp;
+            $aCmts = array_slice($aCmts, 0, $aBp['per_view']); 
+            $aParams['start_from'] = $aBp['start'] + $aBp['per_view'];
+        }
+            
+        $aCmtsRv = [];
+        foreach ($aCmts as $aCmt) {
+            $aCmtsRv[] = $this->getCommentStructure($aCmt['cmt_id'], $aBp, $aDp);
+        }
+        return [
+            'unit' => 'comments',
+            'start' => 0,
+            'order' => 'a',
+            'module' => $this->_sSystem, 
+            'object_id' => $this->_iId,
+            'data' => $aCmtsRv,
+        ];
+        
+    }
+
+    /**
+     * get full comments block with initializations
+     */
+    function getCommentsBlock($aBp = [], $aDp = [])
+    {
+        $mixedResult = $this->isViewAllowed();
+        if($mixedResult !== CHECK_ACTION_RESULT_ALLOWED)
+            return $mixedResult;
+
+        $this->_getParams($aBp, $aDp);
+
+        //add live update
+        $this->actionResumeLiveUpdate();
+
+        if(($oLiveUpdates = BxDolLiveUpdates::getInstance())!== false) {
+            $sServiceCall = BxDolService::getSerializedService('system', 'get_live_update', [$this->_sSystem, $this->_iId, $this->_getAuthorId(), '{count}'], 'TemplCmtsServices');
+            $oLiveUpdates->add($this->_sSystem . '_live_updates_cmts_' . $this->_iId, 1, $sServiceCall);
+        }
+        //add live update
+
+        $sComments = $this->getComments($aBp, $aDp);
+        $sCommentsPinned = $this->getCommentsPinned(array_merge($aBp, ['pinned' => 1]), $aDp);
+        $sContentBefore = $this->_getContentBefore($aBp, $aDp);
+        $sContentAfter = $this->_getContentAfter($aBp, $aDp);
+        $sPostFormTop = $this->getFormBoxPost($aBp, array_merge($aDp, ['type' => $this->_sDisplayType, 'position' => BX_CMT_PFP_TOP]));
+        $sPostFormBottom = $this->getFormBoxPost($aBp, array_merge($aDp, ['type' => $this->_sDisplayType, 'position' => BX_CMT_PFP_BOTTOM]));
+        $sJsContent = $this->getJsScript($aBp, $aDp);
+
+        $sBlockTitle = _t($this->_aT['block_comments_title'], $this->getCommentsCountAll(0, true));
+        $sBlockMenu = $this->_getControlsBox();
+
+        bx_alert('system', 'view_comments', 0, 0, [
+            'object' => $this,
+            'system' => $this->_sSystem,
+            'id' => $this->getId(),
+            'params_browse' => $aBp,
+            'params_display' => $aDp,
+            'post_form_top' => &$sPostFormTop,
+            'content_before' => &$sContentBefore,
+            'comments' => &$sComments,
+            'comments_pinned' => &$sCommentsPinned,
+            'content_after' => &$sContentAfter,
+            'post_form_bottom'  => &$sPostFormBottom,
+            'js_content' => &$sJsContent,
+            'block_title' => &$sBlockTitle,
+            'block_menu' => &$sBlockMenu,
+        ]);
+
+        $sContent = $this->_oTemplate->parseHtmlByName('comments_block.html', [
+            'system' => $this->_sSystem,
+            'list_anchor' => $this->getListAnchor(),
+            'id' => $this->getId(),
+            'content_before' => $sContentBefore,
+            'comments' => $sComments,
+            'comments_pinned' => $sCommentsPinned,
+            'bx_if:show_divider_hidden' => [
+                'condition' => empty($sCommentsPinned),
+                'content' => []
+            ],
+            'content_after' => $sContentAfter,
+            'post_form_top' => $sPostFormTop,
+            'post_form_bottom'  => $sPostFormBottom,
+            'image_preview' => $this->_oTemplate->parseHtmlByName('comments_photoswipe.html', []),
+            'script' => $sJsContent
+        ]);
+
+        return $aDp['in_designbox'] ? DesignBoxContent($sBlockTitle, $sContent, BX_DB_DEF, $sBlockMenu) : [
+            'title' => $sBlockTitle,
+            'content' => $sContent,
+            'menu' => $sBlockMenu,
+        ];
+    }
+
+    /**
+     * get comments list for specified parent comment
+     *
+     * @param array $aBp - browse params array
+     * @param array $aDp - display params array
+     *
+     */
+    function getComments($aBp = [], $aDp = [])
+    {
+        $this->_prepareParams($aBp, $aDp);
+
+        $aCmts = $this->getCommentsArray($aBp['vparent_id'], $aBp['filter'], $aBp['order'], $aBp['start'], $aBp[($aBp['init_view'] != -1 ? 'init' : 'per') . '_view']);
+        if(empty($aCmts) || !is_array($aCmts))
+            return isset($aDp['show_empty']) && $aDp['show_empty'] === true ? $this->_getEmpty($aDp) : '';
+
+        $sCmts = '';
+        foreach($aCmts as $k => $aCmt)
+            $sCmts .= $this->getComment($aCmt, $aBp, $aDp);
+        $sCmts = $this->_getMoreLink($sCmts, $aBp, $aDp);       
+
+        return $sCmts;
+    }
+
+    function getCommentsPinned($aBp = [], $aDp = [])
+    {
+        $this->_prepareParams($aBp, $aDp);
+
+        $aCmts = $this->getCommentsArray($aBp['vparent_id'], BX_CMT_FILTER_PINNED, $aBp['order'], 0, -1);
+        if(empty($aCmts) || !is_array($aCmts))
+            return '';
+
+        $sCmts = '';
+        foreach($aCmts as $k => $aCmt)
+            $sCmts .= $this->getComment($aCmt, $aBp, $aDp);
+
+        return $sCmts;
+    }
+
+    public function getCommentsByStructure($aBp = array(), $aDp = array())
+    {
+        if(empty($aDp['structure']))
+            return isset($aDp['show_empty']) && $aDp['show_empty'] === true ? $this->_getEmpty($aDp) : '';
+
+        $aBp['count'] = count($aDp['structure']);
+        $this->_prepareParams($aBp, $aDp);
+        $aDp['structure'] = array_slice($aDp['structure'], $aBp['start'], $aBp[($aBp['init_view'] != -1 ? 'init' : 'per') . '_view'], true);
+
+        $sCmts = '';
+        foreach($aDp['structure'] as $iCmtId => $aCmt)
+            $sCmts .= $this->getComment($iCmtId, $aBp, $aDp);
+
+        $sCmts = $this->_getMoreLink($sCmts, $aBp, $aDp);
+        return $sCmts;
+    }    
+
+    /**
+     * get comment view block with initializations
+     */
+    function getCommentBlock($iCmtId = 0, $aBp = array(), $aDp = array())
+    {
+        $mixedResult = $this->isViewAllowed();
+        if($mixedResult !== CHECK_ACTION_RESULT_ALLOWED)
+            return $mixedResult;
+
+        $aBp = array_merge(array('type' => $this->_sBrowseType), $aBp);
+        $aDp = array_merge(array('type' => BX_CMT_DISPLAY_THREADED), $aDp);
+
+        //--- Beg: Using pregenerated structure
+        $mixedStructure = $this->getCommentStructure((int)$iCmtId, $aBp, $aDp);
+        if($mixedStructure !== false)
+            $aDp['structure'] = $mixedStructure;
+        //--- End: Using pregenerated structure
+
+        $sComment = $this->getComment($iCmtId, $aBp, $aDp);
+        if (!$sComment)
+            return '';
+
+        BxDolTemplate::getInstance()->addInjection ('injection_footer', 'text', $this->_oTemplate->parseHtmlByName('comments_photoswipe.html', []));
+
+        return $this->_oTemplate->parseHtmlByName('comment_block.html', array(
+            'system' => $this->_sSystem,
+            'id' => $this->getId(),
+            'comment' => $sComment,
+            'script' => $this->getJsScript($aBp, $aDp)
+        ));
+    }
+    
+    /**
+     * get one just posted comment
+     *
+     * @param  int    $iCmtId - comment id
+     * @return string
+     */
+    function getComment($mixedCmt, $aBp = array(), $aDp = array())
+    {
+        $iUserId = $this->_getAuthorId();
+        $aCmt = !is_array($mixedCmt) ? $this->getCommentRow((int)$mixedCmt) : $mixedCmt;
+        if (!$aCmt)
+            return '';
+
+        list($sAuthorName, $sAuthorLink, $sAuthorIcon) = $this->_getAuthorInfo($aCmt['cmt_author_id']);
+
+        $sClass = $sClassCnt = '';
+        if(isset($aCmt['vote_rate']) && (float)$aCmt['vote_rate'] < $this->_aSystem['viewing_threshold']) {
+            $this->_oTemplate->pareseHtmlByName('comment_hidden.html', array(
+                'js_object' => $this->_sJsObjName,
+                'id' => $aCmt['cmt_id'],
+                'title' => bx_process_output(_t('_hidden_comment', $sAuthorName)),
+                'bx_if:show_replies' => array(
+                    'condition' => $aCmt['cmt_replies'] > 0,
+                    'content' => array(
+                        'replies' => _t('_Show N replies', $aCmt['cmt_replies'])
+                    )
+                )
+            ));
+
+            $sClass = ' ' . $this->_sStylePrefix . '-hidden';
+        }
+
+        if($aCmt['cmt_author_id'] == $iUserId)
+            $sClass .= ' ' . $this->_sStylePrefix . '-mine';
+
+        $aAuthorAcl = BxDolAcl::getInstance()->getMemberMembershipInfo($aCmt['cmt_author_id']);
+        if(!empty($aAuthorAcl) && isset($this->_aAclId2Name[$aAuthorAcl['id']]))
+            $sClass .= ' ' . $this->_sStylePrefix . '-aml-' . $this->_aAclId2Name[$aAuthorAcl['id']];
+
+        if(!empty($aDp['blink']) && in_array($aCmt['cmt_id'], $aDp['blink']))
+            $sClass .= ' ' . $this->_sStylePrefix . '-blink';
+
+        if(!empty($aDp['class_comment']))
+            $sClass .= ' ' . $aDp['class_comment'];
+
+        if(!empty($aDp['class_comment_content']))
+            $sClassCnt .= ' ' . $aDp['class_comment_content'];
+
+        $sActions = $this->_getActionsBox($aCmt, $aBp, $aDp);
+
+        $aTmplReplyTo = array();
+        if((int)$aCmt['cmt_parent_id'] != 0) {
+            $aParent = $this->getCommentRow($aCmt['cmt_parent_id']);
+
+            if(!empty($aParent) && is_array($aParent)) {
+                $oProfile = $this->_getAuthorObject($aParent['cmt_author_id']);
+                $sParAuthorName = $oProfile->getDisplayName();
+                $sParAuthorUnit = $oProfile->getUnit(0, array('template' => array('name' => 'unit_wo_info_links', 'size' => 'icon')));
+
+                $aTmplReplyTo = array(
+                    'style_prefix' => $this->_sStylePrefix,
+                    'par_cmt_link' => $this->getItemUrl($aCmt['cmt_parent_id']),
+                    'par_cmt_title' => bx_html_attribute(_t('_in_reply_to_x', $sParAuthorName)),
+                    'par_cmt_author' => $sParAuthorName,
+                    'par_cmt_author_unit' => $sParAuthorUnit
+                );
+            }
+        }
+
+        $sReplies = '';
+        if(!(isset($aBp['pinned']) && (int)$aBp['pinned'] != 0 && (int)$aCmt['cmt_pinned'] != 0) && !empty($aDp)) {
+            $aDp['show_empty'] = false;
+
+            if(!empty($aDp['structure'][$aCmt['cmt_id']]) && is_array($aDp['structure'][$aCmt['cmt_id']])) {
+                if(!empty($aDp['structure'][$aCmt['cmt_id']]['items'])) {
+                    $aDp['structure'] = $aDp['structure'][$aCmt['cmt_id']]['items'];
+                    $sReplies = $this->getCommentsByStructure(array('parent_id' => $aCmt['cmt_id'], 'type' => $aBp['type']), $aDp);
+                }
+            } 
+            else if((int)$aCmt['cmt_replies'] > 0 && $aDp['type'] == BX_CMT_DISPLAY_THREADED)
+                $sReplies = $this->getComments(array('parent_id' => $aCmt['cmt_id'], 'vparent_id' => $aCmt['cmt_id'], 'type' => $aBp['type']), $aDp);
+        }
+
+        $aTmplVarsMeta = array();
+        if(!empty($this->_sMenuObjMeta)) {
+            $oMenuMeta = BxDolMenu::getObjectInstance($this->_sMenuObjMeta, $this->_oTemplate);
+            if($oMenuMeta) {
+                $oMenuMeta->setCmtsData($this, $aCmt['cmt_id']);
+
+                $aTmplVarsMeta = array(
+                    'style_prefix' => $this->_sStylePrefix,
+                    'meta' => $oMenuMeta->getCode()
+                );
+            }
+        }
+
+        $oProfileAuthor = BxDolProfile::getInstance($aCmt['cmt_author_id']);
+        if($this->_isShowContent($aCmt)) {
+            $sContent = $this->_getContent($aCmt, $aBp, $aDp);
+        }
+        else {
+            $sClass .= ' cmt-author-not-active';
+            $sContent = _t('_hidden_comment', BxDolProfileUndefined::getInstance()->getDisplayName());
+        }
+
+        $aVars = array_merge(array(
+            'system' => $this->_sSystem,
+            'style_prefix' => $this->_sStylePrefix,
+            'js_object' => $this->_sJsObjName,
+            'id' => $aCmt['cmt_id'],
+            'anchor' => $this->getItemAnchor($aCmt['cmt_id']),
+            'class' => $sClass,
+            'class_cnt' => $sClassCnt,
+            'bx_if:show_reply_to' => array(
+                'condition' => !empty($aTmplReplyTo),
+                'content' => $aTmplReplyTo
+            ),
+            'bx_if:meta' => array(
+                'condition' => !empty($aTmplVarsMeta),
+                'content' => $aTmplVarsMeta
+            ),
+            'bx_if:show_pinned' => array(
+                'condition' => (int)$aCmt['cmt_pinned'] > 0,
+                'content' => array(
+                    'style_prefix' => $this->_sStylePrefix,
+                )
+            ),
+            'content' => $sContent,
+            'actions' => $sActions,
+            'replies' =>  $sReplies,
+        ), $this->_getTmplVarsAuthor($aCmt), $this->_getTmplVarsNotes($aCmt));
+        
+        $sResult = $this->_oTemplate->parseHtmlByName($this->_sTmplNameItem, $aVars);
+        
+        bx_alert('system', 'view_comment', $aCmt['cmt_id'], 0, array('comment' => $aCmt, 'system' => $this->_sSystem, 'tmpl_name' => $this->_sTmplNameItem, 'tmpl_vars' => $aVars, 'override_result' => &$sResult));
+        
+        return $sResult;
+    }
+
+    public function getCommentStructure($iCmtId, $aBp = array(), $aDp = array())
+    {
+        $aRoot = $this->getCommentRow((int)$iCmtId);
+
+        $aBps = $aBp;
+        if(!empty($aRoot))
+            $aBps['parent_id'] = $aRoot['cmt_id'];
+        $this->_prepareStructureBp($aDp['type'], $aBps);
+
+        $iLevel = 0;
+        $aStructure = array();
+        $this->{'_getStructure' . ($this->_bIsApi ? 'API' : '')}($aRoot, $aBps, $iLevel, $aStructure);
+
+        return !empty($aStructure) && is_array($aStructure) ? $aStructure : false;
+    }
+
+    function getCommentSearch($iCmtId, &$sAddon)
+    {
+        $aBp = array();
+        $aDp = array(
+            'type' => BX_CMT_DISPLAY_FLAT, 
+            'view_only' => true
+        );
+
+        if(empty($sAddon))
+            $sAddon = $this->getJsScript($aBp, $aDp);
+
+        BxDolTemplate::getInstance()->addInjection ('injection_footer', 'text', $this->_oTemplate->parseHtmlByName('comments_photoswipe.html', []));
+
+        return $this->_oTemplate->parseHtmlByName('comment_search.html', array(
+            'comment' => $this->getComment($iCmtId, $aBp, $aDp),
+        )); 
+    }
+
+    /**
+     * get one comment for "Live Search"
+     *
+     * @param  int    $iCmtId - comment id
+     * @return string
+     */
+    function getCommentLiveSearch($mixedCmt, $aParams = array())
+    {
+        $aCmt = !is_array($mixedCmt) ? $this->getCommentRow((int)$mixedCmt) : $mixedCmt;
+
+        list($sAuthorName, $sAuthorLink, $sAuthorIcon) = $this->_getAuthorInfo($aCmt['cmt_author_id']);
+        $bAuthorIcon = !empty($sAuthorIcon);
+
+        $sViewLink = $this->getViewUrl($aCmt['cmt_id']);
+        if(empty($sViewLink))
+            $sViewLink = '#';
+
+        return $this->_oTemplate->parseHtmlByName('comment_live_search.html', array(
+            'bx_if:show_icon' => array(
+                'condition' => $bAuthorIcon,
+                'content' => array(
+                    'author_icon' => $sAuthorIcon,
+        			'view_link' => $sViewLink
+                )
+            ),
+            'bx_if:show_icon_empty' => array(
+                'condition' => !$bAuthorIcon,
+                'content' => array()
+            ),
+            'view_link' => $sViewLink,
+            'text' => BxTemplFunctions::getInstance()->getStringWithLimitedLength(strip_tags($aCmt['cmt_text']), $this->_sSnippetLenthLiveSearch),
+            'sample' => isset($aParams['txt_sample_single']) ? _t($aParams['txt_sample_single']) : ''
+        ));
+    }
+
+    function getFormBoxPost($aBp = array(), $aDp = array())
+    {
+        return $this->_getFormBox(BX_CMT_ACTION_POST, $aBp, $aDp);
+    }
+
+    function getFormBoxEdit($aBp = array(), $aDp = array())
+    {
+        return $this->_getFormBox(BX_CMT_ACTION_EDIT, $aBp, $aDp);
+    }
+
+    function getFormPost($iCmtParentId = 0, $aDp = array())
+    {
+        return $this->_getFormPost($iCmtParentId, $aDp);
+    }
+
+    function getFormEdit($iCmtId, $aDp = array())
+    {
+        return $this->_getFormEdit($iCmtId, $aDp);
+    }
+
+    function getControlsBox()
+    {
+        return $this->_getControlsBox();
+    }
+    
+    function getLiveUpdate($iCountOld = 0, $iCountNew = 0)
+    {
+        $iCount = (int)$iCountNew - (int)$iCountOld;
+        if($iCount < 0)
+            return '';
+
+        $aComments = $this->_oQuery->getCommentsBy(array('type' => 'latest', 'object_id' => $this->_iId, 'author' => $this->_getAuthorId(), 'others' => 1, 'start' => '0', 'per_page' => $iCount));
+        if(empty($aComments) || !is_array($aComments))
+            return '';
+
+        $aComment = array_shift($aComments);
+        if(empty($aComment) || !is_array($aComment))
+            return '';
+
+        $sJsObject = $this->getJsObjectName();
+        return $this->_oTemplate->parseHtmlByName('comments_lu_button.html', array(
+            'style_prefix' => $this->_sStylePrefix,
+            'html_id' => $this->getNotificationId(),
+            'onclick_show' => "javascript:" . $sJsObject . ".goToBtn(this, '" . $this->getItemAnchor($aComment['cmt_id']) . "', '" . $aComment['cmt_id'] . "');",
+        ));
+    }
+
+    function getLiveUpdates($iCountOld = 0, $iCountNew = 0)
+    {
+        $bShowAll = true;
+        $bShowActions = false;
+
+        $iCount = (int)$iCountNew - (int)$iCountOld;
+        if($iCount < 0)
+            return '';
+
+        $aComments = $this->_oQuery->getCommentsBy(array('type' => 'latest', 'object_id' => $this->_iId, 'author' => $this->_getAuthorId(), 'others' => 1, 'start' => '0', 'per_page' => $iCount));
+        if(empty($aComments) || !is_array($aComments))
+            return '';
+
+        $sJsObject = $this->getJsObjectName();
+
+        $iUserId = $this->_getAuthorId();
+        $bModerator = $this->isModerator();
+
+        $aComments = array_reverse($aComments);
+        $iComments = count($aComments);
+
+        $aTmplVarsNotifs = array();
+        foreach($aComments as $iIndex => $aComment) {
+            $iCommentId = $aComment['cmt_id'];
+
+            $sShowOnClick = "javascript:" . $sJsObject . ".goTo(this, '" .  $this->getItemAnchor($iCommentId) . "', '" . $iCommentId . "');";
+            $sReplyOnClick = "javascript:" . $sJsObject . ".goToAndReply(this, '" . $this->getItemAnchor($iCommentId) . "', '" . $iCommentId . "');";
+
+            $iAuthorId = (int)$aComment['cmt_author_id'];
+            if($iAuthorId < 0) {
+                if(abs($iAuthorId) == $iUserId)
+                    continue;
+                else if($bModerator)
+                    $iAuthorId *= -1;
+            }
+
+            $oAuthor = $this->_getAuthorObject($iAuthorId);
+            $sAuthorName = $oAuthor->getDisplayName();
+
+            $aTmplVarsNotifs[] = array(
+                'bx_if:show_as_hidden' => array(
+                    'condition' => !$bShowAll && $iIndex < ($iComments - 1),
+                    'content' => array(),
+                ),
+                'item' => $this->_oTemplate->parseHtmlByName('comments_lu_notifications.html', array(
+                    'style_prefix' => $this->_sStylePrefix,
+                    'onclick_show' => $sShowOnClick,
+                    'onclick_reply' => $sReplyOnClick,
+                    'author_link' => $oAuthor->getUrl(), 
+                    'author_title' => bx_html_attribute($sAuthorName),
+                    'author_name' => $sAuthorName,
+                    'author_unit' => $oAuthor->getUnit(0, array('template' => 'unit_wo_info_links')), 
+                    'text' => _t('_cmt_txt_added_sample')
+                )),
+                'bx_if:show_previous' => array(
+                    'condition' => $bShowActions && $iIndex > 0,
+                    'content' => array(
+                        'onclick_previous' => $sJsObject . '.previousLiveUpdate(this)'
+                    )
+                ),
+                'bx_if:show_close' => array(
+                    'condition' => $bShowActions,
+                    'content' => array(
+                        'onclick_close' => $sJsObject . '.hideLiveUpdate(this)'
+                    )
+                )
+            );
+        }
+
+        return $this->_oTemplate->parseHtmlByName('popup_chain.html', array(
+            'html_id' => $this->getNotificationId(),
+            'bx_repeat:items' => $aTmplVarsNotifs
+        ));
+    }
+
+    public function getElementBlock($aParams = array())
+    {
+        $aParams['usage'] = BX_CMT_USAGE_BLOCK;
+
+        return $this->getElement($aParams);
+    }
+
+    public function getElementInline($aParams = array())
+    {
+        $aParams['usage'] = BX_CMT_USAGE_INLINE;
+
+        return $this->getElement($aParams);
+    }
+
+    public function getElement($aParams = array())
+    {
+    	$aParams = array_merge($this->_aElementDefaults, $aParams);
+
+        $bShowDoCommentAsButtonSmall = isset($aParams['show_do_comment_as_button_small']) && $aParams['show_do_comment_as_button_small'] == true;
+        $bShowDoCommentAsButton = !$bShowDoCommentAsButtonSmall && isset($aParams['show_do_comment_as_button']) && $aParams['show_do_comment_as_button'] == true;
+        $bShowCounterEmpty = isset($aParams['show_counter_empty']) && $aParams['show_counter_empty'] == true;
+        $bRecalculateCounter = isset($aParams['recalculate_counter']) && $aParams['recalculate_counter'] == true;
+
+        $iObjectId = $this->getId();
+        $iAuthorId = $this->_getAuthorId();
+        $iAuthorIp = $this->_getAuthorIp();
+
+        $iCount = $this->getCommentsCountAll(0, $bRecalculateCounter);
+        $bCount = (int)$iCount != 0;
+
+        $isAllowedComment = $this->isPostAllowed();
+
+        //--- Do Comment
+        $bTmplVarsDoComment = $this->_isShowDoComment($aParams, $isAllowedComment, $bCount);
+        $aTmplVarsDoComment = array();
+        if($bTmplVarsDoComment) {
+            $sClass = '';
+            if($bShowDoCommentAsButton)
+                $sClass = 'bx-btn';
+            else if ($bShowDoCommentAsButtonSmall)
+                $sClass = 'bx-btn bx-btn-small';
+
+            if(!$isAllowedComment)
+                $sClass .= $bShowDoCommentAsButton || $bShowDoCommentAsButtonSmall ? ' bx-btn-disabled' : 'bx-cmts-disabled';
+
+            $aTmplVarsDoComment = array(
+                'style_prefix' => $this->_sStylePrefix,
+                'do_comment' => $this->_oTemplate->parseLink($this->getListUrl(), $this->_getLabelDo($aParams), array(
+                    'class' => $this->_sStylePrefix . '-do-comment ' . $this->_sStylePrefix . '-dc ' . $sClass,
+                    'title' => _t($this->_getTitleDo())
+                )),
+            );
+        }
+
+        //--- Counter
+        $bTmplVarsCounter = $this->_isShowCounter($aParams, $isAllowedComment, $bCount);
+
+        $aTmplVarsCounter = array();
+        if($bTmplVarsCounter)
+            $aTmplVarsCounter = array(
+                'style_prefix' => $this->_sStylePrefix,
+                'bx_if:show_hidden' => array(
+                    'condition' => !$bShowCounterEmpty && !$bCount,
+                    'content' => array()
+                ),
+                'counter' => $this->getCounter(array_merge($aParams, [
+                    'show_counter_only' => false
+                ]))
+            );
+
+        if(!$bTmplVarsDoComment && !$bTmplVarsCounter)
+            return '';
+
+        $sTmplName = $this->{'_getTmplElement' . bx_gen_method_name(!empty($aParams['usage']) ? $aParams['usage'] : BX_CMT_USAGE_DEFAULT)}();
+        return $this->_oTemplate->parseHtmlByContent($sTmplName, array(
+            'style_prefix' => $this->_sStylePrefix,
+            'html_id' => $this->_aHtmlIds['main'],
+            'class' => $this->_sStylePrefix . ($bShowDoCommentAsButton ? '-button' : '') . ($bShowDoCommentAsButtonSmall ? '-button-small' : ''),
+            'count' => $iCount,
+            'bx_if:show_do_comment' => array(
+                'condition' => $bTmplVarsDoComment,
+                'content' => $aTmplVarsDoComment
+            ),
+            'bx_if:show_counter' => array(
+                'condition' => $bTmplVarsCounter,
+                'content' => $aTmplVarsCounter
+            ),
+            'script' => ''
+        ));
+    }
+
+    protected function _getCounterItems($iCmtsLimit, $iCmtsStart = 0)
+    {
+        return $this->_oQuery->getCommentsBy(array('type' => 'object_id', 'object_id' => $this->getId(), 'order_way' => 'desc', 'start' => $iCmtsStart, 'per_page' => $iCmtsLimit * 4));
+    }
+    
+    private function _isShowContent($aCmt)
+    {
+        $oProfileAuthor = BxDolProfile::getInstance($aCmt['cmt_author_id']);
+        return (int)$aCmt['cmt_author_id'] == 0 || ($oProfileAuthor && $oProfileAuthor->isActive()) || isAdmin() || BxDolAcl::getInstance()->isMemberLevelInSet([MEMBERSHIP_ID_MODERATOR, MEMBERSHIP_ID_ADMINISTRATOR]);
+    }
+    
+    public function getCounter($aParams = [])
+    {
+        $aParams = array_merge($this->_aElementDefaults, $aParams);
+
+        $bShowDoCommentAsButtonSmall = isset($aParams['show_do_comment_as_button_small']) && $aParams['show_do_comment_as_button_small'] == true;
+        $bShowDoCommentAsButton = !$bShowDoCommentAsButtonSmall && isset($aParams['show_do_comment_as_button']) && $aParams['show_do_comment_as_button'] == true;
+        $bShowEmpty = isset($aParams['show_counter_empty']) && $aParams['show_counter_empty'] == true;
+        $bShowReversed = isset($aParams['show_counter_reversed']) && $aParams['show_counter_reversed'] == true;
+        $bRecalculateCounter = isset($aParams['recalculate_counter']) && $aParams['recalculate_counter'] == true;
+
+        $sClass = 'sys-action-counter';
+        if(isset($aParams['show_counter_only']) && (bool)$aParams['show_counter_only'] === true)
+            $sClass .= ' sys-ac-only';
+
+        $iCount = (int)$this->getCommentsCountAll(0, $bRecalculateCounter);        
+        if($iCount == 0 && !$bShowEmpty)
+            $sClass .= ' sys-ac-hidden';
+
+        $sClass .= ' ' . $this->_sStylePrefix . '-counter';
+        if($bShowDoCommentAsButtonSmall)
+            $sClass .= ' bx-btn-small-height';
+        if($bShowDoCommentAsButton)
+            $sClass .= ' bx-btn-height';
+
+        $iCmtsLimit = 5;
+        $aCmts = $this->_getCounterItems($iCmtsLimit);
+        if(!$bShowReversed)
+            $aCmts = array_reverse($aCmts);
+
+        $aTmplVarsProfiles = [];
+        foreach($aCmts as $aCmt) {
+            $iAuthor = (int)$aCmt['cmt_author_id'];
+            if(array_key_exists($iAuthor, $aTmplVarsProfiles))
+                continue;
+
+            $oAuthor = BxDolProfile::getInstanceMagic($iAuthor);
+            if(!$oAuthor)
+                continue;
+
+            $aTmplVarsProfiles[$iAuthor] = [
+                'icon' => $oAuthor->getUnit(0, ['template' => ['name' => 'unit_wo_info_links', 'size' => 'icon']]) 
+            ];
+
+            if(count($aTmplVarsProfiles) >= $iCmtsLimit)
+                break;
+        }
+        $aTmplVarsProfiles = array_values($aTmplVarsProfiles);
+
+        $sHref = !empty($aParams['overwrite_counter_link_href']) ? $aParams['overwrite_counter_link_href'] : $this->getListUrl();
+        $sOnclick = '';
+        if(!empty($aParams['overwrite_counter_link_onclick']))
+            $sOnclick = $aParams['overwrite_counter_link_onclick'];
+
+        return $this->_oTemplate->parseHtmlByContent($this->_getTmplCounter(), [
+            'style_prefix' => $this->_sStylePrefix,
+            'id' => $this->_aHtmlIds['counter'],
+            'class' => $sClass,
+            'href' => $sHref,
+            'bx_if:show_onclick' => [
+                'condition' => !empty($sOnclick),
+                'content' => [
+                    'onclick' => $sOnclick
+                ]
+            ],
+            'content' => $this->_getCounterLabel($iCount, $aParams),
+            'bx_repeat:profiles' => $aTmplVarsProfiles,
+            'bx_if:show_icon' => [
+                'condition' => ($bShowEmpty || !empty($aTmplVarsProfiles)) && (!isset($aParams['show_icon']) || $aParams['show_icon']),
+                'content' => [
+                    'style_prefix' => $this->_sStylePrefix,
+                    'icon' => !empty($aParams['custom_icon']) ? $aParams['custom_icon'] : $this->_oTemplate->getImageAuto('comment|' . $this->_sStylePrefix . '-counter-icon')
+                ]
+            ]
+        ]);
+    }
+
+    protected function _getLabelDo($aParams = [])
+    {
+        return $this->_oTemplate->parseHtmlByContent($this->_getTmplLabelDo(), [
+            'style_prefix' => $this->_sStylePrefix,
+            'bx_if:show_icon' => [
+                'condition' => isset($aParams['show_do_comment_icon']) && $aParams['show_do_comment_icon'] == true,
+                'content' => [
+                    'style_prefix' => $this->_sStylePrefix,
+                    'icon' => $this->_oTemplate->getImageAuto($this->_getIconDo())
+                ]
+            ],
+            'bx_if:show_text' => [
+                'condition' => isset($aParams['show_do_comment_label']) && $aParams['show_do_comment_label'] == true,
+                'content' => [
+                    'style_prefix' => $this->_sStylePrefix,
+                    'text' => _t($this->_getTitleDo())
+                ]
+            ]
+        ]);
+    }
+
+    protected function _getCounterLabel($iCount, $aParams = [])
+    {
+        $aParams = array_merge($this->_aElementDefaults, $aParams);
+
+        $sResult = '';
+        if((int)$iCount != 0)
+            $sResult = _t(isset($aParams['caption']) ? $aParams['caption'] : '_cmt_txt_counter', $iCount);
+        else
+            $sResult = _t(isset($aParams['caption_empty']) ? $aParams['caption_empty'] : '_cmt_txt_counter_empty');
+        
+        return $sResult;
+    }
+
+    protected function _getTmplElementBlock()
+    {
+        return self::$_sTmplContentElementBlock;
+    }
+
+    protected function _getTmplElementInline()
+    {
+        return self::$_sTmplContentElementInline;
+    }
+
+    protected function _getTmplLabelDo()
+    {
+        return self::$_sTmplContentDoCommentLabel;
+    }
+
+    protected function _getTmplCounter()
+    {
+        return self::$_sTmplContentCounter;
+    }
+
+
+    /**
+     * private functions
+     */
+    protected function _getContentBefore($aBp = array(), $aDp = array())
+    {
+        return '';
+    }
+
+    protected function _getContentAfter($aBp = array(), $aDp = array())
+    {
+        return '';
+    }
+
+    protected function _getControlsBox()
+    {
+        $sDisplay = '';
+        $bDisplay = (int)$this->_aSystem['is_display_switch'] == 1;
+        if($bDisplay) {
+            $aDisplayLinks = array(
+                array('id' => $this->_sSystem . '-flat', 'name' => $this->_sSystem . '-flat', 'class' => '', 'title' => '_cmt_display_flat', 'target' => '_self', 'onclick' => 'javascript:' . $this->_sJsObjName . '.cmtChangeDisplay(this, \'flat\');'),
+                array('id' => $this->_sSystem . '-threaded', 'name' => $this->_sSystem . '-threaded', 'class' => '', 'title' => '_cmt_display_threaded', 'target' => '_self', 'onclick' => 'javascript:' . $this->_sJsObjName . '.cmtChangeDisplay(this, \'threaded\');')
+            );
+
+            $oMenu = new BxTemplMenuInteractive(array('template' => 'menu_interactive_vertical.html', 'menu_id'=> $this->_sSystem . '-display', 'menu_items' => $aDisplayLinks));
+            $oMenu->setSelected('', $this->_sSystem . '-' . $this->_sDisplayType);
+            $sDisplay = $oMenu->getCode();
+        }
+
+        $sBrowseType = '';
+        $bBrowseType = (int)$this->_aSystem['is_browse_switch'] == 1;
+        if($bBrowseType) {
+            $aBrowseLinks = array(
+                array('id' => $this->_sSystem . '-tail', 'name' => $this->_sSystem . '-tail', 'class' => '', 'title' => '_cmt_browse_tail', 'target' => '_self', 'onclick' => 'javascript:' . $this->_sJsObjName . '.cmtChangeBrowse(this, \'tail\');'),
+                array('id' => $this->_sSystem . '-head', 'name' => $this->_sSystem . '-head', 'class' => '', 'title' => '_cmt_browse_head', 'target' => '_self', 'onclick' => 'javascript:' . $this->_sJsObjName . '.cmtChangeBrowse(this, \'head\');'),
+                array('id' => $this->_sSystem . '-popular', 'name' => $this->_sSystem . '-popular', 'class' => '', 'title' => '_cmt_browse_popular', 'target' => '_self', 'onclick' => 'javascript:' . $this->_sJsObjName . '.cmtChangeBrowse(this, \'popular\');'),
+            );
+
+            $oMenu = new BxTemplMenuInteractive(array('template' => 'menu_interactive_vertical.html', 'menu_id'=> $this->_sSystem . '-browse', 'menu_items' => $aBrowseLinks));
+            $oMenu->setSelected('', $this->_sSystem . '-' . $this->_sBrowseType);
+            $sBrowseType = $oMenu->getCode();
+        }
+
+        $sBrowseFilter = '';
+        $bBrowseFilter = (int)$this->_aSystem['is_browse_filter'] == 1;
+        if($bBrowseFilter) {
+            $aFilterLinks = array(
+                array('id' => $this->_sSystem . '-all', 'name' => $this->_sSystem . '-all', 'class' => '', 'title' => '_cmt_browse_all', 'target' => '_self', 'onclick' => 'javascript:' . $this->_sJsObjName . '.cmtChangeFilter(this, \'all\');'),
+                array('id' => $this->_sSystem . '-friends', 'name' => $this->_sSystem . '-friends', 'class' => '', 'title' => '_cmt_browse_friends', 'target' => '_self', 'onclick' => 'javascript:' . $this->_sJsObjName . '.cmtChangeFilter(this, \'friends\');'),
+                array('id' => $this->_sSystem . '-subscriptions', 'name' => $this->_sSystem . '-subscriptions', 'class' => '', 'title' => '_cmt_browse_subscriptions', 'target' => '_self', 'onclick' => 'javascript:' . $this->_sJsObjName . '.cmtChangeFilter(this, \'subscriptions\');')
+            );
+
+            $oMenu = new BxTemplMenuInteractive(array('template' => 'menu_interactive_vertical.html', 'menu_id'=> $this->_sSystem . '-filter', 'menu_items' => $aFilterLinks));
+            $oMenu->setSelected('', $this->_sSystem . '-' . $this->_sBrowseFilter);
+            $sBrowseFilter = $oMenu->getCode();
+        }
+
+        return $this->_oTemplate->parseHtmlByName('comments_controls.html', array(
+            'display_switcher' => $bDisplay ? $sDisplay : '',
+            'bx_if:is_divider_1' => array(
+                'condition' => $bDisplay && $bBrowseType,
+                'content' => array(
+                    'style_prefix' => $this->_sStylePrefix,
+                )
+            ),
+            'browse_switcher' => $bBrowseType ? $sBrowseType : '',
+            'bx_if:is_divider_2' => array(
+                'condition' => $bBrowseType && $bBrowseFilter,
+                'content' => array(
+                    'style_prefix' => $this->_sStylePrefix,
+                )
+            ),
+            'filter_switcher' => $bBrowseFilter ? $sBrowseFilter : '',
+        ));
+    }
+
+    protected function _getActionsBox(&$aCmt, $aBp = [], $aDp = [])
+    {
+    	$bViewOnly = isset($aDp['view_only']) && $aDp['view_only'] === true;
+    	$bDynamicMode = isset($aDp['dynamic_mode']) && $aDp['dynamic_mode'] === true;
+
+        $sMenuActions = '';
+        if(!$bViewOnly) {
+            $oMenuActions = BxDolMenu::getObjectInstance($this->_sMenuObjActions);
+            $oMenuActions->setCmtsData($this, $aCmt['cmt_id'], $aBp, $aDp);
+            $oMenuActions->setDynamicMode($bDynamicMode);
+            $sMenuActions = $oMenuActions->getCode();
+        }
+
+        return $this->_oTemplate->parseHtmlByName('comment_actions.html', array(
+            'id' => $aCmt['cmt_id'],
+            'js_object' => $this->_sJsObjName,
+            'style_prefix' => $this->_sStylePrefix,
+            'menu_actions' => $sMenuActions,
+        ));
+    }
+
+    protected function _getFormBox($sType, $aBp, $aDp)
+    {
+        $iCmtParentId = isset($aBp['parent_id']) ? (int)$aBp['parent_id'] : 0;
+        $sPosition = isset($aDp['position']) ? $aDp['position'] : '';
+        $bQuote = isset($aDp['quote']) && (bool)$aDp['quote'];
+        $bFormMin = $iCmtParentId == 0 && isset($aDp['min_post_form']) && (bool)$aDp['min_post_form'];
+
+        $sPositionSystem = $this->_aSystem['post_form_position'];
+        if(!empty($sPosition) && $sPositionSystem != $sPosition)
+            return '';
+
+        $sClass = '';
+        if(!empty($sPosition))
+            $sClass .= ' ' . $this->_sStylePrefix . '-reply-' . $sPosition;
+        if($bQuote)
+            $sClass .= ' ' . $this->_sStylePrefix . '-reply-quote';
+        if($bFormMin)
+            $sClass .= ' ' . $this->_sStylePrefix . '-reply-min';
+        if(!empty($aDp['class']))
+            $sClass .= ' ' . $aDp['class'];
+
+        $sClassBody = '';
+        if(!empty($aDp['class_body']))
+            $sClassBody .= ' ' . $aDp['class_body'];
+
+        $aTmplVarsFormMin = array();
+        if($bFormMin) {
+            list($sAuthorName, $sAuthorLink, $sAuthorIcon, $sAuthorUnit) = $this->_getAuthorInfo($this->_getAuthorId());
+
+            $oForm = new BxTemplFormView(array());
+            $aInputPlaceholder = array(
+                'type' => 'text',
+                'name' => 'comment',
+                'caption' => '',
+                'attrs' => array(
+                    'onclick' => 'javascript:' . $this->_sJsObjName . '.cmtShowForm(this)',
+                    'placeholder' => _t($this->_aT['txt_min_form_placeholder'], $sAuthorName),
+                    'autocomplete' => 'off',
+                    'readonly' => 'readonly'
+                ),
+                'value' => '',
+            );
+
+            $sClassBodyMin = '';
+            if(!empty($aDp['class_body_min']))
+                $sClassBodyMin .= ' ' . $aDp['class_body_min'];
+
+            $aTmplVarsFormMin = array(
+                'js_object' => $this->_sJsObjName,
+                'class_body_min' => $sClassBodyMin,
+                'style_prefix' => $this->_sStylePrefix,
+                'author_unit' => $sAuthorUnit,
+                'placeholder' => $oForm->genRow($aInputPlaceholder)
+            );
+        }
+
+        $aForm = $this->{'_getForm' . ucfirst($sType)}($iCmtParentId, $aDp);
+        if(empty($aForm['form']))
+            return !empty($aForm['msg']) ? MsgBox($aForm['msg']) : '';
+
+        return $this->_oTemplate->parseHtmlByName('comment_reply_box.html', array(
+            'js_object' => $this->_sJsObjName,
+            'style_prefix' => $this->_sStylePrefix,
+            'class' => $sClass,
+            'class_body' => $sClassBody,
+            'bx_if:show_form_min' => array(
+                'condition' => $bFormMin,
+                'content' => $aTmplVarsFormMin
+            ),
+            'form' => $aForm['form'],
+            'form_id' => $aForm['form_id'],
+        ));
+    }
+
+    protected function _getFormAdd($aValues)
+    {
+        $iCmtAuthorId = isset($aValues['cmt_author_id']) ? (int)$aValues['cmt_author_id'] : $this->_getAuthorId();
+        $iCmtParentId = isset($aValues['cmt_parent_id']) ? (int)$aValues['cmt_parent_id'] : 0;
+
+        $oForm = $this->_getForm(BX_CMT_ACTION_POST, $iCmtParentId);
+        $oForm->aFormAttrs['method'] = BX_DOL_FORM_METHOD_SPECIFIC;
+        $oForm->aParams['csrf']['disable'] = true;
+        $oForm->aInputs['cmt_parent_id']['value'] = $iCmtParentId;
+        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' => 1, 'message' => '_sys_txt_error_occured');
+
+        $iLevel = 0;
+        $iCmtVisualParentId = 0;
+        if((int)$iCmtParentId > 0) {
+            $aParent = $this->getCommentRow($iCmtParentId);
+
+            $iLevel = (int)$aParent['cmt_level'] + 1;
+            $iCmtVisualParentId = $iLevel > $this->getMaxLevel() ? $aParent['cmt_vparent_id'] : $iCmtParentId;
+        }
+
+        $iCmtId = (int)$oForm->insert(array('cmt_vparent_id' => $iCmtVisualParentId, 'cmt_object_id' => $this->_iId, 'cmt_author_id' => $iCmtAuthorId, 'cmt_level' => $iLevel, 'cmt_time' => time()));
+        if(!$iCmtId) {
+            if(!$oForm->isValid())
+                return array('code' => 1, 'message' => '_sys_txt_error_occured');
+            else
+                return array('code' => 2, 'message' => '_cmt_err_cannot_perform_action');
+        }
+
+        $iCmtUniqId = $this->_oQuery->getUniqId($this->_aSystem['system_id'], $iCmtId, [
+            'author_id' => $iCmtAuthorId, 
+            'status_admin' => $this->getStatusAdmin()
+        ]);
+
+        if($iCmtParentId) {
+            $this->_oQuery->updateRepliesCount($iCmtParentId, 1);
+
+            if(!BxDolModuleQuery::getInstance()->isEnabledByName('bx_notifications'))
+                $this->_sendNotificationEmail($iCmtId, $iCmtParentId);
+        }
+
+        $this->_triggerComment();
+
+        if($this->_sMetatagsObj && ($oMetatags = BxDolMetatags::getObjectInstance($this->_sMetatagsObj)) !== false)
+            $oMetatags->metaAdd($iCmtUniqId, $aValues['cmt_text']);
+
+        $mixedResult = $this->onPostAfter($iCmtId);
+        if($mixedResult === false)
+            return array('code' => 2, 'message' => '_cmt_err_cannot_perform_action');
+
+        return $mixedResult;
+    }
+
+    protected function _getFormPost($iCmtParentId = 0, $aDp = [])
+    {
+        $bCmtParentId = !empty($iCmtParentId);
+
+        if(!$bCmtParentId && !$this->isPostAllowed()) {
+            $sMsg = '';
+            if(!isLogged()) {
+                $oPermalink = BxDolPermalinks::getInstance();
+                $sMsg = _t('_cmt_msg_login_required', $oPermalink->permalink('page.php?i=login'), $oPermalink->permalink('page.php?i=create-account'));
+            }
+            else
+                $sMsg = $this->msgErrPostAllowed();
+
+            return bx_is_api() ? bx_api_get_msg($sMsg) : ['msg' => $sMsg];
+        }
+
+        if($bCmtParentId && !$this->isReplyAllowed($iCmtParentId))
+            return bx_is_api() ? bx_api_get_msg($sMsg) : ['msg' => $this->msgErrReplyAllowed()];
+
+        $bDynamic = isset($aDp['dynamic_mode']) && (bool)$aDp['dynamic_mode'];
+        $bQuote = isset($aDp['quote']) && (bool)$aDp['quote'];
+
+        $oForm = $this->_getForm(BX_CMT_ACTION_POST, $iCmtParentId);
+        $oForm->aInputs['cmt_parent_id']['value'] = $iCmtParentId;
+
+        if($bQuote) {
+            $aCmtParent = $this->getCommentRow((int)$iCmtParentId);
+            if(!empty($aCmtParent['cmt_text']))
+                $oForm->aInputs['cmt_text']['value'] = $this->_oTemplate->parseHtmlByName('comment_quote.html', [
+                    'content' => $aCmtParent['cmt_text']
+                ]);
+        }
+
+        $oForm->initChecker();
+        if($oForm->isSubmittedAndValid()) {
+            $iCmtAuthorId = $this->_getAuthorId();
+            $iCmtParentId = (int)$oForm->getCleanValue('cmt_parent_id');
+            
+            //--- Process Text ---//
+            $sCmtText = $oForm->getCleanValue('cmt_text');
+            $bCmtText = !empty($sCmtText);
+
+            //--- Process Media ---//
+            $aImageIds = $oForm->getCleanValue('cmt_image');
+            $bImageIds = !empty($aImageIds) && is_array($aImageIds);
+
+            if(!$bCmtText && !$bImageIds) {
+                $oForm->aInputs['cmt_text']['error'] =  _t('_Please enter characters');
+                $oForm->setValid(false);
+                return bx_is_api() ? ['form' => $oForm, 'res' => 0] : ['form' => $oForm->getCode($bDynamic), 'form_id' => $oForm->id];
+            }
+
+            $aParent = [];
+            if($iCmtParentId > 0) {
+                $aParent = $this->getCommentRow($iCmtParentId);
+                if(empty($aParent) || !is_array($aParent)) {
+                    $iCmtParentId = 0;
+                    $oForm->setSubmittedValue('cmt_parent_id', $iCmtParentId, $oForm->aFormAttrs['method']);
+                }
+            }
+
+            $iLevel = 0;
+            $iCmtVisualParentId = 0;
+            if($iCmtParentId > 0) {
+                $iLevel = (int)$aParent['cmt_level'] + 1;
+                $iCmtVisualParentId = $iLevel > $this->getMaxLevel() ? $aParent['cmt_vparent_id'] : $iCmtParentId;
+            }
+
+            $iCmtId = (int)$oForm->insert(['cmt_vparent_id' => $iCmtVisualParentId, 'cmt_object_id' => $this->_iId, 'cmt_author_id' => $iCmtAuthorId, 'cmt_level' => $iLevel, 'cmt_time' => time()]);
+            if($iCmtId != 0) {
+                $iCmtUniqId = $this->_oQuery->getUniqId($this->_aSystem['system_id'], $iCmtId, [
+                    'author_id' => $iCmtAuthorId,
+                    'status_admin' => $this->getStatusAdmin()
+                ]);
+
+                if($this->isAttachImageEnabled())
+                    $oForm->processImages($this, 'cmt_image', $iCmtUniqId, $iCmtId, $iCmtAuthorId, true);
+
+                if($iCmtParentId > 0) {
+                    $this->_oQuery->updateRepliesCount($iCmtParentId, 1);
+
+                    if(!BxDolModuleQuery::getInstance()->isEnabledByName('bx_notifications'))
+                        $this->_sendNotificationEmail($iCmtId, $iCmtParentId);
+                }
+
+                $this->_triggerComment();
+
+                if($iCmtParentId > 0)
+                    $this->isReplyAllowed($iCmtParentId, true);
+                else
+                    $this->isPostAllowed(true);
+
+                if($this->_sMetatagsObj && ($oMetatags = BxDolMetatags::getObjectInstance($this->_sMetatagsObj)) !== false)
+                    $oMetatags->metaAdd($iCmtUniqId, $sCmtText);
+
+                if(($mixedResult = $this->onPostAfter($iCmtId, $aDp)) !== false){
+                    if (bx_is_api()){
+                        $this->_unsetFormObject(BX_CMT_ACTION_POST);
+                        return ['form' => $this->_getForm(BX_CMT_ACTION_POST, $iCmtParentId), 'res' => $iCmtId];
+                    }
+                    else{
+                        return $mixedResult;
+                    }
+                }
+            }
+            return bx_is_api() ? bx_api_get_msg('_cmt_err_cannot_perform_action') : ['msg' => _t('_cmt_err_cannot_perform_action')];
+        }
+        return bx_is_api() ? ['form' => $oForm, 'res' => 0] : ['form' => $oForm->getCode($bDynamic), 'form_id' => $oForm->id];
+    }
+
+    protected function _getFormEdit($iCmtId, $aDp = [])
+    {
+        $bDynamic = isset($aDp['dynamic_mode']) && (bool)$aDp['dynamic_mode'];
+
+        $aCmt = $this->getCommentSimple($iCmtId);
+        if(!$aCmt)
+            return array('msg' => _t('_No such comment'));
+
+        $iCmtAuthorId = $this->_getAuthorId();
+        if(!$this->isEditAllowed($aCmt))
+            return array('msg' => $aCmt['cmt_author_id'] == $iCmtAuthorId ? strip_tags($this->msgErrEditAllowed()) : _t('_Access denied'));
+
+        $oForm = $this->_getForm(BX_CMT_ACTION_EDIT, $aCmt['cmt_id']);
+
+        $oForm->initChecker($aCmt);
+        if($oForm->isSubmittedAndValid()) {
+            $sCmtText = $oForm->getCleanValue('cmt_text');
+
+            if($oForm->update($iCmtId) !== false) {
+                $iCmtUniqId = $this->_oQuery->getUniqId($this->_aSystem['system_id'], $iCmtId, [
+                    'author_id' => (int)$aCmt['cmt_author_id']
+                ]);
+                
+                $sStatusAdmin = $this->getStatusAdmin();
+                if($sStatusAdmin !== BX_CMT_STATUS_ACTIVE)
+                    $this->_oQuery->updateUniqId(['status_admin' => $sStatusAdmin], ['id' => $iCmtUniqId]);
+
+                if($this->isAttachImageEnabled())
+                    $oForm->processImages($this, 'cmt_image', $iCmtUniqId, $iCmtId, $iCmtAuthorId, false);
+
+                $this->isEditAllowed($aCmt, true);
+
+                if($this->_sMetatagsObj && ($oMetatags = BxDolMetatags::getObjectInstance($this->_sMetatagsObj)) !== false)
+                    $oMetatags->metaAdd($iCmtUniqId, $sCmtText);
+
+                if(($mixedResult = $this->onEditAfter($iCmtId, $aDp)) !== false)
+                    return $mixedResult;
+            }
+
+            return array('msg' => _t('_cmt_err_cannot_perform_action'));
+        }
+
+        return array('form' => $oForm->getCode($bDynamic), 'form_id' => $oForm->id);
+    }
+
+    protected function _getForm($sAction, $iId)
+    {
+        $oForm = $this->_getFormObject($sAction);
+        $oForm->setId(sprintf($oForm->getAttributeMask('id'), $sAction, $this->_sSystem, $this->_iId, $iId));
+        $oForm->setName(sprintf($oForm->getAttributeMask('name'), $sAction, $this->_sSystem, $this->_iId, $iId));
+        $oForm->aParams['db']['table'] = $this->_aSystem['table'];
+        $oForm->aInputs['sys']['value'] = $this->_sSystem;
+        $oForm->aInputs['id']['value'] = $this->_iId;
+        $oForm->aInputs['action']['value'] = 'Submit' . ucfirst($sAction) . 'Form';
+
+        if(!$this->isAttachImageEnabled())
+            unset($oForm->aInputs['cmt_image']);
+
+        if(isset($oForm->aInputs['cmt_text'])) {
+            $oForm->aInputs['cmt_text']['html'] = $this->_aSystem['html'];
+            $oForm->aInputs['cmt_text']['db']['pass'] = $this->isHtml() ? 'XssHtml' : 'XssMultiline';
+
+            if(isset($oForm->aInputs['cmt_text']['checker']['func']) && strtolower($oForm->aInputs['cmt_text']['checker']['func']) == 'length') {
+                $iCmtTextMin = (int)$this->_aSystem['chars_post_min'];
+                $iCmtTextMax = (int)$this->_aSystem['chars_post_max'];
+                $oForm->aInputs['cmt_text']['checker']['params'] = array($iCmtTextMin, $iCmtTextMax);
+                $oForm->aInputs['cmt_text']['checker']['error'] = _t('_Please enter n1-n2 characters', $iCmtTextMin, $iCmtTextMax);
+            }
+        }
+
+        if($sAction == BX_CMT_ACTION_EDIT && isset($oForm->aInputs['cmt_controls']))
+            foreach($oForm->aInputs['cmt_controls'] as $mixedKey => $mixedValue) {
+                if(!is_numeric($mixedKey) || empty($mixedValue['name']) || $mixedValue['name'] != 'cmt_cancel')
+                    continue;
+
+                if(!isset($oForm->aInputs['cmt_controls'][$mixedKey]['attrs']))
+                    $oForm->aInputs['cmt_controls'][$mixedKey]['attrs'] = array();
+
+                $oForm->aInputs['cmt_controls'][$mixedKey]['attrs']['onclick'] = $this->_sJsObjName . '.cmtEdit(this, ' . $iId . ', false)';
+            }
+
+        return $oForm;
+    }
+
+    protected function _getContent($aCmt, $aBp = [], $aDp = [])
+    {
+        $bDynamicMode = isset($aDp['dynamic_mode']) && $aDp['dynamic_mode'] === true;
+
+        $sAttachments = $this->_getAttachments($aCmt);
+
+        $oMenuCounters = BxDolMenu::getObjectInstance($this->_sMenuObjCounters);
+        $oMenuCounters->setCmtsData($this, $aCmt['cmt_id'], $aBp, $aDp);
+        $oMenuCounters->setDynamicMode($bDynamicMode);
+
+        return $this->_oTemplate->parseHtmlByName($this->_sTmplNameItemContent, array_merge(array(
+            'style_prefix' => $this->_sStylePrefix,
+            'js_object' => $this->_sJsObjName,
+            'bx_if:show_attached' => array(
+                'condition' => !empty($sAttachments),
+                'content' => array(
+                    'style_prefix' => $this->_sStylePrefix,
+                    'attached' => $sAttachments
+                )
+            ),
+            'counters' => $oMenuCounters->getCode(),
+        ), $this->_getTmplVarsText($aCmt)));
+    }
+
+    protected function _getMoreLink($sCmts, $aBp = array(), $aDp = array())
+    {
+        $iStart = $iPerView = 0;
+        switch($aBp['type']) {
+            case BX_CMT_BROWSE_HEAD:
+            case BX_CMT_BROWSE_POPULAR:
+            case BX_CMT_BROWSE_CONNECTION:
+                $iPerView = $aBp['per_view'];
+
+                $iStart = $aBp['start'] + $iPerView;
+                if($iStart >= $aBp['count'])
+                    return $sCmts;
+
+                break;
+
+            case BX_CMT_BROWSE_TAIL:
+                $iPerView = $aBp['per_view'];
+
+                $iStart = $aBp['start'] - $iPerView;
+                if($iStart < 0) {
+                    $iPerView += $iStart;
+                    $iStart = 0;
+                }
+
+                if($iStart == 0 && $iPerView == 0)
+                    return $sCmts;
+
+                break;
+        }
+
+        $sParentKey = isset($aBp['vparent_id']) ? 'vparent_id' : 'parent_id';
+        $bRoot = (int)$aBp[$sParentKey] <= 0;
+
+        $sMore = $this->_oTemplate->parseHtmlByName('comment_more.html', array(
+            'js_object' => $this->_sJsObjName,
+            'style_prefix' => $this->_sStylePrefix,
+            'bx_if:is_root' => array(
+                'condition' => $bRoot,
+                'content' => array()
+            ),
+            'parent_id' => $aBp[$sParentKey],
+            'start' => $iStart,
+            'per_view' => $iPerView,
+            'title' => _t('_cmt_load_more_' . ($aBp[$sParentKey] == 0 ? 'comments' : 'replies') . '_' . $aBp['type'])
+        ));
+
+        switch($aBp['type']) {
+            case BX_CMT_BROWSE_HEAD:
+                case BX_CMT_BROWSE_POPULAR:
+                case BX_CMT_BROWSE_CONNECTION:
+                $sCmts .= $sMore;
+                break;
+
+            case BX_CMT_BROWSE_TAIL:
+                $sCmts = $sMore . $sCmts;
+                break;
+        }
+
+        return $sCmts;
+    }
+
+    protected function _getEmpty($aDp = array())
+    {
+        $sClass = '';
+        if(!empty($aDp['class']))
+            $sClass .= ' ' . $aDp['class'];
+
+        return $this->_oTemplate->parseHtmlByName('comment_empty.html', array(
+            'style_prefix' => $this->_sStylePrefix,
+            'class' => $sClass,
+            'content' => MsgBox(_t('_Empty'))
+        ));
+    }
+
+    protected function _getAttachments($aCmt)
+    {
+        $aTmplImages = array();
+        if(!$this->isAttachImageEnabled())
+            return ''; 
+
+        $aFiles = $this->_oQuery->getFiles($this->_aSystem['system_id'], $aCmt['cmt_id']);
+        if(!empty($aFiles) && is_array($aFiles)) {
+            $oStorage = BxDolStorage::getObjectInstance($this->getStorageObjectName());
+            $oTranscoder = BxDolTranscoderImage::getObjectInstance($this->getTranscoderPreviewName());
+
+            foreach($aFiles as $aFile) {
+                $sFile = $oStorage->getFileUrlById($aFile['image_id']);
+                $bImage = $oTranscoder && $oTranscoder->isMimeTypeSupported($aFile['mime_type']);
+
+                $sPreview = '';
+                $iWidth = $iHeight = 0;
+
+                if($bImage) {
+                    if($oTranscoder)
+                        $sPreview = $oTranscoder->getFileUrl($aFile['image_id']);
+
+                    $aImageInfo = BxDolImageResize::getInstance()->getImageSize($sFile);
+                    if(isset($aImageInfo['w']))
+                        $iWidth = (int)$aImageInfo['w'];
+                    if(isset($aImageInfo['h']))
+                        $iHeight = (int)$aImageInfo['h'];
+                }
+
+                if(!$sPreview)
+                    $sPreview = $this->_oTemplate->getIconUrl($oStorage->getIconNameByFileName($aFile['file_name']));
+
+                $aTmplVarsFile = array(
+                    'js_object' => $this->_sJsObjName,
+                    'preview' => $sPreview,
+                    'file' => $sFile,
+                    'file_id' => $aFile['id'],
+                    'file_name' => $aFile['file_name'],
+                    'file_icon' => $oStorage->getFontIconNameByFileName($aFile['file_name']),
+                    'file_size' => _t_format_size($aFile['size']),
+                    'w' => $iWidth, 
+                    'h' => $iHeight
+                );
+
+                if (bx_is_api()){
+                    $aTmplImages[] = array(
+                        'is_image' => $bImage,
+                        'file' => $aTmplVarsFile['file'],
+                        'file_id' => $aTmplVarsFile['file_id'],
+                        'file_name' => $aTmplVarsFile['file_name'],
+                        'file_icon' => $aTmplVarsFile['file_icon'],
+                        'file_size' => $aTmplVarsFile['file_size'],
+                        'width' => $aTmplVarsFile['w'],
+                        'height' => $aTmplVarsFile['h'],
+                    );
+                }
+                else{
+                    $aTmplImages[] = array(
+                        'style_prefix' => $this->_sStylePrefix,
+                        'bx_if:show_image' => array(
+                            'condition' => $bImage,
+                            'content' => $aTmplVarsFile
+                        ),
+                        'bx_if:show_file' => array(
+                            'condition' => !$bImage,
+                            'content' => $aTmplVarsFile
+                        ),
+                    );
+                }
+            }
+        }
+        
+        if (bx_is_api()){
+            return $aTmplImages;
+        }
+
+        return $this->_oTemplate->parseHtmlByName('comment_attachments.html', array(
+            'bx_repeat:attached' => $aTmplImages
+        ));
+    }
+
+    protected function _getTmplVarsNotes($aCmt)
+    {
+        $aTmplVars = array();
+        
+        //--- Pending status related notes.
+        if($aCmt['cmt_status_admin'] == BX_CMT_STATUS_PENDING)
+            $aTmplVars[] = [
+                'style_prefix' => $this->_sStylePrefix,
+                'bx_if:show_note_color' => [
+                    'condition' => true,
+                    'content' => [
+                        'item_note_color' => 'red3'
+                    ]
+                ],
+                'item_note' => _t('_cmt_txt_note_approve_pending')
+            ];
+
+        return [
+            'bx_if:show_notes' => [
+                'condition' => !empty($aTmplVars),
+                'content' => [
+                    'style_prefix' => $this->_sStylePrefix,
+                    'bx_repeat:notes' => $aTmplVars
+                ]
+            ],
+        ];
+    }
+
+    protected function _getTmplVarsAuthor($aCmt)
+    {
+    	list($sAuthorName, $sAuthorLink, $sAuthorIcon, $sAuthorUnit) = $this->_getAuthorInfo($aCmt['cmt_author_id']);
+    	$bAuthorIcon = !empty($sAuthorIcon);
+
+    	return array(
+    	    'author_unit' => $sAuthorUnit,
+            'bx_if:show_icon' => array(
+                'condition' => $bAuthorIcon,
+                'content' => array(
+                    'author_icon' => $sAuthorIcon
+                )
+            ),
+            'bx_if:show_icon_empty' => array(
+                'condition' => !$bAuthorIcon,
+                'content' => array()
+            ),
+            'bx_if:show_author_link' => array(
+                'condition' => !empty($sAuthorLink),
+                'content' => array(
+                    'author_link' => $sAuthorLink,
+            		'author_title' => bx_html_attribute($sAuthorName),
+                    'author_name' => $sAuthorName
+                )
+            ),
+            'bx_if:show_author_text' => array(
+                'condition' => empty($sAuthorLink),
+                'content' => array(
+                    'author_name' => $sAuthorName
+                )
+            ),
+    	);
+    }
+
+    protected function _getTmplVarsText($aCmt)
+    {
+    	$sText = $aCmt['cmt_text'];
+        $sTextMore = '';
+
+        if(!$this->isHtml()) {
+            $iMaxLength = (int)$this->_aSystem['chars_display_max'];
+            if(strlen($sText) > $iMaxLength) {
+                $iLength = strpos($sText, ' ', $iMaxLength);
+    
+                $sTextMore = trim(substr($sText, $iLength));
+                $sText = trim(substr($sText, 0, $iLength));
+            }
+        }
+
+        $sText = $this->_prepareTextForOutput($sText, $aCmt['cmt_id']);
+        $sTextMore = $this->_prepareTextForOutput($sTextMore, $aCmt['cmt_id']);
+
+        return array(
+			'text' => $sText,
+            'bx_if:show_more' => array(
+                'condition' => !empty($sTextMore),
+                'content' => array(
+                    'style_prefix' => $this->_sStylePrefix,
+                    'js_object' => $this->_sJsObjName,
+                    'text_more' => $sTextMore
+                )
+            ),
+        );
+    }
+}
+
+/** @} */

+ 296 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/template/scripts/BxBaseCmtsMenuActions.php

@@ -0,0 +1,296 @@
+<?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 BxBaseCmtsMenuActions extends BxTemplMenuCustom
+{
+    protected static $_sModeActions = 'actions';
+    protected static $_sModeCounters = 'counters';
+
+    protected $_bIsApi;
+
+    protected $_oCmts;
+    protected $_aCmt;
+    protected $_aBp;
+    protected $_aDp;
+
+    protected $_sMode;
+    protected $_bShowTitles;
+    protected $_bShowCounters;
+    protected $_bShowCountersEmpty;
+    protected $_bShowCountersIcons;
+
+    public function __construct ($aObject, $oTemplate)
+    {
+        parent::__construct ($aObject, $oTemplate);
+
+        $this->_bIsApi = bx_is_api();
+
+        $this->_aBp = [];
+        $this->_aDp = [];
+
+        $this->_sMode = strpos($this->_sObject, '_actions') !== false ? self::$_sModeActions : self::$_sModeCounters;
+        $this->_bShowTitles = true;
+        $this->_bShowCounters = false;
+        $this->_bShowCountersEmpty = false;
+        $this->_bShowCountersIcons = true;
+    }
+
+    public function setCmtsData($oCmts, $iCmtId, $aBp = [], $aDp = [])
+    {
+        if(empty($oCmts) || empty($iCmtId))
+            return;
+
+        $this->_oCmts = $oCmts;
+        $this->_aCmt = $oCmts->getCommentRow($iCmtId);
+
+        $this->_aBp = $aBp;
+        $this->_aDp = $aDp;
+
+        $sJsObject = $oCmts->getJsObjectName();
+        $this->addMarkers(array(
+            'js_object' => $sJsObject,
+            'cmt_system' => $this->_oCmts->getSystemName(),
+            'cmt_object_id' => $this->_oCmts->getId(),
+            'cmt_id' => $iCmtId,
+            'content_id' => $iCmtId,
+            'reply_onclick' => $sJsObject . '.toggleReply(this, ' . $iCmtId . ')',
+            'quote_onclick' => $sJsObject . '.toggleQuote(this, ' . $iCmtId . ')'
+        ));
+    }
+
+    public function getMenuItems()
+    {
+        if($this->_sMode == self::$_sModeActions && (int)$this->_aCmt['cmt_pinned'] > 0 && !empty($this->_aBp['pinned']))
+            $this->_aObject['menu_items'] = [
+                'item-unpin' => [
+                    'name' => 'item-unpin', 
+                    'title' => '_sys_menu_item_title_cmts_item_unpin', 
+                    'link' => 'javascript:void(0)',
+                    'onclick' => 'javascript:{js_object}.cmtPin(this, {content_id}, 0)',
+                    'icon' => 'thumbtack'
+                ]
+            ];
+
+        return parent::getMenuItems();
+    }
+
+    protected function _getMenuItemItemVote($aItem)
+    {
+        $oVote = $this->_oCmts->getVoteObject($this->_aCmt['cmt_unique_id']);
+        if(!$oVote)
+            return false;
+
+        $aVotesParams = [
+            'dynamic_mode' => $this->_bDynamicMode,
+            'show_do_vote_label' => $this->_bShowTitles,
+            'show_counter' => $this->_bShowCounters,
+            'show_counter_empty' => $this->_bShowCountersEmpty,
+            'show_counter_label_icon' => $this->_bShowCountersIcons,
+        ];
+
+        switch($this->_sMode) {
+            case self::$_sModeActions:
+                $sVotesMethod = 'getElementInline';
+                break;
+
+            case self::$_sModeCounters:
+                $sVotesMethod = 'getCounter';
+                break;
+        }
+
+        if($this->_bIsApi)
+            return $this->_getMenuItemElementApi($aItem, $oVote->getElementApi(array_merge($aVotesParams, [
+                'show_counter' => true,
+            ])));
+
+        return $oVote->$sVotesMethod($aVotesParams);
+    }
+
+    protected function _getMenuItemItemReaction($aItem)
+    {
+        $oReaction = $this->_oCmts->getReactionObject($this->_aCmt['cmt_unique_id']);
+        if(!$oReaction)
+            return false;
+
+        $aReactionsParams = [
+            'dynamic_mode' => $this->_bDynamicMode,
+            'show_do_vote_label' => $this->_bShowTitles,
+            'show_counter' => $this->_bShowCounters,
+            'show_counter_empty' => $this->_bShowCountersEmpty,
+            'show_counter_style' => 'compound',
+            'show_counter_label_icon' => $this->_bShowCountersIcons,
+        ];
+
+        switch($this->_sMode) {
+            case self::$_sModeActions:
+                $sReactionsMethod = 'getElementInline';
+                break;
+
+            case self::$_sModeCounters:
+                $sReactionsMethod = 'getCounter';
+                break;
+        }
+
+        if($this->_bIsApi)
+            return $this->_getMenuItemElementApi($aItem, $oReaction->getElementApi(array_merge($aReactionsParams, [
+                'show_counter' => true,
+            ])));
+
+        return $oReaction->$sReactionsMethod($aReactionsParams);
+    }
+
+    protected function _getMenuItemItemScore($aItem)
+    {
+        $oScore = $this->_oCmts->getScoreObject($this->_aCmt['cmt_unique_id']);
+        if(!$oScore)
+            return false;
+
+        $aScoresParams = array(
+            'dynamic_mode' => $this->_bDynamicMode,
+            'show_do_vote_label' => $this->_bShowTitles,
+            'show_counter' => $this->_bShowCounters,
+            'show_counter_empty' => $this->_bShowCountersEmpty,
+            'show_counter_label_icon' => $this->_bShowCountersIcons,
+        );
+
+        switch($this->_sMode) {
+            case self::$_sModeActions:
+                $sScoresMethod = 'getElementInline';
+                break;
+
+            case self::$_sModeCounters:
+                $sScoresMethod = 'getCounter';
+                break;
+        }
+
+        if($this->_bIsApi)
+            return $this->_getMenuItemElementApi($aItem, $oScore->getElementApi(array_merge($aScoresParams, [
+                'show_counter' => true,
+            ])));
+
+    	return $oScore->$sScoresMethod($aScoresParams);
+    }
+
+    protected function _getMenuItemItemReport($aItem)
+    {
+        $oReport = $this->_oCmts->getReportObject($this->_aCmt['cmt_unique_id']);
+        if(!$oReport)
+            return false;
+
+        $aReportParams = [
+            'dynamic_mode' => $this->_bDynamicMode,
+            'show_do_report_label' => $this->_bShowTitles,
+            'show_counter' => $this->_bShowCounters,
+        ];
+
+    	return $oReport->getElementInline($aReportParams);
+    }
+
+    protected function _getMenuItemElementApi($aItem, $aElement)
+    {
+        if(!$this->_bIsApi)
+            return $aItem;
+
+        return [
+                'id' => $aItem['id'],
+                'name' => $aItem['name'],
+                'display_type' => 'element',
+                'data' => $aElement
+            ];
+    }
+
+    /**
+     * Check if menu items is visible.
+     * @param $a menu item array
+     * @return boolean
+     */
+    protected function _isVisible ($a)
+    {
+        if(!parent::_isVisible($a))
+            return false;
+
+        if((int)$this->_aCmt['cmt_pinned'] > 0 && !empty($this->_aBp['pinned']) && $a['name'] != 'item-unpin')
+            return false;
+
+        $sCheckFuncName = '';
+        $aCheckFuncParams = array();
+        switch ($a['name']) {
+            case 'item-reply':
+                $sCheckFuncName = 'isReplyAllowed';
+                if(!empty($this->_aCmt))
+                    $aCheckFuncParams = array($this->_aCmt);
+                break;
+
+            case 'item-quote':
+                $sCheckFuncName = 'isQuoteAllowed';
+                if(!empty($this->_aCmt))
+                    $aCheckFuncParams = array($this->_aCmt);
+                break;
+
+            case 'item-vote':
+                $sCheckFuncName = 'isVoteAllowed';
+                if(!empty($this->_aCmt))
+                    $aCheckFuncParams = array($this->_aCmt);
+                break;
+
+            case 'item-score':
+                $sCheckFuncName = 'isScoreAllowed';
+                if(!empty($this->_aCmt))
+                    $aCheckFuncParams = array($this->_aCmt);
+                break;
+
+            case 'item-report':
+                $sCheckFuncName = 'isReportAllowed';
+                if(!empty($this->_aCmt))
+                    $aCheckFuncParams = array($this->_aCmt);
+                break;
+
+            case 'item-pin':
+                $sCheckFuncName = 'isPinAllowed';
+                if(!empty($this->_aCmt))
+                    $aCheckFuncParams = array($this->_aCmt);
+                break;
+
+            case 'item-unpin':
+                $sCheckFuncName = 'isUnpinAllowed';
+                if(!empty($this->_aCmt))
+                    $aCheckFuncParams = array($this->_aCmt);
+                break;
+
+            case 'item-edit':
+                $sCheckFuncName = 'isEditAllowed';
+                if(!empty($this->_aCmt))
+                    $aCheckFuncParams = array($this->_aCmt);
+                break;
+
+            case 'item-delete':
+                $sCheckFuncName = 'isRemoveAllowed';
+                if(!empty($this->_aCmt))
+                    $aCheckFuncParams = array($this->_aCmt);
+                break;
+
+            case 'item-more':
+                $sCheckFuncName = 'isMoreAllowed';
+                if(!empty($this->_aCmt))
+                    $aCheckFuncParams = array($this->_aCmt);
+                break;
+        }
+
+        if(!$sCheckFuncName || !method_exists($this->_oCmts, $sCheckFuncName))
+            return true;
+
+        return call_user_func_array(array($this->_oCmts, $sCheckFuncName), $aCheckFuncParams);
+    }
+}
+
+/** @} */

+ 396 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/template/scripts/BxBaseMenu.php

@@ -0,0 +1,396 @@
+<?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
+ * @{
+ */
+
+require_once(BX_DIRECTORY_PATH_INC . "design.inc.php");
+
+/**
+ * Menu representation.
+ * @see BxDolMenu
+ */
+class BxBaseMenu extends BxDolMenu
+{
+    protected $_oTemplate;
+    protected $_iPageType; 
+    protected $_aOptionalParams = array('target' => '', 'onclick' => '');
+    protected $_bDisplayAddons = false;
+
+    public function __construct ($aObject, $oTemplate)
+    {
+        parent::__construct ($aObject);
+
+        if ($oTemplate)
+            $this->_oTemplate = $oTemplate;
+        else
+            $this->_oTemplate = BxDolTemplate::getInstance();
+
+        $this->_iPageType = false;
+    }
+
+    public function getDisplayAddons()
+    {
+        return $this->_bDisplayAddons;
+    }   
+
+    public function setDisplayAddons($b)
+    {
+        $bRet = $this->_bDisplayAddons;
+        $this->_bDisplayAddons = $b;
+        return $bRet;
+    }
+
+    /**
+     * Get menu code.
+     * @return string
+     */
+    public function getCode ()
+    {
+        $sMenuTitle = isset($this->_aObject['title']) ? _t($this->_aObject['title']) : 'Menu-' . rand(0, PHP_INT_MAX);
+        if (isset($GLOBALS['bx_profiler'])) $GLOBALS['bx_profiler']->beginMenu($sMenuTitle);
+
+        if(!$this->_iPageType)
+            $this->_iPageType = BxDolTemplate::getInstance()->getPageType();
+
+        $s = '';
+        $aVars = $this->_getTemplateVars ();
+        if (!empty($aVars['bx_repeat:menu_items'])) {
+            $this->_addJsCss();
+            $s = $this->_getCode($this->_aObject['template'], $aVars);
+        }
+
+        if (isset($GLOBALS['bx_profiler'])) $GLOBALS['bx_profiler']->endMenu($sMenuTitle);
+
+        return $s;
+    }
+
+    /**
+     * Get menu code API.
+     * @return array
+     */
+    public function getCodeAPI ()
+    {
+        $aItems = [];
+
+        $aVars = $this->_getTemplateVars();
+        if(!empty($aVars['bx_repeat:menu_items']))
+            $aItems = $aVars['bx_repeat:menu_items'];
+
+        return [
+            'object' => $this->_sObject, 
+            'items' => $aItems
+        ];
+    }
+
+    protected function _getCode($sTmplName, $aTmplVars)
+    {
+        return $this->_oTemplate->parseHtmlByName($this->getTemplateName($sTmplName), $aTmplVars);
+    }
+
+    public function getCodeItem ($sName)
+    {
+        if(empty($sName))
+            return '';
+
+        $sCode = $this->_oTemplate->getHtml(str_replace('.html', '_item.html', $this->_aObject['template']));
+        if(empty($sCode))
+            return '';
+
+        $mixedTmplVars = $this->getMenuItem($sName);
+        if($mixedTmplVars === false)
+            return '';
+
+        return $this->_oTemplate->parseHtmlByContent($sCode, $mixedTmplVars);
+    }
+
+    /**
+     * Get template variables array
+     * @return array
+     */
+    protected function _getTemplateVars ()
+    {
+        return array (
+            'object' => $this->_sObject,
+            'bx_repeat:menu_items' => $this->getMenuItems (),
+        );
+    }
+
+    /**
+     * Get menu items array, which is ready to pass to menu template. 
+     * @return array or false
+     */
+    public function getMenuItems ()
+    {
+        if (!isset($this->_aObject['menu_items']))
+            $this->_aObject['menu_items'] = $this->getMenuItemsRaw ();
+
+        $aItems = array();
+        foreach ($this->_aObject['menu_items'] as $aItem) {
+            $aItem = $this->_getMenuItem ($aItem);
+            if($aItem !== false)
+                $aItems[] = $aItem;
+        }
+
+        return $aItems;
+    }
+    
+    /**
+     * Get menu item array, which is ready to pass to whole menu or 
+     * single menu item template. May return false if single menu item 
+     * is requested but cannot be shown by circumstances.
+     * @return array or false
+     */
+    public function getMenuItem ($sName)
+    {
+        if (!isset($this->_aObject['menu_items']))
+            $this->_aObject['menu_items'] = $this->getMenuItemsRaw ();
+
+        if(!empty($sName))
+            return $this->_getMenuItem($this->_aObject['menu_items'][$sName]);
+    }
+
+    /**
+     * Get menu items array, this is just a wrapper for DB function for make it easier to override.
+     * It is used in @see BxBaseMenu::getMenuItems
+     * @return array
+     */
+    protected function getMenuItemsRaw ()
+    {
+        if($this->_bMultilevel)
+            return $this->_oQuery->getMenuItemsHierarchy();
+        else 
+            return $this->_oQuery->getMenuItems();
+    }
+
+    protected function _getMenuItem ($a)
+    {
+        if (!$this->_isActive($a) || !$this->_isVisible($a))
+            return false;
+
+        if ($this->_bIsApi) {
+            list ($sIcon, $sIconUrl) = $this->_getMenuIcon($a);
+
+            if(($aMarkers = $this->_getMenuMarkers($a)) && is_array($aMarkers))
+                $this->addMarkers($aMarkers);
+
+            $a = $this->_replaceMarkers($a);
+
+            return [
+                'id' => $a['id'],
+                'name' => $a['name'],
+                'title' => _t($a['title']),
+                'link' => isset($a['link']) ? $this->_oPermalinks->permalink($a['link']) : '',
+                'icon' => $sIcon ? $sIcon : '',
+                'image' => $sIconUrl ? $sIconUrl : '',
+                'submenu' => !empty($a['submenu_object']) ? $a['submenu_object'] : ''
+            ];
+        }
+
+        $a['object'] = $this->_sObject;
+
+        $a['title'] = _t($a['title']);
+        $a['bx_if:title'] = array(
+            'condition' => !empty($a['title']),
+            'content' => array(
+                'title' => $a['title']
+            )
+        );
+
+        $this->removeMarker('addon');
+
+        $a = $this->_replaceMarkers($a);
+
+        if ($this->_bDisplayAddons) {
+            $mixedAddon = $this->_getMenuAddon($a);
+            if (!is_array($mixedAddon)) {
+                $this->addMarkers(array('addon' => $mixedAddon));
+                $a = $this->_replaceMarkers($a);
+            }
+        }
+
+        $aMarkers = $this->_getMenuMarkers($a);
+        if ($aMarkers && is_array($aMarkers)) {
+            $this->addMarkers($aMarkers);
+            $a = $this->_replaceMarkers($a);
+        }
+
+        list ($sIcon, $sIconUrl, $sIconA, $sIconHtml) = $this->_getMenuIcon($a);
+
+        $a['class_add'] = $this->_isSelected($a) ? 'bx-menu-tab-active' : '';
+        $a['class_add'] .= $this->_getVisibilityClass($a);
+
+        $a['link'] = isset($a['link']) ? $this->_oPermalinks->permalink($a['link']) : 'javascript:void(0);';
+        $a['title_attr'] = bx_html_attribute(strip_tags($a['title']));
+
+        $a['attrs'] = $this->_getMenuAttrs($a);
+
+        $a['bx_if:image'] = array (
+            'condition' => (bool)$sIconUrl,
+            'content' => array('icon_url' => $sIconUrl),
+        );
+        $a['bx_if:image_inline'] = array (
+            'condition' => false,
+            'content' => array('image' => ''),
+        );
+        $a['bx_if:icon'] = array (
+            'condition' => (bool)$sIcon,
+            'content' => array('icon' => $sIcon),
+        );
+        $a['bx_if:icon-html'] = array (
+            'condition' => (bool)$sIconHtml,
+            'content' => array('icon' => $sIconHtml),
+        );
+        $a['bx_if:icon-a'] = array (
+            'condition' => (bool)$sIconA,
+            'content' => array('icon-a' => $sIconA),
+        );
+        $a['bx_if:title'] = array (
+            'condition' => (bool)$a['title'],
+            'content' => array(
+                'title' => $a['title'],
+                'title_attr' => $a['title_attr']
+            ),
+        );
+
+        $aTmplVarsAddon = $this->_bDisplayAddons ? $this->_getTmplVarsAddon($mixedAddon, $a) : array('addon' => '', 'addonf' => '');
+        $a['bx_if:addon'] = array (
+            'condition' => $this->_bDisplayAddons && !empty($aTmplVarsAddon['addon']),
+            'content' => $aTmplVarsAddon
+        );
+
+        $aTmplVarsSubitems = array('subitems' => '');
+        $bTmplVarsSubitems = $this->_bMultilevel && !empty($a['subitems']);
+        if($bTmplVarsSubitems) {
+            $sClassCollpsed = 'bx-mi-collapsed';
+            if(($iCollapsed = $this->getUserChoiceCollapsedSubmenu($a)) !== false)
+                $a['class_add'] .= $iCollapsed ? ' ' . $sClassCollpsed : '';
+            else if(isset($a['collapsed']) && $a['collapsed'])
+                $a['class_add'] .= ' ' . $sClassCollpsed;
+
+            $aSubitems = array();
+            foreach($a['subitems'] as $aSubitem) {
+                $aSubitem = $this->_getMenuItem($aSubitem);
+                if($aSubitem !== false)
+                    $aSubitems[] = $aSubitem;
+            }
+
+            $aTmplVarsSubitems['subitems'] = $this->_oTemplate->parseHtmlByName(str_replace('.html', '_subitems.html', $this->getTemplateName()), array(
+                'bx_repeat:menu_items' => $aSubitems,
+            ));
+        }
+
+        $a['bx_if:show_subitems'] = array (
+            'condition' => $bTmplVarsSubitems,
+            'content' => $aTmplVarsSubitems
+        );
+
+        unset($a['subitems']);
+
+        foreach ($this->_aOptionalParams as $sName => $sDefaultValue)
+            if (!isset($a[$sName]))
+                $a[$sName] = $sDefaultValue;
+
+        return $a;
+    }
+    
+    protected function _getMenuIcon ($a)
+    {
+        return BxTemplFunctions::getInstanceWithTemplate($this->_oTemplate)->getIcon(!empty($a['icon']) ? $a['icon'] : '');
+    }
+    
+    public function getMenuIconHtml($sIcon)
+    {
+        list ($sIcon, $sIconUrl, $sIconA, $sIconHtml) = BxTemplFunctions::getInstanceWithTemplate($this->_oTemplate)->getIcon($sIcon);
+
+        $a['bx_if:image'] = array (
+            'condition' => (bool)$sIconUrl,
+            'content' => array('icon_url' => $sIconUrl),
+        );
+        $a['bx_if:icon'] = array (
+            'condition' => (bool)$sIcon,
+            'content' => array('icon' => $sIcon),
+        );
+        $a['bx_if:icon-a'] = array (
+            'condition' => (bool)$sIconA,
+            'content' => array('icon-a' => $sIconA),
+        );
+        $a['bx_if:icon-html'] = array (
+            'condition' => (bool)$sIconHtml,
+            'content' => array('icon' => $sIconHtml),
+        );
+
+        return $this->_oTemplate->parseHtmlByName('menu_icon.html', $a);
+    }
+
+    protected function _getMenuAddon ($aMenuItem)
+    {
+        if (empty($aMenuItem['addon']))
+            return '';
+
+        if (isset($aMenuItem['addon_cache']) && $aMenuItem['addon_cache']) {
+            $oCache = BxDolDb::getInstance()->getDbCacheObject();
+            $sKey = 'menu_' . $this->_sObject . '_' . $aMenuItem['name'] . '_' . bx_get_logged_profile_id() . '_' . bx_site_hash() . '.php';
+            $s = $oCache->getData($sKey);
+            if ($s !== null) {
+                return $s;
+            }
+            else {
+                $s = BxDolService::callSerialized($aMenuItem['addon'], $this->_aMarkers);
+                $oCache->setData($sKey, $s);
+            }
+
+            return $s;
+        }
+        else {
+            return BxDolService::callSerialized($aMenuItem['addon'], $this->_aMarkers);
+        }
+    }
+
+    protected function _getMenuMarkers ($aMenuItem)
+    {
+        if (empty($aMenuItem['markers']))
+            return '';
+
+        return BxDolService::callSerialized($aMenuItem['markers'], $this->_aMarkers);
+    }
+
+    protected function _getMenuAttrs ($aMenuItem)
+    {
+        $sAttrs = '';
+        if(!empty($aMenuItem['target']))
+            $sAttrs .= ' target="' . $aMenuItem['target'] . '"';
+
+        if($this->_bAddNoFollow && !empty($aMenuItem['link']) && preg_match('@^https?://@', $aMenuItem['link']) && strncmp($aMenuItem['link'], BX_DOL_URL_ROOT, strlen(BX_DOL_URL_ROOT)) !== 0)
+            $sAttrs .= ' rel="noreferrer"';
+
+        return $sAttrs;
+    }
+
+    /**
+     * Add css/js files which are needed for menu display and functionality.
+     */
+    protected function _addJsCss()
+    {
+        $this->_oTemplate->addCss('menu.css');
+    }
+
+    protected function _getTmplVarsAddon($mixedAddon, $aMenuItem) 
+    {
+        $sAddon = '';
+        if(!is_array($mixedAddon))
+            $sAddon = $mixedAddon;
+        else if(!empty($mixedAddon['addon']))
+            $sAddon = $mixedAddon['addon'];
+
+        return array(
+            'addon' => $sAddon,
+            'addonf' => ''
+        );
+    }
+}
+
+/** @} */

+ 154 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/template/scripts/BxBaseMenuCustom.php

@@ -0,0 +1,154 @@
+<?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
+ *
+ * @{
+ */
+
+class BxBaseMenuCustom extends BxTemplMenuMoreAuto
+{
+    protected $_sTmplContent;
+    protected $_sTmplContentItem;
+
+    public function __construct($aObject, $oTemplate = false)
+    {
+        parent::__construct($aObject, $oTemplate);
+
+        $this->_sTmplContent = $this->_oTemplate->getHtml($this->getTemplateName());
+        $this->_sTmplContentItem = $this->_oTemplate->getHtml($this->getTemplateNameItem());
+
+        $this->_sTmplNameItemMore = 'menu_custom_item_more.html';
+    }
+
+    /**
+     * Get template name with checking for custom template related to exactly this menu object.
+     * @return string with template name.
+     */
+    public function getTemplateNameItem($sName = '')
+    {
+        if(empty($sName))
+            $sName = 'menu_custom_item.html';
+
+        $sNameCustom = str_replace('.html', '_' . $this->_sObject . '.html', $sName);
+        return $this->_oTemplate->isHtml($sNameCustom) ? $sNameCustom : $sName;
+    }
+
+    public function setTemplateById ($iTemplateId)
+    {
+        $sTemplate = $this->_aObject['template'];
+
+        switch($iTemplateId) {
+            case BX_DB_MENU_TEMPLATE_TABS:
+                $iTemplateId = BX_MENU_TEMPLATE_CUSTOM_HOR;
+                break;
+
+            case BX_DB_MENU_TEMPLATE_POPUP:
+                $iTemplateId = BX_MENU_TEMPLATE_CUSTOM_VER;
+                break;
+        }
+
+        parent::setTemplateById ($iTemplateId);
+
+        if($sTemplate != $this->_aObject['template'])
+            $this->_sTmplContent = $this->_oTemplate->getHtml($this->getTemplateName());
+    }
+
+    protected function _getCode($sTmplName, $aTmplVars)
+    {
+        if($sTmplName != $this->_aObject['template'])
+            return parent::_getCode($sTmplName, $aTmplVars);
+
+        return $this->_oTemplate->parseHtmlByContent($this->_sTmplContent, $aTmplVars);
+    }
+
+    protected function _getMenuItem ($aItem)
+    {
+    	if (!$this->_isActive($aItem) || !$this->_isVisible($aItem))
+            return false;
+
+    	$sMethod = '_getMenuItem' . str_replace(' ', '', ucwords(str_replace('-', ' ', $aItem['name'])));
+        $bMethod = method_exists($this, $sMethod);
+
+        $mixedItem = false;
+        if($bMethod)
+            $mixedItem = $this->$sMethod($aItem);
+
+    	if($mixedItem === true || !$bMethod) {
+            $aItem = parent::_getMenuItem($aItem);
+            if($aItem === false)
+                return false;
+
+            $mixedItem = $this->{'_getMenuItemDefault' . ($this->_bIsApi ? 'Api' : '')}($aItem);
+    	}
+
+    	if(empty($mixedItem))
+            return false;
+
+        if($this->_bIsApi)
+            return $mixedItem;
+
+        $sItem = $sClass = '';
+        if(is_array($mixedItem)) 
+            list($sItem, $sClass) = $mixedItem;
+        else
+            $sItem = $mixedItem;
+
+        if(!empty($sClass))
+            $sClass = ' ' . $sClass;
+        if($this->_isSelected($aItem))
+            $sClass .= ' bx-menu-tab-active';
+
+        $sClass .= $this->_getVisibilityClass($aItem);
+
+        return [
+            'name' => $aItem['name'],
+            'class' => $sClass,
+            'item' => $sItem
+        ];
+    }
+
+    protected function _getMenuItemMoreAuto ($aItem)
+    {
+        $aItem['onclick'] = $this->_sJsObjectMoreAuto . '.more(this);';
+
+        $aItem = parent::_getMenuItem($aItem);
+        if($aItem === false)
+            return false;
+
+        if($this->_bIsApi)
+            return $aItem;
+
+        return $this->_oTemplate->parseHtmlByContent($this->_getTmplContentItemMore(), array(
+            'item' => $this->_getMenuItemDefault($aItem),
+            'popup' => BxTemplFunctions::getInstance()->transBox($this->_aHtmlIds['more_auto_popup'], $this->_oTemplate->parseHtmlByContent($this->_getTmplContentItemMorePopup(), array(
+                'content' => ''
+            )), true)
+        ));
+    }
+
+    protected function _getMenuItemDefault ($aItem)
+    {
+        if(!isset($aItem['class_wrp']))
+            $aItem['class_wrp'] = '';
+
+        if(!isset($aItem['class_link']))
+            $aItem['class_link'] = '';
+
+        return $this->_oTemplate->parseHtmlByContent($this->_getTmplContentItem(), $aItem);
+    }
+
+    protected function _getMenuItemDefaultApi ($aItem)
+    {
+        return $aItem;
+    }
+
+    protected function _getTmplContentItem()
+    {
+        return $this->_sTmplContentItem;
+    }
+}
+
+/** @} */

+ 1076 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/template/scripts/BxBasePage.php

@@ -0,0 +1,1076 @@
+<?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
+ * @{
+ */
+
+require_once(BX_DIRECTORY_PATH_INC . 'design.inc.php');
+
+/**
+ * Page representation.
+ * @see BxDolPage
+ */
+class BxBasePage extends BxDolPage
+{
+    protected $_oTemplate;
+
+    protected $_sStorage; //--- Storage object for page's images like custom cover, HTML block attachments, etc.
+    protected $_oPageCacheObject = null;
+    protected $_oBlockCacheObject = null;
+    
+    protected $_bStickyColumns = false;
+
+    protected $_bSubPage = false;
+
+    public function __construct ($aObject, $oTemplate)
+    {
+        parent::__construct ($aObject);
+
+        if ($oTemplate)
+            $this->_oTemplate = $oTemplate;
+        else
+            $this->_oTemplate = BxDolTemplate::getInstance();
+
+		$this->_sStorage = 'sys_images';
+        
+        $this->_sJsClassName = 'BxDolPage';
+        $this->_sJsObjectName = 'oBxDolPage';
+        
+        $this->_bStickyColumns = isset($this->_aObject['sticky_columns']) && $this->_aObject['sticky_columns'] == 1;
+
+        $sHtmlId = str_replace(array('_' , ' '), array('-', '-'), $this->_sObject);
+        $this->_aHtmlIds = array(
+            'help_popup' => $sHtmlId . '-help-popup-',
+        );
+    }
+
+    public function performActionGetHelp ()
+    {
+        $iBlockId = (int)bx_get('block_id');
+        if(empty($iBlockId))
+            return;
+
+        $aBlock = $this->_oQuery->getPageBlock($iBlockId);
+        if(empty($aBlock) || !is_array($aBlock) || empty($aBlock['help']))
+            return;
+
+        echo $this->_oTemplate->parseHtmlByName('designbox_help_popup.html', array(
+            'content' => _t($aBlock['help'])
+        ));
+    }
+    
+    public function performActionGetShare ()
+    {
+        $aEmbedData = BxDolPage::getEmbedData(bx_get('url'));
+        if(!isset($aEmbedData['url'])) 
+            return; 
+
+        $sMenu = '';
+        if(getParam('sys_a2a_enable') != 'on') {
+            if(($oMenu = BxDolMenu::getObjectInstance('sys_social_sharing')) !== false) {
+                $oMenu->addMarkers([
+                    'url' => $aEmbedData['url'],
+                    'img_url_encoded' => '',
+                    'title_encoded' => ''
+                ]);
+                $sMenu = $oMenu->getCode();
+            }
+        }
+        else
+            $sMenu = getParam('sys_a2a_code');
+
+        echo PopupBox('sys_share_popup', _t('_sys_txt_share_popup_header'), $this->_oTemplate->parseHtmlByName('designbox_share_popup.html', [
+            'url' => bx_process_output($aEmbedData['url']),
+            'menu' => $sMenu,
+            'code' => htmlspecialchars($aEmbedData['html']),
+        ]), true);
+    }
+    
+    public function performActionEmbed ()
+    {
+        $aCover = $this->getPageCoverImage();
+        $sTitle = $this->_getPageTitle();
+        $this->_oTemplate->getEmbed($this->_oTemplate->parseHtmlByName('embed_card.html', [
+            'title' => $sTitle,
+            'url' => BxDolPermalinks::getInstance()->permalink($this->_aObject['url']),
+            'bx_if:thumb' => [
+                'condition' => $this->isPageCover() && count($aCover) > 0,
+                'content' => [
+                    'title' => $sTitle,
+                    'url' => BxDolPermalinks::getInstance()->permalink($this->_aObject['url']),
+                    'background' => BxDolCover::getInstance(BxDolTemplate::getInstance())->getCoverImageUrl($aCover)
+                ],
+            ],
+            'bx_if:no_thumb' => [
+                'condition' => count($aCover) == 0 || !$this->isPageCover(),
+                'content' => [
+                    'title' => $sTitle,
+                    'url' => BxDolPermalinks::getInstance()->permalink($this->_aObject['url']),
+                ]
+            ]
+        ]
+        ));
+    }
+
+    /**
+     * Very similar to BxBasePage::getCode
+     * but adds css and js files which are needed for the corect page display
+     */ 
+    public function getCodeDynamic ()
+    {
+        // TODO: remake to use use collect* template methods
+
+        $oTemplate = BxDolTemplate::getInstance();
+
+        // get js&css before the page code is generated
+        $aCssBefore = $oTemplate->getCss();
+        $aJsBefore = $oTemplate->getJs();
+
+        // generate page code
+        $sContent = $this->getCode();
+
+        // get js&css after the page code is generated
+        $aCssAfter = $oTemplate->getCss();
+        $aJsAfter = $oTemplate->getJs();
+
+        // compare files which were added before and after page code is generated
+        $f = function ($a1, $a2) {
+            return strcasecmp($a1['url'], $a2['url']);
+        };
+        $aCssNew = array_udiff($aCssAfter, $aCssBefore, $f);
+        $aJsNew = array_udiff($aJsAfter, $aJsBefore, $f);
+
+        // add newly added js&css files in static mode
+        $sCss = $sJs = '';
+        foreach ($aCssNew as $a)
+            $sCss .= $oTemplate->addCss($a['url'], true);
+        foreach ($aJsNew as $a)
+            $sJs .= $oTemplate->addJs($a['url'], true);
+
+        return $sJs . $sCss . $sContent;
+    }
+    
+    /**
+     * Get page code with automatic caching, adding necessary css/js files and system template vars.
+     * @return string.
+     */
+    public function getCode ()
+    {
+        if (bx_get('dynamic') && ($iBlockId = (int)bx_get('pageBlock'))) {
+
+            if (self::isLockedFromUnauthenticated($this->_aObject['uri']) || !$this->_isVisiblePage($this->_aObject)) {
+                header('HTTP/1.0 403 Forbidden');
+                exit;
+            }
+
+            bx_alert('system', 'page_output_block', 0, false, array(
+                'page_name' => $this->_sObject,
+                'page_object' => $this,
+                'page_query' => $this->_oQuery,
+                'block_id' => (int)$iBlockId,
+            ));
+
+            header( 'Content-type:text/html;charset=utf-8' );
+
+            BxDolTemplate::getInstance()->collectingStart();
+
+            $s = $this->_getBlockOnlyCode($iBlockId);
+
+            $sCss = bx_get('includedCss');
+            $sJs = bx_get('includedJs');
+            echo BxDolTemplate::getInstance()->collectingEndGetCode($sCss ? @json_decode($sCss) : [], $sJs ? @json_decode($sJs) : []);
+
+            echo $s;
+            exit;
+        }
+
+        if (!$this->_isVisiblePage($this->_aObject))
+            return $this->_getPageAccessDeniedMsg ();
+
+        $this->_addJsCss();
+
+        if (!$this->_bSubPage) {
+            $this->_addSysTemplateVars();
+
+            $this->_selectMenu();
+
+            $this->_setSubmenu(array());
+        }
+
+        if (!getParam('sys_page_cache_enable') || !$this->_aObject['cache_lifetime'] || (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'POST')) {
+            $sPageCode = $this->_getPageCode();
+        }
+        else {
+            $oCache = $this->_getPageCacheObject();
+            $sKey = $this->_getPageCacheKey();
+            $sKeyCssJs = 'cssjs_' . $sKey;
+
+            $mixedRet = $oCache->getData($sKey, $this->_aObject['cache_lifetime']);
+            $aRetCssJs = $oCache->getData($sKeyCssJs, $this->_aObject['cache_lifetime']);
+
+            if ($mixedRet !== null && $aRetCssJs !== null) {
+                BxDolTemplate::getInstance()->collectingStart();                
+                BxDolTemplate::getInstance()->collectingInject($aRetCssJs['css'], $aRetCssJs['js']);
+                $sPageCode = BxDolTemplate::getInstance()->collectingEndGetCode();
+                $sPageCode .= $mixedRet;
+            } else {
+
+                BxDolTemplate::getInstance()->collectingStart();
+
+                $sPageCode = $this->_getPageCode();
+
+                $aPageCssJs = BxDolTemplate::getInstance()->collectingEndGetCode(array(), array(), 'array');
+
+                $oCache->setData($sKey, $sPageCode, $this->_aObject['cache_lifetime']);
+                $oCache->setData($sKeyCssJs, $aPageCssJs, $this->_aObject['cache_lifetime']);
+            }
+        }
+        
+        bx_alert('system', 'page_output', 0, false, array(
+            'page_name' => $this->_sObject,
+            'page_object' => $this,
+            'page_query' => $this->_oQuery,
+            'page_code' => &$sPageCode,
+            'sub_page' => $this->_bSubPage,
+        ));
+
+        if (!$this->_bSubPage)
+            $sPageCode .= $this->getJsScript();
+        
+        return $sPageCode;
+    }
+    
+    public function getJsClassName()
+    {
+        return $this->_sJsClassName;
+    }
+
+    public function getJsObjectName()
+    {
+        return $this->_sJsObjectName;
+    }
+    
+    function _wrapInTagJsCode($sCode)
+    {
+        return "<script language=\"javascript\">\n<!--\n" . $sCode . "\n-->\n</script>";
+    }
+
+    public function getJsScript()
+    {
+        $sJsObjName = $this->getJsObjectName();
+        $sJsObjClass = $this->getJsClassName();
+        $sCode = "if(window['" . $sJsObjName . "'] == undefined) var " . $sJsObjName . " = new " . $sJsObjClass . "(" . json_encode(array(
+            'sObjName' => $sJsObjName,
+            'sObject' => $this->_sObject,
+            'sRootUrl' => BX_DOL_URL_ROOT,
+            'aHtmlIds' => $this->_aHtmlIds,
+            'isStickyColumns' => $this->_bStickyColumns
+        )) . ");";
+
+        return $this->_wrapInTagJsCode($sCode);
+    }
+
+    /**
+     * Is page cover enabled.
+     * @return string
+     */
+    public function isPageCover()
+    {
+        $bResult = false;
+        switch((int)$this->_aObject['cover']) {
+            case 1: //--- Enabled for all
+                $bResult = true;
+                break;
+
+            case 2: //--- Enabled for visitors only
+                $bResult = !isLogged();
+                break;
+
+            case 3: //--- Enabled for members only
+                $bResult = isLogged();
+                break;
+        }
+
+    	return $bResult;
+    }
+
+    public function setSubPage($b = true)
+    {
+        $this->_bSubPage = $b;
+    }
+
+    public function setPageCover($bCover = true)
+    {
+        $this->_aObject['cover'] = (bool)$bCover;
+    }
+
+	public function getPageCoverImage($bTranscoder = true)
+    {
+    	$iId = (int)$this->_aObject['cover_image'];
+    	if(empty($iId)) {
+    		$iId = (int)getParam('sys_site_cover_common');
+    		if(empty($iId))
+    			return array();
+    	}
+
+    	$aResult = array(
+    		'id' => $iId
+    	);
+    	if($bTranscoder)
+    		$aResult['transcoder'] = BX_DOL_TRANSCODER_OBJ_COVER;
+    	else 
+    		$aResult['object'] = $this->_sStorage;
+
+    	return $aResult;
+    }
+
+    public function getPageCoverParams()
+    {
+        $oMenuSubmenu = BxDolMenu::getObjectInstance('sys_site_submenu');
+        if($oMenuSubmenu) {
+            $aParams = $oMenuSubmenu->getPageCoverParams();
+            if(!empty($aParams) && is_array($aParams))
+                return $aParams;
+        }
+
+    	return array (
+            'title' => $this->_getPageTitle(),
+            'actions' => '',
+            'bx_if:image' => array (
+                'condition' => false,
+                'content' => array(),
+            ),
+            'bx_if:icon' => array (
+                'condition' => false,
+                'content' => array(),
+            ),
+        );
+    }
+
+    /**
+     * Get block title.
+     * @return string
+     */
+    public function getBlockTitle ($aBlock)
+    {
+        return $this->_replaceMarkers(_t($aBlock['title']), array('block_id' => $aBlock['id']));
+    }
+
+    /**
+     * Get help control if help is available for the block.
+     * @return string
+     */
+    public function getBlockHelp ($aBlock)
+    {
+        if(empty($aBlock['help']) || empty(strip_tags(_t($aBlock['help']))))
+            return '';
+
+        return $this->_oTemplate->parseHtmlByName('designbox_help.html', array(
+            'js_object' => $this->_sJsObjectName,
+            'block_id' => $aBlock['id']
+        ));
+    }
+
+    /**
+     * Get code to load block asyncroniously
+     * @param $aBlock block code
+     * @param $iAsync if greater than 0 the it defines loading indicator for the block
+     * @return HTML code string
+     */
+    public function getBlockAsyncCode($aBlock, $iAsync)
+    {
+        $aContentPlaceholder = $this->_oQuery->getPageBlockContentPlaceholder($iAsync);
+        if (!$aContentPlaceholder)
+            return _t('_sys_txt_error_occured');
+        $oTemplate = $this->_oTemplate;
+        if ('system' != $aContentPlaceholder['module']) {
+            $oModule = BxDolModule::getInstance($aContentPlaceholder['module']);
+            if (!$oModule)
+                return _t('_sys_txt_error_occured');
+            $oTemplate = $oModule->_oTemplate;
+        }
+        return $oTemplate->parseHtmlByName($aContentPlaceholder['template'], $aBlock);
+    }
+
+    /**
+     * Get page array with all cells and blocks
+     */
+    public function getPageAPI ($aBlocks = [])
+    {
+        $a = [
+            'id' => $this->_aObject['id'],
+            'title' => $this->_getPageTitle(),
+            'uri' => $this->_aObject['uri'],
+            'url' => bx_get('params')[0],
+            'author' => $this->_aObject['author'],
+            'added' => $this->_aObject['added'],
+            'module' => $this->getModule(),
+            'type' => $this->getType (),
+            'layout' => str_replace('.html', '', $this->_aObject['template']),
+            'cover_block' => '',
+            'menu_top' => '',
+            'menu' => '',
+            'menu_bottom' => '',
+            'elements' => $this->getPageBlocksAPI($aBlocks),
+        ];
+        
+        
+        if (BxDolCover::getInstance($this)->isCover() && isset($this->_aProfileInfo)){
+            
+            $oModule = BxDolModule::getInstance($this->getModule());
+            
+            $a['cover_block'] = [
+                'profile' => BxDolProfile::getInstance()->getData($this->_aProfileInfo['id'], ['with_info' => true]),
+                'actions_menu' => '',
+                'meta_menu' => '',
+                'cover' => $oModule->serviceGetCover($this->_aProfileInfo['content_id'])
+            ];
+            
+            $CNF = $oModule->_oConfig->CNF;
+            
+            if(!empty($CNF['OBJECT_MENU_VIEW_ENTRY_META'])){
+                $oMetaMenu = BxTemplMenu::getObjectInstance($CNF['OBJECT_MENU_VIEW_ENTRY_META']);
+                $a['cover_block']['meta_menu'] =  $oMetaMenu->getCodeAPI();
+            }
+            
+            if(!empty($CNF['OBJECT_MENU_ACTIONS_VIEW_ENTRY'])){
+                $oActionMenu = BxTemplMenu::getObjectInstance($CNF['OBJECT_MENU_ACTIONS_VIEW_ENTRY']);
+                $a['cover_block']['actions_menu'] = $oActionMenu->getCodeAPI();
+            }  
+        }
+        
+        $sMenuTop = getParam('sys_api_menu_top');
+        if (!empty($sMenuTop) && ($oMenuTop = BxDolMenu::getObjectInstance($sMenuTop)) !== false)
+            $a['menu_top'] = $oMenuTop->getCodeAPI();
+
+        if (($oMenuSubmenu = BxDolMenu::getObjectInstance('sys_site_submenu')) !== false) {
+            if(($sSubmenu = $this->getSubMenu()) !== '')
+                $oMenuSubmenu->setObjectSubmenu($sSubmenu, []);
+
+            $a['menu'] = $oMenuSubmenu->getCodeAPI();
+        }
+
+        if (isLogged()) {
+            $o = BxDolProfile::getInstance();
+            $a['user'] = [
+                'id' => $o->id(),
+                'display_name' => $o->getDisplayName(),
+                'url' => $o->getUrl(),
+                'avatar' => $o->getAvatar(),
+                'info' => $o->getInfo(),
+                'active' => $o->isActive(),
+                'status' => $o->getStatus(),
+            ];
+        }
+
+        return $a;
+    }
+
+    public function getPageBlocksAPI($aBlocks = [])
+    {
+        $bBlocks = !empty($aBlocks) && is_array($aBlocks);
+        $aFieldsUnset = ['object', 'cell_id', 'title_system', 'class', 'submenu', 'tabs', 'async', 'visible_for_levels', 'hidden_on', 'type', 'text', 'text_updated', 'help', 'cache_lifetime', 'active', 'active_api', 'copyable', 'deletable', 'order'];
+
+        $aCells = $this->_oQuery->getPageBlocks(true);
+        foreach($aCells as $sKey => &$aCell) {
+            foreach($aCell as $i => $aBlock) {     
+                if(!$this->_isVisibleBlock($aBlock)) {
+                    unset($aCells[$sKey][$i]);
+                    continue;
+                }
+
+                $this->processPageBlock($aCells[$sKey][$i], true);
+                $aBlock = $aCells[$sKey][$i];
+
+                $sSource = '';
+                if($aBlock['type'] == 'service') {
+                    $aContent = @unserialize($aBlock['content']);
+                    if(isset($aContent['module'], $aContent['method']))
+                        $sSource = $aContent['module'] . ':' . $aContent['method'];
+                }
+
+                if($bBlocks && !in_array($sSource, $aBlocks)) {
+                    unset($aCells[$sKey][$i]);
+                    continue;
+                }
+
+                $sFunc = '_getBlock' . ucfirst($aBlock['type']);
+                $mBlock = method_exists($this, $sFunc) ? $this->$sFunc($aBlock) : $aBlock['content'];
+
+                $aCells[$sKey][$i] = array_merge($aCells[$sKey][$i], [
+                    'title' => isset($mBlock['title']) ? $mBlock['title'] : $this->getBlockTitle($aBlock),
+                    'content' => isset($mBlock['content']) ? $mBlock['content'] : $mBlock,
+                    'menu' => isset($mBlock['menu']) ? $mBlock['menu'] : '',
+                    'source' => $sSource
+                ]);
+                $aCells[$sKey][$i] = array_diff_key($aCells[$sKey][$i], array_flip($aFieldsUnset));
+            }
+        }
+
+        return array_map('array_values', $aCells);
+    }
+
+    /**
+     * Get page code vars
+     * @return string
+     */
+    protected function _getPageCodeVars ()
+    {
+    	$aHiddenOn = array(
+            pow(2, BX_DB_HIDDEN_PHONE - 1) => 'bx-def-media-phone-hide',
+            pow(2, BX_DB_HIDDEN_TABLET - 1) => 'bx-def-media-tablet-hide',
+            pow(2, BX_DB_HIDDEN_DESKTOP - 1) => 'bx-def-media-desktop-hide',
+            pow(2, BX_DB_HIDDEN_MOBILE - 1) => 'bx-def-mobile-app-hide'
+        );
+
+        $aVars = array (
+            'page_id' => 'bx-page-' . $this->_aObject['uri'],
+            'bx_if:show_layout_row_dump' => array(
+                'condition' => false,
+                'content' => array()
+            )
+        );
+        $aBlocks = $this->_oQuery->getPageBlocks();
+        foreach ($aBlocks as $sKey => $aCell) {
+            $sCell = '';
+            foreach ($aCell as $aBlock) {
+                $this->processPageBlock($aBlock, false);
+
+                $sContentWithBox = $this->_getBlockCodeWithCache($aBlock, $aBlock['async']);
+
+            	$sClassAdd = '';
+                if(!empty($aBlock['class']))
+                    $sClassAdd .= ' ' . $aBlock['class'];
+
+                if(!empty($aBlock['hidden_on']))
+                    foreach($aHiddenOn as $iHiddenOn => $sHiddenOnClass)
+                        if((int)$aBlock['hidden_on'] & $iHiddenOn)
+                            $sClassAdd .= ' ' . $sHiddenOnClass;
+
+                if ($sContentWithBox)
+                    $sCell .= $this->_oTemplate->parseHtmlByName('designbox_container.html', array(
+                        'class_add' => $sClassAdd,
+                        'bx_if:show_html_id' => array(
+                            'condition' => true,
+                            'content' => array(
+                                'html_id' => 'bx-page-block-' . $aBlock['id']
+                            ),
+                        ),
+                        'content' => $sContentWithBox
+                    ));
+            }
+            $aVars[$sKey] = $sCell;
+        }
+
+        return $aVars;
+    }
+
+    /**
+     * Process block values, especially if someting need to be overrided 
+     */
+    protected function processPageBlock(&$aBlock, $bApi = false) 
+    {
+
+    }
+
+    /**
+     * Get page code only.
+     * @return string
+     */
+    protected function _getPageCode ()
+    {
+        $aVars = $this->_getPageCodeVars ();
+        return $this->_oTemplate->parseHtmlByName($this->_aObject['template'], $aVars);
+    }
+
+    /**
+     * Get one block code only.
+     * @return string
+     */
+    protected function _getBlockOnlyCode ($iBlockId)
+    {
+        if (!($aBlock = $this->_oQuery->getPageBlock((int)$iBlockId)))
+            return '';
+        return $this->_getBlockCodeWithCache($aBlock, 0);
+    }
+
+
+    protected function _getBlockCodeWithCache(&$aBlock, $iAsync = 0)
+    {
+        if (!getParam('sys_pb_cache_enable') || !$aBlock['cache_lifetime'] || $iAsync || (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'POST')) {
+            $sBlockCode = $this->_getBlockCode($aBlock, $iAsync);
+        }
+        else {
+            $oCache = $this->_getBlockCacheObject();
+            $sKey = $this->_getBlockCacheKey(false, $aBlock);
+            $sKeyCssJs = 'cssjs_' . $sKey;
+
+            $mixedRet = $oCache->getData($sKey, $aBlock['cache_lifetime']);
+            $aRetCssJs = $oCache->getData($sKeyCssJs, $aBlock['cache_lifetime']);
+
+            if ($mixedRet !== null && $aRetCssJs !== null) {
+                BxDolTemplate::getInstance()->collectingStart();
+                BxDolTemplate::getInstance()->collectingInject($aRetCssJs['css'], $aRetCssJs['js']);
+                $sBlockCode = BxDolTemplate::getInstance()->collectingEndGetCode();
+                $sBlockCode .= $mixedRet;
+            } else {
+                BxDolTemplate::getInstance()->collectingStart();
+
+                $sBlockCode = $this->_getBlockCode($aBlock, $iAsync);
+
+                $aBlockCssJs = BxDolTemplate::getInstance()->collectingEndGetCode(array(), array(), 'array');
+
+                $oCache->setData($sKey, $sBlockCode, $aBlock['cache_lifetime']);
+                $oCache->setData($sKeyCssJs, $aBlockCssJs, $aBlock['cache_lifetime']);
+            }
+        }
+
+        return $sBlockCode;
+    }
+
+    /**
+     * Get block code.
+     * @return string
+     */
+    protected function _getBlockCode(&$aBlock, $iAsync = 0)
+    {
+        $sContentWithBox = '';
+        $oFunctions = $this->_oTemplate->getTemplateFunctions();
+
+        if (isset($GLOBALS['bx_profiler'])) $GLOBALS['bx_profiler']->beginPageBlock(_t($aBlock['title']), $aBlock['id']);
+
+        $aDbNoTitle = [BX_DB_CONTENT_ONLY, BX_DB_PADDING_CONTENT_ONLY, BX_DB_NO_CAPTION, BX_DB_PADDING_NO_CAPTION];
+
+        $sTitle = $this->getBlockTitle($aBlock);
+        $sHelp = $this->getBlockHelp($aBlock);
+
+        $sFunc = '_getBlock' . ucfirst($aBlock['type']);
+        $bBlockVisible = $this->_isVisibleBlock($aBlock);
+        if ($iAsync && $bBlockVisible) {
+            $iDesignboxId = $aBlock['designbox_id'];
+
+            $sHelpTitle = $sHelpContent = '';
+            if(!in_array($iDesignboxId, $aDbNoTitle))
+                $sHelpTitle = $sHelp;
+            else
+                $sHelpContent = $sHelp;
+
+            $sContent = $this->getBlockAsyncCode($aBlock, $iAsync);
+            $aParams = array(
+                $sTitle . $sHelpTitle,
+                $sContent . $sHelpContent,
+                $iDesignboxId
+            );
+            $sContentWithBox = call_user_func_array(array($oFunctions, 'designBoxContent'), $aParams);
+        } 
+        elseif ($bBlockVisible && method_exists($this, $sFunc)) {
+            $mixedContent = $this->$sFunc($aBlock);
+
+            $this->_oQuery->setReadOnlyMode(true);
+
+            if(is_array($mixedContent) && !empty($mixedContent['content'])) {
+                $iDesignboxId = isset($mixedContent['designbox_id']) ? $mixedContent['designbox_id'] : $aBlock['designbox_id'];
+
+                $sHelpTitle = $sHelpContent = '';
+                if(!in_array($iDesignboxId, $aDbNoTitle))
+                    $sHelpTitle = $sHelp;
+                else
+                    $sHelpContent = $sHelp;
+
+                $aParams = array(
+                    (isset($mixedContent['title']) ? $mixedContent['title'] : $sTitle) . $sHelpTitle,
+                    $mixedContent['content'] . $sHelpContent,
+                    $iDesignboxId
+                );
+
+                $mixedMenu = false;
+                if(isset($mixedContent['menu']))
+                    $mixedMenu = $mixedContent['menu'];
+                else if(!empty($aBlock['submenu']))
+                    $mixedMenu = $aBlock['submenu'];
+                $aParams[] = $mixedMenu;
+
+                if(isset($mixedContent['buttons']))
+                    $aParams[] = $mixedContent['buttons'];
+                else if($mixedMenu)
+                    $aParams[] = (int)$aBlock['tabs'] == 1 ? true : array();
+
+                if(isset($mixedContent['class']))
+                    $aBlock['class'] = $mixedContent['class'];
+            }
+            else if(is_string($mixedContent) && !empty($mixedContent)) {
+                $iDesignboxId = $aBlock['designbox_id'];
+
+                $sHelpTitle = $sHelpContent = '';
+                if(!in_array($iDesignboxId, $aDbNoTitle))
+                    $sHelpTitle = $sHelp;
+                else
+                    $sHelpContent = $sHelp;
+
+                $aParams = array(
+                    $sTitle . $sHelpTitle,
+                    $mixedContent . $sHelpContent,
+                    $iDesignboxId
+                );
+
+                $mixedMenu = !empty($aBlock['submenu']) ? $aBlock['submenu'] : false;
+                $aParams[] = $mixedMenu;
+
+                if($mixedMenu)
+                    $aParams[] = (int)$aBlock['tabs'] == 1 ? true : array();
+            }
+
+            if(!empty($aParams))
+                $sContentWithBox = call_user_func_array(array($oFunctions, 'designBoxContent'), $aParams);
+        }
+
+        if (isset($GLOBALS['bx_profiler'])) $GLOBALS['bx_profiler']->endPageBlock($aBlock['id'], $sContentWithBox ? false : true, false );
+
+        return $sContentWithBox;
+    }
+
+    /**
+     * Add necessary js and css files.
+     */
+    protected function _addJsCss()
+    {
+        $this->_oTemplate->addJs(array('BxDolPage.js', 'theia-sticky-sidebar/theia-sticky-sidebar.min.js', 'theia-sticky-sidebar/ResizeSensor.min.js'));
+        $this->_oTemplate->addCss('page_layouts.css');
+    }
+
+    /**
+     * Set system template variables, like page title, meta description, meta keywords and meta robots.
+     */
+    protected function _addSysTemplateVars ()
+    {
+        $oTemplate = BxDolTemplate::getInstance();
+
+        $sPageTitle = $this->_getPageTitle();
+        if ($sPageTitle)
+            $oTemplate->setPageHeader ($sPageTitle);
+
+        $sMetaDesc = $this->_getPageMetaDesc();
+        if ($sMetaDesc)
+            $oTemplate->setPageDescription ($sMetaDesc);
+
+        $sMetaRobots = $this->_getPageMetaRobots();
+        if ($sMetaRobots)
+            $oTemplate->setPageMetaRobots ($sMetaRobots);
+
+        $sMetaImage = $this->_getPageMetaImage();
+        if ($sMetaImage)
+            $oTemplate->addPageMetaImage($sMetaImage);
+
+        $sMetaKeywords = $this->_getPageMetaKeywords();
+        if ($sMetaKeywords)
+            $oTemplate->addPageKeywords ($sMetaKeywords);
+    }
+
+    /**
+     * Select menu from page properties.
+     */
+    protected function _selectMenu ()
+    {
+        BxDolMenu::setSelectedGlobal ($this->_aObject['module'], $this->_aObject['uri']);
+    }
+
+    /**
+     * Set page submenu if it's specified
+     */
+    protected function _setSubmenu ($aParams)
+    {
+        if(empty($this->_aObject['submenu']) || $this->_aObject['submenu'] == 'disabled')
+            return;
+
+        $oMenuSubmenu = BxDolMenu::getObjectInstance('sys_site_submenu');
+        if(!$oMenuSubmenu)
+            return;
+
+        $oMenuSubmenu->setObjectSubmenu($this->_aObject['submenu'], $aParams);
+    }
+
+    /**
+     * Get content for 'raw' block type.
+     * @return string
+     */
+    protected function _getBlockRaw ($aBlock)
+    {
+        $s = '<div class="bx-page-raw-container">' . BxDolTemplate::getInstance()->parseHtmlByContent($aBlock['content'], array()) . '</div>';
+        $s = $this->_replaceMarkers($s, array('block_id' => $aBlock['id']));
+        $s = bx_process_macros($s);
+        return $s;
+    }
+
+    /**
+     * Get content for 'custom' block type.
+     * @return string
+     */
+    protected function _getBlockCustom ($aBlock)
+    {
+        $s = '<div class="bx-page-custom-container">' . BxDolTemplate::getInstance()->parseHtmlByContent($aBlock['content'], array()) . '</div>';
+        $s = $this->_replaceMarkers($s, array('block_id' => $aBlock['id']));
+        $s = bx_process_macros($s);
+        return $s;
+    }
+
+    /**
+     * Get content for 'html' block type.
+     * @return string
+     */
+    protected function _getBlockHtml ($aBlock)
+    {
+        $s = '<div class="bx-page-html-container">' . $aBlock['content'] . '</div>';
+        $s = $this->_replaceMarkers($s, array('block_id' => $aBlock['id']));
+        $s = bx_process_macros($s);
+        return $s;
+    }
+
+    /**
+     * Get content for 'wiki' block type.
+     * @return string
+     */
+    protected function _getBlockWiki ($aBlock)
+    {
+        $oWiki = BxDolWiki::getObjectInstance($this->_aObject['module']);
+        if (!$oWiki) {
+            $sContent = _t('_sys_wiki_error_missing_wiki_object', $this->_aObject['module']);
+        } 
+        else {
+            $sContent = $oWiki->getBlockContent($aBlock['id'], false, (int)bx_get($aBlock['id'].'rev') ? (int)bx_get($aBlock['id'].'rev') : false);
+        }
+
+        $s = '<div id="bx-page-wiki-container-' . $aBlock['id'] . '" class="bx-page-wiki-container markdown-body bx-def-vanilla-html">' . $sContent . '</div>';
+        $s = $this->_replaceMarkers($s, array('block_id' => $aBlock['id']));
+        $s = bx_process_macros($s);
+        return $s;
+    }
+
+    /**
+     * Get content for 'lang' block type.
+     * @return string
+     */
+    protected function _getBlockLang ($aBlock)
+    {
+        $s = '<div class="bx-page-lang-container">' . _t(trim($aBlock['content'])) . '</div>';
+        $s = $this->_replaceMarkers($s, array('block_id' => $aBlock['id']));
+        $s = bx_process_macros($s);
+        return $s;
+    }
+
+    /**
+     * Get content for 'image' block type.
+     * @return string
+     */
+    protected function _getBlockImage ($aBlock)
+    {
+        if (empty($aBlock['content']))
+            return false;
+
+        list($iFileId, $sAlign ) = explode('#', $aBlock['content']);
+        $iFileId = (int)$iFileId;
+        if (!$iFileId)
+            return false;
+
+        $oStorage = BxDolStorage::getObjectInstance(BX_DOL_STORAGE_OBJ_IMAGES);
+        if (!$oStorage)
+            return false;
+
+        $sUrl = $oStorage->getFileUrlById($iFileId);
+        if (!$sUrl)
+            return false;
+
+        $sStyleAdd = '';
+        if ('center' == $sAlign || 'left' == $sAlign || 'right' == $sAlign)
+            $sStyleAdd = 'style="text-align:' . $sAlign . '"';
+
+        return '<div class="bx-page-image-container" ' . $sStyleAdd . '><img src="' . $sUrl . '" /></div>';
+    }
+
+    /**
+     * Get content for 'rss' block type.
+     * @return string
+     */
+    protected function _getBlockRss ($aBlock)
+    {
+        if (empty($aBlock['content']))
+            return false;
+
+        list( $sUrl, $iNum ) = explode('#', $aBlock['content']);
+        $iNum = (int)$iNum;
+
+        return BxDolRss::getObjectInstance('sys_page_block')->getHolder($aBlock['id'], $iNum);
+    }
+
+    /**
+     * Get content for 'menu' block type.
+     * @return string
+     */
+    protected function _getBlockMenu ($aBlock)
+    {
+        $oMenu = BxTemplMenu::getObjectInstance($aBlock['content']);
+        return $oMenu ? $oMenu->getCode () : '';
+    }
+
+    /**
+     * Get content for 'service' block type.
+     * @return string
+     */
+    protected function _getBlockService ($aBlock)
+    {
+        return BxDolService::callSerialized($aBlock['content'], $this->_aMarkers);
+    }
+
+    /**
+     * Get page title.
+     * @return string
+     */
+    protected function _getPageTitle()
+    {
+        return $this->_replaceMarkers(_t($this->_aObject['title']));
+    }
+
+    /**
+     * Get page meta description.
+     * @return string
+     */
+    protected function _getPageMetaDesc()
+    {
+        return $this->_replaceMarkers(_t($this->_aObject['meta_description']));
+    }
+
+    /**
+     * Get page meta image.
+     * @return string
+     */
+    protected function _getPageMetaImage()
+    {
+        return '';
+    }
+    
+    /**
+     * Get page meta keywords.
+     * @return string
+     */
+    protected function _getPageMetaKeywords()
+    {
+        return $this->_replaceMarkers(_t($this->_aObject['meta_keywords']));
+    }
+
+    /**
+     * Get page meta robots.
+     * @return string
+     */
+    protected function _getPageMetaRobots()
+    {
+        return _t($this->_aObject['meta_robots']);
+    }
+
+    /**
+     * Get access denied message HTML.
+     * @return string
+     */
+    protected function _getPageAccessDeniedMsg ()
+    {
+        return MsgBox(_t('_Access denied'));
+    }
+
+    /**
+     * Get page cache object.
+     * @return cache object instance
+     */
+    protected function _getPageCacheObject ()
+    {
+        return $this->_getCacheObject ();
+    }
+
+    /**
+     * Get block cache object.
+     * @return cache object instance
+     */
+    protected function _getBlockCacheObject ()
+    {
+        return $this->_getCacheObject ('Block', 'pb');
+    }
+
+    protected function _getCacheObject ($sSuffixObj = 'Page', $sSuffixParam = 'page')
+    {
+        $sObj = '_o' . $sSuffixObj . 'CacheObject';
+        $sParam = 'sys_' . $sSuffixParam . '_cache_engine';
+
+        if ($this->{$sObj} != null) {
+            return $this->{$sObj};
+        } else {
+            $sEngine = getParam($sParam);
+            $this->{$sObj} = bx_instance ('BxDolCache' . $sEngine);
+            if (!$this->{$sObj}->isAvailable())
+                $this->{$sObj} = bx_instance ('BxDolCacheFile');
+            return $this->{$sObj};
+        }
+    }
+
+    /**
+     * Get page cache key.
+     * @param $isPrefixOnly return key prefix only.
+     * @return string
+     */
+    protected function _getPageCacheKey ($isPrefixOnly = false)
+    {
+        $s = 'page_' . $this->_aObject['object'] . '_';
+        if ($isPrefixOnly)
+            return $s;
+        $s .= $this->_getPageCacheParams ();
+        $s .= bx_site_hash() . '.php';
+        return $s;
+    }
+
+    /**
+     * Get block cache key.
+     * @param $isPrefixOnly return key prefix only.
+     * @return string
+     */
+    protected function _getBlockCacheKey ($isPrefixOnly = false, $aBlock = array())
+    {
+        $s = $this->_getPageCacheKey(true) . 'block_';
+        if ($isPrefixOnly)
+            return $s;
+        $s .= ($aBlock ? $aBlock['id'] : 0) . '_' . $this->_getPageCacheParams ();
+        $s .= bx_site_hash() . '.php';
+        return $s;
+    }
+
+    /**
+     * Additional cache key. In the case of dynamic page.
+     * For example - profile page, where each profile has own page.
+     * @return string
+     */
+    protected function _getPageCacheParams ()
+    {
+        return md5(serialize($_GET) . bx_lang_name());
+    }
+
+    /**
+     * Clean page cache.
+     * @param $isDelAllWithPagePrefix delete cache by prefix, it can be used for dynamic pages, like profile view, where for each profile separate cache is generated.
+     * @return string
+     */
+    protected function cleanCache ($isDelAllWithPagePrefix = false)
+    {
+        $a = ['Page', 'Block'];
+        $bRet = false;
+        foreach ($a as $s) {
+            $oCache = $this->{'_get' . $s . 'CacheObject'} ();
+            $sKey = $this->{'_get'. $s . 'CacheKey'} ($isDelAllWithPagePrefix);
+
+            if ($isDelAllWithPagePrefix)
+                $bRet = $bRet && $oCache->removeAllByPrefix($sKey);
+            else
+                $bRet = $bRet && $oCache->delData($sKey);
+        }
+        return $bRet;
+    }
+}
+
+/** @} */

+ 440 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/template/scripts/BxBaseSearchResult.php

@@ -0,0 +1,440 @@
+<?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, 'mode' => $this->_sMode, 'search_results' => &$aData));
+
+        if ($this->aCurrent['paginate']['num'] > 0) {
+            $sCode .= $this->addCustomParts();
+
+            foreach ($aData as $aValue)
+                $sCode .= $this->displaySearchUnit($aValue);
+
+            $sHtmlId = 'bx-search-result-block-' . rand(0, PHP_INT_MAX);
+            $sClasses = $this->applyContainerClass();
+
+            $sAttributes = '';
+            if(is_array($this->aContainerAttrs) && !empty($this->aContainerAttrs))
+            	foreach ($this->aContainerAttrs as $sName => $sValue)
+                    $sAttributes .= ' ' . $sName . '="' . $sValue . '"';
+
+            $sCode = BxDolTemplate::getInstance()->parseHtmlByName('search_result_block.html', [
+                'html_id' => $sHtmlId,
+                'class' => $sClasses,
+                'attrs' => $sAttributes,
+                'content' => $sCode,
+                'bx_if:do_center' => [
+                    'condition' => !$this->_bLiveSearch && $this->sCenterContentUnitSelector,
+                    'content' => [
+                        'html_id' => $sHtmlId,
+                        'selector_content' => $this->sCenterContentUnitSelector
+                    ]
+                ]
+            ]);
+        }
+
+        bx_alert('simple_search', 'show_data', 0, false, array('object' => &$this->aCurrent, 'mode' => $this->_sMode, 'search_results' => &$sCode));
+
+        return $sCode;
+    }
+
+    function displaySearchBox ($sContent, $sPaginate = '')
+    {
+		$sContent = BxDolTemplate::getInstance()->parseHtmlByName('paginate_block.html', array(
+			'bx_if:paginate_top' => array(
+				'condition' => (isset($this->aCurrent['paginate']['on_top']) && $this->aCurrent['paginate']['on_top'] == true) && $sPaginate != '',
+				'content' => array(
+					'paginate' => $sPaginate
+				),
+			),
+			'bx_if:paginate_bottom' => array(
+				'condition' => (!isset($this->aCurrent['paginate']['on_bottom']) || $this->aCurrent['paginate']['on_bottom'] != false) && $sPaginate != '',
+				'content' => array(
+					'paginate' => $sPaginate
+				),
+			),
+			'content' => $sContent
+		));
+		
+        $sMenu = $this->getDesignBoxMenu();
+
+        if ($this->id) {
+            $sTitle = _t($this->aCurrent['title']);
+            $sCode = $this->oFunctions->designBoxContent($sTitle, $sContent, $this->iDesignBoxTemplate, $sMenu);
+            return BxDolTemplate::getInstance()->parseHtmlByName('designbox_container.html', array(
+                'class_add' => ' bx-search-results',
+                'bx_if:show_html_id' => array(
+                    'condition' => true,
+                    'content' => array(
+                        'html_id' => 'bx-page-block-' . $this->id
+                    ),
+                ),
+                'content' => $sCode
+            ));
+        }
+
+        $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_absolute_url(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_absolute_url($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[]=' . rawurlencode($this->aCurrent['name']) . ($this->_sCategoryObject ? '&cat=' . rawurlencode($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}',
+            ), false);
+        }
+
+        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]);
+    }
+
+    /**
+     * Apply class(es) for search result container 
+     * @return string with a list of classes.
+     */
+    function applyContainerClass ()
+    {
+        $sModule = $this->getMain() ? $this->getMain()->getName() : 'sys';
+        return implode(' ', $this->aContainerClasses) . ' '. str_replace('_', '-', $sModule) . '-wrapper';
+    }
+
+    /**
+     * 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);
+    }
+}
+
+/** @} */

+ 464 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/template/scripts/BxBaseServiceConnections.php

@@ -0,0 +1,464 @@
+<?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 services for connections.
+ */
+class BxBaseServiceConnections extends BxDol
+{
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_system_general System Services 
+     * @subsection bx_system_general-connections Connections
+     * @subsubsection bx_system_general-perform perform
+     * 
+     * @code bx_srv('system', 'perform', [[...]], 'TemplVoteServices'); @endcode
+     * @code {{~system:perform:TemplVoteServices[[...]]~}} @endcode
+     * 
+     * Performs Perform an action (add, remove, etc) with connection object.
+     * @param $aParams an array with necessary parameters 
+     * 
+     * @see BxBaseServiceConnections::servicePerform
+     */
+    /** 
+     * @ref bx_system_general-perform "perform"
+     * @api @ref bx_system_general-perform "perform"
+     */
+    public function servicePerform($aParams)
+    {
+        if(is_string($aParams))
+            $aParams = json_decode($aParams, true);
+
+        if(!$aParams['o'] || !$aParams['a'] || !$aParams['iid'] || !$aParams['cid'])
+            return ['code' => 1];
+
+        $oConnection = BxDolConnection::getObjectInstance($aParams['o']);
+        if(!$oConnection)
+            return ['code' => 2];
+
+        $sMethod = 'action' . bx_gen_method_name($aParams['a']);
+        if(!method_exists($oConnection, $sMethod))
+            return ['code' => 2];
+
+        $aResult = $oConnection->$sMethod($aParams['cid'], $aParams['iid']);
+        if($aResult['err'] !== false)
+            return ['code' => 3, 'message' => $aResult['msg']];
+
+        $aFlip = ['add' => 'remove', 'remove' => 'add'];
+
+        return [
+            'a' => $aFlip[$aParams['a']],
+            'title' => $this->serviceGetActionTitle($aParams['o'], $aParams['a'], $aParams['iid'], $aParams['cid'], true),
+        ];
+    }
+
+    public function serviceGetActionTitle($sObject, $sAction, $iInitiatorId, $iContentId, $bFlip = false)
+    {
+        if(empty($sAction))
+            return '';
+
+        $oConnection = BxDolConnection::getObjectInstance($sObject);
+        if(!$oConnection)
+            return '';
+
+        $sMethod = 'getActionTitle';
+        if(method_exists($oConnection, $sMethod))
+            return $oConnection->$sMethod($sAction, $iInitiatorId, $iContentId, $bFlip);
+
+        $aResult = [];
+        switch($sObject) {
+            case 'sys_profiles_friends':
+                if($oConnection->isConnectedNotMutual($iInitiatorId, $iContentId))
+                    $aResult = [
+                        'add' => '',
+                        'remove' => '_sys_menu_item_title_sm_unfriend_cancel',
+                    ];
+                else if($oConnection->isConnectedNotMutual($iContentId, $iInitiatorId))
+                    $aResult = [
+                        'add' => '_sys_menu_item_title_sm_befriend_confirm',
+                        'remove' => '_sys_menu_item_title_sm_unfriend_reject',
+                    ];
+                else if($oConnection->isConnected($iInitiatorId, $iContentId, true))
+                    $aResult = [
+                        'add' => '',
+                        'remove' => '_sys_menu_item_title_sm_unfriend',
+                    ];
+                else
+                    $aResult = [
+                        'add' => '_sys_menu_item_title_sm_befriend',
+                        'remove' => '',
+                    ];
+                break;
+
+            case 'sys_profiles_subscriptions':
+                if($oConnection->isConnected($iInitiatorId, $iContentId))
+                    $aResult = [
+                        'add' => '',
+                        'remove' => '_sys_menu_item_title_sm_unsubscribe',
+                    ];
+                else
+                    $aResult = [
+                        'add' => '_sys_menu_item_title_sm_subscribe',
+                        'remove' => '',
+                    ];
+                break;
+
+            default:
+                $aResult = $oConnection->getActionTitle($sAction, $iInitiatorId, $iContentId);
+        }
+
+        $aFlip = ['add' => 'remove', 'remove' => 'add'];
+        if($bFlip)
+            $sAction = $aFlip[$sAction];
+
+        return !empty($aResult[$sAction]) ? _t($aResult[$sAction]) : '';
+    }       
+
+    /**
+     * get grid with friends connections
+     */
+    public function serviceConnectionsTable ($iProfileId = 0)
+    {
+        if(!$iProfileId && bx_get('profile_id') !== false)
+            $iProfileId = bx_process_input(bx_get('profile_id'), BX_DATA_INT);
+        if(!$iProfileId)
+            return false;
+
+        $oGrid = BxDolGrid::getObjectInstance('sys_grid_connections');
+        if(!$oGrid)
+            return false;
+
+        $oGrid->setProfile($iProfileId);
+        return $oGrid->getCode();
+    }
+
+	/**
+     * get grid with friends connection requests
+     */
+    public function serviceConnectionsRequestTable ()
+    {
+        $oGrid = BxDolGrid::getObjectInstance('sys_grid_connections_requests');
+        if (!$oGrid)
+            return false;
+
+        return $oGrid->getCode();
+    }
+
+    /**
+     * get number of received unconfirmed connections (friend requests)
+     * @param $sConnectionsObject connections object to get unconfirmed connections from
+     * @param $mixedId id to get connections for, if omitted then logged-in profile id is used
+     * @return number
+     */
+    public function serviceGetUnconfirmedConnectionsNum ($sConnectionsObject, $mixedId = 0)
+    {
+        $oConnection = BxDolConnection::getObjectInstance($sConnectionsObject);
+        if (!$oConnection)
+            return 0;
+
+        if (!$mixedId)
+            $mixedId = bx_get_logged_profile_id();
+
+        $i = 0;
+        $a = $oConnection->getConnectedInitiators($mixedId, 0); // get received friend requests
+        foreach ($a as $iId)
+            if (BxDolProfile::getInstance($iId))
+                ++$i;
+
+        return $i;
+    }
+
+    public function serviceGetLiveUpdatesUnconfirmedConnections($sModule, $sConnectionsObject, $aMenuItemParent, $aMenuItemChild, $iCount = 0)
+    {
+        $iProfile = bx_get_logged_profile_id();
+        $oProfile = BxDolProfile::getInstance($iProfile);
+        if(!$oProfile || $oProfile->getModule() != $sModule)
+            return false;
+
+        $iCountNew = (int)$this->serviceGetUnconfirmedConnectionsNum($sConnectionsObject, $iProfile);
+        if($iCountNew == (int)$iCount)
+			return false;
+
+        return array(
+    		'count' => $iCountNew, // required
+    		'method' => 'bx_menu_show_live_update(oData)', // required
+    		'data' => array(
+    			'code' => BxDolTemplate::getInstance()->parseHtmlByTemplateName('menu_item_addon', array(
+    				'content' => '{count}'
+                )),
+                'mi_parent' => $aMenuItemParent,
+                'mi_child' => $aMenuItemChild
+    		),  // optional, may have some additional data to be passed in JS method provided using 'method' param above.
+    	);
+    }
+
+    /**
+     * get number of initiated connections (like "my subscriptions")
+     * @param $sConnectionsObject connections object to get connections from
+     * @param $mixedId id to get connections for, if omitted then logged-in profile id is used
+     * @return number
+     */
+    public function serviceGetConnectedContentNum ($sConnectionsObject, $mixedId = 0, $isMutual = false)
+    {
+        $oConnection = BxDolConnection::getObjectInstance($sConnectionsObject);
+        if (!$oConnection)
+            return 0;
+
+        if (!$mixedId)
+            $mixedId = bx_get_logged_profile_id();
+
+        $i = 0;
+        $a = $oConnection->getConnectedContent($mixedId, $isMutual, 0, BX_CONNECTIONS_LIST_NO_LIMIT); // get received friend requests
+        foreach ($a as $iId)
+            if (BxDolProfile::getInstance($iId))
+                ++$i;
+
+        return $i;
+    }
+
+    /**
+     * get number of received connections (like "subscribed me")
+     * @param $sConnectionsObject connections object to get connections from
+     * @param $mixedId id to get connections for, if omitted then logged-in profile id is used
+     * @return number
+     */
+    public function serviceGetConnectedInitiatorsNum ($sConnectionsObject, $mixedId = 0, $isMutual = false)
+    {
+        $oConnection = BxDolConnection::getObjectInstance($sConnectionsObject);
+        if (!$oConnection)
+            return 0;
+
+        if (!$mixedId)
+            $mixedId = bx_get_logged_profile_id();
+
+        $i = 0;
+        $a = $oConnection->getConnectedInitiators($mixedId, $isMutual, 0, BX_CONNECTIONS_LIST_NO_LIMIT); // get received friend requests
+        foreach ($a as $iId)
+            if (BxDolProfile::getInstance($iId))
+                ++$i;
+
+        return $i;
+    }
+
+    /**
+     * get grid with subscriptions connections
+     */
+    public function serviceSubscriptionsTable ($iProfileId = 0)
+    {
+        if(!$iProfileId && bx_get('profile_id') !== false)
+            $iProfileId = bx_process_input(bx_get('profile_id'), BX_DATA_INT);
+
+        $aProfile = BxDolProfile::getInstance($iProfileId)->getInfo();
+        if(empty($aProfile) || !is_array($aProfile))
+            return false;
+
+        $CNF = &BxDolModule::getInstance($aProfile['type'])->_oConfig->CNF;
+        if(getParam($CNF['PARAM_PUBLIC_SBSN']) != 'on' && $aProfile['id'] != bx_get_logged_profile_id())
+            return false;
+
+        $oGrid = BxDolGrid::getObjectInstance('sys_grid_subscriptions');
+        if (!$oGrid)
+            return false;
+
+        $oGrid->setProfileId($iProfileId);
+        $sContent = $oGrid->getCode();
+        if(empty($sContent))
+            return false;
+
+        return BxDolTemplate::getInstance()->parseHtmlByName('connections_list.html', array(
+            'name' => 'subscriptions',
+            'content' => $sContent
+        ));
+    }
+
+    /**
+     * get grid with subscribed me connections
+     */
+    public function serviceSubscribedMeTable ($iProfileId = 0)
+    {
+        if(!$iProfileId && bx_get('profile_id') !== false)
+            $iProfileId = bx_process_input(bx_get('profile_id'), BX_DATA_INT);
+
+        $aProfile = BxDolProfile::getInstance($iProfileId)->getInfo();
+        if(empty($aProfile) || !is_array($aProfile))
+            return false;
+
+        $CNF = &BxDolModule::getInstance($aProfile['type'])->_oConfig->CNF;
+        if(getParam($CNF['PARAM_PUBLIC_SBSD']) != 'on' && $aProfile['id'] != bx_get_logged_profile_id())
+            return false;
+
+        $oGrid = BxDolGrid::getObjectInstance('sys_grid_subscribed_me');
+        if(!$oGrid)
+            return false;
+
+        $oGrid->setProfileId($iProfileId);
+        $sContent = $oGrid->getCode();
+        if(empty($sContent))
+            return false;
+
+        return BxDolTemplate::getInstance()->parseHtmlByName('connections_list.html', array(
+            'name' => 'subscribers',
+            'content' => $sContent
+        ));
+    }
+
+    /**
+     * get grid with 'relations' connections
+     */
+    public function serviceRelationsTable ($iProfileId = 0)
+    {
+        if(!BxDolRelation::isEnabled())
+            return false;
+
+        if(!$iProfileId && bx_get('profile_id') !== false)
+            $iProfileId = bx_process_input(bx_get('profile_id'), BX_DATA_INT);
+
+        $aProfile = BxDolProfile::getInstance($iProfileId)->getInfo();
+        if(empty($aProfile) || !is_array($aProfile))
+            return false;
+
+        if(!BxDolConnection::getObjectInstance('sys_profiles_relations')->isRelationAvailableFromProfile($aProfile['type']))
+            return false;
+
+        $oGrid = BxDolGrid::getObjectInstance('sys_grid_relations');
+        if (!$oGrid)
+            return false;
+
+        $oGrid->setProfileId($iProfileId);
+        $sContent = $oGrid->getCode();
+        if(empty($sContent))
+            return false;
+
+        return BxDolTemplate::getInstance()->parseHtmlByName('connections_list.html', array(
+            'name' => 'relations',
+            'content' => $sContent
+        ));
+    }
+
+    /**
+     * get grid with 'related me' connections
+     */
+    public function serviceRelatedMeTable ($iProfileId = 0)
+    {
+        if(!BxDolRelation::isEnabled())
+            return false;
+
+        if(!$iProfileId && bx_get('profile_id') !== false)
+            $iProfileId = bx_process_input(bx_get('profile_id'), BX_DATA_INT);
+
+        $aProfile = BxDolProfile::getInstance($iProfileId)->getInfo();
+        if(empty($aProfile) || !is_array($aProfile))
+            return false;
+
+        if(!BxDolConnection::getObjectInstance('sys_profiles_relations')->isRelationAvailableWithProfile($aProfile['type']))
+            return false;
+
+        $oGrid = BxDolGrid::getObjectInstance('sys_grid_related_me');
+        if(!$oGrid)
+            return false;
+
+        $oGrid->setProfileId($iProfileId);
+        $sContent = $oGrid->getCode();
+        if(empty($sContent))
+            return false;
+
+        return BxDolTemplate::getInstance()->parseHtmlByName('connections_list.html', array(
+            'name' => 'related-me',
+            'content' => $sContent
+        ));
+    }
+
+    /*
+     * Get notification data for Notifications module - action Subscribe. 
+     */
+    public function serviceGetNotificationsPost($aEvent)
+    {
+        $iOwner = (int)$aEvent['owner_id'];
+        $oOwner = BxDolProfile::getInstance($iOwner);
+        if(!$oOwner)
+            return array();
+
+        $iProfile = (int)$aEvent['object_id'];
+        $oProfile = BxDolProfile::getInstance($iProfile);
+        if(!$oProfile)
+            return array();
+
+        return array(
+            'entry_sample' => '_sys_profile_sample_single',
+            'entry_url' => str_replace(BX_DOL_URL_ROOT, '{bx_url_root}', $oOwner->getUrl()),
+            'entry_caption' => $oProfile->getDisplayName(),
+            'entry_author' => $oProfile->id(),
+            'lang_key' => '_sys_profile_subscription_added',
+        );
+    }
+
+    /*
+     * Get notification data for Notifications module - action Friend. 
+     */
+    public function serviceGetNotificationsPostFriendship($aEvent)
+    {
+        $iOwner = (int)$aEvent['owner_id'];
+        $oOwner = BxDolProfile::getInstance($iOwner);
+        if(!$oOwner)
+            return array();
+
+        $iProfile = (int)$aEvent['object_id'];
+        $oProfile = BxDolProfile::getInstance($iProfile);
+        if(!$oProfile)
+            return array();
+
+        $sLangKey = '_sys_profile_friendship_added';
+        if(isset($aEvent['content']['request']) && (int)$aEvent['content']['request'] == 1)
+            $sLangKey = '_sys_profile_friend_request_added';
+
+        return array(
+            'entry_sample' => '_sys_profile_sample_single',
+            'entry_url' => str_replace(BX_DOL_URL_ROOT, '{bx_url_root}', $oOwner->getUrl()),
+            'entry_caption' => $oProfile->getDisplayName(),
+            'entry_author' => $oProfile->id(),
+            'lang_key' => $sLangKey,
+        );
+    }
+
+    public function serviceAlertResponseConnections($oAlert)
+    {
+        $sMethod = 'process' . bx_gen_method_name($oAlert->sUnit . '_' . $oAlert->sAction);
+        if(!method_exists($this, $sMethod))
+            return;
+            
+        $this->$sMethod($oAlert);
+    }
+
+    protected function processSysProfilesFriendsConnectionAdded(&$oAlert)
+    {
+        if((int)$oAlert->aExtras['mutual'] != 1)
+            return;
+
+        $oConnection = BxDolConnection::getObjectInstance('sys_profiles_subscriptions');
+        $oConnection->addConnection((int)$oAlert->aExtras['initiator'], (int)$oAlert->aExtras['content'], array('alert_extras' => array('silent_mode' => 1)));
+        $oConnection->addConnection((int)$oAlert->aExtras['content'], (int)$oAlert->aExtras['initiator'], array('alert_extras' => array('silent_mode' => 1)));
+    }
+
+    protected function processSysProfilesFriendsConnectionRemoved(&$oAlert)
+    {
+        if((int)$oAlert->aExtras['mutual'] != 1)
+            return;
+
+        $oConnection = BxDolConnection::getObjectInstance('sys_profiles_subscriptions');
+        $oConnection->removeConnection((int)$oAlert->aExtras['initiator'], (int)$oAlert->aExtras['content']);
+        $oConnection->removeConnection((int)$oAlert->aExtras['content'], (int)$oAlert->aExtras['initiator']);
+    }
+}
+
+/** @} */

+ 1296 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/template/scripts/BxBaseServices.php

@@ -0,0 +1,1296 @@
+<?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 services.
+ */
+class BxBaseServices extends BxDol implements iBxDolProfileService
+{
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    public function serviceIsPublicService($s)
+    {
+        $sService = bx_gen_method_name($s);
+        $aServices = $this->serviceGetPublicServices();
+        return isset($aServices[$sService]);
+    }
+
+    public function serviceGetPublicServices()
+    {
+        return array (
+            'GetProductsNames' => 'BxBaseServices',
+            'GetPageByRequest' => 'BxBaseServicePages',
+        );
+    }
+
+    public function serviceIsSafeService($s)
+    {
+        $sService = bx_gen_method_name($s);
+        $aSafeServices = $this->serviceGetSafeServices();
+        return isset($aSafeServices[$sService]);
+    }
+
+    public function serviceGetSafeServices()
+    {
+        return array(
+            'GetCreatePostForm' => 'BxBaseServices',
+            'GetProductsNames' => 'BxBaseServices',
+            'KeywordSearch' => 'BxBaseServices',
+            'GetDataSearch' => 'BxBaseServices',
+            'Cmts' => 'BxBaseServices',
+
+            'CreateAccountForm' => 'BxBaseServiceAccount',
+            'ForgotPassword' => 'BxBaseServiceAccount',
+            'SwitchProfile' => 'BxBaseServiceAccount',
+
+            'CategoriesList' => 'BxBaseServiceCategory',
+
+            'Test' => 'BxBaseServiceLogin',
+            'MemberAuthCode' => 'BxBaseServiceLogin',
+            'LoginForm' => 'BxBaseServiceLogin',
+            'LoginFormOnly' => 'BxBaseServiceLogin',
+            'Logout' => 'BxBaseServiceLogin',
+        
+            'KeywordsCloud' => 'BxBaseServiceMetatags',
+
+            'ProfileMembership' => 'BxBaseServiceProfiles',
+            'ProfileNotifications' => 'BxBaseServiceProfiles',
+            'GetCountOnlineProfiles' => 'BxBaseServiceProfiles',
+
+            'GetChartGrowth' => 'BxBaseChartServices',
+            'GetChartStats' => 'BxBaseChartServices',
+
+            'GetCartItemsCount' => 'BxBasePaymentsServices',
+            'GetOrdersCount' => 'BxBasePaymentsServices',
+
+            'Do' => 'BxBaseVoteServices',
+            'GetPerformedBy' => 'BxBaseVoteServices',
+
+            'Perform' => 'BxBaseServiceConnections',
+            'GetDataApi' => 'BxBaseCmtsServices',
+            
+            'GetDataApi' => 'BxBaseUploaderServices',
+        );
+    }
+
+    public function serviceGetPreloaderContent($sName) {
+        $sResult = '';
+
+        switch($sName) {
+            case 'tailwind':
+                $sFile = getParam('sys_css_tailwind_default');
+                if(empty($sFile))
+                    $sFile = 'tailwind.min.css';
+
+                $sResult = '{dir_plugins_public}tailwind/css/|' . $sFile;
+                break;
+        }
+
+        return $sResult;
+    }
+            
+    /**
+     * @page service Service Calls
+     * @section bx_system_general System Services 
+     * @subsection bx_system_general-general General
+     * @subsubsection bx_system_general-module_check_for_update module_check_for_update
+     * 
+     * @code bx_srv('system', 'module_check_for_update', ["bx_posts"]); @endcode
+     * @code bx_srv('system', 'module_check_for_update', ["bx_posts"], 'TemplServices'); @endcode
+     * @code {{~system:module_check_for_update:TemplServices["bx_posts"]~}} @endcode
+     * 
+     * Checks whether an update is available for requested module or not.
+     * @param $mixedModule - module ID/name to check the updates for.
+     * @return array with information about the update, false if nothing was found
+     * 
+     * @see BxBaseServices::serviceModuleCheckForUpdate
+     */
+    /** 
+     * @ref bx_system_general-module_check_for_update "module_check_for_update"
+     */
+    public function serviceModuleCheckForUpdate($mixedModule)
+    {
+        $aModule = BxDolModuleQuery::getInstance()->{'getModuleBy' . (is_numeric($mixedModule) ? 'Id' : 'Name')}($mixedModule, false);
+        if(empty($aModule) || !is_array($aModule))
+            return false;
+
+        $aUpdates = BxDolStudioInstallerUtils::getInstance()->checkUpdatesByModule($aModule['name']);
+        if(empty($aUpdates) || !is_array($aUpdates)) 
+            return false;
+
+        $aUpdate = array_shift($aUpdates);
+        return array(
+            'version_from' => $aUpdate['file_version'],
+            'version_to' => $aUpdate['file_version_to'],
+            'file_id' => $aUpdate['file_id']
+        );
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_system_general System Services 
+     * @subsection bx_system_general-general General
+     * @subsubsection bx_system_general-module_update module_update
+     * 
+     * @code bx_srv('system', 'module_update', ["bx_posts"]); @endcode
+     * @code bx_srv('system', 'module_update', ["bx_posts"], 'TemplServices'); @endcode
+     * @code {{~system:module_update:TemplServices["bx_posts"]~}} @endcode
+     * 
+     * Download and install an update for requested module. The operation may be performed 
+     * immediately or by Transient Cron task. The second way is more common.
+     * @param $mixedModule - module ID/name to check the updates for.
+     * @return integer value determining the result of the operation. It can be one of the following values:
+     * BX_DOL_STUDIO_IU_RC_SUCCESS, BX_DOL_STUDIO_IU_RC_FAILED or BX_DOL_STUDIO_IU_RC_SCHEDULED.
+     * 
+     * @see BxBaseServices::serviceModuleUpdate
+     */
+    /** 
+     * @ref bx_system_general-module_update "module_update"
+     */
+    public function serviceModuleUpdate($mixedModule)
+    {
+        $aModule = BxDolModuleQuery::getInstance()->{'getModuleBy' . (is_numeric($mixedModule) ? 'Id' : 'Name')}($mixedModule, false);
+        if(empty($aModule) || !is_array($aModule))
+            return BX_DOL_STUDIO_IU_RC_FAILED;
+
+        $aResult = BxDolStudioInstallerUtils::getInstance()->downloadUpdatePublic($aModule['name'], true);
+        return $aResult['code'];
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_system_general System Services 
+     * @subsection bx_system_general-general General
+     * @subsubsection bx_system_general-module_delete module_delete
+     * 
+     * @code bx_srv('system', 'module_delete', ["bx_posts"]); @endcode
+     * @code bx_srv('system', 'module_delete', ["bx_posts", true], 'TemplServices'); @endcode
+     * @code {{~system:module_delete:TemplServices["bx_posts"]~}} @endcode
+     * 
+     * Delete the requested module. By default only uninstalled module can be deleted.
+     * The operation may be performed immediately or by Transient Cron task. The second way is more common.
+     * @param $sModule - module name to delete.
+     * @param $bForceUninstall - if 'true' is passed the module will be uninstalled before deletion.
+     * @return integer value determining the result of the operation. It can be one of the following values:
+     * BX_DOL_STUDIO_IU_RC_SUCCESS, BX_DOL_STUDIO_IU_RC_FAILED or BX_DOL_STUDIO_IU_RC_SCHEDULED.
+     * 
+     * @see BxBaseServices::serviceModuleDelete
+     */
+    /** 
+     * @ref bx_system_general-module_delete "module_delete"
+     */
+    public function serviceModuleDelete($sModule, $bForceUninstall = false)
+    {
+        $sModulePath = '';
+        $oInstallerUtils = BxDolStudioInstallerUtils::getInstance();
+
+        $aModule = BxDolModuleQuery::getInstance()->getModuleByName($sModule, false);
+        if(!empty($aModule) && is_array($aModule)) {
+            if(!$bForceUninstall)
+                return BX_DOL_STUDIO_IU_RC_FAILED;
+
+            $sModulePath = $aModule['path'];
+
+            $aResult = $oInstallerUtils->perform($sModulePath, 'uninstall', array('auto_disable' => $bForceUninstall));
+            if($aResult['code'] != BX_DOL_STUDIO_IU_RC_SUCCESS)
+                return BX_DOL_STUDIO_IU_RC_FAILED;
+        }
+        else {
+            $aModules = $oInstallerUtils->getModules(false);
+            if(!isset($aModules[$sModule]))
+                return BX_DOL_STUDIO_IU_RC_FAILED;
+            
+            $sModulePath = $aModules[$sModule]['dir'];
+        }
+
+        $aResult = $oInstallerUtils->perform($sModulePath, 'delete');
+        return $aResult['code'];
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_system_general System Services 
+     * @subsection bx_system_general-general General
+     * @subsubsection bx_system_general-module_install module_install
+     * 
+     * @code bx_srv('system', 'module_install', ["bx_posts"]); @endcode
+     * @code bx_srv('system', 'module_install', ["bx_posts", true], 'TemplServices'); @endcode
+     * @code {{~system:module_install:TemplServices["bx_posts"]~}} @endcode
+     * 
+     * Install the requested module. By default the module will be installed only.
+     * @param $sModule - module name to install.
+     * @param $bForceEnable - if 'true' is passed the module will be automatically enabled after installation.
+     * @return boolean value determining the result of the operation.
+     * 
+     * @see BxBaseServices::serviceModuleInstall
+     */
+    /** 
+     * @ref bx_system_general-module_install "module_install"
+     */
+    public function serviceModuleInstall($sModule, $bForceEnable = false)
+    {
+        $oInstallerUtils = BxDolStudioInstallerUtils::getInstance();
+
+        $aModules = $oInstallerUtils->getModules(false);
+        if(!isset($aModules[$sModule]))
+            return false;
+
+        $aResult = $oInstallerUtils->perform($aModules[$sModule]['dir'], 'install', array('auto_enable' => $bForceEnable));
+        if($aResult['code'] != BX_DOL_STUDIO_IU_RC_SUCCESS)
+            return false;
+
+        return true;
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_system_general System Services 
+     * @subsection bx_system_general-general General
+     * @subsubsection bx_system_general-module_uninstall module_uninstall
+     * 
+     * @code bx_srv('system', 'module_uninstall', ["bx_posts"]); @endcode
+     * @code bx_srv('system', 'module_uninstall', ["bx_posts", true], 'TemplServices'); @endcode
+     * @code {{~system:module_uninstall:TemplServices["bx_posts"]~}} @endcode
+     * 
+     * Uninstall the requested module. By default only disabled module can be uninstalled.
+     * @param $mixedModule - module ID/name to uninstall.
+     * @param $bForceDisable - if 'true' is passed the module will be automatically disabled before uninstallation.
+     * @return boolean value determining the result of the operation.
+     * 
+     * @see BxBaseServices::serviceModuleUninstall
+     */
+    /** 
+     * @ref bx_system_general-module_uninstall "module_uninstall"
+     */
+    public function serviceModuleUninstall($mixedModule, $bForceDisable = false)
+    {
+        $aModule = BxDolModuleQuery::getInstance()->{'getModuleBy' . (is_numeric($mixedModule) ? 'Id' : 'Name')}($mixedModule, false);
+        if(empty($aModule) || !is_array($aModule))
+            return false;
+
+        if((int)$aModule['enabled'] != 0 && !$bForceDisable)
+            return false;
+
+        $aResult = BxDolStudioInstallerUtils::getInstance()->perform($aModule['path'], 'uninstall', array('auto_disable' => $bForceDisable));
+        if($aResult['code'] != BX_DOL_STUDIO_IU_RC_SUCCESS)
+            return false;
+
+        return true;
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_system_general System Services 
+     * @subsection bx_system_general-general General
+     * @subsubsection bx_system_general-module_enable module_enable
+     * 
+     * @code bx_srv('system', 'module_enable', ["bx_posts"]); @endcode
+     * @code bx_srv('system', 'module_enable', ["bx_posts"], 'TemplServices'); @endcode
+     * @code {{~system:module_enable:TemplServices["bx_posts"]~}} @endcode
+     * 
+     * Enable the requested module.
+     * @param $mixedModule - module ID/name to enable.
+     * @return boolean value determining the result of the operation.
+     * 
+     * @see BxBaseServices::serviceModuleEnable
+     */
+    /** 
+     * @ref bx_system_general-module_enable "module_enable"
+     */
+    public function serviceModuleEnable($mixedModule)
+    {
+        $aModule = BxDolModuleQuery::getInstance()->{'getModuleBy' . (is_numeric($mixedModule) ? 'Id' : 'Name')}($mixedModule, false);
+        if(empty($aModule) || !is_array($aModule))
+            return false;
+
+        if((int)$aModule['enabled'] != 0)
+            return false;
+
+        $aResult = BxDolStudioInstallerUtils::getInstance()->perform($aModule['path'], 'enable');
+        if($aResult['code'] != BX_DOL_STUDIO_IU_RC_SUCCESS)
+            return false;
+
+        return true;
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_system_general System Services 
+     * @subsection bx_system_general-general General
+     * @subsubsection bx_system_general-module_disable module_disable
+     * 
+     * @code bx_srv('system', 'module_disable', ["bx_posts"]); @endcode
+     * @code bx_srv('system', 'module_disable', ["bx_posts"], 'TemplServices'); @endcode
+     * @code {{~system:module_disable:TemplServices["bx_posts"]~}} @endcode
+     * 
+     * Disable the requested module.
+     * @param $mixedModule - module ID/name to disable.
+     * @return boolean value determining the result of the operation.
+     * 
+     * @see BxBaseServices::serviceModuleDisable
+     */
+    /** 
+     * @ref bx_system_general-module_disable "module_disable"
+     */
+    public function serviceModuleDisable($mixedModule)
+    {
+        $aModule = BxDolModuleQuery::getInstance()->{'getModuleBy' . (is_numeric($mixedModule) ? 'Id' : 'Name')}($mixedModule, false);
+        if(empty($aModule) || !is_array($aModule))
+            return false;
+
+        if((int)$aModule['enabled'] == 0)
+            return false;
+
+        $aResult = BxDolStudioInstallerUtils::getInstance()->perform($aModule['path'], 'disable');
+        if($aResult['code'] != BX_DOL_STUDIO_IU_RC_SUCCESS)
+            return false;
+
+        return true;
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_system_general System Services 
+     * @subsection bx_system_general-general General
+     * @subsubsection bx_system_general-is_module_installed is_module_installed
+     * 
+     * @code bx_srv('system', 'is_module_installed', ["bx_posts"]); @endcode
+     * @code bx_srv('system', 'is_module_installed', ["bx_posts"], 'TemplServices'); @endcode
+     * @code {{~system:is_module_installed:TemplServices["bx_posts"]~}} @endcode
+     * 
+     * Checks whether the requested module is installed or not.
+     * @param $mixedModule - module ID/name to check.
+     * @return boolean value determining the result of the operation.
+     * 
+     * @see BxBaseServices::serviceIsModuleInstalled
+     */
+    /** 
+     * @ref bx_system_general-is_module_installed "is_module_installed"
+     */
+    public function serviceIsModuleInstalled($mixedModule)
+    {
+        $aModule = BxDolModuleQuery::getInstance()->{'getModuleBy' . (is_numeric($mixedModule) ? 'Id' : 'Name')}($mixedModule, false);
+        return !empty($aModule) && is_array($aModule);
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_system_general System Services 
+     * @subsection bx_system_general-general General
+     * @subsubsection bx_system_general-is_module_enabled is_module_enabled
+     * 
+     * @code bx_srv('system', 'is_module_enabled', ["bx_posts"]); @endcode
+     * @code bx_srv('system', 'is_module_enabled', ["bx_posts"], 'TemplServices'); @endcode
+     * @code {{~system:is_module_enabled:TemplServices["bx_posts"]~}} @endcode
+     * 
+     * Checks whether the requested module is enabled or not.
+     * @param $mixedModule - module ID/name to check.
+     * @return boolean value determining the result of the operation.
+     * 
+     * @see BxBaseServices::serviceIsModuleEnabled
+     */
+    /** 
+     * @ref bx_system_general-is_module_enabled "is_module_enabled"
+     */
+    public function serviceIsModuleEnabled($mixedModule)
+    {
+        $aModule = BxDolModuleQuery::getInstance()->{'getModuleBy' . (is_numeric($mixedModule) ? 'Id' : 'Name')}($mixedModule, false);
+        return !empty($aModule) && is_array($aModule) && (int)$aModule['enabled'] != 0;
+    }
+
+    /**
+     * Checks whether a module is a 'Content' module or not.
+     * 
+     * @param string $mixedModule - module name or an instance of Module class.
+     * @return boolean result of operation.
+     */
+    public function serviceIsModuleContent($mixedModule)
+    {
+        if(is_string($mixedModule))
+            $mixedModule = BxDolModule::getInstance($mixedModule);
+
+
+        return
+            $mixedModule instanceof BxDolModule &&
+            $mixedModule instanceof iBxDolContentInfoService &&
+            !($mixedModule instanceof iBxDolProfileService) &&
+            BxDolRequest::serviceExists($mixedModule->_aModule, 'is_allowed_post_in_context') &&
+            bx_srv($mixedModule->getName(), 'is_allowed_post_in_context');
+    }
+
+    /**
+     * Checks whether a module is a 'Context' module or not.
+     * 
+     * @param string $mixedModule - module name or an instance of Module class.
+     * @return boolean result of operation.
+     */
+    public function serviceIsModuleContext($mixedModule)
+    {
+        if(is_string($mixedModule))
+            $mixedModule = BxDolModule::getInstance($mixedModule);
+
+        return $mixedModule instanceof BxDolModule && $mixedModule instanceof iBxDolProfileService;
+    }
+
+    /**
+     * Checks whether a module is a 'Profile' module or not.
+     * 
+     * @param string $mixedModule - module name or an instance of Module class.
+     * @return boolean result of operation.
+     */
+    public function serviceIsModuleProfile($mixedModule)
+    {
+        if(is_string($mixedModule))
+            $mixedModule = BxDolModule::getInstance($mixedModule);
+
+        return $mixedModule instanceof BxDolModule && $mixedModule instanceof iBxDolProfileService && $mixedModule->serviceActAsProfile();
+    }
+
+    /**
+     * Get modules by type. Available types are 'content', 'context', 'profile'.
+     * 
+     * @param type $sType - string with type.
+     * @return array of modules.
+     */
+    public function serviceGetModulesByType($sType)
+    {
+        $aResults = array();
+
+        $aModules = BxDolModuleQuery::getInstance()->getModulesBy(array('type' => 'modules', 'active' => 1));
+        foreach($aModules as $aModule) {
+            $oModule = BxDolModule::getInstance($aModule['name']);
+            if(!$oModule)
+                continue;
+
+            if($sType == 'content' && !$this->serviceIsModuleContent($oModule))
+                continue;
+
+            if($sType == 'context' && !$this->serviceIsModuleContext($oModule))
+                continue;
+
+            if($sType == 'profile' && !$this->serviceIsModuleProfile($oModule))
+                continue;
+
+            $aResults[] = $aModule;
+        }
+
+        return $aResults;
+    }
+
+    public function serviceProfileUnit ($iContentId, $aParams = array())
+    {
+        return $this->_serviceProfileFunc('getUnit', $iContentId, $aParams);
+    }
+
+    public function serviceHasImage ($iContentId)
+    {
+        return false;
+    }
+
+    public function serviceProfilePicture ($iContentId)
+    {
+        return BxDolTemplate::getInstance()->getImageUrl('account.svg');
+    }
+
+    public function serviceProfileAvatar ($iContentId)
+    {
+        return BxDolTemplate::getInstance()->getImageUrl('account.svg');
+    }
+
+    public function serviceProfileCover ($iContentId)
+    {
+        return '';
+    }
+
+    public function serviceProfileEditUrl ($iContentId)
+    {
+        return bx_absolute_url(BxDolPermalinks::getInstance()->permalink('page.php?i=account-settings-info'));
+    }
+
+    public function serviceProfileThumb ($iContentId)
+    {
+        return $this->_serviceProfileFunc('getThumb', $iContentId);
+    }
+
+    public function serviceProfileIcon ($iContentId)
+    {
+        return $this->_serviceProfileFunc('getIcon', $iContentId);
+    }
+
+    public function serviceProfileName ($iContentId)
+    {
+        return $this->_serviceProfileFunc('getDisplayName', $iContentId);
+    }
+
+    public function serviceProfileUrl ($iContentId)
+    {
+        return $this->_serviceProfileFunc('getUrl', $iContentId);
+    }
+
+    /**
+     * @see iBxDolProfileService::serviceCheckAllowedProfileView
+     */ 
+    public function serviceCheckAllowedProfileView($iContentId)
+    {
+        return _t('_Access denied');
+    }
+
+    /**
+     * @see iBxDolProfileService::serviceCheckAllowedProfileContact
+     */ 
+    public function serviceCheckAllowedProfileContact($iContentId)
+    {
+        return _t('_Access denied');
+    }
+
+    /**
+     * @see iBxDolProfileService::serviceCheckAllowedPostInProfile
+     */ 
+    public function serviceCheckAllowedPostInProfile($iContentId, $sPostModule = '')
+    {
+        return _t('_Access denied');
+    }
+
+    /**
+     * @see iBxDolProfileService::serviceCheckAllowedModuleActionInProfile
+     */
+    public function serviceCheckAllowedModuleActionInProfile($iContentId, $sPostModule, $sActionName)
+    {
+        return _t('_Access denied');
+    }
+
+    /**
+     * @see iBxDolProfileService::serviceGetSpaceTitle
+     */ 
+    public function serviceGetSpaceTitle()
+    {
+        return '';
+    }
+
+    /**
+     * @see iBxDolProfileService::serviceGetParticipatingProfiles
+     */ 
+    public function serviceGetParticipatingProfiles($iProfileId, $aConnectionObject = false)
+    {
+        return array();
+    }
+    
+    /**
+     * @page service Service Calls
+     * @section bx_system_general System Services 
+     * @subsection bx_system_general-general General
+     * @subsubsection bx_system_general-get_create_post_form get_create_post_form
+     * 
+     * @code bx_srv('system', 'get_create_post_form', [false, "bx_posts"], 'TemplServices'); @endcode
+     * @code {{~system:get_create_post_form:TemplServices[false,"bx_posts"]~}} @endcode
+     * 
+     * Get United Create Post form.
+     * @param $mixedContextId - context which the post will be created in:
+     *      - false = 'Public' post form;
+     *      - 0 = 'Profile' post form, which allows to post in your own profile and connections;
+     *      - n = 'Context' post form, which allows to post in context (3d party profile, group, event, etc).
+     * @param $sDefault - tab selected by default.
+     * @param $aCustom - an array with custom paramaters.
+     * @return string with form content
+     * 
+     * @see BxBaseServices::serviceGetCreatePostForm
+     */
+    /** 
+     * @ref bx_system_general-get_create_post_form "get_create_post_form"
+     */
+    public function serviceGetCreatePostForm($mixedContextId = false, $sDefault = '', $aCustom = array())
+    {
+    	if(!isLogged() || ($mixedContextId !== false && !is_numeric($mixedContextId)))
+            return '';
+
+        $oProfile = BxDolProfile::getInstance();
+        if(!$oProfile)
+            return '';
+
+        if($mixedContextId !== false)
+            $mixedContextId = (int)(!empty($mixedContextId) ? -$mixedContextId : $oProfile->id());
+
+    	$oMenu = BxDolMenu::getObjectInstance('sys_create_post');
+        if(!$oMenu)
+            return '';
+
+        $oMenu->setContextId($mixedContextId);
+    	$aMenuItems = $oMenu->getMenuItems();
+    	if(empty($aMenuItems) || !is_array($aMenuItems))
+            return '';
+
+    	if(empty($sDefault)) {
+            $aDefault = array_shift($aMenuItems);
+            $sDefault = !empty($aDefault['module']) ? $aDefault['module'] : $aDefault['name'];
+    	}
+    	$oMenu->setSelected($sDefault, $sDefault);
+
+        $bContext = $mixedContextId !== false;
+        if($bContext && ($aContextInfo = BxDolProfileQuery::getInstance()->getInfoById(abs($mixedContextId))))
+            if(bx_srv($aContextInfo['type'], 'check_allowed_post_in_profile', [$aContextInfo['content_id'], $sDefault]) !== CHECK_ACTION_RESULT_ALLOWED)
+                return '';
+
+        $sTitle = _t('_sys_page_block_title_create_post' . (!$bContext ? '_public' : ($mixedContextId < 0 ? '_context' : '')));
+        $sPlaceholder = _t('_sys_txt_create_post_placeholder', $oProfile->getDisplayName());
+
+        $oDbModules = BxDolModuleQuery::getInstance();
+    	$oTemplate = BxDolTemplate::getInstance();
+    	$oTemplate->addJs(['BxDolCreatePost.js']);
+
+        $aPreloadingList = [];
+        $aModules = explode(',', getParam('sys_create_post_form_preloading_list'));
+        if(!empty($aModules))
+            foreach($aModules as $sModule) {
+                $aModule = $oDbModules->getModuleByName($sModule);
+                if(empty($aModule) || !is_array($aModule))
+                    continue;
+
+                $aPreloadingList[$sModule] = $aModule['uri'];
+            }
+
+        if(bx_is_api()) {
+            return [
+                'title' => $sTitle,
+                'content' => '',
+                'menu' => $oMenu->getCodeAPI()
+            ];
+        }
+
+    	$sJsObject = 'oBxDolCreatePost';
+        $sJsContent = $oTemplate->_wrapInTagJsCode("var " . $sJsObject . " = new BxDolCreatePost(" . json_encode([
+            'sObjName' => $sJsObject,
+            'sRootUrl' => BX_DOL_URL_ROOT,
+            'sDefault' => $sDefault,
+            'iContextId' => $bContext ? $mixedContextId : 0,
+            'oPreloadingList' => $aPreloadingList,
+            'oCustom' => $aCustom
+        ]) . ");");
+
+    	return [
+            'title' => $sTitle,
+            'content' => $oTemplate->parseHtmlByName('create_post_form.html', [
+                'default' => $sDefault,
+                'placeholder' => $sPlaceholder,
+                'user_thumb' => $oProfile->getUnit(0, ['template' => 'unit_wo_info_links']),
+                'form' => bx_srv($sDefault, 'get_create_post_form', [[
+                    'context_id' => $mixedContextId, 
+                    'ajax_mode' => true, 
+                    'absolute_action_url' => true, 
+                    'custom' => $aCustom
+                ]]),
+                'js_object' => $sJsObject,
+                'js_content' => $sJsContent
+            ]),
+            'menu' => $oMenu
+        ];
+    }
+
+    public function serviceGetOptionsCreatePostFormPreloadingList()
+    {
+        $oMenu = BxDolMenu::getObjectInstance('sys_create_post');
+        if(!$oMenu)
+            return [];
+
+        $aMenuItems = $oMenu->getMenuItems();
+
+        $aResult = [];
+        foreach($aMenuItems as $aMenuItem) {
+            $sModule = !empty($aMenuItem['module']) ? $aMenuItem['module'] : $aMenuItem['name'];
+            if(!bx_srv('system', 'is_module_enabled', [$sModule]))
+                continue;
+
+            $aResult[] = [
+                'key' => $sModule, 
+                'value' => _t('_' . $sModule)
+            ];
+        }
+
+        return $aResult;
+    }
+
+    public function serviceGetBlockAuthor($sModule, $iContentId = 0)
+    {
+        if(!$iContentId && bx_get('id') !== false)
+            $iContentId = (int)bx_get('id');
+
+        $sMethodGetAuthor = 'get_author';
+        if(!$sModule || !$iContentId || !BxDolRequest::serviceExists($sModule, $sMethodGetAuthor))
+            return '';
+
+        $iAuthor = bx_srv($sModule, $sMethodGetAuthor, [$iContentId]);
+        if(!$iAuthor)
+            return '';
+
+        $oAuthor = BxDolProfile::getInstance($iAuthor);
+        if(!$oAuthor)
+            return '';
+
+        $sModuleAuthor = $oAuthor->getModule();
+        $sMethodGetCover = 'entity_cover';
+        if(!BxDolRequest::serviceExists($sModuleAuthor, $sMethodGetCover))
+            return '';
+
+        return bx_srv($sModuleAuthor, $sMethodGetCover, [$oAuthor->getContentId(), [
+            'use_as_author' => true,
+            'show_text' => false
+        ]]);
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_system_general System Services 
+     * @subsection bx_system_general-general General
+     * @subsubsection bx_system_general-get_site_submenu get_site_submenu
+     * 
+     * @code bx_srv('system', 'get_site_submenu', [], 'TemplServices'); @endcode
+     * @code {{~system:get_site_submenu:TemplServices[]~}} @endcode
+     * 
+     * Get Site Submenu code.
+     * @see BxBaseServices::serviceGetSiteSubmenu
+     */
+    /** 
+     * @ref bx_system_general-get_site_submenu "get_site_submenu"
+     */
+    public function serviceGetSiteSubmenu()
+    {
+        $oMenu = BxDolMenu::getObjectInstance('sys_site_submenu');
+        return $oMenu ? $oMenu->getCode() : '';
+    }
+    
+    /**
+     * @page service Service Calls
+     * @section bx_system_general System Services 
+     * @subsection bx_system_general-general General
+     * @subsubsection bx_system_general-get_products_names get_products_names
+     * 
+     * @code bx_srv('system', 'get_products_names', [], 'TemplServices'); 
+     *
+     * @code http://example.com/m/oauth2/com/get_products_names?module=system&class=BaseServices @endcode
+     * @code http://hihi.una.io/modules/?r=oauth2/com/get_products_names&module=system&class=BaseServices @endcode
+     * 
+     * Get an array of products names from all modules with payments functionality.
+     * @param $iVendorId filter products by vendor ID.
+     * @param $iLimit limit number of records from one module.
+     * @return array of products where key id product name and value is module name
+     * 
+     * @see BxBaseServices::serviceGetProductsNames
+     */
+    /** 
+     * @ref bx_system_general-get_products_names "get_products_names"
+     */
+    public function serviceGetProductsNames($iVendorId = 0, $iLimit = 1000)
+    {
+        $o = BxDolPayments::getInstance();
+        if (!$o)
+            return array();
+        return $o->getProductsNames($iVendorId, $iLimit);
+    }
+
+    /**
+     * @see iBxDolProfileService::serviceCheckSpacePrivacy
+     */ 
+    public function serviceCheckSpacePrivacy($iContentId)
+    {
+        return _t('_Access denied');
+    }
+    
+    public function serviceFormsHelper ()
+    {
+        return new BxTemplAccountForms();
+    }
+
+    public function serviceActAsProfile ()
+    {
+        return false;
+    }
+
+    public function servicePrepareFields ($aFieldsProfile)
+    {
+        return $aFieldsProfile;
+    }
+
+    public function serviceProfilesSearch ($sTerm, $iLimit)
+    {
+        $oDb = BxDolAccountQuery::getInstance();
+        $aRet = array();
+        $a = $oDb->searchByTerm($sTerm, $iLimit);
+        foreach ($a as $r)
+            $aRet[] = array ('label' => $this->serviceProfileName($r['content_id']), 'value' => $r['profile_id']);
+        return $aRet;
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_system_general System Services 
+     * @subsection bx_system_general-general General
+     * @subsubsection bx_system_general-keyword_search keyword_search
+     * 
+     * @code bx_srv('system', 'keyword_search', ["bx_posts", ["keyword" => "test"}], 'TemplServices'); @endcode
+     * 
+     * @code {{~system:keyword_search:TemplServices["bx_posts", {"keyword":"test"}]~}} @endcode
+     * @code {{~system:keyword_search:TemplServices["bx_albums", {"meta_type": "location_country", "keyword": "AU"}, "unit.html"]~}} @endcode
+     * @code {{~system:keyword_search:TemplServices["bx_albums", {"meta_type": "location_country_state", "state":"NSW", "keyword": "AU"}, "unit.html"]~}} @endcode
+     * @code {{~system:keyword_search:TemplServices["bx_albums", {"meta_type": "location_country_city", "state":"NSW", "city":"Manly", "keyword": "AU"}, "unit.html"]~}} @endcode
+     * @code {{~system:keyword_search:TemplServices["bx_posts", {"meta_type": "mention", "keyword": 2}, "unit_gallery.html"]~}} @endcode
+     * @code {{~system:keyword_search:TemplServices["bx_posts", {"cat": "bx_posts_cats", "keyword": 3}, "unit_gallery.html"]~}} @endcode
+     * 
+     * Search by keyword
+     * @param $sSection - search object to search in, usually module name, for example: bx_posts
+     * @param $aCondition - condition for search, supported conditions: 
+     *          - search by keyword: ["keyword" => "test"]
+     *          - search by country: ["meta_type" => "location_country", "keyword" => "AU"]
+     *          - search by country and state: ["meta_type": "location_country_state", "state":"NSW", "keyword": "AU"]
+     *          - search by country, state and city: ["meta_type": "location_country_city", "state":"NSW", "city":"Manly", "keyword": "AU"]
+     *          - search for mentions: ["meta_type" => "mention", "keyword" => 2]
+     *          - search in category: ["cat": "bx_posts_cats", "keyword": 3]
+     * @param $sTemplate - template for displaying search results, for example: unit.html
+     * @param $iStart - paginate, display records starting from this number
+     * @param $iPerPage - paginate, display this number of records per page
+     * @param $bLiveSearch - search results like in live search
+     * 
+     * @see BxBaseServices::serviceKeywordSearch
+     */
+    /** 
+     * @ref bx_system_general-keyword_search "keyword_search"
+     */
+    public function serviceKeywordSearch ($sSection, $aCondition, $sTemplate = '', $iStart = 0, $iPerPage = 0, $bLiveSearch = 0, $bPaginate = false)
+    {
+        if (!$sSection || !isset($aCondition['keyword']))
+            return '';
+
+        $sClass = 'BxTemplSearch';
+
+        $sElsName = 'bx_elasticsearch';
+        $sElsMethod = 'is_configured';
+        if(BxDolRequest::serviceExists($sElsName, $sElsMethod) && BxDolService::call($sElsName, $sElsMethod)) {
+             $oModule = BxDolModule::getInstance($sElsName);
+
+             bx_import('Search', $oModule->_aModule);
+             $sClass = 'BxElsSearch';
+        }
+
+        $oSearch = new $sClass(array($sSection));
+        $oSearch->setLiveSearch($bLiveSearch);
+        $oSearch->setMetaType(isset($aCondition['meta_type']) ? $aCondition['meta_type'] : '');
+        $oSearch->setCategoryObject(isset($aCondition['cat']) ? $aCondition['cat'] : '');
+        $oSearch->setCustomSearchCondition($aCondition);
+        if (!$bPaginate)
+            $oSearch->setRawProcessing(true);
+        $oSearch->setCustomCurrentCondition(array(
+            'paginate' => array (
+                'start' => $iStart,
+                'perPage' => $iPerPage ? $iPerPage : BX_DOL_SEARCH_RESULTS_PER_PAGE_DEFAULT,
+            )));
+        if ($sTemplate)
+            $oSearch->setUnitTemplate($sTemplate);
+        
+        return $oSearch->response();
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_system_general System Services 
+     * @subsection bx_system_general-general General
+     * @subsubsection bx_system_general-keyword_search keyword_search
+     * 
+     * @code bx_srv('system', 'search_keyword_form', 'TemplServices'); @endcode
+     * 
+     * Block with Search by Keywords Form
+     *  
+     * @see BxBaseServices::serviceSearchKeywordForm
+     */
+    /** 
+     * @ref bx_system_general-keyword_search "keyword_search"
+     */
+    public function serviceSearchKeywordForm ()
+    {
+        return $this->_getSearchObject()->getForm(BX_DB_PADDING_DEF, false, true);
+    }
+    
+    /**
+     * @page service Service Calls
+     * @section bx_system_general System Services 
+     * @subsection bx_system_general-general General
+     * @subsubsection bx_system_general-keyword_search keyword_search
+     * 
+     * @code bx_srv('system', 'search_keyword_result', 'TemplServices'); @endcode
+     * 
+     * Block with Search by Keywords Results
+     *  
+     * @see BxBaseServices::serviceSearchKeywordResult
+     */
+    /** 
+     * @ref bx_system_general-keyword_search "keyword_search"
+     */
+    public function serviceSearchKeywordResult ()
+    {
+        $oSearch = $this->_getSearchObject();
+        
+        $sCode = '';
+        if (bx_get('keyword') !== false) {
+            $sCode = $oSearch->response();
+            if (!$sCode)
+                $sCode = $oSearch->getEmptyResult();
+        }
+
+        return $sCode;
+    }
+
+    /** 
+     * @ref bx_system_general-get_date_search "get_date_search"
+     * @api @ref bx_system_general-get_date_search "get_date_search"
+     */
+    public function serviceGetDataSearch ($aParams)
+    {
+        if(!bx_is_api())
+            return false;
+
+        $aParams = array_merge([
+            'keyword' => '',
+            'section' => '',
+            'start' => 0,
+            'per_page' => 12
+        ], is_string($aParams) ? json_decode($aParams, true) : $aParams);
+
+        if(!empty($aParams['section']))
+            $aParams['section'] = explode(',', $aParams['section']);
+
+        $sClass = 'BxTemplSearch';
+        $oSearch = new $sClass($aParams['section']);
+        $oSearch->setLiveSearch(true);
+        $oSearch->setDataProcessing(true);
+        $oSearch->setCustomSearchCondition(['keyword' => $aParams['keyword']]);
+        $oSearch->setCustomCurrentCondition([
+            'paginate' => [
+                'start' => $aParams['start'],
+                'perPage' => $aParams['per_page'],
+            ]
+        ]);
+
+        $aData = [];
+        $aResponse = $oSearch->response();
+        foreach($aResponse as $sSection => $aItems) {
+            if(empty($aItems))
+                continue;
+
+            $aData = array_merge($aData, $aItems);
+        }
+        
+        if(count($aData) > $aParams['per_page'])
+            $aData = array_slice($aData, $aParams['start'], $aParams['per_page']);
+
+        return [
+            bx_api_get_block('browse', [
+                'unit' => 'search-results',  
+                'request_url' => '/api.php?r=system/get_data_search/TemplServices&params[]=',
+                'params' => $aParams,
+                'data' => $aData
+            ])
+        ];
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_system_general System Services 
+     * @subsection bx_system_general-general General
+     * @subsubsection bx_system_general-cmts cmts
+     * 
+     * @code bx_srv('system', 'cmts', ["sys_blocks", 1], 'TemplServices'); @endcode
+     * 
+     * @code {{~system:cmts:TemplServices["sys_blocks", 1]~}} @endcode
+     * 
+     * Comments block
+     * @param $sObject - comments object name
+     * @param $sId - content id assiciated tith the comments
+     * 
+     * @see BxBaseServices::serviceCmts
+     */
+    /** 
+     * @ref bx_system_general-cmts "cmts"
+     */
+    public function serviceCmts ($sObject, $sId)
+    {
+        $o = BxDolCmts::getObjectInstance($sObject, $sId);
+        if (!$o || !$o->isEnabled())
+            return '';
+        return $o->getCommentsBlock(array(), array('in_designbox' => false, 'show_empty' => true));
+    }
+
+    public function _serviceProfileFunc ($sFunc, $iContentId, $aParams = array())
+    {
+        if (!$iContentId)
+            return false;
+        if (!($oAccount = BxDolAccount::getInstance($iContentId)))
+            return false;
+
+        return $oAccount->$sFunc(false, $aParams);
+    }
+
+    public function serviceAlertResponseProcessInstalled()
+    {
+        BxDolTranscoderImage::registerHandlersSystem();
+    }
+
+    public function serviceAlertResponseProcessStorageChange ($oAlert)
+    {
+        if ('sys_storage_default' != $oAlert->aExtras['option'])
+            return;
+
+        $aStorages = BxDolStorageQuery::getStorageObjects();
+        foreach ($aStorages as $r) {
+            if (0 == $r['current_size'] && 0 == $r['current_number'] && ($oStorage = BxDolStorage::getObjectInstance($r['object'])))
+                $oStorage->changeStorageEngine($oAlert->aExtras['value']);
+        }
+
+    }
+
+    public function serviceGetOptionsProfileBot()
+    {
+        $aResult = array(
+            array('key' => '', 'value' => _t('_Select_one'))
+        );
+
+        $aAccountsIds = BxDolAccountQuery::getInstance()->getOperators();
+        foreach($aAccountsIds as $iAccountId) {
+            $aProfilesIds = BxDolAccount::getInstance($iAccountId)->getProfilesIds(true, false);
+            foreach($aProfilesIds as $iProfileId) {
+                $oProfile = BxDolProfile::getInstance($iProfileId);
+                $aResult[] = array(
+                    'key' => $iProfileId,
+                    'value' => _t('_sys_profile_with_type', $oProfile->getDisplayName(), $oProfile->getModule()),
+                );
+            }
+        }
+
+        return $aResult;
+    }
+    
+    public function serviceGetOptionsModuleListForPrivacySelector()
+    {
+        $aResult = array();
+        $BxDolModuleQuery = BxDolModuleQuery::getInstance();
+        $aModules = $BxDolModuleQuery->getModulesBy(array('type' => 'modules', 'active' => 1));
+        foreach($aModules as $aModule){
+            $oModule = BxDolModule::getInstance($aModule['name']);
+            if($oModule instanceof iBxDolContentInfoService){
+                if (!BxDolRequest::serviceExists($aModule['name'], 'act_as_profile'))
+                    continue;
+                $aResult[$aModule['name']] = $aModule['title'];
+            }
+        }
+        return $aResult;
+    }
+    
+
+    public function serviceGetOptionsCaptchaDefault()
+    {
+        $aResults = [];
+        $aObjects = BxDolCaptchaQuery::getObjects();
+        foreach($aObjects as $aObject)
+            $aResults[$aObject['object']] = $aObject['title'];
+
+        return $aResults;
+    }
+
+    public function serviceGetOptionsEmbedDefault()
+    {
+        $aResults = array(
+            '' => _t('_None')
+        );
+
+        $aObjects = BxDolEmbedQuery::getObjects();
+        foreach($aObjects as $aObject)
+            $aResults[$aObject['object']] = $aObject['title'];
+
+        return $aResults;
+    }
+
+    public function serviceGetOptionsLocationFieldDefault()
+    {
+        $aResults = array();
+
+        $aObjects = BxDolLocationFieldQuery::getObjects();
+        foreach($aObjects as $aObject)
+            $aResults[$aObject['object']] = _t($aObject['title']);
+
+        return $aResults;
+    }
+
+    public function serviceGetOptionsLocationMapDefault()
+    {
+        $aResults = array();
+
+        $aObjects = BxDolLocationMapQuery::getObjects();
+        foreach($aObjects as $aObject)
+            $aResults[$aObject['object']] = _t($aObject['title']);
+
+        return $aResults;
+    }
+
+    public function serviceGetOptionsLocationLeafletGetProviders()
+    {
+        return BxDolLocationMapLeaflet::getProviders();
+    }
+
+    public function serviceGetOptionsRelations()
+    {
+        $aModules = BxDolModuleQuery::getInstance()->getModulesBy(array('type' => 'modules', 'active' => 1));
+
+        $aProfiles = array();
+        foreach($aModules as $aModule) {
+            $sMethod = 'act_as_profile';
+            if(!BxDolRequest::serviceExists($aModule['name'], $sMethod) || !BxDolService::call($aModule['name'], $sMethod))
+                continue;
+
+            $aProfiles[$aModule['name']] = _t('_' . $aModule['name']);
+        }
+
+        $aResults = array();
+        foreach($aProfiles as $sName1 => $sTitle1)
+            foreach($aProfiles as $sName2 => $sTitle2)
+                $aResults[$sName1 . '_' . $sName2] = $sTitle1 . ' - ' . $sTitle2;
+
+        return $aResults;
+    }
+
+    public function serviceGetOptionsCfProhibited()
+    {
+        return BxDolFormQuery::getDataItems('sys_content_filter');
+    }
+
+    public function serviceGetOptionsTaiwindDefault()
+    {
+        $sPath = BX_DIRECTORY_PATH_PLUGINS_PUBLIC . 'tailwind/css';
+        $aExcludes = ['.', '..', 'tailwind.css'];
+
+        $aResults = [];
+        if(($oHandle = opendir($sPath)) !== false) {
+            while(($sFile = readdir($oHandle)) !== false) {
+                if(in_array($sFile, $aExcludes))
+                    continue;
+
+                if(($oHandlerFile = fopen($sPath . '/' . $sFile, 'r')) === false)
+                    continue;
+
+                if(($sContent = fread($oHandlerFile, 1024)) !== false && strpos($sContent, '@tailwind base') === false)
+                    $aResults[$sFile] = $sFile;
+
+                fclose($oHandlerFile);
+            }
+
+            closedir($oHandle);
+        }
+
+        return $aResults;
+    }
+
+    public function serviceGetOptionsApiMenuTop()
+    {
+        $aMenus = BxDolMenuQuery::getMenuObjects();
+
+        $aResults = [];
+        foreach($aMenus as $aMenu)
+            if($aMenu['module'] == 'system')
+                $aResults[$aMenu['object']] = _t($aMenu['title']);
+
+        return $aResults;
+    }
+
+    public function serviceRedirect($sUrl = false)
+    {
+        if (false === $sUrl)
+            $sUrl = bx_get('url');
+
+        if (!$sUrl || !preg_match('@^https?://@', $sUrl)) {
+            return MsgBox(_t('_error occured'));
+        }
+        else {
+
+            $bSpam = null;
+            bx_alert('system', 'check_spam_url', 0, getLoggedId(), array('is_spam' => &$bSpam, 'content' => &$sUrl, 'where' => 'redirect'));
+
+            if (false === $bSpam) {
+                header('Location: ' . $sUrl);
+            } 
+            elseif (true === $bSpam || null === $bSpam) {
+                return BxDolTemplate::getInstance()->parseHtmlByName('redirect.html', [
+                    'text' => _t('_sys_redirect_confirmation', bx_process_output($sUrl), getParam('site_title')),
+                    'url' => bx_js_string($sUrl, BX_ESCAPE_STR_APOS),
+                ]);
+            }
+        }
+    }
+
+    public function serviceGetBadge($aBadge, $bIsCompact = false)
+    {
+        $sClass = '';
+		$sStyleFont = '';
+		$sStyleBg = '';
+		if ($aBadge['color'] == '')
+			$aBadge['color'] = 'purple-600';
+		
+        if ($bIsCompact && $aBadge['icon'] != ''){
+            $aBadge['is_icon_only'] = 1;
+        }
+        if ($aBadge['is_icon_only'] == 1){
+            $sClass .= ' bx-badge-compact';
+            if (substr_count($aBadge['color'], 'rgb('))
+                $sStyleFont = 'color: ' . $aBadge['color'];
+            else
+                $sClass .= ' text-' . $aBadge['color'];
+        }
+		else{
+            if (substr_count($aBadge['color'], 'rgb('))
+                $sStyleBg = 'background-color: ' . $aBadge['color'];
+            else
+                $sClass .= ' bg-' . $aBadge['color'];
+		}
+        
+        return BxDolTemplate::getInstance()->parseHtmlByName('badge.html', array(
+            'bx_if:content' => array(
+                'condition' => $aBadge['is_icon_only'] != '1',
+                'content' => array('content' => _t($aBadge['text'])),
+            ),
+            'bx_if:icon' => array(
+                'condition' => $aBadge['icon'] != '',
+                'content' => array('content' => BxDolTemplate::getInstance()->getIcon($aBadge['icon'], array('class' => 'bx-badge-icon sys-colored', 'style' => $sStyleFont))),
+            ),
+            'title' => $aBadge['text'],
+			'style_font' => $sStyleFont,
+			'style_bg' => $sStyleBg,
+            'class' => $sClass,
+            )
+    	);
+    }
+
+    private function _getSearchObject()
+    {
+        $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')));
+
+        return $oSearch;
+    }
+}
+
+/** @} */

+ 174 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files/template/scripts/BxBaseUploaderHTML5.php

@@ -0,0 +1,174 @@
+<?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
+ * @{
+ */
+
+/**
+ * Upload files using AJAX uploader with multiple files selection support (without flash),
+ * it works in Firefox and WebKit(Safari, Chrome) browsers only, but has fallback for other browsers (IE, Opera).
+ * @see BxDolUploader
+ */
+class BxBaseUploaderHTML5 extends BxDolUploader
+{
+    protected $_sDivId; ///< div id where upload button will be placed
+    protected $_sFocusDivId;
+    protected $_sProgressDivId;
+
+    public function __construct ($aObject, $sStorageObject, $sUniqId, $oTemplate)
+    {
+        parent::__construct($aObject, $sStorageObject, $sUniqId, $oTemplate);
+
+        $this->_sDivId = 'bx-form-input-files-' . $sUniqId . '-div-' . $this->_aObject['object'];
+        $this->_sFocusDivId = 'bx-form-input-files-' . $sUniqId . '-focus-' . $this->_aObject['object'];
+        $this->_sProgressDivId = 'bx-form-input-files-' . $sUniqId . '-progress';
+
+        $this->_sButtonTemplate = 'uploader_button_html5.html';
+        $this->_sJsTemplate = 'uploader_button_html5_js.html';
+        $this->_sUploaderFormTemplate = 'uploader_form_html5.html';
+
+        $aUploaderLangs = array('ar-ar' => 1, 'cs-cz' => 1, 'da-dk' => 1, 'de-de' => 1, 'el-el' => 1, 'en-en' => 1, 'es-es' => 1, 'fa-ir' => 1, 'fi-fi' => 1, 'fr-fr' => 1, 'he-he' => 1, 'hr-hr' => 1, 'hu-hu' => 1, 'id-id' => 1, 'it-it' => 1, 'ja-ja' => 1, 'lt-lt' => 1, 'nl-nl' => 1, 'no-nb' => 1, 'pl-pl' => 1, 'pt-br' => 1, 'ro-ro' => 1, 'ru-ru' => 1, 'sk-sk' => 1, 'sv-se' => 1, 'tr-tr' => 1, 'uk-ua' => 1, 'vi-vi' => 1, 'zh-cn' => 1, 'zh-tw' => 1);
+        $sUploaderLang = BxDolLanguages::getInstance()->getCurrentLanguage() . '-' . BxDolLanguages::getInstance()->getLangFlag();
+        if (!isset($aUploaderLangs[$sUploaderLang]))
+            $sUploaderLang = 'en-en';
+
+        $this->addJs([
+            'filepond/locale/' . $sUploaderLang . '.js',
+            'filepond/filepond.min.js',
+            'filepond/filepond-plugin-image-preview.min.js',
+            'filepond/filepond-plugin-image-transform.min.js',
+            'filepond/filepond-plugin-image-crop.min.js',
+            'filepond/filepond-plugin-image-resize.min.js',
+            'filepond/filepond-plugin-file-validate-size.min.js',
+            'filepond/filepond-plugin-file-validate-type.js',
+        ]);
+
+        $this->addCss([
+            BX_DIRECTORY_PATH_PLUGINS_PUBLIC . 'filepond/|filepond.min.css',
+            BX_DIRECTORY_PATH_PLUGINS_PUBLIC . 'filepond/|filepond-plugin-image-preview.min.css',
+        ]);
+    }
+
+    /**
+     * Get uploader button title
+     */
+    public function getUploaderButtonTitle($mixed = false)
+    {
+        if (is_string($mixed))
+            return $mixed;
+        elseif (is_array($mixed) && isset($mixed['HTML5']))
+            return $mixed['HTML5'];
+        else
+            return _t('_sys_uploader_html5_button_name');
+    }
+    
+    /**
+     * Show uploader button.
+     * @return HTML string
+     */
+    public function getUploaderButton($aParams = array())
+    {
+        $iResizeWidth = (int)getParam('client_image_resize_width');
+        $iResizeHeight = (int)getParam('client_image_resize_height');
+        $sAcceptedFiles = $this->getAcceptedFilesExtensions();
+
+        $iContentId = $aParams['content_id'];
+        $isPrivate  = $aParams['storage_private'];
+        
+        $aParams = array_merge(
+            $aParams,
+            [
+                'form_container_id' => $this->_sFormContainerId,
+                'errors_container_id' => $this->_sErrorsContainerId,
+                'uploader_instance_name' => $this->getNameJsInstanceUploader(),
+                'restrictions_text' => $this->getRestrictionsText(),
+                'div_id' => $this->_sDivId,
+                'focus_div_id' => $this->_sFocusDivId,
+                'progress_div_id' => $this->_sProgressDivId,
+                'content_id' => $iContentId,
+                'storage_private' => $isPrivate,
+                'max_file_size' => $this->getMaxUploadFileSize(),
+                'accepted_files' => null === $sAcceptedFiles ? 'null' : "'" . bx_js_string($sAcceptedFiles) . "'",
+                'resize_width' => $iResizeWidth ? $iResizeWidth : 'null',
+                'resize_height' => $iResizeHeight ? $iResizeHeight : 'null',
+                'resize_method' => $iResizeWidth && $iResizeHeight ? "'contain'" : 'null',
+            ]
+        );
+        return $this->_oTemplate->parseHtmlByName(isset($aParams['button_template']) ? $aParams['button_template'] : $this->_sButtonTemplate, $aParams);
+    }
+
+    public function getUploaderJsParams(){
+        $iResizeWidth = (int)getParam('client_image_resize_width');
+        $iResizeHeight = (int)getParam('client_image_resize_height');
+        $sAcceptedFiles = $this->getAcceptedFilesExtensions();
+        return [
+            'max_file_size' => $this->getMaxUploadFileSize(),
+            'accepted_files' => null === $sAcceptedFiles ? 'null' : "'" . bx_js_string($sAcceptedFiles) . "'",
+            'resize_width' => $iResizeWidth ? $iResizeWidth : 'null',
+            'resize_height' => $iResizeHeight ? $iResizeHeight : 'null',
+            'resize_method' => $iResizeWidth && $iResizeHeight ? "'contain'" : 'null',
+        ];
+    }                                     
+    
+    /**
+     * Show uploader form.
+     * @return HTML string
+     */
+    public function getUploaderForm($isMultiple = true, $iContentId = false, $isPrivate = true)
+    {
+        $iResizeWidth = (int)getParam('client_image_resize_width');
+        $iResizeHeight = (int)getParam('client_image_resize_height');
+        $sAcceptedFiles = $this->getAcceptedFilesExtensions();
+        return $this->_oTemplate->parseHtmlByName($this->_sUploaderFormTemplate, array(
+            'form_container_id' => $this->_sFormContainerId,
+            'errors_container_id' => $this->_sErrorsContainerId,
+            'uploader_instance_name' => $this->getNameJsInstanceUploader(),
+            'restrictions_text' => $this->getRestrictionsText(),
+            'div_id' => $this->_sDivId,
+            'focus_div_id' => $this->_sFocusDivId,
+            'content_id' => $iContentId,
+            'storage_private' => $isPrivate,
+            'max_file_size' => $this->getMaxUploadFileSize(),
+            'accepted_files' => null === $sAcceptedFiles ? 'null' : "'" . bx_js_string($sAcceptedFiles) . "'",
+            'resize_width' => $iResizeWidth ? $iResizeWidth : 'null',
+            'resize_height' => $iResizeHeight ? $iResizeHeight : 'null',
+            'resize_method' => $iResizeWidth && $iResizeHeight ? "'contain'" : 'null',
+        ));
+    }
+
+    /**
+     * Handle uploads here.
+     * @param $mixedFiles as usual $_FILES['some_name'] array, but maybe some other params depending on the uploader
+     * @return nothing, but if some files failed to upload, the actual error message can be determined by calling BxDolUploader::getUploadErrorMessages()
+     */
+    public function handleUploads ($iProfileId, $mixedFiles, $isMultiple = true, $iContentId = false, $bPrivate = true)
+    {
+        $oStorage = BxDolStorage::getObjectInstance($this->_sStorageObject);
+
+        if (!$isMultiple)
+            $this->deleteGhostsForProfile($iProfileId, $iContentId);
+
+        if (bx_get('file')) {
+            $iId = $oStorage->storeFileFromXhr(bx_get('file'), $bPrivate, $iProfileId, $iContentId);
+        } 
+        else {
+            $iId = $oStorage->storeFileFromForm($_FILES['file'], $bPrivate, $iProfileId, $iContentId);
+        }
+
+        if ($iId) {
+            $aResponse = array ('success' => 1);
+        } else {
+            $this->appendUploadErrorMessage(_t('_sys_uploader_err_msg', isset($_FILES['file']['name']) ? $_FILES['file']['name'] : bx_get('file'), $oStorage->getErrorString()));
+            $aResponse = array ('error' => $this->getUploadErrorMessages());
+        }
+        if (bx_is_api())
+            return $aResponse;
+        else
+            echo htmlspecialchars(json_encode($aResponse), ENT_NOQUOTES);
+    }
+}
+
+/** @} */

+ 12 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/files_delete.php

@@ -0,0 +1,12 @@
+<?php
+
+$aFilesDelete = array(
+
+//	'plugins/phpmailer/Exception.php',
+//	'plugins/phpmailer/LICENSE',
+//	'plugins/phpmailer/PHPMailer.php',
+//	'plugins/phpmailer/README.md',
+//	'plugins/phpmailer/SMTP.php',
+//	'plugins/phpmailer/VERSION',
+);
+

+ 3 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/script.php

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

+ 5 - 0
upgrade/files/13.0.0.RC5-13.0.0.RC6/sql.sql

@@ -0,0 +1,5 @@
+
+-- Last step is to update current version
+
+UPDATE `sys_modules` SET `version` = '13.0.0-RC6' WHERE (`version` = '13.0.0.RC5' OR `version` = '13.0.0-RC5') AND `name` = 'system';
+