From b183c7d6b82feec99af5176ee98e8ad59f3f08b4 Mon Sep 17 00:00:00 2001 From: Vasily Bezruchkin Date: Wed, 22 Mar 2017 23:35:34 +0600 Subject: [PATCH 001/111] Better session save path validation. --- install/modules/module.install.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/install/modules/module.install.php b/install/modules/module.install.php index 97f18d58..4a126997 100644 --- a/install/modules/module.install.php +++ b/install/modules/module.install.php @@ -399,8 +399,10 @@ $filename = IA_HOME . 'includes' . IA_DS . 'config.inc.php'; $configMsg = ''; - // session path test - $testResult = is_writable(session_save_path()) ? '' : "session_save_path('" . IA_HOME . "tmp');"; + // session path test, session_save_path might be empty in many configs + $testResult = empty(session_save_path()) || is_writable(session_save_path()) ? + '' : + "session_save_path('" . IA_HOME . "tmp');"; $config = str_replace('{sessionpath}', $testResult, $config); // From 96c6242612ada055ee99c5868550ce44f22258ad Mon Sep 17 00:00:00 2001 From: Janur Jangaraev Date: Thu, 23 Mar 2017 16:29:29 +0600 Subject: [PATCH 002/111] #405 --- includes/classes/ia.core.php | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/includes/classes/ia.core.php b/includes/classes/ia.core.php index bdbd6387..b232ab23 100644 --- a/includes/classes/ia.core.php +++ b/includes/classes/ia.core.php @@ -428,13 +428,23 @@ public function getCustomConfig($user = null, $group = null) $stmt = []; if ($user) { - $stmt[] = "(`type` = 'user' AND `type_id` = $user) "; + $stmt[] = "(cc.`type` = 'user' AND cc.`type_id` = $user) "; } if ($group) { - $stmt[] = "(`type` = 'group' AND `type_id` = $group) "; + $stmt[] = "(cc.`type` = 'group' AND cc.`type_id` = $group) "; } - $rows = $this->iaDb->all(['type', 'name', 'value'], implode(' OR ', $stmt), null, null, self::getCustomConfigTable()); + $sql = << $this->iaDb->prefix, 'table_config' => self::getConfigTable(), + 'table_custom_config' => self::getCustomConfigTable(), 'where' => implode(' OR ', $stmt)]); + $rows = $this->iaDb->getAll($sql); if (empty($rows)) { return $result; @@ -442,8 +452,21 @@ public function getCustomConfig($user = null, $group = null) $result = ['group' => [], 'user' => [], 'plan' => []]; + $currentLangCode = $this->iaView->language; foreach ($rows as $row) { - $result[$row['type']][$row['name']] = $row['value']; + $value = $row['value']; + + if ('text' == $row['config_type'] || 'textarea' == $row['config_type']) { + $options = empty($row['config_options']) ? [] : json_decode($row['config_options'], true); + + if (isset($options['multilingual']) && $options['multilingual']) { + $value = preg_match('#\{\:' . $currentLangCode . '\:\}(.*?)(?:$|\{\:[a-z]{2}\:\})#s', $value, $matches) + ? $matches[1] + : ''; + } + } + + $result[$row['type']][$row['name']] = $value; } $result = array_merge($result['group'], $result['user'], $result['plan']); From fb5816f967600e7a8fdeefcb7c4811af910367b7 Mon Sep 17 00:00:00 2001 From: Janur Jangaraev Date: Fri, 24 Mar 2017 09:55:25 +0600 Subject: [PATCH 003/111] Strict standards notices --- includes/classes/ia.debug.php | 37 +++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/includes/classes/ia.debug.php b/includes/classes/ia.debug.php index 24128cf3..01799773 100644 --- a/includes/classes/ia.debug.php +++ b/includes/classes/ia.debug.php @@ -463,7 +463,7 @@ public static function log($message, $context = null, $fileName = '') * @param string $message Line to write to the log * @param string $fileName filename to write to * - * @return void + * @return bool */ public static function write($message, $fileName = '') { @@ -476,7 +476,7 @@ public static function write($message, $fileName = '') self::$_fileHandle = fopen(IA_TMP . self::$_fileName . '.txt', 'a'); } - return fwrite(self::$_fileHandle, $message); + return (bool)fwrite(self::$_fileHandle, $message); } /** @@ -486,7 +486,7 @@ public static function write($message, $fileName = '') * * @return string */ - private function _formatMessage($message, $context = null) + private static function _formatMessage($message, $context = null) { if (!empty($context)) { $message .= PHP_EOL . self::_indent(self::_contextToString($context)); @@ -501,26 +501,29 @@ private function _formatMessage($message, $context = null) * @param array $context The Context * @return string */ - private function _contextToString($context) + private static function _contextToString($context) { $export = ''; if (is_object($context)) { $context = json_decode(json_encode($context), true); } - foreach ($context as $key => $value) { - $export .= "{$key}: "; - $export .= preg_replace([ - '/=>\s+([a-zA-Z])/im', - '/array\(\s+\)/im', - '/^ |\G /m', - ], [ - '=> $1', - 'array()', - ' ', - ], str_replace('array (', 'array(', var_export($value, true))); - $export .= PHP_EOL; + if ($context instanceof Traversable) { + foreach ($context as $key => $value) { + $export .= "{$key}: "; + $export .= preg_replace([ + '/=>\s+([a-zA-Z])/im', + '/array\(\s+\)/im', + '/^ |\G /m', + ], [ + '=> $1', + 'array()', + ' ', + ], str_replace('array (', 'array(', var_export($value, true))); + $export .= PHP_EOL; + } } + return str_replace(['\\\\', '\\\''], ['\\', '\''], rtrim($export)); } @@ -532,7 +535,7 @@ private function _contextToString($context) * * @return string */ - private function _indent($string, $indent = ' ') + private static function _indent($string, $indent = ' ') { return $indent.str_replace("\n", "\n" . $indent, $string); } From 1af28bd20294d3c031df1fee844472969cae5ba9 Mon Sep 17 00:00:00 2001 From: Daiyrbek Artelov Date: Fri, 24 Mar 2017 14:56:06 +0600 Subject: [PATCH 004/111] Resolves #407 --- admin/templates/default/pages.tpl | 2 +- js/admin/pages.js | 15 ++++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/admin/templates/default/pages.tpl b/admin/templates/default/pages.tpl index a47a5f05..b10aca37 100644 --- a/admin/templates/default/pages.tpl +++ b/admin/templates/default/pages.tpl @@ -278,7 +278,7 @@ -
+
{if count($core.languages) > 1}
diff --git a/js/admin/pages.js b/js/admin/pages.js index f6f477d0..c6440146 100644 --- a/js/admin/pages.js +++ b/js/admin/pages.js @@ -146,11 +146,16 @@ $(function () { fillUrlBox(); } - var $obj = $('.js-local-url-field'); - isRemoteUrl ? $obj.hide() : $obj.show(); - - $obj = $('#js-field-remote-url'); - isRemoteUrl ? $obj.show() : $obj.hide(); + if (isRemoteUrl) { + $('.js-local-url-field').hide(); + $('.js-page-content-field').hide(); + $('#js-field-remote-url').show(); + } + else { + $('.js-local-url-field').show(); + $('.js-page-content-field').show(); + $('#js-field-remote-url').hide(); + } }).trigger('change'); // Page custom template From 08581e8ba2293d14f97dba76f34504a930a616e9 Mon Sep 17 00:00:00 2001 From: Daiyrbek Artelov Date: Fri, 24 Mar 2017 16:28:53 +0600 Subject: [PATCH 005/111] Resolves #402 --- front/members.php | 2 +- includes/classes/ia.core.users.php | 7 +++++-- install/dump/install.sql | 2 ++ templates/_common/members.tpl | 4 +--- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/front/members.php b/front/members.php index a149f290..ea13a4ca 100644 --- a/front/members.php +++ b/front/members.php @@ -83,7 +83,7 @@ // gets current page and defines start position $pagination = [ - 'limit' => 20, + 'limit' => $iaCore->get('members_per_page', 20), 'url' => IA_URL . 'members/' . ($letters['active'] ? $letters['active'] . '/' : '') . '?page={page}' ]; $page = !empty($_GET['page']) ? (int)$_GET['page'] : 1; diff --git a/includes/classes/ia.core.users.php b/includes/classes/ia.core.users.php index 6b7090c3..63fc0354 100644 --- a/includes/classes/ia.core.users.php +++ b/includes/classes/ia.core.users.php @@ -821,9 +821,8 @@ public function coreSearch($stmt, $start, $limit, $order) empty($order) || $stmt.= ' ORDER BY ' . $order; $rows = $this->iaDb->all(iaDb::STMT_CALC_FOUND_ROWS . ' ' . iaDb::ALL_COLUMNS_SELECTION, $stmt, $start, $limit, self::getTable()); - !$rows ||$this->_processValues($rows); - $count = $this->iaDb->foundRows(); + !$rows ||$this->_processValues($rows); return [$count, $rows]; } @@ -905,6 +904,10 @@ public function hybridAuth($providerName) */ protected function _processValues(array &$rows, $singleRow = false, $fieldNames = []) { + if (!$rows) { + return; + } + $iaField = $this->iaCore->factory('field'); $serializedFields = array_merge($fieldNames, $iaField->getSerializedFields($this->getItemName())); diff --git a/install/dump/install.sql b/install/dump/install.sql index 9994873b..c606b443 100644 --- a/install/dump/install.sql +++ b/install/dump/install.sql @@ -901,6 +901,7 @@ INSERT INTO `{install:prefix}config` (`config_group`, `name`, `value`, `multiple ('members', '', 'General', '1', 'divider', 1, '', 1, 0, '{\"wysiwyg\":\"0\",\"code_editor\":\"0\",\"show\":\"\",\"multilingual\":\"0\"}'), ('members', 'members_enabled', '1', '\'1\',\'0\'', 'radio', 2, '', 1, 0, '{\"wysiwyg\":\"0\",\"code_editor\":\"0\",\"show\":\"\",\"multilingual\":\"0\"}'), ('members', 'members_autoapproval', '1', '\'1\',\'0\'', 'radio', 3, '', 0, 0, '{\"wysiwyg\":\"0\",\"code_editor\":\"0\",\"show\":\"members_enabled|1\",\"multilingual\":\"0\"}'), +('members', 'members_per_page', '20', '0', 'text', 4, '', 1, 0, '{\"wysiwyg\":\"0\",\"code_editor\":\"0\",\"show\":\"members_enabled|1\",\"multilingual\":\"0\"}'), ('members', '', 'HybridAuth', '1', 'divider', 5, '', 1, 0, '{\"wysiwyg\":\"0\",\"code_editor\":\"0\",\"show\":\"\",\"multilingual\":\"0\"}'), ('members', 'hybrid_enabled', '0', '\'1\',\'0\'', 'radio', 6, '', 1, 0, '{\"wysiwyg\":\"0\",\"code_editor\":\"0\",\"show\":\"\",\"multilingual\":\"0\"}'), ('members', 'hybrid_debug_mode', '0', '\'1\',\'0\'', 'radio', 7, '', 1, 0, '{\"wysiwyg\":\"0\",\"code_editor\":\"0\",\"show\":\"hybrid_enabled|1\",\"multilingual\":\"0\"}'), @@ -1409,6 +1410,7 @@ INSERT INTO `{install:prefix}language` (`key`,`value`,`category`) VALUES ('config_timezone', 'Default timezone', 'admin'), ('config_members_enabled', 'Members functionality', 'admin'), ('config_members_autoapproval', 'Members auto-approval', 'admin'), +('config_members_per_page', 'Members per page', 'admin'), ('config_hybrid_enabled', 'Enable HybridAuth', 'admin'), ('config_hybrid_debug_mode', 'Debug mode', 'admin'), ('config_gravatar_enabled', 'Enable Gravatars', 'admin'), diff --git a/templates/_common/members.tpl b/templates/_common/members.tpl index 0acc2132..d6325616 100644 --- a/templates/_common/members.tpl +++ b/templates/_common/members.tpl @@ -53,6 +53,4 @@
-
- {navigation aTotal=$pagination.total aTemplate=$pagination.url aItemsPerPage=$pagination.limit aNumPageItems=5 aTruncateParam=1} -
\ No newline at end of file +{navigation aTotal=$pagination.total aTemplate=$pagination.url aItemsPerPage=$pagination.limit aNumPageItems=5 aTruncateParam=1} From a314d60cd386df4f20ee6452baf1a6edc3c7e06b Mon Sep 17 00:00:00 2001 From: Janur Jangaraev Date: Fri, 24 Mar 2017 16:45:02 +0600 Subject: [PATCH 006/111] Resolves #408 --- includes/classes/ia.base.controller.admin.php | 6 +++--- includes/classes/ia.base.controller.module.admin.php | 12 ++++++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/includes/classes/ia.base.controller.admin.php b/includes/classes/ia.base.controller.admin.php index 82834e1c..cee7b0e4 100644 --- a/includes/classes/ia.base.controller.admin.php +++ b/includes/classes/ia.base.controller.admin.php @@ -362,9 +362,9 @@ protected function _gridGetSorting(array $params) $direction = in_array($params['dir'], [iaDb::ORDER_ASC, iaDb::ORDER_DESC]) ? $params['dir'] : iaDb::ORDER_ASC; - $column = isset($this->_gridSorting[$params['sort']]) ? (is_array($this->_gridSorting[$params['sort']]) - ? $this->_gridSorting[$params['sort']][0] - : $this->_gridSorting[$params['sort']]) : $params['sort']; + $column = isset($this->_gridSorting[$params['sort']]) + ? (is_array($this->_gridSorting[$params['sort']]) ? $this->_gridSorting[$params['sort']][0] : $this->_gridSorting[$params['sort']]) + : $params['sort']; $tableAlias = isset($this->_gridSorting[$params['sort']][1]) && is_array($this->_gridSorting[$params['sort']]) ? $this->_gridSorting[$params['sort']][1] . '.' : $this->_gridQueryMainTableAlias; diff --git a/includes/classes/ia.base.controller.module.admin.php b/includes/classes/ia.base.controller.module.admin.php index 09266a49..20a2efec 100644 --- a/includes/classes/ia.base.controller.module.admin.php +++ b/includes/classes/ia.base.controller.module.admin.php @@ -152,6 +152,18 @@ protected function _unpackGridColumnsArray() return parent::_unpackGridColumnsArray(); } + + protected function _gridGetSorting(array $params) + { + if ($params['sort']) { + $multilingualFields = $this->_iaCore->factory('field')->getMultilingualFields($this->getItemName()); + if (in_array($params['sort'], $multilingualFields)) { + $params['sort'].= '_' . $this->_iaCore->language['iso']; + } + } + + return parent::_gridGetSorting($params); + } // protected function _indexPage(&$iaView) From 3965e044aab0030e4389c1864e30feb1b23228b0 Mon Sep 17 00:00:00 2001 From: Daiyrbek Artelov Date: Fri, 24 Mar 2017 17:02:08 +0600 Subject: [PATCH 007/111] Resolves #391 --- admin/languages.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/admin/languages.php b/admin/languages.php index 0c873cb4..597ad191 100644 --- a/admin/languages.php +++ b/admin/languages.php @@ -404,9 +404,11 @@ protected function _indexPage(&$iaView) break; case 'rm': + $defaultLanguage = $this->_iaDb->row(['code'], iaDb::convertIds(1, 'default'), iaLanguage::getLanguagesTable()); + $url = IA_CLEAR_URL . $defaultLanguage['code'] . IA_DS . $this->_iaCore->get('admin_page') . IA_DS . $this->_name . IA_DS; // TODO: set checkAccess $this->_removeLanguage($iaView); - iaUtil::go_to($this->getPath()); + iaUtil::go_to($url); break; From 204a2bc6ada9e240741d3fe52897cd164eb785e8 Mon Sep 17 00:00:00 2001 From: Janur Jangaraev Date: Fri, 24 Mar 2017 17:35:15 +0600 Subject: [PATCH 008/111] #391 --- admin/languages.php | 2 +- admin/templates/default/languages.tpl | 8 ++++---- js/admin/languages.js | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/admin/languages.php b/admin/languages.php index 597ad191..05ba84f8 100644 --- a/admin/languages.php +++ b/admin/languages.php @@ -405,7 +405,7 @@ protected function _indexPage(&$iaView) case 'rm': $defaultLanguage = $this->_iaDb->row(['code'], iaDb::convertIds(1, 'default'), iaLanguage::getLanguagesTable()); - $url = IA_CLEAR_URL . $defaultLanguage['code'] . IA_DS . $this->_iaCore->get('admin_page') . IA_DS . $this->_name . IA_DS; + $url = IA_CLEAR_URL . $defaultLanguage['code'] . IA_URL_DELIMITER . $this->_iaCore->get('admin_page') . IA_URL_DELIMITER . $this->_name . IA_URL_DELIMITER; // TODO: set checkAccess $this->_removeLanguage($iaView); iaUtil::go_to($url); diff --git a/admin/templates/default/languages.tpl b/admin/templates/default/languages.tpl index d3945818..7f1d936d 100644 --- a/admin/templates/default/languages.tpl +++ b/admin/templates/default/languages.tpl @@ -97,14 +97,14 @@ {$language.direction} {if $language.master} - + {/if} {if $code == $core.config.lang} - + {elseif iaCore::STATUS_ACTIVE == $language.status} - + {/if} {$language.status} @@ -115,7 +115,7 @@ {lang key='settings'} {if count($core.languages) > 1 && $code != $core.config.lang} - {lang key='delete'} + {/if} diff --git a/js/admin/languages.js b/js/admin/languages.js index b47c9462..39b3ef10 100644 --- a/js/admin/languages.js +++ b/js/admin/languages.js @@ -290,7 +290,7 @@ Ext.onReady(function () { $(this).on('click', function (e) { e.preventDefault(); - var link = $(this); + var $this = $(this); Ext.Msg.show( { @@ -299,7 +299,7 @@ Ext.onReady(function () { buttons: Ext.Msg.YESNO, fn: function (btn) { if ('yes' == btn) { - window.location = link.attr('href'); + window.location = $this.data('href'); } }, icon: Ext.MessageBox.QUESTION From 04a5cc99c328cb7ca01623b98adcee0e9a8edfbd Mon Sep 17 00:00:00 2001 From: Vasily Bezruchkin Date: Sat, 25 Mar 2017 09:31:58 +0600 Subject: [PATCH 009/111] Closes #410. --- admin/fieldgroups.php | 9 ++++++++- includes/classes/ia.admin.module.php | 4 ++-- install/dump/install.sql | 1 + js/admin/fieldgroups.js | 2 +- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/admin/fieldgroups.php b/admin/fieldgroups.php index 7a40e120..7f37f93d 100644 --- a/admin/fieldgroups.php +++ b/admin/fieldgroups.php @@ -159,7 +159,7 @@ protected function _preSaveEntry(array &$entry, array $data, $action) 'collapsible' => iaUtil::checkPostParam('collapsible'), 'collapsed' => iaUtil::checkPostParam('collapsed'), 'tabview' => iaUtil::checkPostParam('tabview'), - 'tabcontainer' => iaUtil::checkPostParam('tabcontainer') + 'tabcontainer' => iaUtil::checkPostParam('tabcontainer'), ]; iaUtil::loadUTF8Functions('ascii', 'bad', 'validation'); @@ -177,8 +177,15 @@ protected function _preSaveEntry(array &$entry, array $data, $action) if (empty($data['item'])) { $this->addMessage('at_least_one_item_should_be_checked'); + } else { + $stmt = '`name` = :name && `item` = :item'; + $this->_iaDb->bind($stmt, ['name' => $entry['name'], 'item' => $entry['item']]); + if ($this->_iaDb->exists($stmt)) { + $this->addMessage('error_fieldgroup_duplicate'); + } } + $entry['module'] = $this->_iaCore->factory('item')->getPackageByItem($data['item']); $entry['order'] = $this->_iaDb->getMaxOrder(iaField::getTableGroups()) + 1; } diff --git a/includes/classes/ia.admin.module.php b/includes/classes/ia.admin.module.php index 076f43a4..15cc397a 100644 --- a/includes/classes/ia.admin.module.php +++ b/includes/classes/ia.admin.module.php @@ -681,9 +681,9 @@ public function uninstall($moduleName) 'pages', 'hooks', 'acl_objects', - 'fields_groups', - 'fields_tree_nodes', + iaField::getTableGroups(), iaField::getTableImageTypes(), + 'fields_tree_nodes', 'cron' ]; $iaDb->cascadeDelete($tableList, iaDb::convertIds($moduleName, 'module')); diff --git a/install/dump/install.sql b/install/dump/install.sql index c606b443..a4df9f15 100644 --- a/install/dump/install.sql +++ b/install/dump/install.sql @@ -1582,6 +1582,7 @@ INSERT INTO `{install:prefix}language` (`key`,`value`,`category`) VALUES ('error_block_name_duplicate','Please change block name. There is a block with the same name in the database.','admin'), ('error_compare_same_languages','No way to compare same languages.','admin'), ('error_contents','Content field is empty.','admin'), +('error_fieldgroup_duplicate','Please change name. There is a field group with the same name for the same item.','admin'), ('error_filename','Filename field is empty.','admin'), ('error_incorrect_dimensions','Set correct image dimensions.','admin'), ('error_incorrect_format_from_subrion','The module files are in the incorrect format. Please contact the Subrion team.','admin'), diff --git a/js/admin/fieldgroups.js b/js/admin/fieldgroups.js index 6b5e2093..7c3ae2e6 100644 --- a/js/admin/fieldgroups.js +++ b/js/admin/fieldgroups.js @@ -88,7 +88,7 @@ $(function () { if (response.length > 0) { var selected = $('#tabcontainer').val(); $.each(response, function (i, name) { - $fieldGroups.append($('
- {if !isset($smarty.cookies.loader) || 'loaded' != $smarty.cookies.loader} -
-
-

-
- {/if} + {if !isset($smarty.cookies.loader) || 'loaded' != $smarty.cookies.loader} +
+
+

+
+ {/if} - {ia_hooker name='smartyAdminFooterBeforeJsDisplay'} - {ia_print_js display='on'} - {ia_hooker name='smartyAdminFooterAfterJsDisplay'} - + {ia_hooker name='smartyAdminFooterBeforeJsDisplay'} + {ia_print_js display='on'} + {ia_hooker name='smartyAdminFooterAfterJsDisplay'} + \ No newline at end of file diff --git a/admin/templates/default/modules.tpl b/admin/templates/default/modules.tpl index 7a5307b4..43124dac 100644 --- a/admin/templates/default/modules.tpl +++ b/admin/templates/default/modules.tpl @@ -1,127 +1,127 @@ {if $modules} - - -
- {foreach $modules as $module} -
-
- {if $module.buttons} -
- - -
- {/if} -
- {$module.title} -
-
-

{$module.title}

-

{$module.summary}

-
- {if !empty($module.buttons.upgrade)} - {lang key='update_available'} - {/if} - - {if !empty($module.notes)} - {foreach $module.notes as $note} - {lang key='package_required'} - {/foreach} - {/if} - - {if !empty($module.remote) && $module.price > 0} - Premium ${$module.price} - {/if} - {lang key='compatibility'}: {$module.compatibility} -
-
-
- -
- - {$module.version} · {$module.date|date_format:$core.config.date_format} - - - {if !empty($module.buttons.install)} - {access object='admin_page' id=$core.page.name action='install'} - {lang key='install'} - {/access} - {elseif !empty($module.buttons.reinstall)} - {lang key='installed'} - {elseif !empty($module.buttons.activate)} - {lang key='deactivated'} - {elseif !empty($module.buttons.download)} - {lang key='download'} - {elseif $module.price > 0} - {lang key='view'} - {elseif empty($module.buttons.download)} - {lang key='unable_to_install'} - {/if} -
-
- {/foreach} -
- - {ia_print_js files='admin/modules'} + + +
+ {foreach $modules as $module} +
+
+ {if $module.buttons} +
+ + +
+ {/if} +
+ {$module.title} +
+
+

{$module.title}

+

{$module.summary}

+
+ {if !empty($module.buttons.upgrade)} + {lang key='update_available'} + {/if} + + {if !empty($module.notes)} + {foreach $module.notes as $note} + {lang key='package_required'} + {/foreach} + {/if} + + {if !empty($module.remote) && $module.price > 0} + Premium ${$module.price} + {/if} + {lang key='compatibility'}: {$module.compatibility} +
+
+
+ +
+ + {$module.version} · {$module.date|date_format:$core.config.date_format} + + + {if !empty($module.buttons.install)} + {access object='admin_page' id=$core.page.name action='install'} + {lang key='install'} + {/access} + {elseif !empty($module.buttons.reinstall)} + {lang key='installed'} + {elseif !empty($module.buttons.activate)} + {lang key='deactivated'} + {elseif !empty($module.buttons.download)} + {lang key='download'} + {elseif $module.price > 0} + {lang key='view'} + {elseif empty($module.buttons.download)} + {lang key='unable_to_install'} + {/if} +
+
+ {/foreach} +
+ + {ia_print_js files='admin/modules'} {else} -
{lang key='no_modules'}
+
{lang key='no_modules'}
{/if} \ No newline at end of file diff --git a/js/admin/footer.js b/js/admin/footer.js index 97d29c01..8c53fbd9 100644 --- a/js/admin/footer.js +++ b/js/admin/footer.js @@ -1,869 +1,764 @@ -$(function() -{ - setTimeout(function() - { - $('#js-ajax-loader').fadeOut(); - intelli.cookie.write('loader', 'loaded'); - }, 2000); - - // panel toggle - $('.panel-toggle').on('click', function(e) - { - e.preventDefault(); - - var $o = $('#panel-center'), - $this = $(this); - - window.dispatchEvent(new Event('resize')); - - if (!$o.hasClass('is-hidden')) - { - $o.addClass('is-hidden'); - $this.find('i').removeClass('fa-angle-left').addClass('fa-angle-right'); - intelli.cookie.write('panelHidden', '1'); - } - else - { - $o.removeClass('is-hidden'); - $this.find('i').removeClass('fa-angle-right').addClass('fa-angle-left'); - intelli.cookie.write('panelHidden', '0'); - } - }); - - $('#user-logout').on('click', function() - { - intelli.cookie.write('loader', 'notloaded'); - }); - - // main nav - $('.nav-main > li > a').on('click', function(e) - { - if (!$(this).hasClass('dashboard')) - { - e.preventDefault(); - - var toggler = $(this).data('toggle'), - $panel = $('#panel-center'); - - $(this).parent().addClass('active').siblings().removeClass('active'); - $('#' + toggler).addClass('active').siblings().removeClass('active'); - - if ($panel.hasClass('is-hidden')) - { - $panel.removeClass('is-hidden'); - } - - if ($(window).scrollTop() > 0) - { - $('html, body').animate({scrollTop: 0}, 'fast'); - } - } - }); - - // minmax - var widgetsState = JSON.parse(intelli.cookie.read('widgetsState')); - if (typeof widgetsState == 'undefined' || widgetsState == null) - { - widgetsState = {}; - } - - $('.widget').each(function() - { - if ('collapsed' == widgetsState[$(this).attr('id')]) - { - $(this).addClass('collapsed'); - } - }); - - $('.widget-toggle').on('click', function(e) - { - e.preventDefault(); - - var $obj = $(this).closest('.widget'); - var objContent = $obj.find('.widget-content'); - var objId = $obj.attr('id'); - - if (!$obj.hasClass('collapsed')) - { - objContent.slideUp('fast', function() - { - $obj.addClass('collapsed'); - }); - - widgetsState[objId] = 'collapsed'; - } - else - { - objContent.slideDown('fast', function() - { - $obj.removeClass('collapsed'); - if (objContent.hasClass('mCustomScrollbar')) - { - objContent.mCustomScrollbar('update'); - } - }); - - widgetsState[objId] = ''; - } - - intelli.cookie.write('widgetsState', JSON.stringify(widgetsState)); - }); - - // Tree toggle - $('.js-categories-toggle').on('click', function(e) - { - e.preventDefault(); - - var toggleWhat = $(this).data('toggle'); - - $(toggleWhat).toggle(); - }); - - if ('function' == typeof $.fn.numeric) - { - $('.js-filter-numeric').numeric(); - } - - $('textarea.js-code-editor').each(function() - { - editAreaLoader.init( - { - id : $(this).attr('id'), - display: 'later', - min_height: 200, - syntax: 'php', - start_highlight: true, - toolbar: 'undo, redo' - }); - }); - - $('textarea.js-wysiwyg').each(function() - { - intelli.ckeditor($(this).attr('id'), {height: '200px'}); - }); - - $('.js-edit-lang-group').on('click', function() - { - var $this = $(this), - $parent = $($this.data('group')), - $group = $parent.find('.translate-group__langs'); - - $parent.hasClass('is-opened') ? $group.slideUp('fast') : $group.slideDown('fast'); - $parent.toggleClass('is-opened'); - }); - - $('.js-copy-lang-group').on('click', function() - { - var $this = $(this), - $parent = $($this.data('group')), - defaultVal = $parent.find('input:first, textarea:first').val(); - - $parent.find('.translate-group__langs input, .translate-group__langs textarea').val(defaultVal); - - // TODO: add an ability to copy content from CKEDITOR to other instances of it in same group - }); - - // switching - $('.js-input-switch').on('switch-change', function(e, data) - { - $('input', this).val(data.value == true ? 1 : 0); - }); - - - // file upload - var fileUpload = function(elem) - { - var $parent = $(elem).closest('.file-upload'); - - $('input[type="file"]', $parent) - .trigger('click') - .on('change', function() - { - var filename = $(this).val(); - var lastIndex = filename.lastIndexOf("\\"); - - if (lastIndex >= 0) - { - filename = filename.substring(lastIndex + 1); - } - - $('input[type="text"]:not(.file-title)', $parent).val(filename); - }); - }; - - var addFileUploadField = function(elem) - { - var clone = $(elem).closest('.file-upload').clone(); - var counterObj = $('#' + $('input[type="file"]', clone).attr('name').replace('[]', '')); - var counterNum = parseInt(counterObj.val()); - - if (1 < counterNum) - { - $('input[type="file"], input[type="text"]', clone).val(''); - $(elem).closest('.file-upload').after(clone); - counterObj.val(counterNum - 1); - } - else - { - intelli.notifFloatBox({msg: intelli.admin.lang.no_more_files, type: 'error', autohide: true, pause: 2500}); - } - }; - - var removeFileUploadField = function(elem) - { - var $parent = $(elem).closest('.file-upload'); - var counterObj = $('#'+$('input[type="file"]', $parent).attr('name').replace('[]', '')); - var counterNum = parseInt(counterObj.val()); - - if ($parent.prev().hasClass('file-upload') || $parent.next().hasClass('file-upload')) - { - $parent.remove(); - counterObj.val(counterNum + 1); - } - }; - - // activating buttons - $('.upload-group') - .on('click', '.js-file-browse', function(e) - { - e.preventDefault(); - fileUpload(this); - }) - .on('click', '.js-file-add', function(e) - { - e.preventDefault(); - addFileUploadField(this); - }) - .on('click', '.js-file-remove', function(e) - { - e.preventDefault(); - removeFileUploadField(this); - }); - - // tooltips - $('.js-tooltip').tooltip({container: 'body'}).on('click', function(e) - { - e.preventDefault(); - }); - - $('#api_token').passField( - { - showWarn: false, - showTip: false, - pattern: 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ab' // 64 symbols - }); - - // add password generator - $('.js-input-password').passField({showWarn: false, showTip: false}); - - if ($('.right-column .box').length > 1) - { - var items = []; - var name,rand; - $('.right-column .box').each(function() - { - rand = 'box'+Math.random(); - if ($(this).attr('id')) - { - name = $(this).attr('id'); - $(this).attr('id', rand); - } - else - { - name = rand; - } - $(this).find('.box-content').attr('id', name).show(); - items.push( - { - contentEl: name, - title: '
'+$(this).find('.box-caption').text()+'
' - }); - }).hide(); - - - $('.right-column .box:first').before('
'); - $('.x-tab-panel > div').removeClass('x-tab-panel-header'); - } - - if ($().datetimepicker) - { - $('.js-datepicker').datetimepicker( - { - format: 'YYYY-MM-DD HH:mm:ss', - locale: intelli.config.lang, - icons: { - time: 'i-clock', - date: 'i-calendar', - up: 'i-chevron-up', - down: 'i-chevron-down', - previous: 'i-chevron-left', - next: 'i-chevron-right', - today: 'i-checkmark', - clear: 'i-remove', - close: 'i-remove-sign' - } - }); - - $('.js-datepicker-toggle').on('click', function(e) - { - e.preventDefault(); - - $(this).prev().datetimepicker('show'); - }); - } - - $('.js-iconpicker').iconpicker(); - - /* header-menu show/hide START */ - if ($('#alert').length) - { - $('#alert').show(); - } - if ($('#success')) - { - var text = []; - $('#success .inner li').each(function() - { - text.push($(this).html()); - }); - $('#success').html(''); - if (text.length > 0) - { - intelli.admin.notifBox({msg: text, type: 'notification', autohide: true}); - } - } - if ($('#notification').length) - { - $('#notification').show(); - } - - /* feedback form START */ - var $feedbackForm = $('form', '#feedback-modal'); - $('select[name="subject"]', $feedbackForm).on('change', function() - { - var $option = $('option:selected', this); - if ($option.val() != '') - { - $('#feedback_subject_label').html(' ' + _t('subject')); - } - }); - - $('input[name="fullname"], input[name="email"]', $feedbackForm).focus(function() - { - var $this = $(this); - if ($this.data('def') == $this.val()) - { - $this.val(''); - } - }).blur(function() - { - var $this = $(this); - if ($this.val() == '') - { - $this.val($this.data('def')); - } - }); - - $feedbackForm.on('submit', function() - { - var $subject = $('[name="subject"]', this); - if ('' != $('[name="body"]', this).val() && '' != $('option:selected', $subject).val()) - { - $.ajax( - { - data: $(this).serialize(), - success: function(response) - { - $('#feedback-modal').modal('hide'); - intelli.notifFloatBox({msg: response.message, type: response.result ? 'success' : 'error', autohide: true}); - }, - type: 'POST', - url: intelli.config.admin_url + '.json' - }); - } - else - { - intelli.notifFloatBox({msg: _t('body_incorrect'), type: 'error', autohide: true}); - } - - return false; - }); - - $('#clearFeedback').on('click', function() - { - $('[name="body"]').val(''); - }); - /* feedback form END */ - - $('div.minmax').each(function() - { - $(this).on('click', function() - { - if ($(this).next('.box-content').css('display') == 'block') - { - $(this).next('.box-content').slideUp(); - Ext.util.Cookies.set(this.id, 0); - } - else - { - $(this).next('.box-content').slideDown(); - Ext.util.Cookies.set(this.id, 1); - } - $(this).toggleClass('white-close white-open'); - }); - }); - - function getMousePosition(e) - { - return {x: e.clientX + document.documentElement.scrollLeft, y: e.clientY + document.documentElement.scrollTop}; - } - - // get substring count with limit - $.fn.substrCount = function(needle) - { - var p; - var h = this.text(); - var times = 0; - while((p=h.indexOf(needle)) != -1) - { - h = h.substr(p+needle.length); - times++; - } - - return times; - }; - - function stopPropagation(ev) - { - ev = ev||event;/* get IE event ( not passed ) */ - ev.stopPropagation? ev.stopPropagation() : ev.cancelBubble = true; - } - - textareaResizer = function() - { - $('textarea.resizable').each(function() - { - var obj = $(this); - - cl = obj.attr('class'); - if (cl && -1 != cl.indexOf('noresize')) - { - return false; - } - - var content = obj.text(); - var Height = 75; - if (content.length) - { - // IE - doesnt find \n I gave up I don't know why it is so .. - // Firefox works just as it must work as well as Opera - var times = obj.substrCount(navigator.userAgent.match(/msie/i) ? "\r" : "\n"); - if (times > 20) - { - Height = 200; - } - else - { - Height = 70+10*times; - } - } - - obj.height(Height); - - var offset = null; - - $(this).wrap('
').after($('
').bind("mousedown", dragBegins)); - - - var image = $('div.resizable-textarea2', $(this).parent())[0]; - image.style.marginRight = (image.offsetWidth - $(this)[0].offsetWidth) +'px'; - - function dragBegins(e) - { - offset = obj.height() - getMousePosition(e).y; - if ($.browser.opera) - { - offset -= 6; - } - $(document) - .bind('mousemove', doDrag) - .bind('mouseup', dragEnds); - stopPropagation(e); - } - - function doDrag(e) - { - obj.height(Math.max(15, offset + getMousePosition(e).y) + 'px'); - stopPropagation(e); - } - - function dragEnds(e) - { - $(document).unbind(); - } - }); - }(); - - /* - * Help tooltips - */ - $('.tip-header').each(function() - { - var id = $(this).attr('id').replace('tip-header-', ''); - - if ($('#tip-content-' + id).length > 0) - { - $(this).append('').find("span.question").each(function() - { - new Ext.ToolTip({target: this, dismissDelay: 0, contentEl: 'tip-content-' + id}); - }); - } - }); - - /* - * Init AJAX notification box - */ - $('.collapsed[rel]').on('click', function() - { - $($(this).attr('rel')).toggle(); - }); - - /* - * Resolving issues - */ - - if ($('.navbar-nav__notifications__alerts').length > 0) - { - // remove installer - var $installerAlert = $('.alert-danger:contains("module.install.php")'); - if ($installerAlert.length > 0) - { - $installerAlert.on('click', '.b-resolve__btn', function(event) - { - event.preventDefault(); - event.stopPropagation(); - - var $this = $(this); - - if (!$this.hasClass('disabled')) - { - $this.animate( - { - left: '10px', - opacity: 0 - }, 150, function() - { - $this.hide().prev().show(function() - { - $.post(intelli.config.admin_url + '/actions/read.json', {action: 'remove-installer'}, function(response) - { - if (!response.error) - { - $this.prev().animate( - { - left: '10px', - opacity: 0 - }, 150, function() - { - $this.prev().hide().prev().show(); - }); - - $installerAlert - .removeClass('alert-danger') - .addClass('alert-info'); - - setTimeout(function() - { - clearNotification($installerAlert); - }, 2000); - } - }); - }); - }); - } - }); - - var resolveBtnHtml = '
' + - ' ' + _t('notification_resolve--resolved') + '' + - ' ' + _t('notification_resolve--working') + '' + - ' ' + _t('notification_resolve--resolve') + '' + - '
'; - $installerAlert.addClass('b-resolve').append(resolveBtnHtml); - } - } - - // moving upload blocks up and down - - disableMoveButtons(); - - $('.js-upload-moveup').click(function(e) - { - e.preventDefault(); - - var $this = $(this), - $parent = $this.closest('.uploads-list-item'); - - $parent.insertBefore($parent.prev()); - - disableMoveButtons(); - }); - - $('.js-upload-movedown').click(function(e) - { - e.preventDefault(); - - var $this = $(this), - $parent = $this.closest('.uploads-list-item'); - - $parent.insertAfter($parent.next()); - - disableMoveButtons(); - }); - - if (typeof Dropzone !== 'undefined') - { - Dropzone.autoDiscover = false; - } - - $('.js-dropzone').each(function() - { - var $dropzone = $(this); - var fieldName = $dropzone.data('field'); - var itemName = $dropzone.data('item'); - var itemId = $dropzone.data('item_id'); - var submitButtonText = $dropzone.data('submit_btn_text'); - var values = $dropzone.data('values'); - - var dropZone = new Dropzone('#' + $dropzone.attr('id'), - { - url: intelli.config.admin_url + '/actions/read.json', - addRemoveLinks: true, - acceptedFiles: 'image/*', - parallelUploads: 20, - maxFiles: $dropzone.data('max_num'), - dictRemoveFile: '', - dictMaxFilesExceeded: _t('no_more_files'), - dictDefaultMessage: _t('drop_files_here'), - dictInvalidFileType: _t('field_tooltip_members_avatar'), - dictCancelUpload: '', - dictCancelUploadConfirmation: _t('cancel_upload_confirmation'), - init: function() - { - var dropZone = this, - error = false, - errorMessage = ''; - - this.on('success', function(file, response) - { - if (response.error) - { - return false; - } - - var $preview = $(file.previewElement), - htmlInputs = - '' - + '' - + '', - htmlFancybox = ''; - - $preview.append(htmlInputs).find('.dz-details').append(htmlFancybox); - $('[data-dz-name]', $preview).text(response.file); - }); - - var $submit = $('.js-btn-submit'); - this.on('sending', function(file, xhr, formData) - { - $submit - .attr('disabled', true) - .html('' + _t('uploading_please_wait') + ' ' + _t('uploading_please_wait')); - formData.append('action', 'dropzone-upload-file'); - formData.append('field', fieldName); - formData.append('item', itemName); - }); - - this.on('error', function(file) - { - if ('canceled' != file.status) - { - error = true; - errorMessage = 'error'; - } - }); - - this.on('maxfilesexceeded', function(file) - { - error = true; - errorMessage = 'no_more_files'; - - $(file.previewElement).remove(); - }); - - this.on('removedfile', function(file) - { - if ('undefined' == typeof file.status || 'success' == file.status) - { - var params = { - action: 'dropzone-delete-file', - item: itemName, - field: fieldName, - path: $('input[type="hidden"]:first', file.previewElement).val(), - file: $('[data-dz-name]', file.previewElement).text() - }; - - if (typeof existingFiles !== 'undefined' && -1 !== existingFiles.indexOf(file.name)) - { - dropZone.options.maxFiles = dropZone.options.maxFiles + 1; - - params.action = 'delete-file'; - params.itemid = itemId; - } - - $.post(intelli.config.admin_url + '/actions/read.json', params).done(function(response) - { - intelli.notifFloatBox({msg: response.message, type: response.error ? 'error' : 'success', autohide: true}); - }); - } - }); - - this.on('queuecomplete', function() - { - if (error) { - message = errorMessage; - error = false; - errorMessage = ''; - intelli.notifFloatBox({msg: _t(message), type: 'error', autohide: true}); - } - $submit.attr('disabled', false).html(_t(submitButtonText)); - }); - } - }); - - if (typeof values == 'object' && values) - { - var imageTypes = {primary: $dropzone.data('imagetype-primary'), thumbnail: $dropzone.data('imagetype-thumbnail')}; - var existingFiles = [], mock; - - values.forEach(function(entry) - { - mock = {name: entry.file, size: entry.size}; - - dropZone.emit('addedfile', mock); - dropZone.createThumbnailFromUrl(mock, intelli.config.ia_url + 'uploads/' + entry.path + imageTypes.thumbnail + '/' + entry.file); - - var htmlInputs = - '' - + '' - + '', - htmlFancybox = ''; - - $(mock.previewElement).append(htmlInputs).find('.dz-details').append(htmlFancybox); - - dropZone.emit('complete', mock); - existingFiles.push(entry.file); - }); - - dropZone.options.maxFiles = dropZone.options.maxFiles - existingFiles.length; - } - - intelli.sortable($dropzone.attr('id'), {handle: '.dz-image-preview'}); - }); - - $('.js-cmd-delete-file').on('click', function(e) - { - e.preventDefault(); - var data = $(this).data(); - intelli.admin.removeFile(data.file, this, data.item, data.field, data.id); - }); - - // Live filter on Modules pages - $('.js-filter-modules-text').on('keyup', function() { - var $this = $(this), - text = $this.val(), - $collection = $('.card--module'); - - if (text != '') { - var rx = RegExp(text, 'i') - - $collection.each(function() { - var $item = $(this), - name = String($('.card__item__body > h4', $item).text()); - - (name.match(rx) !== null) ? $item.show() : $item.hide(); - }); - } else { - $collection.show(); - } - }); - - $('.js-filter-modules').click(function(e) { - e.preventDefault(); - - var $this = $(this), - type = $this.data('type'), - filtered = $this.data('filtered'); - - if (filtered == 'no') { - $this.data('filtered', 'yes'); - $('.card--' + type).hide(); - } else { - $this.data('filtered', 'no'); - $('.card--' + type).show(); - } - - $('.fa', $this).toggleClass('fa-circle-thin fa-check'); - }); - - $('.js-filter-modules-reset').click(function(e) { - e.preventDefault(); - - $('.js-filter-modules-text').val('').trigger('keyup'); - $('.js-filter-modules').data('filtered', 'no') - .find('.fa') - .removeClass('fa-circle-thin') - .addClass('fa-check'); - }); +$(function () { + setTimeout(function () { + $('#js-ajax-loader').fadeOut(); + intelli.cookie.write('loader', 'loaded'); + }, 2000); + + // panel toggle + $('.panel-toggle').on('click', function (e) { + e.preventDefault(); + + var $o = $('#panel-center'), + $this = $(this); + + window.dispatchEvent(new Event('resize')); + + if (!$o.hasClass('is-hidden')) { + $o.addClass('is-hidden'); + $this.find('i').removeClass('fa-angle-left').addClass('fa-angle-right'); + intelli.cookie.write('panelHidden', '1'); + } + else { + $o.removeClass('is-hidden'); + $this.find('i').removeClass('fa-angle-right').addClass('fa-angle-left'); + intelli.cookie.write('panelHidden', '0'); + } + }); + + $('#user-logout').on('click', function () { + intelli.cookie.write('loader', 'notloaded'); + }); + + // main nav + $('.nav-main > li > a').on('click', function (e) { + if (!$(this).hasClass('dashboard')) { + e.preventDefault(); + + var toggler = $(this).data('toggle'), + $panel = $('#panel-center'); + + $(this).parent().addClass('active').siblings().removeClass('active'); + $('#' + toggler).addClass('active').siblings().removeClass('active'); + + if ($panel.hasClass('is-hidden')) { + $panel.removeClass('is-hidden'); + } + + if ($(window).scrollTop() > 0) { + $('html, body').animate({scrollTop: 0}, 'fast'); + } + } + }); + + // minmax + var widgetsState = JSON.parse(intelli.cookie.read('widgetsState')); + if (typeof widgetsState == 'undefined' || widgetsState == null) { + widgetsState = {}; + } + + $('.widget').each(function () { + if ('collapsed' == widgetsState[$(this).attr('id')]) { + $(this).addClass('collapsed'); + } + }); + + $('.widget-toggle').on('click', function (e) { + e.preventDefault(); + + var $obj = $(this).closest('.widget'); + var objContent = $obj.find('.widget-content'); + var objId = $obj.attr('id'); + + if (!$obj.hasClass('collapsed')) { + objContent.slideUp('fast', function () { + $obj.addClass('collapsed'); + }); + + widgetsState[objId] = 'collapsed'; + } + else { + objContent.slideDown('fast', function () { + $obj.removeClass('collapsed'); + if (objContent.hasClass('mCustomScrollbar')) { + objContent.mCustomScrollbar('update'); + } + }); + + widgetsState[objId] = ''; + } + + intelli.cookie.write('widgetsState', JSON.stringify(widgetsState)); + }); + + // Tree toggle + $('.js-categories-toggle').on('click', function (e) { + e.preventDefault(); + + var toggleWhat = $(this).data('toggle'); + + $(toggleWhat).toggle(); + }); + + if ('function' == typeof $.fn.numeric) { + $('.js-filter-numeric').numeric(); + } + + $('textarea.js-code-editor').each(function () { + editAreaLoader.init( + { + id: $(this).attr('id'), + display: 'later', + min_height: 200, + syntax: 'php', + start_highlight: true, + toolbar: 'undo, redo' + }); + }); + + $('textarea.js-wysiwyg').each(function () { + intelli.ckeditor($(this).attr('id'), {height: '200px'}); + }); + + $('.js-edit-lang-group').on('click', function () { + var $this = $(this), + $parent = $($this.data('group')), + $group = $parent.find('.translate-group__langs'); + + $parent.hasClass('is-opened') ? $group.slideUp('fast') : $group.slideDown('fast'); + $parent.toggleClass('is-opened'); + }); + + $('.js-copy-lang-group').on('click', function () { + var $this = $(this), + $parent = $($this.data('group')), + defaultVal = $parent.find('input:first, textarea:first').val(); + + $parent.find('.translate-group__langs input, .translate-group__langs textarea').val(defaultVal); + + // TODO: add an ability to copy content from CKEDITOR to other instances of it in same group + }); + + // switching + $('.js-input-switch').on('switch-change', function (e, data) { + $('input', this).val(data.value == true ? 1 : 0); + }); + + + // file upload + var fileUpload = function (elem) { + var $parent = $(elem).closest('.file-upload'); + + $('input[type="file"]', $parent) + .trigger('click') + .on('change', function () { + var filename = $(this).val(); + var lastIndex = filename.lastIndexOf("\\"); + + if (lastIndex >= 0) { + filename = filename.substring(lastIndex + 1); + } + + $('input[type="text"]:not(.file-title)', $parent).val(filename); + }); + }; + + var addFileUploadField = function (elem) { + var clone = $(elem).closest('.file-upload').clone(); + var counterObj = $('#' + $('input[type="file"]', clone).attr('name').replace('[]', '')); + var counterNum = parseInt(counterObj.val()); + + if (1 < counterNum) { + $('input[type="file"], input[type="text"]', clone).val(''); + $(elem).closest('.file-upload').after(clone); + counterObj.val(counterNum - 1); + } + else { + intelli.notifFloatBox({msg: intelli.admin.lang.no_more_files, type: 'error', autohide: true, pause: 2500}); + } + }; + + var removeFileUploadField = function (elem) { + var $parent = $(elem).closest('.file-upload'); + var counterObj = $('#' + $('input[type="file"]', $parent).attr('name').replace('[]', '')); + var counterNum = parseInt(counterObj.val()); + + if ($parent.prev().hasClass('file-upload') || $parent.next().hasClass('file-upload')) { + $parent.remove(); + counterObj.val(counterNum + 1); + } + }; + + // activating buttons + $('.upload-group') + .on('click', '.js-file-browse', function (e) { + e.preventDefault(); + fileUpload(this); + }) + .on('click', '.js-file-add', function (e) { + e.preventDefault(); + addFileUploadField(this); + }) + .on('click', '.js-file-remove', function (e) { + e.preventDefault(); + removeFileUploadField(this); + }); + + // tooltips + $('.js-tooltip').tooltip({container: 'body'}).on('click', function (e) { + e.preventDefault(); + }); + + $('#api_token').passField( + { + showWarn: false, + showTip: false, + pattern: 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ab' // 64 symbols + }); + + // add password generator + $('.js-input-password').passField({showWarn: false, showTip: false}); + + if ($('.right-column .box').length > 1) { + var items = []; + var name, rand; + $('.right-column .box').each(function () { + rand = 'box' + Math.random(); + if ($(this).attr('id')) { + name = $(this).attr('id'); + $(this).attr('id', rand); + } + else { + name = rand; + } + $(this).find('.box-content').attr('id', name).show(); + items.push( + { + contentEl: name, + title: '
' + $(this).find('.box-caption').text() + '
' + }); + }).hide(); + + + $('.right-column .box:first').before('
'); + $('.x-tab-panel > div').removeClass('x-tab-panel-header'); + } + + if ($().datetimepicker) { + $('.js-datepicker').datetimepicker( + { + format: 'YYYY-MM-DD HH:mm:ss', + locale: intelli.config.lang, + icons: { + time: 'i-clock', + date: 'i-calendar', + up: 'i-chevron-up', + down: 'i-chevron-down', + previous: 'i-chevron-left', + next: 'i-chevron-right', + today: 'i-checkmark', + clear: 'i-remove', + close: 'i-remove-sign' + } + }); + + $('.js-datepicker-toggle').on('click', function (e) { + e.preventDefault(); + + $(this).prev().datetimepicker('show'); + }); + } + + $('.js-iconpicker').iconpicker(); + + /* header-menu show/hide START */ + if ($('#alert').length) { + $('#alert').show(); + } + if ($('#success')) { + var text = []; + $('#success .inner li').each(function () { + text.push($(this).html()); + }); + $('#success').html(''); + if (text.length > 0) { + intelli.admin.notifBox({msg: text, type: 'notification', autohide: true}); + } + } + if ($('#notification').length) { + $('#notification').show(); + } + + /* feedback form START */ + var $feedbackForm = $('form', '#feedback-modal'); + $('select[name="subject"]', $feedbackForm).on('change', function () { + var $option = $('option:selected', this); + if ($option.val() != '') { + $('#feedback_subject_label').html(' ' + _t('subject')); + } + }); + + $('input[name="fullname"], input[name="email"]', $feedbackForm).focus(function () { + var $this = $(this); + if ($this.data('def') == $this.val()) { + $this.val(''); + } + }).blur(function () { + var $this = $(this); + if ($this.val() == '') { + $this.val($this.data('def')); + } + }); + + $feedbackForm.on('submit', function () { + var $subject = $('[name="subject"]', this); + if ('' != $('[name="body"]', this).val() && '' != $('option:selected', $subject).val()) { + $.ajax( + { + data: $(this).serialize(), + success: function (response) { + $('#feedback-modal').modal('hide'); + intelli.notifFloatBox({ + msg: response.message, + type: response.result ? 'success' : 'error', + autohide: true + }); + }, + type: 'POST', + url: intelli.config.admin_url + '.json' + }); + } + else { + intelli.notifFloatBox({msg: _t('body_incorrect'), type: 'error', autohide: true}); + } + + return false; + }); + + $('#clearFeedback').on('click', function () { + $('[name="body"]').val(''); + }); + /* feedback form END */ + + $('div.minmax').each(function () { + $(this).on('click', function () { + if ($(this).next('.box-content').css('display') == 'block') { + $(this).next('.box-content').slideUp(); + Ext.util.Cookies.set(this.id, 0); + } + else { + $(this).next('.box-content').slideDown(); + Ext.util.Cookies.set(this.id, 1); + } + $(this).toggleClass('white-close white-open'); + }); + }); + + function getMousePosition(e) { + return {x: e.clientX + document.documentElement.scrollLeft, y: e.clientY + document.documentElement.scrollTop}; + } + + // get substring count with limit + $.fn.substrCount = function (needle) { + var p; + var h = this.text(); + var times = 0; + while ((p = h.indexOf(needle)) != -1) { + h = h.substr(p + needle.length); + times++; + } + + return times; + }; + + function stopPropagation(ev) { + ev = ev || event; + /* get IE event ( not passed ) */ + ev.stopPropagation ? ev.stopPropagation() : ev.cancelBubble = true; + } + + textareaResizer = function () { + $('textarea.resizable').each(function () { + var obj = $(this); + + cl = obj.attr('class'); + if (cl && -1 != cl.indexOf('noresize')) { + return false; + } + + var content = obj.text(); + var Height = 75; + if (content.length) { + // IE - doesnt find \n I gave up I don't know why it is so .. + // Firefox works just as it must work as well as Opera + var times = obj.substrCount(navigator.userAgent.match(/msie/i) ? "\r" : "\n"); + if (times > 20) { + Height = 200; + } + else { + Height = 70 + 10 * times; + } + } + + obj.height(Height); + + var offset = null; + + $(this).wrap('
').after($('
').bind("mousedown", dragBegins)); + + + var image = $('div.resizable-textarea2', $(this).parent())[0]; + image.style.marginRight = (image.offsetWidth - $(this)[0].offsetWidth) + 'px'; + + function dragBegins(e) { + offset = obj.height() - getMousePosition(e).y; + if ($.browser.opera) { + offset -= 6; + } + $(document) + .bind('mousemove', doDrag) + .bind('mouseup', dragEnds); + stopPropagation(e); + } + + function doDrag(e) { + obj.height(Math.max(15, offset + getMousePosition(e).y) + 'px'); + stopPropagation(e); + } + + function dragEnds(e) { + $(document).unbind(); + } + }); + }(); + + /* + * Help tooltips + */ + $('.tip-header').each(function () { + var id = $(this).attr('id').replace('tip-header-', ''); + + if ($('#tip-content-' + id).length > 0) { + $(this).append('').find("span.question").each(function () { + new Ext.ToolTip({target: this, dismissDelay: 0, contentEl: 'tip-content-' + id}); + }); + } + }); + + /* + * Init AJAX notification box + */ + $('.collapsed[rel]').on('click', function () { + $($(this).attr('rel')).toggle(); + }); + + /* + * Resolving issues + */ + + if ($('.navbar-nav__notifications__alerts').length > 0) { + // remove installer + var $installerAlert = $('.alert-danger:contains("module.install.php")'); + if ($installerAlert.length > 0) { + $installerAlert.on('click', '.b-resolve__btn', function (event) { + event.preventDefault(); + event.stopPropagation(); + + var $this = $(this); + + if (!$this.hasClass('disabled')) { + $this.animate( + { + left: '10px', + opacity: 0 + }, 150, function () { + $this.hide().prev().show(function () { + $.post(intelli.config.admin_url + '/actions/read.json', {action: 'remove-installer'}, function (response) { + if (!response.error) { + $this.prev().animate( + { + left: '10px', + opacity: 0 + }, 150, function () { + $this.prev().hide().prev().show(); + }); + + $installerAlert + .removeClass('alert-danger') + .addClass('alert-info'); + + setTimeout(function () { + clearNotification($installerAlert); + }, 2000); + } + }); + }); + }); + } + }); + + var resolveBtnHtml = '
' + + ' ' + _t('notification_resolve--resolved') + '' + + ' ' + _t('notification_resolve--working') + '' + + ' ' + _t('notification_resolve--resolve') + '' + + '
'; + $installerAlert.addClass('b-resolve').append(resolveBtnHtml); + } + } + + // moving upload blocks up and down + + disableMoveButtons(); + + $('.js-upload-moveup').click(function (e) { + e.preventDefault(); + + var $this = $(this), + $parent = $this.closest('.uploads-list-item'); + + $parent.insertBefore($parent.prev()); + + disableMoveButtons(); + }); + + $('.js-upload-movedown').click(function (e) { + e.preventDefault(); + + var $this = $(this), + $parent = $this.closest('.uploads-list-item'); + + $parent.insertAfter($parent.next()); + + disableMoveButtons(); + }); + + if (typeof Dropzone !== 'undefined') { + Dropzone.autoDiscover = false; + } + + $('.js-dropzone').each(function () { + var $dropzone = $(this); + var fieldName = $dropzone.data('field'); + var itemName = $dropzone.data('item'); + var itemId = $dropzone.data('item_id'); + var submitButtonText = $dropzone.data('submit_btn_text'); + var values = $dropzone.data('values'); + + var dropZone = new Dropzone('#' + $dropzone.attr('id'), + { + url: intelli.config.admin_url + '/actions/read.json', + addRemoveLinks: true, + acceptedFiles: 'image/*', + parallelUploads: 20, + maxFiles: $dropzone.data('max_num'), + dictRemoveFile: '', + dictMaxFilesExceeded: _t('no_more_files'), + dictDefaultMessage: _t('drop_files_here'), + dictInvalidFileType: _t('field_tooltip_members_avatar'), + dictCancelUpload: '', + dictCancelUploadConfirmation: _t('cancel_upload_confirmation'), + init: function () { + var dropZone = this, + error = false, + errorMessage = ''; + + this.on('success', function (file, response) { + if (response.error) { + return false; + } + + var $preview = $(file.previewElement), + htmlInputs = + '' + + '' + + '', + htmlFancybox = ''; + + $preview.append(htmlInputs).find('.dz-details').append(htmlFancybox); + $('[data-dz-name]', $preview).text(response.file); + }); + + var $submit = $('.js-btn-submit'); + this.on('sending', function (file, xhr, formData) { + $submit + .attr('disabled', true) + .html('' + _t('uploading_please_wait') + ' ' + _t('uploading_please_wait')); + formData.append('action', 'dropzone-upload-file'); + formData.append('field', fieldName); + formData.append('item', itemName); + }); + + this.on('error', function (file) { + if ('canceled' != file.status) { + error = true; + errorMessage = 'error'; + } + }); + + this.on('maxfilesexceeded', function (file) { + error = true; + errorMessage = 'no_more_files'; + + $(file.previewElement).remove(); + }); + + this.on('removedfile', function (file) { + if ('undefined' == typeof file.status || 'success' == file.status) { + var params = { + action: 'dropzone-delete-file', + item: itemName, + field: fieldName, + path: $('input[type="hidden"]:first', file.previewElement).val(), + file: $('[data-dz-name]', file.previewElement).text() + }; + + if (typeof existingFiles !== 'undefined' && -1 !== existingFiles.indexOf(file.name)) { + dropZone.options.maxFiles = dropZone.options.maxFiles + 1; + + params.action = 'delete-file'; + params.itemid = itemId; + } + + $.post(intelli.config.admin_url + '/actions/read.json', params).done(function (response) { + intelli.notifFloatBox({ + msg: response.message, + type: response.error ? 'error' : 'success', + autohide: true + }); + }); + } + }); + + this.on('queuecomplete', function () { + if (error) { + message = errorMessage; + error = false; + errorMessage = ''; + intelli.notifFloatBox({msg: _t(message), type: 'error', autohide: true}); + } + $submit.attr('disabled', false).html(_t(submitButtonText)); + }); + } + }); + + if (typeof values == 'object' && values) { + var imageTypes = { + primary: $dropzone.data('imagetype-primary'), + thumbnail: $dropzone.data('imagetype-thumbnail') + }; + var existingFiles = [], mock; + + values.forEach(function (entry) { + mock = {name: entry.file, size: entry.size}; + + dropZone.emit('addedfile', mock); + dropZone.createThumbnailFromUrl(mock, intelli.config.ia_url + 'uploads/' + entry.path + imageTypes.thumbnail + '/' + entry.file); + + var htmlInputs = + '' + + '' + + '', + htmlFancybox = ''; + + $(mock.previewElement).append(htmlInputs).find('.dz-details').append(htmlFancybox); + + dropZone.emit('complete', mock); + existingFiles.push(entry.file); + }); + + dropZone.options.maxFiles = dropZone.options.maxFiles - existingFiles.length; + } + + intelli.sortable($dropzone.attr('id'), {handle: '.dz-image-preview'}); + }); + + $('.js-cmd-delete-file').on('click', function (e) { + e.preventDefault(); + var data = $(this).data(); + intelli.admin.removeFile(data.file, this, data.item, data.field, data.id); + }); + + // Live filter on Modules pages + $('.js-filter-modules-text').on('keyup', function () { + var $this = $(this), + text = $this.val(), + $collection = $('.card--module'); + + if (text != '') { + var rx = RegExp(text, 'i') + + $collection.each(function () { + var $item = $(this), + name = String($('.card__item__body > h4', $item).text()); + + (name.match(rx) !== null) ? $item.show() : $item.hide(); + }); + } else { + $collection.show(); + } + }); + + $('.js-filter-modules').click(function (e) { + e.preventDefault(); + + var $this = $(this), + type = $this.data('type'), + filtered = $this.data('filtered'); + + if (filtered == 'no') { + $this.data('filtered', 'yes'); + $('.card--' + type).hide(); + } else { + $this.data('filtered', 'no'); + $('.card--' + type).show(); + } + + $('.fa', $this).toggleClass('fa-circle-thin fa-check'); + }); + + $('.js-filter-modules-reset').click(function (e) { + e.preventDefault(); + + $('.js-filter-modules-text').val('').trigger('keyup'); + $('.js-filter-modules').data('filtered', 'no') + .find('.fa') + .removeClass('fa-circle-thin') + .addClass('fa-check'); + }); }); -function clearNotification(el) -{ - var $nLabel = $('.navbar-nav__notifications > a .label-info'), - $nLabelCount = parseInt($nLabel.text()), - $nBlock = $('.navbar-nav__notifications__alerts'); - - if (0 != $nLabelCount) - { - $nLabel.text($nLabelCount - 1); - } - - if ($('.alert', $nBlock).length >= 2) - { - el.animate( - { - top: '-10px', - opacity: 0 - }, 150, function() - { - el.hide(); - }) - } - else - { - el.closest('.dropdown').find('.dropdown-toggle').dropdown('toggle'); - } +function clearNotification(el) { + var $nLabel = $('.navbar-nav__notifications > a .label-info'), + $nLabelCount = parseInt($nLabel.text()), + $nBlock = $('.navbar-nav__notifications__alerts'); + + if (0 != $nLabelCount) { + $nLabel.text($nLabelCount - 1); + } + + if ($('.alert', $nBlock).length >= 2) { + el.animate( + { + top: '-10px', + opacity: 0 + }, 150, function () { + el.hide(); + }) + } + else { + el.closest('.dropdown').find('.dropdown-toggle').dropdown('toggle'); + } } -function disableMoveButtons() -{ - $('.uploads-list-item') - .find('.js-upload-moveup, .js-upload-movedown') - .prop('disabled', false); +function disableMoveButtons() { + $('.uploads-list-item') + .find('.js-upload-moveup, .js-upload-movedown') + .prop('disabled', false); - $('.uploads-list-item:first-child') - .find('.js-upload-moveup') - .prop('disabled', true); + $('.uploads-list-item:first-child') + .find('.js-upload-moveup') + .prop('disabled', true); - $('.uploads-list-item:last-child') - .find('.js-upload-movedown') - .prop('disabled', true); + $('.uploads-list-item:last-child') + .find('.js-upload-movedown') + .prop('disabled', true); } \ No newline at end of file From 4f75d8553688ae845c68ae673afe200e4a2cea48 Mon Sep 17 00:00:00 2001 From: Vasily Bezruchkin Date: Thu, 30 Mar 2017 12:40:40 +0600 Subject: [PATCH 017/111] Minor PSR checks. --- admin/fields.php | 3 +-- admin/index.php | 1 - front/actions.php | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/admin/fields.php b/admin/fields.php index c12cfb2d..7d0e2095 100644 --- a/admin/fields.php +++ b/admin/fields.php @@ -105,8 +105,7 @@ protected function _gridRead($params) "`relation` != 'parent'" . (empty($_GET['item']) ? '' : " AND `item` = '" . iaSanitize::sql($_GET['item']) . "'")); $output = []; - foreach ($rows as $row) - { + foreach ($rows as $row) { $output[] = [ 'id' => $row['name'], 'text' => iaField::getFieldTitle($row['item'], $row['name']), diff --git a/admin/index.php b/admin/index.php index 958fe1a7..5978fc3c 100644 --- a/admin/index.php +++ b/admin/index.php @@ -133,7 +133,6 @@ protected function _indexPage(&$iaView) $line = trim($line); if ($line) { if ($line[0] == '>') { - $url = false !== stripos($line, '4.1.') ? 'https://github.com/intelliants/subrion/issues/$1' : 'https://dev.subrion.org/issues/$1'; diff --git a/front/actions.php b/front/actions.php index 48df0c60..24f1aa33 100644 --- a/front/actions.php +++ b/front/actions.php @@ -200,7 +200,7 @@ $stmt = '(`username` LIKE :name OR `email` LIKE :name OR `fullname` LIKE :name) AND `status` = :status ORDER BY `username` ASC'; $iaDb->bind($stmt, ['name' => $_GET['q'] . '%', 'status' => iaCore::STATUS_ACTIVE]); - $sql = << Date: Thu, 30 Mar 2017 14:49:53 +0600 Subject: [PATCH 018/111] WIP --- admin/templates/default/tree.tpl | 2 +- .../ia.base.controller.module.admin.php | 37 +- includes/classes/ia.base.module.front.api.php | 2 +- includes/classes/ia.system.php | 15 + includes/helpers/ia.category.front.hybrid.php | 80 ++++ includes/helpers/ia.category.hybrid.php | 395 ++++++++++++++++++ includes/helpers/ia.category.interface.php | 33 ++ includes/helpers/ia.category.nestedsets.php | 30 ++ templates/_common/tree.tpl | 2 +- 9 files changed, 557 insertions(+), 39 deletions(-) create mode 100644 includes/helpers/ia.category.front.hybrid.php create mode 100644 includes/helpers/ia.category.hybrid.php create mode 100644 includes/helpers/ia.category.interface.php create mode 100644 includes/helpers/ia.category.nestedsets.php diff --git a/admin/templates/default/tree.tpl b/admin/templates/default/tree.tpl index 8872c508..27159cf7 100644 --- a/admin/templates/default/tree.tpl +++ b/admin/templates/default/tree.tpl @@ -22,7 +22,7 @@ $(function() url: '{$url}', onchange: intelli.fillUrlBox,{if iaCore::ACTION_EDIT == $pageAction} nodeSelected: $('#input-tree').val(), - nodeOpened: [0,{$item.parents}],{/if} + nodeOpened: [{$treeParents}],{/if} search: {if $search}true{else}false{/if} }); }); diff --git a/includes/classes/ia.base.controller.module.admin.php b/includes/classes/ia.base.controller.module.admin.php index 20a2efec..12d740b4 100644 --- a/includes/classes/ia.base.controller.module.admin.php +++ b/includes/classes/ia.base.controller.module.admin.php @@ -351,42 +351,7 @@ protected function _setSystemDefaults(&$entryData) protected function _getJsonTree(array $data) { - $output = []; - - $dynamicLoadMode = ((int)$this->_iaDb->one(iaDb::STMT_COUNT_ROWS) > 150); - $noRootMode = isset($data['noroot']) && '' == $data['noroot']; - - $rootId = $noRootMode ? 1 : 0; - $parentId = isset($data['id']) && is_numeric($data['id']) ? (int)$data['id'] : $rootId; - - $where = $dynamicLoadMode - ? '`parent_id` = ' . $parentId - : ($noRootMode ? '`id` != ' . $rootId : iaDb::EMPTY_CONDITION); - - // TODO: better solution should be found here. this code will break jstree composition in case if - // category to be excluded from the list has children of 2 and more levels deeper - empty($data['cid']) || $where.= ' AND `id` != ' . (int)$data['cid'] . ' AND `parent_id` != ' . (int)$data['cid']; - - $where.= ' ORDER BY `title`'; - - $rows = $this->_iaDb->all(['id', 'title' => 'title_' . $this->_iaCore->language['iso'], 'parent_id', 'child'], $where); - - foreach ($rows as $row) { - $entry = ['id' => $row['id'], 'text' => $row['title']]; - - if ($dynamicLoadMode) { - $entry['children'] = ($row['child'] && $row['child'] != $row['id']) || 0 === (int)$row['id']; - } else { - $entry['state'] = []; - $entry['parent'] = $noRootMode - ? ($rootId == $row['parent_id'] ? '#' : $row['parent_id']) - : (1 == $row['id'] ? '#' : $row['parent_id']); - } - - $output[] = $entry; - } - - return $output; + return $this->getHelper()->getJsonTree($data); } protected function _getPlans() diff --git a/includes/classes/ia.base.module.front.api.php b/includes/classes/ia.base.module.front.api.php index 72e48de1..f78d2570 100644 --- a/includes/classes/ia.base.module.front.api.php +++ b/includes/classes/ia.base.module.front.api.php @@ -24,7 +24,7 @@ * ******************************************************************************/ -abstract class abstractModuleFrontApiResponder extends abstractModuleFront +abstract class abstractModuleFrontApiResponder extends iaAbstractFrontHelperCategoryHybrid { protected $_request; protected $_response; diff --git a/includes/classes/ia.system.php b/includes/classes/ia.system.php index c282387e..9e91e398 100644 --- a/includes/classes/ia.system.php +++ b/includes/classes/ia.system.php @@ -53,6 +53,12 @@ public static function autoload($className) 'iaAbstractControllerModuleBackend' => 'ia.base.controller.module.admin' ]; + $helperClasses = [ + 'iaAbstractHelperCategoryHybrid' => 'ia.category.hybrid', + 'iaAbstractHelperCategoryNestedsets' => 'ia.category.nestedsets', + 'iaAbstractFrontHelperCategoryHybrid' => 'ia.category.front.hybrid' + ]; + if (isset($systemClasses[$className])) { $fileName = $systemClasses[$className] . self::EXECUTABLE_FILE_EXT; @@ -61,6 +67,15 @@ public static function autoload($className) return true; } } + elseif (isset($helperClasses[$className])) { + $filePath = IA_INCLUDES . 'helpers' . IA_DS; + $fileName = $helperClasses[$className] . self::EXECUTABLE_FILE_EXT; + + if (include_once $filePath . $fileName) { + iaDebug::debug('autoload: ' . $fileName . ' (' . self::byteView(filesize($filePath . $fileName)) . ')', 'Initialized Classes List', 'info'); + return true; + } + } return false; } diff --git a/includes/helpers/ia.category.front.hybrid.php b/includes/helpers/ia.category.front.hybrid.php new file mode 100644 index 00000000..d883425b --- /dev/null +++ b/includes/helpers/ia.category.front.hybrid.php @@ -0,0 +1,80 @@ + + * + * This file is part of Subrion. + * + * Subrion is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Subrion is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Subrion. If not, see . + * + * + * @link https://subrion.org/ + * + ******************************************************************************/ + +require_once IA_INCLUDES . 'helpers/ia.category.interface.php'; + +abstract class iaAbstractFrontHelperCategoryHybrid extends abstractModuleFront +{ + const ROOT_PARENT_ID = 0; + + const COL_PARENT_ID = '_pid'; + const COL_PARENTS = '_parents'; + const COL_CHILDREN = '_children'; + const COL_LEVEL = 'level'; + + protected $_flatStructureEnabled = false; + + private $_rootId; + + + public function getRootId() + { + if (is_null($this->_rootId)) { + $this->_rootId = $this->iaDb->one(iaDb::ID_COLUMN_SELECTION, iaDb::convertIds(self::ROOT_PARENT_ID, self::COL_PARENT_ID), self::getTable()); + } + + return $this->_rootId; + } + + public function fetch($where, $columns = null, $start = null, $limit = null) + { + is_null($columns) && $columns = iaDb::ALL_COLUMNS_SELECTION; + + $rows = $this->iaDb->all($columns, $where, (int)$start, (int)$limit, self::getTable()); + + $this->_processValues($rows); + + return $rows; + } + + public function getJsonTree(array $data) + { + $output = []; + + $categoryId = isset($data['id']) ? (int)$data['id'] : 1; + $rows = $this->fetch(iaDb::convertIds($categoryId, self::COL_PARENT_ID), ['id', 'title' => 'title_' . $this->iaView->language, self::COL_CHILDREN]); + + foreach ($rows as $entry) { + $output[] = [ + 'id' => $entry['id'], + 'text' => $entry['title'], + 'children' => ($entry[self::COL_CHILDREN] && $entry[self::COL_CHILDREN] != $entry['id']) || (-1 == $categoryId) + ]; + } + + return $output; + } +} \ No newline at end of file diff --git a/includes/helpers/ia.category.hybrid.php b/includes/helpers/ia.category.hybrid.php new file mode 100644 index 00000000..7eb9a6f0 --- /dev/null +++ b/includes/helpers/ia.category.hybrid.php @@ -0,0 +1,395 @@ + + * + * This file is part of Subrion. + * + * Subrion is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Subrion is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Subrion. If not, see . + * + * + * @link https://subrion.org/ + * + ******************************************************************************/ + +require_once IA_INCLUDES . 'helpers/ia.category.interface.php'; + +abstract class iaAbstractHelperCategoryHybrid extends abstractModuleAdmin implements iaAbstractHelperCategoryInterface +{ + const ROOT_PARENT_ID = 0; + + const COL_PARENT_ID = '_pid'; + const COL_PARENTS = '_parents'; + const COL_CHILDREN = '_children'; + const COL_LEVEL = 'level'; + + protected $_flatStructureEnabled = false; + + private $_rootId; + + + public function setupDbStructure() + { + $queries = [ + 'ALTER TABLE `:table` ADD `_pid` mediumint(8) unsigned NOT NULL default 0', + 'ALTER TABLE `:table` ADD `_parents` tinytext NOT NULL', + 'ALTER TABLE `:table` ADD `_children` text NOT NULL', + 'ALTER TABLE `:table` ADD `level` smallint(5) unsigned NOT NULL default 0', + 'ALTER TABLE `:table` ADD INDEX `PARENT` (`_pid`)' + ]; + + foreach ($queries as $query) { + $sql = iaDb::printf($query, ['table' => self::getTable(true)]); + $this->iaDb->query($sql); + } + + $rootEntry = [ + self::COL_PARENT_ID => self::ROOT_PARENT_ID, + self::COL_PARENTS => '1', + self::COL_CHILDREN => '1', + self::COL_LEVEL => 0, + 'id' => 1, + 'title_' . $this->iaView->language => 'ROOT', + 'date_added' => date(iaDb::DATE_FORMAT), + 'date_modified' => date(iaDb::DATE_FORMAT) + ]; + + $this->iaDb->insert($rootEntry, null, self::getTable()); + + if ($this->_flatStructureEnabled) { + $sql = <<iaDb->query(iaDb::printf($sql, ['prefix' => $this->iaDb->prefix, 'options' => $this->iaDb->tableOptions])); + } + } + + public function getJsonTree(array $data) + { + $output = []; + + $this->iaDb->setTable(self::getTable()); + + $dynamicLoadMode = ((int)$this->iaDb->one(iaDb::STMT_COUNT_ROWS) > 150); + $noRootMode = isset($data['noroot']) && '' == $data['noroot']; + + $rootId = $noRootMode ? 1 : 0; // TODO: hardcoded '1' should be reviewed + $parentId = isset($data['id']) && is_numeric($data['id']) ? (int)$data['id'] : $rootId; + + $where = $dynamicLoadMode + ? sprintf('`%s` = %d', self::COL_PARENT_ID, $parentId) + : ($noRootMode ? '`id` != ' . $rootId : iaDb::EMPTY_CONDITION); + + // TODO: better solution should be found here. this code will break jstree composition in case if + // category to be excluded from the list has children of 2 and more levels deeper + if (!empty($data['cid'])) { + $where .= sprintf(' AND `id` != %d AND `%s` != %d', $data['cid'], self::COL_PARENT_ID, $data['cid']); + } + + $where .= ' ORDER BY `title`'; + + $rows = $this->iaDb->all(['id', 'title' => 'title_' . $this->iaCore->language['iso'], self::COL_PARENT_ID, self::COL_CHILDREN], $where); + + foreach ($rows as $row) { + $entry = ['id' => $row['id'], 'text' => $row['title']]; + + if ($dynamicLoadMode) { + $entry['children'] = ($row['child'] && $row['child'] != $row['id']) || 0 === (int)$row['id']; + } else { + $entry['state'] = []; + $entry['parent'] = $noRootMode + ? ($rootId == $row[self::COL_PARENT_ID] ? '#' : $row[self::COL_PARENT_ID]) + : (1 == $row['id'] ? '#' : $row[self::COL_PARENT_ID]); + } + + $output[] = $entry; + } + + return $output; + } + + public function get($columns, $where, $order = '', $start = null, $limit = null) + { + $sql = << $this->iaCore->language['iso'], + 'table_categories' => self::getTable(true), + 'columns' => $columns, + 'where' => $where, + 'order' => $order, + 'start' => $start, + 'limit' => $limit + ]); + + return $this->iaDb->getAll($sql); + } + + public function getRootId() + { + if (is_null($this->_rootId)) { + $this->_rootId = $this->iaDb->one(iaDb::ID_COLUMN_SELECTION, iaDb::convertIds(self::ROOT_PARENT_ID, self::COL_PARENT_ID), self::getTable()); + } + + return $this->_rootId; + } + + public function getRoot() + { + $row = $this->iaDb->row(iaDb::ALL_COLUMNS_SELECTION, iaDb::convertIds($this->getRootId()), self::getTable()); + + $this->_processValues($row, true); + + return $row; + } + + public function updateCounters($itemId, array $itemData, $action, $previousData = null) + { + $this->rebuildRelations($itemId); + } + + public function rebuildRelations($id = null) + { + return is_null($id) + ? $this->_rebuildAll() + : $this->_rebuildEntry($id); + } + + protected function _rebuildEntry($id) + { + $this->iaDb->setTable(self::getTable()); + + $category = $this->iaDb->row(iaDb::ALL_COLUMNS_SELECTION, iaDb::convertIds($id)); + + // update parents + $parents = [$category['id']]; + $parents = $this->_getParents($category['id'], $parents); + $level = count($parents) - 1; + + $children = [$category['id']]; + $children = $this->_getChildren($category['id'], $children); + + $entry = [ + self::COL_PARENTS => implode(',', $parents), + self::COL_CHILDREN => implode(',', $children), + self::COL_LEVEL => $level + ]; + + $this->iaDb->update($entry, iaDb::convertIds($category['id'])); + + $this->iaDb->resetTable(); + } + + /* + * Rebuild categories relations. + * Fields to be updated: parents, child, level, title_alias + */ + protected function _rebuildAll() + { + $table_flat = $this->iaDb->prefix . 'coupons_categories_flat'; + $table = self::getTable(true); + + $insert_second = 'INSERT INTO ' . $table_flat . ' (`parent_id`, `category_id`) SELECT t.`parent_id`, t.`id` FROM ' . $table . ' t WHERE t.`parent_id` != 0'; + $insert_first = 'INSERT INTO ' . $table_flat . ' (`parent_id`, `category_id`) SELECT t.`id`, t.`id` FROM ' . $table . ' t WHERE t.`parent_id` != 0'; + $update_level = 'UPDATE ' . $table . ' s SET `level` = (SELECT COUNT(`category_id`)-1 FROM ' . $table_flat . ' f WHERE f.`category_id` = s.`id`) WHERE s.`parent_id` != 0;'; + $update_child = 'UPDATE ' . $table . ' s SET `child` = (SELECT GROUP_CONCAT(`category_id`) FROM ' . $table_flat . ' f WHERE f.`parent_id` = s.`id`);'; + $update_parent = 'UPDATE ' . $table . ' s SET `parents` = (SELECT GROUP_CONCAT(`parent_id`) FROM ' . $table_flat . ' f WHERE f.`category_id` = s.`id`);'; + + $num = 1; + $count = 0; + + $iaDb = &$this->iaDb; + $iaDb->truncate($table_flat); + $iaDb->query($insert_first); + $iaDb->query($insert_second); + + while($num > 0 && $count < 10) + { + $count++; + $num = 0; + $sql = 'INSERT INTO ' . $table_flat . ' (`parent_id`, `category_id`) ' + . 'SELECT DISTINCT t . `id`, h' . $count . ' . `id` FROM ' . $table . ' t, ' . $table . ' h0 '; + $where = ' WHERE h0 . `parent_id` = t . `id` '; + + for ($i = 1; $i <= $count; $i++) + { + $sql .= 'LEFT JOIN ' . $table . ' h' . $i . ' ON (h' . $i . '.`parent_id` = h' . ($i - 1) . '.`id`) '; + $where .= ' AND h' . $i . '.`id` is not null'; + } + + if ($iaDb->query($sql . $where)) + { + $num = $iaDb->getAffected(); + } + } + + $iaDb->query($update_level); + $iaDb->query($update_child); + $iaDb->query($update_parent); + + $iaDb->query('UPDATE ' . $table . ' SET `order` = 1 WHERE `order` = 0'); + } + + protected function _getPathForRebuild($title, $pid, $path = '') + { + static $cache; + + $str = preg_replace('#[^a-z0-9_-]+#i', '-', $title); + $str = trim($str, '-'); + $str = str_replace("'", '', $str); + + $path = $path ? $str . '/' . $path : $str . '/'; + + if ($pid != 1) { + if (isset($cache[$pid])) { + $parent = $cache[$pid]; + } else { + $parent = $this->iaDb->row(['id', 'parent_id', 'title'], "`id` = '{$pid}'"); + + $cache[$pid] = $parent; + } + + $path = $this->_getPathForRebuild($parent['title'], $parent['parent_id'], $path); + } + + return $path; + } + + public function rebuildAliases($id) + { + $this->iaDb->setTable(self::getTable()); + + $category = $this->iaDb->row(iaDb::ALL_COLUMNS_SELECTION, iaDb::convertIds($id)); + $path = $this->_getPathForRebuild($category['title'], $category['parent_id']); + $this->iaDb->update(['title_alias' => $path], iaDb::convertIds($category['id'])); + + $this->iaDb->resetTable(); + } + + /** + * Updates number of active articles for each category + */ + public function calculateArticles($start = 0, $limit = 10) + { + $this->iaDb->setTable(self::getTable()); + + $categories = $this->iaDb->all(['id', 'parent_id', 'child'], '1 ORDER BY `level` DESC', $start, $limit); + + foreach ($categories as $cat) { + if (0 != $cat['parent_id']) { + $_id = $cat['id']; + + $sql = 'SELECT COUNT(a.`id`) `num`'; + $sql .= "FROM `{$this->iaDb->prefix}articles` a "; + $sql .= "LEFT JOIN `{$this->iaDb->prefix}members` acc ON (a.`member_id` = acc.`id`) "; + $sql .= "WHERE a.`status`= 'active' AND (acc.`status` = 'active' OR acc.`status` IS NULL) "; + $sql .= "AND a.`category_id` = {$_id}"; + + $num_articles = $this->iaDb->getOne($sql); + $_num_articles = $num_articles ? $num_articles : 0; + $num_all_articles = 0; + + if (!empty($cat['child']) && $cat['child'] != $cat['id']) { + $num_all_articles = $this->iaDb->one('SUM(`num_articles`)', "`id` IN ({$cat['child']})", self::getTable()); + } + + $num_all_articles += $_num_articles; + + $this->iaDb->update(['num_articles' => $_num_articles, 'num_all_articles' => $num_all_articles], iaDb::convertIds($_id)); + } + } + + $this->iaDb->resetTable(); + + return true; + } + + protected function _getParents($cId, $parents = [], $update = true) + { + $parentId = $this->iaDb->one(self::COL_PARENT_ID, iaDb::convertIds($cId)); + + if ($parentId != 0) { + $parents[] = $parentId; + + if ($update) { + $childrenIds = $this->iaDb->one(self::COL_CHILDREN, iaDb::convertIds($parentId)); + $childrenIds = $childrenIds ? explode(',', $childrenIds) : []; + + if (!in_array($cId, $childrenIds)) { + $childrenIds[] = $cId; + } + + foreach ($parents as $pid) { + if (!in_array($pid, $childrenIds)) { + $childrenIds[] = $pid; + } + } + + $this->iaDb->update([self::COL_CHILDREN => implode(',', $childrenIds)], iaDb::convertIds($parentId)); + } + + $parents = $this->_getParents($parentId, $parents, $update); + } + + return $parents; + } + + protected function _getChildren($cId, $children = [], $update = false) + { + if ($childrenIds = $this->iaDb->onefield(iaDb::ID_COLUMN_SELECTION, iaDb::convertIds($cId, 'parent_id'), null, null, self::getTable())) { + foreach ($childrenIds as $childId) { + $children[] = $childId; + + if ($update) { + $parentIds = $this->iaDb->one(self::COL_PARENTS, iaDb::convertIds($cId), self::getTable()); + $parentIds = $parentIds ? explode(',', $parentIds) : []; + + $parentIds[] = $childId; + + $this->iaDb->update([self::COL_PARENTS => implode(',', $parentIds)], iaDb::convertIds($childId), null, self::getTable()); + } + + $children = $this->_getChildren($childId, $children, $update); + } + } + + return $children; + } + + public function dropRelations() + { + $this->iaDb->update(['child' => '', 'parents' => ''], iaDb::EMPTY_CONDITION, self::getTable()); + } + + public function clearArticlesNum() + { + $this->iaDb->update(['num_articles' => 0, 'num_all_articles' => 0], iaDb::EMPTY_CONDITION, self::getTable()); + } + + public function getCount() + { + return $this->iaDb->one(iaDb::STMT_COUNT_ROWS, null, self::getTable()); + } +} \ No newline at end of file diff --git a/includes/helpers/ia.category.interface.php b/includes/helpers/ia.category.interface.php new file mode 100644 index 00000000..c97dbb69 --- /dev/null +++ b/includes/helpers/ia.category.interface.php @@ -0,0 +1,33 @@ + + * + * This file is part of Subrion. + * + * Subrion is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Subrion is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Subrion. If not, see . + * + * + * @link https://subrion.org/ + * + ******************************************************************************/ + +interface iaAbstractHelperCategoryInterface +{ + + + public function setupDbStructure(); + public function getJsonTree(array $data); +} \ No newline at end of file diff --git a/includes/helpers/ia.category.nestedsets.php b/includes/helpers/ia.category.nestedsets.php new file mode 100644 index 00000000..a56409d8 --- /dev/null +++ b/includes/helpers/ia.category.nestedsets.php @@ -0,0 +1,30 @@ + + * + * This file is part of Subrion. + * + * Subrion is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Subrion is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Subrion. If not, see . + * + * + * @link https://subrion.org/ + * + ******************************************************************************/ + +abstract class iaAbstractHelperCategoryNestedsets extends abstractModuleAdmin implements iaAbstractHelperCategoryInterface +{ + +} \ No newline at end of file diff --git a/templates/_common/tree.tpl b/templates/_common/tree.tpl index fa87bc8b..b178e973 100644 --- a/templates/_common/tree.tpl +++ b/templates/_common/tree.tpl @@ -12,7 +12,7 @@ {ia_add_js} $(function() { - new IntelliTree({ url: '{$url}', nodeOpened: [{$category.parents}], nodeSelected: '{$item.category_id}', search: {if $search}true{else}false{/if} }); + new IntelliTree({ url: '{$url}', nodeOpened: [{$category._parents}], nodeSelected: '{$item.category_id}', search: {if $search}true{else}false{/if} }); }); {/ia_add_js} {ia_add_media files='tree'} From 4f03adc231e6a78c0604fee60f1ce2b1909a64ae Mon Sep 17 00:00:00 2001 From: Janur Jangaraev Date: Thu, 30 Mar 2017 16:49:00 +0600 Subject: [PATCH 019/111] Category helper: WIP --- admin/templates/default/tree.tpl | 2 +- includes/helpers/ia.category.hybrid.php | 45 ++++++++++++------------- 2 files changed, 22 insertions(+), 25 deletions(-) diff --git a/admin/templates/default/tree.tpl b/admin/templates/default/tree.tpl index 27159cf7..4e4625aa 100644 --- a/admin/templates/default/tree.tpl +++ b/admin/templates/default/tree.tpl @@ -13,7 +13,7 @@
{/if} - + {ia_add_js} $(function() { diff --git a/includes/helpers/ia.category.hybrid.php b/includes/helpers/ia.category.hybrid.php index 7eb9a6f0..89b6bcac 100644 --- a/includes/helpers/ia.category.hybrid.php +++ b/includes/helpers/ia.category.hybrid.php @@ -46,7 +46,7 @@ public function setupDbStructure() 'ALTER TABLE `:table` ADD `_pid` mediumint(8) unsigned NOT NULL default 0', 'ALTER TABLE `:table` ADD `_parents` tinytext NOT NULL', 'ALTER TABLE `:table` ADD `_children` text NOT NULL', - 'ALTER TABLE `:table` ADD `level` smallint(5) unsigned NOT NULL default 0', + 'ALTER TABLE `:table` ADD `level` tinyint(3) unsigned NOT NULL default 0', 'ALTER TABLE `:table` ADD INDEX `PARENT` (`_pid`)' ]; @@ -111,7 +111,7 @@ public function getJsonTree(array $data) $entry = ['id' => $row['id'], 'text' => $row['title']]; if ($dynamicLoadMode) { - $entry['children'] = ($row['child'] && $row['child'] != $row['id']) || 0 === (int)$row['id']; + $entry['children'] = ($row[self::COL_CHILDREN] && $row[self::COL_CHILDREN] != $row['id']) || 0 === (int)$row['id']; } else { $entry['state'] = []; $entry['parent'] = $noRootMode @@ -125,28 +125,6 @@ public function getJsonTree(array $data) return $output; } - public function get($columns, $where, $order = '', $start = null, $limit = null) - { - $sql = << $this->iaCore->language['iso'], - 'table_categories' => self::getTable(true), - 'columns' => $columns, - 'where' => $where, - 'order' => $order, - 'start' => $start, - 'limit' => $limit - ]); - - return $this->iaDb->getAll($sql); - } - public function getRootId() { if (is_null($this->_rootId)) { @@ -165,6 +143,25 @@ public function getRoot() return $row; } + public function update(array $itemData, $id) + { + // makes impossible to change the alias for the root + if ($this->getRootId() == $itemData[self::COL_PARENT_ID] && isset($itemData['title_alias'])) { + unset($itemData['title_alias']); + } + + return parent::update($itemData, $id); + } + + public function delete($itemId) + { + if ($itemId == $this->getRootId()) { + return false; + } + + return parent::delete($itemId); + } + public function updateCounters($itemId, array $itemData, $action, $previousData = null) { $this->rebuildRelations($itemId); From 881b4bde6a1a952c5aaefa3e36562caf0ccce77d Mon Sep 17 00:00:00 2001 From: Gleb Surinov Date: Thu, 30 Mar 2017 16:55:40 +0600 Subject: [PATCH 020/111] Admin Template task runner optimizations --- admin/templates/default/Gulpfile.js | 39 +++++++++++------- .../default/img/ico/apple-touch-icon.png | Bin 13188 -> 10877 bytes .../default/img/ico/favicon-16x16.png | Bin 1089 -> 677 bytes .../default/img/ico/favicon-32x32.png | Bin 2041 -> 1674 bytes admin/templates/default/package.json | 7 ++-- 5 files changed, 28 insertions(+), 18 deletions(-) diff --git a/admin/templates/default/Gulpfile.js b/admin/templates/default/Gulpfile.js index fedb574d..6cdcd170 100644 --- a/admin/templates/default/Gulpfile.js +++ b/admin/templates/default/Gulpfile.js @@ -1,14 +1,20 @@ +// +// Please don't forget to do `gulp build` before +// pushing to repo +// -------------------------------------------------- + + var pjson = require('./package.json'), gulp = require("gulp"), + path = require('path'), gutil = require('gulp-util'), + notify = require("gulp-notify"), concat = require("gulp-concat"), rename = require("gulp-rename"), imagemin = require("gulp-imagemin"), less = require("gulp-less"), + sourcemaps = require('gulp-sourcemaps'), cleanCSS = require('gulp-clean-css'); - //progeny = require('gulp-progeny'), - //cache = require('gulp-cached'), - //remember = require('gulp-remember'), var config = { paths: { @@ -43,8 +49,6 @@ gulp.task("images", function(){ gulp.task("less", function(){ return gulp.src(config.paths.less.src) - //.pipe(cache('less')) - //.pipe(progeny()) .pipe(less().on('error', function(err) { gutil.log(err); this.emit('end'); @@ -52,29 +56,36 @@ gulp.task("less", function(){ .pipe(cleanCSS({ advanced: false })) - //.pipe(remember('less')) .pipe(rename(function (path) { path.basename = path.basename.replace('base', 'bootstrap'); })) - .pipe(gulp.dest(config.paths.less.dest)); + .pipe(gulp.dest(config.paths.less.dest)) + .pipe(notify({ + sound: true, + title: "Build completed!", + message: "File: <%= file.relative %>. No errors. Images optimized", + icon: path.join(__dirname, "img/ico/apple-touch-icon.png") + })); }); gulp.task("less-dev", function(){ return gulp.src(config.paths.less.srcDev) - //.pipe(cache('less')) - //.pipe(progeny()) + .pipe(sourcemaps.init()) .pipe(less().on('error', function(err) { gutil.log(err); this.emit('end'); })) - .pipe(cleanCSS({ - advanced: false - })) - //.pipe(remember('less')) + .pipe(sourcemaps.write()) .pipe(rename(function (path) { path.basename = path.basename.replace('base', 'bootstrap'); })) - .pipe(gulp.dest(config.paths.less.dest)); + .pipe(gulp.dest(config.paths.less.dest)) + .pipe(notify({ + sound: true, + title: "Compilation done! =)", + message: "File: <%= file.relative %>. No errors.", + icon: path.join(__dirname, "img/ico/apple-touch-icon.png") + })); }); gulp.task("build", ["less", "images"]); diff --git a/admin/templates/default/img/ico/apple-touch-icon.png b/admin/templates/default/img/ico/apple-touch-icon.png index 4fc3b9eae665fb116888c11a79bac47165ec6462..9aff6ca74768380fcb4ae101641ec76a7f6c3bb4 100644 GIT binary patch literal 10877 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 literal 13188 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 K#A-wg1OEr{cXyNk diff --git a/admin/templates/default/img/ico/favicon-16x16.png b/admin/templates/default/img/ico/favicon-16x16.png index a83e18d192b703e51af780b34681349cd5149f34..79ea0627ade522918c6a9328ca04c1e339eaf1dc 100644 GIT binary patch delta 195 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 Eg1H7oWB>pF delta 2028 zcmV;4fzj{BYyw{XF*Lt006O%3;baP0000WV@Og>004R>004l5008;`004mK z004C`008P>0026e000+ooVrmw00006VoOIv0RI600RN!9r;`8x00(qQO+^Rc0}u)o z9>!P^X#fBQbxA})R9M5kmu+m8R~g5D*SYWedF|7dmbPo3zJE|k>3|Lfb^#m)9W#s? z2foZ`jPQYo(P*-mOpP&_+Xp|)m}Q!M*}`-mbS6u5>h{IBL02<6He`;rv>k12p%hBH z7M?z@_kEw^hv$K|w9*P;*T<7w*Z+6^*L9!!9BJEk5MVd(B(M_*ad#sFoB*BzI_!e@ z3&8rjEPOpez<=j}IB>+OY2NY@(Dn(5&yRATUXi!`4#V7T;!Ya~-=1bc)CquPf$xKx z%dac7(kfy5cG0CWjmq_hGtAb+L+pbcmZP5^1%jMaV!+4#V+ z04sCgx`+){42&LScGkgY7ZC@`ielTvSXKljHbxtWm16BafEI=BJ$VNJ1|%#5n;k+M z0|eq0Ip<%D553E5QX?XWIM{ZKV4#XZ^m zJrpL`=zoBdUyA^1)>sUDq{-$qVzT6N8M3(v63Jcy&T2}Ew^0(?iWMzE?bwSyH4e$d zqL$dV5)ZaGl`IH`_DDfIpJe=5{PZYZYJV7=ogk)DRE0gpW(>YISjr+Ah+=yo zGU))}8bBb@7R0W!Knq4^@MimQ6FunM6d1o?rBG2} zz>m5bEn+VwJGf;Lz+&0Btc^X^qt zD1Uk=f>mtcXD*SQI*xno48~6u0Q2J7XsV)h{AAa&e1H2-38%w!cYnyafqnvBh<%&B zz}L1NAaU&j`Vv{3nE+fqzW`uK2gDW#3SM%M++-)(n<0N>$UII%X|RIFw?5Ckp*DW= z;*UA{`m0P#Phqsdv2B{_n|b_OPw?cPCx3{Yev>!4r&llo0y=4kj+`S^)rspS5!4On z`RR|Y{SK|Mo&56GM>zKOpO6d)f+)e)KD~oy`QbA^Am?WJ-oCFhbo^y{j6uz-vgpIq zMyRcv=G#qO9KI*b!{v$%MM6~oq82Ct-)PE1aX#PhO^$WGO6NO&!Lo#^h)^83{(o;2 zN{V7+a~Xd1yJtvDU7`KHjVMdq+JT_s@x?v8G*%}tMzJXhIRhylu6c%!T^P@3N@A^) z*_Cv4y@u=gC`+&v#ET$1WBAPWNBGgRyjgL7=P}M2_>bqIIhcQQx_F)m6e>VpARhhUco zlUf+@3?l)UdH)su@M3_v_!gQkWoX~fhUaUv)=Rg*7>I;j8f&LlaK909xqo3KWD~02 z!tCH_90fI&A#NFV1Z|82u}-{8aWKW^P$R3>xANYVNfOC9`)_hU2!~u^(d><#{!e*P zgmeucMO*9IK459Vg;661Hn`R1CC1HLu02t>eTie31p*KRun;D&={>zb=8FT@1^hJW&YmLwP5!P z07ML_w8Qk(2-TH|MJ_01Fh-cZ8liuvf)9FY7#zLE{=NU;;8z~Qj&CRX&y)CLm%#N< zMG@?lCW5=SGv<0c_x#ITnwVU$`$Z09vJM~iRkLB;Wt5sDNM{^I$A3yW)l}30gjP> zI^XTc6ts>29sdQkhKNtHyfSV8001R)MObuXVRU6WV{&C-bbn<4GB7bWEif}JF*Z~& zFgh|ZIx{dUFflqXFmzKEjsO4vC3HntbYx+4WjbwdWNBu305UK!H!UzTEipD!F)%tZ zF*-9aD=;xSFfauUt}*}s02y>eSaefwW^{L9a%BKPWN%_+AW3auXJt}lVPtu6$z?nM z0038dR9JLUVP$t9aB^>EX>4U6ba`-PAZc)PV*mhnoa6Eg2ys>@D9TUE%t_@^00Scn zE@KN5BNI!L6ay0=M1VBIWCJ6!R3OXP)X2ol#2my2%YaCrN-hBE7ZG&wLN%2D0000< KMNUMnLSTaWc$`@P diff --git a/admin/templates/default/package.json b/admin/templates/default/package.json index d4f74ad3..5e0c3683 100644 --- a/admin/templates/default/package.json +++ b/admin/templates/default/package.json @@ -9,14 +9,13 @@ "license": "GPL", "devDependencies": { "gulp": "^3.9.1", - "gulp-cached": "^1.1.1", "gulp-clean-css": "^2.0.11", "gulp-concat": "^2.6.0", "gulp-imagemin": "^3.0.1", "gulp-less": "^3.1.0", - "gulp-progeny": "^0.3.2", - "gulp-remember": "^0.3.1", "gulp-rename": "^1.2.2", - "gulp-util": "^3.0.7" + "gulp-util": "^3.0.7", + "gulp-notify": "^3.0.0", + "gulp-sourcemaps": "^2.3.1" } } From 19ef3fffa2a41437dac2c36c2bc369fe1840a9d7 Mon Sep 17 00:00:00 2001 From: Janur Jangaraev Date: Fri, 31 Mar 2017 12:09:09 +0600 Subject: [PATCH 021/111] Category helper: WIP --- admin/templates/default/tree.tpl | 51 +++++++++--------- .../ia.base.controller.module.admin.php | 54 ++++++++++++++----- includes/classes/ia.base.module.front.php | 2 +- includes/helpers/ia.category.hybrid.php | 52 +++++------------- js/intelli/intelli.tree.js | 11 ++-- 5 files changed, 82 insertions(+), 88 deletions(-) diff --git a/admin/templates/default/tree.tpl b/admin/templates/default/tree.tpl index 4e4625aa..7f5a471d 100644 --- a/admin/templates/default/tree.tpl +++ b/admin/templates/default/tree.tpl @@ -1,32 +1,33 @@ -{$search = !isset($nosearch)} -
- -
- - {if $search} - - {/if} - - - {ia_add_js} +{if isset($tree)} + {$search = !isset($nosearch)} +
+ +
+ + {if $search} + + {/if} + + + {ia_add_js} $(function() { - new IntelliTree( - { - url: '{$url}', - onchange: intelli.fillUrlBox,{if iaCore::ACTION_EDIT == $pageAction} + new IntelliTree({ + url: '{$tree.url}', + onchange: intelli.fillUrlBox, nodeSelected: $('#input-tree').val(), - nodeOpened: [{$treeParents}],{/if} + nodeOpened: [{$tree.nodes}], search: {if $search}true{else}false{/if} }); }); - {/ia_add_js} - {ia_add_media files='tree'} + {/ia_add_js} + {ia_add_media files='tree'} +
-
\ No newline at end of file +{/if} \ No newline at end of file diff --git a/includes/classes/ia.base.controller.module.admin.php b/includes/classes/ia.base.controller.module.admin.php index 12d740b4..4792ca8f 100644 --- a/includes/classes/ia.base.controller.module.admin.php +++ b/includes/classes/ia.base.controller.module.admin.php @@ -46,6 +46,8 @@ abstract class iaAbstractControllerModuleBackend extends iaAbstractControllerBac */ protected $_activityLog; + protected $_treeSettings = false; + protected $_iaField; @@ -178,9 +180,46 @@ protected function _assignValues(&$iaView, array &$entryData) $entryData['item'] = $this->getItemName(); $sections = $this->_iaField->getGroups($this->getItemName()); + $plans = $this->_getPlans(); $iaView->assign('item_sections', $sections); - $iaView->assign('plans', $this->_getPlans()); + $iaView->assign('plans', $plans); + + if ($this->_treeSettings) { + $iaView->assign('tree', $this->_getTreeVars($entryData)); + } + } + + protected function _getPlans() + { + $iaPlan = $this->_iaCore->factory('plan'); + + if ($plans = $iaPlan->getPlans($this->getItemName())) { + foreach ($plans as &$plan) { + list(, $plan['defaultEndDate']) = $iaPlan->calculateDates($plan['duration'], $plan['unit']); + } + } + + return $plans; + } + + protected function _getTreeVars(array $entryData) + { + $parent = empty($entryData[$this->_treeSettings['parent_id']]) + ? $this->getHelper()->getRoot() + : $this->getHelper()->getById($entryData[$this->_treeSettings['parent_id']]); + + $url = $this->getPath() . 'tree.json'; + if (iaCore::ACTION_EDIT == $this->_iaCore->iaView->get('action')) { + $url .= '?cid=' . $this->getEntryId(); + } + + return [ + 'url' => $url, + 'nodes' => $parent[$this->_treeSettings['parents']], + 'id' => $parent['id'], + 'title' => $parent['title'] + ]; } protected function _insert(array $entryData) @@ -354,19 +393,6 @@ protected function _getJsonTree(array $data) return $this->getHelper()->getJsonTree($data); } - protected function _getPlans() - { - $iaPlan = $this->_iaCore->factory('plan'); - - if ($plans = $iaPlan->getPlans($this->getItemName())) { - foreach ($plans as &$plan) { - list(, $plan['defaultEndDate']) = $iaPlan->calculateDates($plan['duration'], $plan['unit']); - } - } - - return $plans; - } - protected function _validateMultilingualFieldsKeys(array $data) { if ($multilingualFields = $this->_iaCore->factory('field')->getMultilingualFields($this->getItemName())) { diff --git a/includes/classes/ia.base.module.front.php b/includes/classes/ia.base.module.front.php index 27eec932..9a3d7e83 100644 --- a/includes/classes/ia.base.module.front.php +++ b/includes/classes/ia.base.module.front.php @@ -56,7 +56,7 @@ public function url($action, array $data) return '#'; } - public function makeUrl(array $itemData) + public function getUrl(array $itemData) { return $this->getInfo($this->getModuleName()) . '#'; } diff --git a/includes/helpers/ia.category.hybrid.php b/includes/helpers/ia.category.hybrid.php index 89b6bcac..1c21ca61 100644 --- a/includes/helpers/ia.category.hybrid.php +++ b/includes/helpers/ia.category.hybrid.php @@ -183,15 +183,16 @@ protected function _rebuildEntry($id) // update parents $parents = [$category['id']]; $parents = $this->_getParents($category['id'], $parents); - $level = count($parents) - 1; + $parents = array_reverse($parents); $children = [$category['id']]; $children = $this->_getChildren($category['id'], $children); + $children = array_reverse($children); $entry = [ self::COL_PARENTS => implode(',', $parents), self::COL_CHILDREN => implode(',', $children), - self::COL_LEVEL => $level + self::COL_LEVEL => count($parents) - 1 ]; $this->iaDb->update($entry, iaDb::convertIds($category['id'])); @@ -285,44 +286,6 @@ public function rebuildAliases($id) $this->iaDb->resetTable(); } - /** - * Updates number of active articles for each category - */ - public function calculateArticles($start = 0, $limit = 10) - { - $this->iaDb->setTable(self::getTable()); - - $categories = $this->iaDb->all(['id', 'parent_id', 'child'], '1 ORDER BY `level` DESC', $start, $limit); - - foreach ($categories as $cat) { - if (0 != $cat['parent_id']) { - $_id = $cat['id']; - - $sql = 'SELECT COUNT(a.`id`) `num`'; - $sql .= "FROM `{$this->iaDb->prefix}articles` a "; - $sql .= "LEFT JOIN `{$this->iaDb->prefix}members` acc ON (a.`member_id` = acc.`id`) "; - $sql .= "WHERE a.`status`= 'active' AND (acc.`status` = 'active' OR acc.`status` IS NULL) "; - $sql .= "AND a.`category_id` = {$_id}"; - - $num_articles = $this->iaDb->getOne($sql); - $_num_articles = $num_articles ? $num_articles : 0; - $num_all_articles = 0; - - if (!empty($cat['child']) && $cat['child'] != $cat['id']) { - $num_all_articles = $this->iaDb->one('SUM(`num_articles`)', "`id` IN ({$cat['child']})", self::getTable()); - } - - $num_all_articles += $_num_articles; - - $this->iaDb->update(['num_articles' => $_num_articles, 'num_all_articles' => $num_all_articles], iaDb::convertIds($_id)); - } - } - - $this->iaDb->resetTable(); - - return true; - } - protected function _getParents($cId, $parents = [], $update = true) { $parentId = $this->iaDb->one(self::COL_PARENT_ID, iaDb::convertIds($cId)); @@ -389,4 +352,13 @@ public function getCount() { return $this->iaDb->one(iaDb::STMT_COUNT_ROWS, null, self::getTable()); } + + public function assignTreeVars() + { + $result = [ + + ]; + + return $result; + } } \ No newline at end of file diff --git a/js/intelli/intelli.tree.js b/js/intelli/intelli.tree.js index efbf2312..e6bca088 100644 --- a/js/intelli/intelli.tree.js +++ b/js/intelli/intelli.tree.js @@ -4,11 +4,6 @@ function IntelliTree(params) { this.url = params.url || window.location.href + 'read.json'; this.selector = params.selector || '#js-tree'; - if (typeof params.value == 'undefined' && !$('#input-tree').length) // compatibility layer - { - params.value = '#input-category'; - } - this.$tree = null; this.$label = params.label ? $(params.label) : $('#js-category-label'); this.$value = params.value ? $(params.value) : $('#input-tree'); @@ -40,7 +35,7 @@ function IntelliTree(params) { self.$tree.on('load_node.jstree after_open.jstree', _cascadeOpen); self.$tree.on('changed.jstree', this.onchange); - if (typeof params.onchange == 'function') { + if ('function' === typeof params.onchange) { self.$tree.on('click.jstree', params.onchange); } @@ -54,7 +49,7 @@ function IntelliTree(params) { if (params.search) { var timeout = false; - self.$search.keyup(function (e) { + self.$search.keyup(function () { if (timeout) clearTimeout(timeout); timeout = setTimeout(function () { self.$tree.jstree(true).search(self.$search.val()); @@ -83,7 +78,7 @@ function IntelliTree(params) { tree.select_node(nodes[i]); return; } - else if ($.inArray(parseInt(nodes[i]), params.nodeOpened) != -1) { + else if ($.inArray(parseInt(nodes[i]), params.nodeOpened) !== -1) { if (tree.get_node(nodes[i])) { tree.open_node(nodes[i]); continue; From f7ba702c714736d8924f8d60237d0fde8454ac3e Mon Sep 17 00:00:00 2001 From: Janur Jangaraev Date: Fri, 31 Mar 2017 15:24:36 +0600 Subject: [PATCH 022/111] Category helper: WIP --- admin/templates/default/tree.tpl | 1 + includes/helpers/ia.category.hybrid.php | 6 ++---- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/admin/templates/default/tree.tpl b/admin/templates/default/tree.tpl index 7f5a471d..0a0c3703 100644 --- a/admin/templates/default/tree.tpl +++ b/admin/templates/default/tree.tpl @@ -1,4 +1,5 @@ {if isset($tree)} +
{$tree|var_dump}
{$search = !isset($nosearch)}