Kaynağa Gözat

Upgrade script from 14.0.0-B2 to 14.0.0-RC1

Alex Trofimov 2 yıl önce
ebeveyn
işleme
eb7e9ede4e

+ 14 - 3
upgrade/files/14.0.0.B2-14.0.0.RC1/files/inc/classes/BxDolGrid.php

@@ -383,7 +383,7 @@ class BxDolGrid extends BxDolFactory implements iBxDolFactoryObject, iBxDolRepla
         // sort
         $sSortField = false;
         $iSortDir = 1;
-        if ($sOrderField && !empty($this->_aOptions['sorting_fields']) && is_array($this->_aOptions['sorting_fields']) && in_array($sOrderField, $this->_aOptions['sorting_fields'])) { // explicit order
+        if ($sOrderField && ($aSortingFields = $this->_getOrderFields()) && in_array($sOrderField, $aSortingFields)) { // explicit order
             $sSortField = $sOrderField;
             $iSortDir = 0 === strcasecmp($sOrderDir, 'desc') ? -1 : 1;
         } elseif (!empty($this->_aOptions['field_order'])) { // order by "order" field
@@ -568,11 +568,11 @@ class BxDolGrid extends BxDolFactory implements iBxDolFactoryObject, iBxDolRepla
     {
         $sOrderClause = '';
 
-        if ($sOrderField && is_array($this->_aOptions['sorting_fields']) && in_array($sOrderField, $this->_aOptions['sorting_fields'])) { // explicit order
+        if ($sOrderField && ($aSortingFields = $this->_getOrderFields()) && in_array($sOrderField, $aSortingFields)) { // explicit order
 
             $sDir = (0 === strcasecmp($sOrderDir, 'desc') ? 'DESC' : 'ASC');
 
-            if (is_array($this->_aOptions['sorting_fields_translatable']) && in_array($sOrderField, $this->_aOptions['sorting_fields_translatable'])) {
+            if (($aSortingFieldsTranslatable = $this->_getOrderFieldsTranslatable()) && in_array($sOrderField, $aSortingFieldsTranslatable)) {
 
                 // translatable fields
                 $iLang = BxDolLanguages::getInstance()->getCurrentLangId();
@@ -647,6 +647,17 @@ class BxDolGrid extends BxDolFactory implements iBxDolFactoryObject, iBxDolRepla
         return bx_unicode_urldecode(bx_process_input(bx_get($this->_aOptions['filter_get'])));
     }
 
+    protected function _getOrderFields($bTranslatable = false)
+    {
+        $sKey = 'sorting_fields' . ($bTranslatable ? '_translatable' : '');
+        return !empty($this->_aOptions[$sKey]) && is_array($this->_aOptions[$sKey]) ? $this->_aOptions[$sKey] : [];
+    }
+
+    protected function _getOrderFieldsTranslatable()
+    {
+        return $this->_getOrderFields(true);
+    }
+
     protected function _getOrderValue()
     {
         return bx_unicode_urldecode(bx_process_input(bx_get($this->_aOptions['order_get_field'])));

+ 1 - 1
upgrade/files/14.0.0.B2-14.0.0.RC1/files/plugins/autoload.php

@@ -4,4 +4,4 @@
 
 require_once __DIR__ . '/composer/autoload_real.php';
 
-return ComposerAutoloaderInit03a8066c470486be30535b543d47f96a::getLoader();
+return ComposerAutoloaderInitfd83e10aca1f370f3d7f53da5d1e7afa::getLoader();

+ 7 - 7
upgrade/files/14.0.0.B2-14.0.0.RC1/files/plugins/composer/autoload_real.php

@@ -2,7 +2,7 @@
 
 // autoload_real.php @generated by Composer
 
-class ComposerAutoloaderInit03a8066c470486be30535b543d47f96a
+class ComposerAutoloaderInitfd83e10aca1f370f3d7f53da5d1e7afa
 {
     private static $loader;
 
@@ -24,15 +24,15 @@ class ComposerAutoloaderInit03a8066c470486be30535b543d47f96a
 
         require __DIR__ . '/platform_check.php';
 
-        spl_autoload_register(array('ComposerAutoloaderInit03a8066c470486be30535b543d47f96a', 'loadClassLoader'), true, true);
+        spl_autoload_register(array('ComposerAutoloaderInitfd83e10aca1f370f3d7f53da5d1e7afa', 'loadClassLoader'), true, true);
         self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
-        spl_autoload_unregister(array('ComposerAutoloaderInit03a8066c470486be30535b543d47f96a', 'loadClassLoader'));
+        spl_autoload_unregister(array('ComposerAutoloaderInitfd83e10aca1f370f3d7f53da5d1e7afa', '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\ComposerStaticInit03a8066c470486be30535b543d47f96a::getInitializer($loader));
+            call_user_func(\Composer\Autoload\ComposerStaticInitfd83e10aca1f370f3d7f53da5d1e7afa::getInitializer($loader));
         } else {
             $map = require __DIR__ . '/autoload_namespaces.php';
             foreach ($map as $namespace => $path) {
@@ -53,19 +53,19 @@ class ComposerAutoloaderInit03a8066c470486be30535b543d47f96a
         $loader->register(true);
 
         if ($useStaticLoader) {
-            $includeFiles = Composer\Autoload\ComposerStaticInit03a8066c470486be30535b543d47f96a::$files;
+            $includeFiles = Composer\Autoload\ComposerStaticInitfd83e10aca1f370f3d7f53da5d1e7afa::$files;
         } else {
             $includeFiles = require __DIR__ . '/autoload_files.php';
         }
         foreach ($includeFiles as $fileIdentifier => $file) {
-            composerRequire03a8066c470486be30535b543d47f96a($fileIdentifier, $file);
+            composerRequirefd83e10aca1f370f3d7f53da5d1e7afa($fileIdentifier, $file);
         }
 
         return $loader;
     }
 }
 
-function composerRequire03a8066c470486be30535b543d47f96a($fileIdentifier, $file)
+function composerRequirefd83e10aca1f370f3d7f53da5d1e7afa($fileIdentifier, $file)
 {
     if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
         require $file;

+ 5 - 5
upgrade/files/14.0.0.B2-14.0.0.RC1/files/plugins/composer/autoload_static.php

@@ -4,7 +4,7 @@
 
 namespace Composer\Autoload;
 
-class ComposerStaticInit03a8066c470486be30535b543d47f96a
+class ComposerStaticInitfd83e10aca1f370f3d7f53da5d1e7afa
 {
     public static $files = array (
         '7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php',
@@ -216,10 +216,10 @@ class ComposerStaticInit03a8066c470486be30535b543d47f96a
     public static function getInitializer(ClassLoader $loader)
     {
         return \Closure::bind(function () use ($loader) {
-            $loader->prefixLengthsPsr4 = ComposerStaticInit03a8066c470486be30535b543d47f96a::$prefixLengthsPsr4;
-            $loader->prefixDirsPsr4 = ComposerStaticInit03a8066c470486be30535b543d47f96a::$prefixDirsPsr4;
-            $loader->prefixesPsr0 = ComposerStaticInit03a8066c470486be30535b543d47f96a::$prefixesPsr0;
-            $loader->classMap = ComposerStaticInit03a8066c470486be30535b543d47f96a::$classMap;
+            $loader->prefixLengthsPsr4 = ComposerStaticInitfd83e10aca1f370f3d7f53da5d1e7afa::$prefixLengthsPsr4;
+            $loader->prefixDirsPsr4 = ComposerStaticInitfd83e10aca1f370f3d7f53da5d1e7afa::$prefixDirsPsr4;
+            $loader->prefixesPsr0 = ComposerStaticInitfd83e10aca1f370f3d7f53da5d1e7afa::$prefixesPsr0;
+            $loader->classMap = ComposerStaticInitfd83e10aca1f370f3d7f53da5d1e7afa::$classMap;
 
         }, null, ClassLoader::class);
     }

+ 2 - 2
upgrade/files/14.0.0.B2-14.0.0.RC1/files/plugins/composer/installed.php

@@ -5,7 +5,7 @@
         'type' => 'library',
         'install_path' => __DIR__ . '/../../',
         'aliases' => array(),
-        'reference' => '53588980042992e9da0cf02b65071d4a81815440',
+        'reference' => '53576e1540d7625f6f9bed8ee0f17a99467d2870',
         'name' => 'unaio/una',
         'dev' => true,
     ),
@@ -359,7 +359,7 @@
             'type' => 'library',
             'install_path' => __DIR__ . '/../../',
             'aliases' => array(),
-            'reference' => '53588980042992e9da0cf02b65071d4a81815440',
+            'reference' => '53576e1540d7625f6f9bed8ee0f17a99467d2870',
             'dev_requirement' => false,
         ),
         'wikimedia/less.php' => array(

+ 931 - 0
upgrade/files/14.0.0.B2-14.0.0.RC1/files/template/scripts/BxBaseGrid.php

@@ -0,0 +1,931 @@
+<?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
+ * @{
+ */
+
+/**
+ * Grid representation.
+ * @see BxDolGrid
+ */
+class BxBaseGrid extends BxDolGrid
+{
+    protected $_oTemplate;
+    protected $_aPopupOptions = false;
+    protected $_aQueryAppend = [];
+    protected $_aQueryAppendExclude = false; // an array of keys which shouldn't be pathed in http requests, but can be stored (used) in 'Query Append' array.
+    protected $_aQueryAppendExcludeApi = false;
+    protected $_aQueryReset = false;
+    protected $_aConfirmMessages = false;
+    protected $_bSelectAll = false;
+    protected $_isDisplayPopupOnTextOverflow = true;
+
+    public function __construct ($aOptions, $oTemplate)
+    {
+        parent::__construct ($aOptions);
+
+        if ($oTemplate)
+            $this->_oTemplate = $oTemplate;
+        else
+            $this->_oTemplate = BxDolTemplate::getInstance();
+
+        $this->_aPopupOptions = [];
+
+        $this->_aQueryAppend = [
+            $this->_oTemplate->getCodeKey() => $this->_oTemplate->getCode()
+        ];
+        $this->_aQueryAppendExclude = [];
+        $this->_aQueryAppendExcludeApi = [$this->_oTemplate->getCodeKey()];
+
+        $this->_aQueryReset = [
+            $this->_aOptions['filter_get'], 
+            $this->_aOptions['order_get_field'], 
+            $this->_aOptions['order_get_dir'], 
+            $this->_aOptions['paginate_get_start'], 
+            $this->_aOptions['paginate_get_per_page']
+        ];
+
+        $this->_aConfirmMessages = [];
+    }
+
+    public function performActionDisplay()
+    {
+        if($this->_bIsApi)
+            return $this->getCodeAPI();
+
+        require_once(BX_DIRECTORY_PATH_INC . "design.inc.php");
+
+        echoJson(array(
+            'grid' => $this->getCode(false), 
+            'total_count' => $this->_iTotalCount,
+            'total_count_f' => $this->_getCounter()
+        ));
+    }
+
+    public function performActionReorder()
+    {
+        $this->_replaceMarkers ();
+
+        $aOrder = bx_get($this->_sObject . '_row');
+        $iOrder = 0;
+        foreach ($aOrder as $mixedId)
+            $this->_updateOrder($mixedId, ++$iOrder);
+
+        echoJson(array());
+    }
+
+    public function performActionDelete()
+    {
+        $this->_replaceMarkers ();
+
+        $aResult = [];
+        $iAffected = 0;
+        $aIds = bx_get('ids');
+        if (!$aIds || !is_array($aIds))
+            return $this->_getActionResult($aResult);
+
+        foreach ($aIds as $mixedId)
+            $iAffected += $this->_delete($mixedId) ? 1 : 0;
+
+        if($iAffected)
+            $aResult = !$this->_bIsApi ? ['grid' => $this->getCode(false)] : [];
+        else
+            $aResult = ['msg' => _t("_sys_grid_delete_failed")];
+
+        return $this->_getActionResult($aResult);
+    }
+
+    public function performActionEnable($mixedChecked = null)
+    {
+        $this->_replaceMarkers ();
+
+        $aIds = bx_get('ids');
+        if (!$aIds || !is_array($aIds)) {
+            echoJson(array());
+            exit;
+        }
+
+        $iChecked = (int)($mixedChecked !== null ? $mixedChecked : bx_get('checked'));
+        
+        $aAffectedIds = array ();
+        foreach ($aIds as $mixedId)
+            if ($this->_enable($mixedId, $iChecked))
+                $aAffectedIds[] = preg_match("/^[\d\w]+$/", $mixedId) ? $mixedId : (int)$mixedId;
+
+        $sAction = $iChecked ? 'enable' : 'disable';
+
+        if($this->_bIsApi)
+            return $aAffectedIds;
+
+        echo echoJson(array($sAction => $aAffectedIds));
+    }
+
+    public function getCode ($isDisplayHeader = true)
+    {
+        $this->_replaceMarkers ();
+
+        if ($isDisplayHeader && empty($this->_aOptions['paginate_url'])) {
+            // reset page query params if grid is just initialized and it uses AJAX paginate
+            $this->resetQueryParams();
+        }
+
+        $sPaginate = '';
+        $aData = array();
+
+        $sIdWrapper = 'bx-grid-wrap-' . $this->_sObject;
+        $sIdContainer = 'bx-grid-cont-' . $this->_sObject;
+        $sIdTable = 'bx-grid-table-' . $this->_sObject;
+
+        $sFilter = $this->_getFilterValue();
+        $sOrderField = $this->_getOrderValue();
+        $sOrderDir = 0 === strcasecmp('desc', bx_get($this->_aOptions['order_get_dir'])) ? 'DESC' : 'ASC';
+
+        $iStart = 0;
+        if($this->_aOptions['paginate_get_start'] && ($iStartGet = (int)bx_get($this->_aOptions['paginate_get_start'])) >= 0)
+            $iStart = $iStartGet;
+
+        $iPerPage = 10;
+        if($this->_aOptions['paginate_get_per_page'] && ($iPerPageGet = (int)bx_get($this->_aOptions['paginate_get_per_page'])) > 0)
+            $iPerPage = $iPerPageGet;
+        else if($this->_aOptions['paginate_per_page'])
+            $iPerPage = (int)$this->_aOptions['paginate_per_page'];            
+
+        if ($this->_aOptions['paginate_get_start']) {
+
+            $aData = $this->_getData ($sFilter, $sOrderField, $sOrderDir, $iStart, $iPerPage + 1);
+
+            $sPageUrl = false;
+            if (!empty($this->_aOptions['paginate_url'])) {
+
+                $sPageUrl = $this->_aOptions['paginate_url'];
+
+                $aParamsAppend = array();
+                if ($sFilter) {
+                    $aParamsAppend['filter'] = bx_process_input(bx_get($this->_aOptions['filter_get']));
+                }
+                if ($sOrderField) {
+                    $aParamsAppend['order_field'] = bx_process_input(bx_get($this->_aOptions['order_get_field']));
+                    $aParamsAppend['order_dir'] = bx_process_input(bx_get($this->_aOptions['order_get_dir']));
+                }
+                if ($aParamsAppend)
+                    $sPageUrl = bx_append_url_params($sPageUrl, $aParamsAppend);
+            }
+
+            $aPaginateParams = array(
+                'start' => $iStart,
+                'per_page' => $iPerPage,
+                'page_url' =>  $sPageUrl ? $sPageUrl : "javascript:glGrids." . $this->_sObject . ".reload('{start}'); void(0);",
+            );
+
+            $oPaginate = new BxTemplPaginate($aPaginateParams, $this->_oTemplate);
+            $oPaginate->setNumFromDataArray($aData);
+
+            if (isset($this->_aOptions['paginate_simple']) && false !== $this->_aOptions['paginate_simple'])
+                $sPaginate = $oPaginate->getSimplePaginate($this->_aOptions['paginate_simple']);
+            else
+                $sPaginate = $oPaginate->getPaginate();
+        } else {
+            $aData = $this->_getData ($sFilter, $sOrderField, $sOrderDir, $iStart, $iPerPage);
+        }
+
+        if((empty($aData) || !is_array($aData)) && isset($this->_aBrowseParams['empty_message']) && !(bool)$this->_aBrowseParams['empty_message'])
+            return '';
+        
+        $sPopupOptions = '{}';
+        if (!empty($this->_aPopupOptions) && is_array($this->_aPopupOptions))
+            $sPopupOptions = json_encode($this->_aPopupOptions);
+
+        $aQueryAppend = array_merge(is_array($this->_aQueryAppend) ? $this->_aQueryAppend : array(), is_array($this->_aMarkers) ? $this->_aMarkers : array());
+        if(!empty($this->_aQueryAppendExclude) && is_array($this->_aQueryAppendExclude))
+            $aQueryAppend = array_diff_key($aQueryAppend, array_flip($this->_aQueryAppendExclude));
+
+        $sQueryAppend = '{}';
+        if (!empty($aQueryAppend) && is_array($aQueryAppend))
+            $sQueryAppend = json_encode($aQueryAppend);
+
+        $sConfirmMessages = '{}';
+        if (!empty($this->_aConfirmMessages) && is_array($this->_aConfirmMessages))
+            $sConfirmMessages = json_encode($this->_aConfirmMessages);
+
+        $iColumns = count($this->_aOptions['fields']);
+        $aVarsHead = $this->_getRowHead();
+
+        $sClassHeader = '';
+        if($this->_aOptions['show_total_count'] == 0)
+            $sClassHeader .= ' bx-gh-no-counter';
+
+        $aVars = array (
+            'object' => $this->_sObject,
+            'csrf_token' => BxDolForm::getCsrfToken(),
+            'id_table' => $sIdTable,
+            'id_cont' => $sIdContainer,
+            'id_wrap' => $sIdWrapper,
+            'class_table_wrapper' => $this->_aOptions['responsive'] ? 'bx-grid-table-wrapper-responsive' : '',
+            'sortable' => empty($this->_aOptions['field_order']) ? 0 : 1,
+            'sorting' => empty($this->_getOrderFields()) ? 0 : 1,
+            'sorting_field' => $sOrderField,
+            'sorting_dir' => $sOrderDir,
+        	'bx_if:display_head' => array(
+        		'condition' => !empty($aVarsHead),
+        		'content' => array(
+        			'bx_repeat:row_header' => $aVarsHead,
+        			'columns' => $iColumns,
+        		)
+        	),
+            'bx_repeat:rows_data' => $this->_getRowsDataDesign ($aData),
+            'paginate_get_start' => $this->_aOptions['paginate_get_start'],
+            'paginate_get_per_page' => $this->_aOptions['paginate_get_per_page'],
+            'start' => $iStart,
+            'per_page' => $iPerPage,
+            'filter' => bx_js_string($sFilter, BX_ESCAPE_STR_APOS),
+            'filter_get' => bx_js_string($this->_aOptions['filter_get']),
+            'order_field' => bx_js_string($sOrderField, BX_ESCAPE_STR_APOS),
+            'order_dir' => bx_js_string($sOrderDir, BX_ESCAPE_STR_APOS),
+            'order_get_field' => bx_js_string($this->_aOptions['order_get_field']),
+            'order_get_dir' => bx_js_string($this->_aOptions['order_get_dir']),
+            'popup_options' => $sPopupOptions,
+            'query_append' => $sQueryAppend,
+            'confirm_messages' => $sConfirmMessages,
+            'columns' => $iColumns,
+            'bx_if:display_footer' => array(
+        		'condition' => !empty($this->_aOptions['actions_bulk']) || !empty($sPaginate),
+        		'content' => array(
+		            'bx_if:actions_bulk' => array (
+		                'condition' => !empty($this->_aOptions['actions_bulk']),
+		                'content' => array(
+		                    'actions_bulk' => $this->_getActions ('bulk'),
+		                ),
+		            ),
+		            'paginate' => $sPaginate,
+				)
+			),
+            'bx_if:display_header' => array (
+                'condition' => $isDisplayHeader,
+                'content' => array (
+                    'class' => $sClassHeader,
+                    'bx_if:actions_independent' => array (
+                        'condition' => !empty($this->_aOptions['actions_independent']),
+                        'content' => array(
+                            'actions_independent' => $this->_getActions ('independent'),
+                        ),
+                    ),
+                    'bx_if:filter' => array (
+                        'condition' => !empty($this->_aOptions['filter_fields']) || !empty($this->_aOptions['filter_fields_translatable']),
+                        'content' => array(
+                            'controls' => $this->_getFilterControls(),
+                        ),
+                    ),
+                    'bx_if:counter' => array (
+                        'condition' => $this->_aOptions['show_total_count'] == 1,
+                        'content' => array(
+                            'counter' => $this->_getCounter(),
+                        ),
+                    ),
+                ),
+            ),
+        );
+
+        $this->_addJsCss();
+
+        return $this->_oTemplate->parseHtmlByName('grid.html', $aVars);
+    }
+
+    /**
+     * Get grid code API.
+     * @return array
+     */
+    public function getCodeAPI($bForceReturn = false)
+    {
+        $this->_replaceMarkers();
+
+        $sFilter = bx_unicode_urldecode(bx_process_input(bx_get($this->_aOptions['filter_get'])));
+        $sOrderField = bx_unicode_urldecode(bx_process_input(bx_get($this->_aOptions['order_get_field'])));
+        $sOrderDir = 0 === strcasecmp('desc', bx_get($this->_aOptions['order_get_dir'])) ? 'DESC' : 'ASC';
+
+        $iStart = 0;
+        if($this->_aOptions['paginate_get_start'] && ($iStartGet = (int)bx_get($this->_aOptions['paginate_get_start'])) >= 0)
+            $iStart = $iStartGet;
+
+        $iPerPage = 10;
+        if($this->_aOptions['paginate_get_per_page'] && ($iPerPageGet = (int)bx_get($this->_aOptions['paginate_get_per_page'])) > 0)
+            $iPerPage = $iPerPageGet;
+        else if($this->_aOptions['paginate_per_page'])
+            $iPerPage = (int)$this->_aOptions['paginate_per_page']; 
+
+        $aData = $this->_getData($sFilter, $sOrderField, $sOrderDir, $iStart, $iPerPage);
+        if(!empty($aData) && is_array($aData))
+            $aData = $this->decodeDataAPI($aData);
+
+        $aQueryAppend = [];
+        if(!empty($this->_aQueryAppend) && is_array($this->_aQueryAppend))
+            $aQueryAppend = array_merge($aQueryAppend, $this->_aQueryAppend);
+        if(!empty($this->_aMarkers) && is_array($this->_aMarkers))
+            $aQueryAppend = array_merge($aQueryAppend, $this->_aMarkers);
+        if(!empty($this->_aQueryAppendExclude) && is_array($this->_aQueryAppendExclude))
+            $aQueryAppend = array_diff_key($aQueryAppend, array_flip($this->_aQueryAppendExclude));
+        if(!empty($this->_aQueryAppendExcludeApi) && is_array($this->_aQueryAppendExcludeApi))
+            $aQueryAppend = array_diff_key($aQueryAppend, array_flip($this->_aQueryAppendExcludeApi));       
+
+        return [
+            'settings' => [
+                'object' => $this->_aOptions['object'], 
+                'field_id' => $this->_aOptions['field_id'], 
+                'start' => $iStart, 
+                'per_page' => $iPerPage,
+                'filters' => $this->_getFilterControlsAPI(),
+                'query_append' => $aQueryAppend
+            ],
+            'header' => $this->_getRowHeadAPI(),
+            'data' => defined('BX_API_PAGE') && !$bForceReturn ? [] : $aData,
+            'actions' => [
+                'independent' => $this->_getActionsAPI('independent'), 
+                'bulk' => $this->_getActionsAPI('bulk')
+            ]
+        ];
+    }
+
+    public function decodeDataAPI($aData)
+    {
+        $sKeyId = $this->_aOptions['field_id'];
+        $sMethodDefault = '_getCellDefault';
+
+        $aDataRv = [];
+        foreach($aData as $iKey => $aRow) {
+            $aDataRv[$iKey] = [$sKeyId => $aData[$iKey][$sKeyId]];
+
+            foreach($this->_aOptions['fields'] as $sKey => $aField) {
+                $sMethod = '_getCell' . $this->_genMethodName($sKey);
+                if(!method_exists($this, $sMethod))
+                    $sMethod = $sMethodDefault;
+
+                $aDataRv[$iKey][$sKey] = $this->$sMethod(isset($aData[$iKey][$sKey]) ? $aData[$iKey][$sKey] : _t('_undefined'), $sKey, $aField, $aRow);
+            }
+        }
+
+        return $aDataRv;
+    }
+
+    public function getFormBlockAPI($oForm, $sAction, $iId = 0)
+    {
+        return [
+            bx_api_get_block('form', $oForm->getCodeAPI(), [
+                'ext' => [
+                    'name' => $this->_oModule->getName(), 
+                    'title' => $this->getFormBlockTitleAPI($sAction, $iId),
+                    'request' => ['url' => $this->getFormCallBackUrlAPI($sAction, $iId), 'immutable' => true]]
+                ]
+            )
+        ];
+    }
+
+    public function getFormBlockTitleAPI($sAction, $iId)
+    {
+        return '';
+    }
+
+    public function getFormCallBackUrlAPI($sAction, $iId)
+    {
+        return '';
+    }
+
+    /**
+     * Reset query params, like filter and page number
+     */
+    public function resetQueryParams()
+    {
+        foreach ($this->_aQueryReset as $sKey) {
+            unset($_GET[$sKey]);
+            unset($_POST[$sKey]);
+        }
+    }
+
+    protected function _getRowHead()
+    {
+        $aRet = array();
+        foreach ($this->_aOptions['fields'] as $sKey => $a) {
+
+            $sMethod = '_getCellHeaderDefault';
+            $sCustomMethod = '_getCellHeader' . $this->_genMethodName($sKey);
+            if (method_exists($this, $sCustomMethod))
+                $sMethod = $sCustomMethod;
+
+            $aRet[] = array('header_cell' => $this->$sMethod($sKey, $a));
+        }
+        return $aRet;
+    }
+
+    protected function _getRowHeadAPI()
+    {
+        $aHeader = [];
+
+        foreach($this->_aOptions['fields'] as $sKey => $aField) {
+            $sMethod = '_getCellHeader' . $this->_genMethodName($sKey);
+            if(method_exists($this, $sMethod))
+                $aHeader[] = $this->$sMethod($sKey, $aField);
+            else
+                $aHeader[] = [
+                    'name' => bx_process_output($aField['name']),
+                    'title' => bx_process_output($aField['title']),
+                    'width' => $aField['width']
+                ];
+        }
+
+        return $aHeader;
+    }
+
+    protected function _getCellHeaderDefault ($sKey, $aField)
+    {
+        $sHeader = bx_process_output($aField['title']);
+
+        if (($aSortingFields = $this->_getOrderFields()) && in_array($sKey, $aSortingFields)) {
+            $sHeader = '<a href="javascript:void(0);" class="bx-grid-sort-handle">' . $sHeader . '</a><span class="bx-grid-sort-indi"></span>';
+            $aField['attr_head']['bx_grid_sort_head'] = $sKey;
+        }
+
+        $sAttr = $this->_convertAttrs(
+            $aField, 'attr_head',
+            'bx-def-padding-sec-bottom bx-def-padding-sec-top bx-grid-header-' . $sKey, // add default classes
+            isset($aField['width']) ? 'width:' . $aField['width'] : false // add default styles
+        );
+
+        return $this->_getCellHeaderWrapper ($sKey, $aField, $sHeader, $sAttr);
+    }
+
+    protected function _getCellHeaderCheckbox ($sKey, $aField)
+    {
+        if($this->_bIsApi){
+            return [
+                'name' => bx_process_output($aField['name']),
+                'title' => bx_process_output($aField['title']),
+                'width' => $aField['width']
+            ];   
+        }
+        
+    	$aAttr = array(
+    		'type' => 'checkbox',
+    		'id' => $this->_sObject . '_check_all',
+    		'name' => $this->_sObject . '_check_all',
+    		'onclick' => "$('input[name=" . $this->_sObject . "_check]:not([disabled])').attr('checked', this.checked)"
+    	);
+    	if($this->_bSelectAll)
+    		$aAttr['checked'] = 'checked';
+
+    	$aField['attr'] = isset($aField['attr']) && is_array($aField['attr']) ? array_merge($aAttr, $aField['attr']) : $aAttr;
+
+        $sAttrHead = $this->_convertAttrs(
+            $aField, 'attr_head',
+            'bx-def-padding-sec-bottom bx-def-padding-sec-top bx-grid-header-' . $sKey, // add default classes
+            isset($aField['width']) ? 'width:' . $aField['width'] : false // add default styles
+        );
+        return $this->_getCellHeaderWrapper ($sKey, $aField, ' <input ' . $this->_convertAttrs($aField, 'attr') . ' /> ', $sAttrHead);
+    }
+
+    protected function _getCellHeaderWrapper ($sKey, $aField, $sHeader, $sAttr)
+    {
+        return '<th ' . $sAttr . '>' . $sHeader . '</th>';
+    }
+
+    /**
+     * Check if the whole row is disabled.
+     * When row is disabled - checkbox is not selectable, actions aren't clickable and text is grayed out.
+     * By default all rows aren't disabled.
+     * @param $aRow row array
+     * @return boolean
+     */
+    protected function _isRowDisabled($aRow)
+    {
+        if (isset($aRow[$this->_aOptions['field_active']]) && !$this->_switcherState2Checked($aRow[$this->_aOptions['field_active']]))
+            return true;
+        return false;
+    }
+
+    /**
+     * Determine how actions are disabled when whole row is disabled.
+     * @param $aRow row array
+     * @return null - disable/enable actions when row is disabled/enabled, true - actions are always disabled, false - actions are always enabled
+     */
+    protected function _getActionsDisabledBehavior($aRow)
+    {
+        return null;
+    }
+
+    /**
+     * Check if the checkbox is disabled.
+     * @param $aRow row array
+     * @return boolean
+     */
+    protected function _isCheckboxDisabled($aRow)
+    {
+        return $this->_isRowDisabled($aRow);
+    }
+
+    /**
+     * Is checkbox checked by default ?
+     * By default no one checkbox is selected.
+     * @return boolean
+     */
+    protected function _isCheckboxSelected($mixedValue, $sKey, $aField, $aRow)
+    {
+        return $this->_bSelectAll;
+    }
+
+    /**
+     * Is switcher on by default ?
+     * By default no one switcher is on.
+     * @return boolean
+     */
+    protected function _isSwitcherOn($mixedValue, $sKey, $aField, $aRow)
+    {
+        return $this->_switcherState2Checked($mixedValue);
+    }
+
+    /**
+     * Convert switcher checked status to the actual value ?
+     * @return boolean
+     */
+    protected function _switcherChecked2State($isChecked)
+    {
+        return $isChecked ? 1 : 0;
+    }
+
+    /**
+     * Convert switcher value to checked(boolean) value ?
+     * @return boolean
+     */
+    protected function _switcherState2Checked($mixedState)
+    {
+        return $mixedState ? true : false;
+    }
+
+    protected function _getRowsDataDesign (&$aData)
+    {
+        $aGrid = array();
+
+        if(empty($aData))
+            $aGrid[] = array(
+                'id_row' => 0,
+                'row_class' => 'bx-grid-table-row-empty',
+                'row' => '<td class="bx-def-padding-sec-topbottom" colspan="' . count($this->_aOptions['fields']) . '">' . MsgBox(_t('_Empty')) . '</td>'
+            );
+        else
+            foreach ($aData as $aRow)
+                $aGrid[] = array(
+                    'id_row' => $this->_getRowId($aRow),
+                    'row_class' => $this->_isRowDisabled($aRow) ? 'bx-grid-table-row-disabled bx-def-font-grayed' : '',
+                    'row' => $this->_getRowDesign($aRow)
+                );
+
+        return $aGrid;
+    }
+
+    protected function _getRowId($mixedRow)
+    {
+        if(is_string($mixedRow))
+            $sId = rand(1, getrandmax());
+        else
+            $sId = $mixedRow[$this->_aOptions['field_id']];
+
+        return $this->_sObject . '_row_' . $sId;
+    }
+
+    protected function _getRowDesign($mixedRow)
+    {
+        $sRow = '';
+        if(is_string($mixedRow))
+            $sRow = '<td class="bx-def-padding-sec-topbottom bx-def-font-bold" colspan="' . count($this->_aOptions['fields']) . '">' . $mixedRow . '</td>';
+        else
+            foreach($this->_aOptions['fields'] as $sKey => $aField)
+                $sRow .= $this->_getCellDesign($sKey, $aField, $mixedRow);
+
+        return $sRow;
+    }
+
+    protected function _getCellDesign($sKey, $aField, $aRow)
+    {
+        $sMethod = '_getCellDefault';
+        if ($this->_aOptions['field_order'] == $sKey)
+            $sMethod = '_getCellOrder';
+
+        $sCustomMethod = '_getCell' . $this->_genMethodName($sKey);
+        if (method_exists($this, $sCustomMethod))
+            $sMethod = $sCustomMethod;
+
+        $mixedValue = $this->_getCellData($sKey, $aField, $aRow);
+
+        if ($aField['translatable'])
+            $mixedValue = _t($mixedValue);
+
+        $mixedValue = $this->_limitMaxLength($mixedValue, $sKey, $aField, $aRow, $this->_isDisplayPopupOnTextOverflow);
+
+        return $this->$sMethod($mixedValue, $sKey, $aField, $aRow);
+    }
+
+    protected function _getCellCheckbox ($mixedValue, $sKey, $aField, $aRow)
+    {
+        if($this->_bIsApi)
+            return ['type' => 'checkbox', 'data' => $aRow[$this->_aOptions['field_id']]];
+
+        $sAttr = $this->_convertAttrs(
+            $aField, 'attr_cell',
+            'bx-def-padding-sec-bottom bx-def-padding-sec-top', // add default classes
+            isset($aField['width']) ? 'width:' . $aField['width'] : false  // add default styles
+        );
+        $sDisabled = ($this->_isCheckboxDisabled($aRow) ? 'disabled="disabled"' : '');
+        $sSelected = ($this->_isCheckboxSelected($mixedValue, $sKey, $aField, $aRow) ? 'checked="checked"' : '');
+        $sVal = $aRow[$this->_aOptions['field_id']];
+        return '<td ' . $sAttr . '><input type="checkbox" name="'. $this->_sObject . '_check" value="' . $sVal . '" ' . $sDisabled . ' ' . $sSelected . '/></td>';
+    }
+
+    protected function _getCellSwitcher ($mixedValue, $sKey, $aField, $aRow)
+    {
+        if($this->_bIsApi)
+            return ['type' => 'switcher', 'data' => $aRow[$this->_aOptions['field_active']], 'fld' => $this->_aOptions['field_active']];
+
+        $sAttr = $this->_convertAttrs(
+            $aField, 'attr_cell',
+            'bx-def-padding-sec-bottom bx-def-padding-sec-top', // add default classes
+            isset($aField['width']) ? 'width:' . $aField['width'] : false  // add default styles
+        );
+
+        $oForm = new BxTemplFormView(array(), $this->_oTemplate);
+        $oForm->addCssJs();
+        $aInput = array(
+            'type' => 'switcher',
+            'name' => $this->_sObject . '_switch_' . $aRow[$this->_aOptions['field_id']],
+            'caption' => '',
+            'attrs' => array (
+                'bx_grid_action_single' => 'enable',
+                'bx_grid_action_confirm' => '',
+                'bx_grid_action_data' => $aRow[$this->_aOptions['field_id']],
+            ),
+            'value' => $aRow[$this->_aOptions['field_id']],
+            'checked' => $this->_isSwitcherOn(isset($aRow[$this->_aOptions['field_active']]) ? $aRow[$this->_aOptions['field_active']] : false, $sKey, $aField, $aRow),
+        );
+        $sSwitcher = $oForm->genInput($aInput);
+        return '<td ' . $sAttr . '>' . $sSwitcher . '</td>';
+    }
+
+    protected function _getCellOrder ($mixedValue, $sKey, $aField, $aRow)
+    {
+        if($this->_bIsApi)
+            return ['type' => 'order', 'data'=> $mixedValue];    
+
+        $sAttr = $this->_convertAttrs(
+            $aField, 'attr_cell',
+            'bx-def-padding-sec-bottom bx-def-padding-sec-top', // add default classes
+            isset($aField['width']) ? 'width:' . $aField['width'] : false  // add default styles
+        );
+
+        return '<td ' . $sAttr . '><div id="' . $this->_sObject . '_cell_' . $aRow[$this->_aOptions['field_id']] . '" class="bx-grid-drag-handle"><i class="sys-icon align-justify"></i></div></td>';
+    }
+
+    protected function _getCellActions ($mixedValue, $sKey, $aField, $aRow)
+    {
+        $sAttr = $this->_convertAttrs(
+            $aField, 'attr_cell',
+            'bx-def-padding-sec-bottom bx-def-padding-sec-top', // add default classes
+            isset($aField['width']) ? 'width:' . $aField['width'] : false  // add default styles
+        );
+
+        $mixedDisabledBehavior = $this->_getActionsDisabledBehavior($aRow);
+        $mixedActions = $this->_getActions('single', $aRow[$this->_aOptions['field_id']], false, null === $mixedDisabledBehavior ? $this->_isRowDisabled($aRow) : $mixedDisabledBehavior, null !== $mixedDisabledBehavior, $aRow);
+
+        if($this->_bIsApi)
+            return ['type' => 'actions', 'data' => $mixedActions];
+
+        return '<td ' . $sAttr . '><div class="bx-grid-cell-single-actions-wrapper bx-def-margin-thd-neg">' . $mixedActions . '</div></td>';
+    }
+
+    protected function _getCellDefault ($mixedValue, $sKey, $aField, $aRow)
+    {
+        if($this->_bIsApi)
+            return ['type' => 'text', 'value'=> (int)$aField['translatable'] ? _t($mixedValue) : $mixedValue];    
+
+        $sAttr = $this->_convertAttrs(
+            $aField, 'attr_cell',
+            'bx-def-padding-sec-bottom bx-def-padding-sec-top', // add default classes
+            isset($aField['width']) ? 'width:' . $aField['width'] : false // add default styles
+        );
+        return '<td ' . $sAttr . '>' . $mixedValue . '</td>';
+    }
+
+    protected function _getActions ($sType, $sActionData = false, $isSmall = false, $isDisabled = false, $isPermanentState = false, $aRow = array())
+    {
+        $sActionsType = 'actions_' . $sType;
+        if (empty($this->_aOptions[$sActionsType]) || !is_array($this->_aOptions[$sActionsType]))
+            return '';
+
+        $mixedResult = $this->_bIsApi ? [] : '';
+        foreach ($this->_aOptions[$sActionsType] as $sKey => $a) {
+            if(!$a['active'] || (!$a['title'] && !$a['icon']))
+                continue;
+
+            $sFunc = '_getAction' . $this->_genMethodName($sKey);
+            if (!method_exists($this, $sFunc))
+                $sFunc = empty($a) ? '_getActionDivider' : '_getActionDefault';
+
+            if (!isset($a['attr']['bx_grid_action'])) {
+                $a['attr']['bx_grid_action_' . $sType] = $sKey;
+                if (!empty($sActionData))
+                    $a['attr']['bx_grid_action_data'] = $sActionData;
+                if (isset($a['confirm']))
+                    $a['attr']['bx_grid_action_confirm'] = $a['confirm'] ? 1 : 0;
+                $a['attr']['bx_grid_action_reset_paginate'] = false === strpos($sKey, 'delete') ? 0 : 1; // reset paginate after deleting row 
+            }
+
+            if ($isPermanentState && !isset($a['attr']['bx_grid_permanent_state']))
+                $a['attr']['bx_grid_permanent_state'] = 1;
+
+            if ($this->_bIsApi)
+                $mixedResult[] = $this->$sFunc($sType, $sKey, $a, $isSmall, $isDisabled, $aRow);
+            else
+                $mixedResult .= $this->$sFunc($sType, $sKey, $a, $isSmall, $isDisabled, $aRow);
+        }
+
+        return $mixedResult;
+    }
+
+    protected function _getActionsAPI ($sType)
+    {
+        $sActionsType = 'actions_' . $sType;
+        if(empty($this->_aOptions[$sActionsType]) || !is_array($this->_aOptions[$sActionsType]))
+            return [];
+
+        
+        
+        foreach ($this->_aOptions[$sActionsType] as $sKey => $aAction){
+            $sFunc = '_getAction' . $this->_genMethodName($sType . '_' . $sKey);
+            if (method_exists($this, $sFunc)){
+                $this->_aOptions[$sActionsType][$sKey] = $this->$sFunc($aAction);
+            }
+            else{
+                $this->_aOptions[$sActionsType][$sKey]['type'] = 'modal';
+                $this->_aOptions[$sActionsType][$sKey]['action'] = $sKey;
+                $this->_aOptions[$sActionsType][$sKey]['params'] = '';
+            }
+        }
+        return $this->_aOptions[$sActionsType];
+    }
+
+    protected function _getActionDelete($sType, $sKey, $a, $isSmall = false, $isDisabled = false, $aRow = array())
+    {
+        if($this->_bIsApi)
+            return array_merge($a, ['name' => $sKey, 'type' => 'callback', 'on_callback' => 'hide_row']);
+
+    	return $this->_getActionDefault($sType, $sKey, $a, $isSmall, $isDisabled, $aRow);
+    }
+
+    protected function _getActionDefault ($sType, $sKey, $a, $isSmall = false, $isDisabled = false, $aRow = array())
+    {
+        
+        if ($a['icon_only'] && empty($a['attr']['title']) && !empty($a['title']))
+            $a['attr']['title'] = $a['title'];
+
+        if ($this->_bIsApi) {
+            $sFunc = '_getAction' . $this->_genMethodName($sType . '_' . $sKey);
+            if (method_exists($this, $sFunc)) {
+                $aAction = $this->$sFunc($a, ['id'=> $aRow[$this->_aOptions['field_id']]]);
+                if(empty($aAction) || !is_array($aAction))
+                    return [];
+
+                return array_merge($a, ['name' => $sKey], $aAction);
+            }
+            else {
+                return array_merge($a, ['name' => $sKey, 'type' => 'modal', 'action' => $sKey, 'params' => '&id=' . $aRow[$this->_aOptions['field_id']] ]);
+            }
+            
+        }
+
+        $sAttr = $this->_convertAttrs(
+            $a, 'attr',
+            'bx-btn bx-def-margin-thd' . ($isSmall ? ' bx-btn-small' : '') . ($isDisabled ? ' bx-btn-disabled' : '') // add default classes
+        );
+
+        $sIcon = '';
+        $sImage = '';
+        if (!empty($a['icon'])) {
+            if (false === strpos($a['icon'], '.'))
+                $sIcon = '<i class="sys-icon ' . $a['icon'] . '"></i>';
+            elseif ($sIconUrl = $this->_oTemplate->getIconUrl($a['icon']))
+                $sImage = '<img style="background-image:url(' . $sIconUrl . ');" src="' . $this->_oTemplate->getIconUrl('spacer.gif') .'" />';
+        }
+
+        return '<button ' . $sAttr . '>' . $sIcon . $sImage . ($a['icon_only'] || empty($a['title']) ? '' : '<u>' . $a['title'] . '</u>') . '</button>';
+    }
+
+    protected function _getActionDivider ($sType, $sKey, $a, $isSmall = false, $isDisabled = false, $aRow = array())
+    {
+        return '<div class="bx-grid-actions-divider bx-def-margin-sec-left"> | </div>';
+    }
+
+    protected function _getActionResult($aResult)
+    {
+        return $this->_bIsApi ? $aResult : echoJson($aResult);
+    }
+
+    protected function _getFilterControls ()
+    {
+        $oForm = new BxTemplFormView(array());
+
+        $aInput = array(
+            'type' => 'text',
+            'name' => 'keyword',
+            'attrs' => array(
+                'id' => 'bx-grid-search-' . $this->_sObject
+            )
+        );
+
+        $this->_oTemplate->addCss('forms.css');
+        return $oForm->genRow($aInput);
+    }
+
+    protected function _getFilterControlsAPI($aFilters = [])
+    {
+        if(!empty($this->_aOptions['filter_fields']) || !empty($this->_aOptions['filter_fields_translatable']))
+            $aFilters['search'] = [];
+
+        return $aFilters;
+    }
+
+    protected function _getCounter()
+    {
+        return _t('_sys_grid_total_count', $this->_iTotalCount);
+    }
+
+    protected function _limitMaxLength ($mixedValue, $sKey, $aField, $aRow, $isDisplayPopupOnTextOverflow, $bReturnString = true)
+    {
+        if ($aField['chars_limit'] > 0)
+            $mixedValue = BxTemplFunctions::getInstance()->getStringWithLimitedLength($mixedValue, $aField['chars_limit'], $isDisplayPopupOnTextOverflow, $bReturnString);
+        return $mixedValue;
+    }
+
+    protected function _convertAttrs ($aField, $sAttrName, $sClasses = false, $sStyles = false)
+    {
+        if (!empty($aField['hidden_on'])) {
+            $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'
+            );
+            foreach ($aHiddenOn as $iHiddenOn => $sClass)
+                if ((int)$aField['hidden_on'] & $iHiddenOn)
+                    $sClasses .= ' ' . $sClass;
+        }
+            
+        return bx_convert_array2attrs(
+            isset($aField[$sAttrName]) && is_array($aField[$sAttrName]) ? $aField[$sAttrName] : array(),
+            $sClasses,
+            $sStyles
+        );
+    }
+
+    protected function _updateOrder($mixedId, $iOrder)
+    {
+        $oDb = BxDolDb::getInstance();
+        $sTable = $this->_aOptions['table'];
+        $sFieldId = $this->_aOptions['field_id'];
+        $sFieldOrder = $this->_aOptions['field_order'];
+        $sQuery = $oDb->prepare("UPDATE `{$sTable}` SET `{$sFieldOrder}` = ? WHERE `{$sFieldId}` = ?", $iOrder, $mixedId);
+        return $oDb->query($sQuery);
+    }
+
+    protected function _delete ($mixedId)
+    {
+        $oDb = BxDolDb::getInstance();
+        $sTable = $this->_aOptions['table'];
+        $sFieldId = $this->_aOptions['field_id'];
+        $sQuery = $oDb->prepare("DELETE FROM `{$sTable}` WHERE `{$sFieldId}` = ?", $mixedId);
+        return $oDb->query($sQuery);
+    }
+
+    protected function _enable ($mixedId, $isChecked)
+    {
+        $oDb = BxDolDb::getInstance();
+        $sTable = $this->_aOptions['table'];
+        $sFieldId = $this->_aOptions['field_id'];
+        $sFieldActive = $this->_aOptions['field_active'];
+        $sQuery = $oDb->prepare("UPDATE `{$sTable}` SET `$sFieldActive` = ? WHERE `{$sFieldId}` = ?", $this->_switcherChecked2State($isChecked), $mixedId);
+        return $oDb->query($sQuery);
+    }
+
+    protected function _addJsCss()
+    {
+        if ($this->_aOptions['field_order']) {            
+            $this->_oTemplate->addJs(array(
+                'jquery-ui/jquery-ui.custom.min.js',
+                'URI.min.js',
+            ));
+        }
+
+        $this->_oTemplate->addJs('BxDolGrid.js');
+        $this->_oTemplate->addCss('grid.css');
+
+        $this->_oTemplate->addJsTranslation('_sys_grid_confirmation');
+    }
+}
+
+/** @} */