diff --git a/cli.php b/cli.php index 503ef417..2ca1c8fe 100644 --- a/cli.php +++ b/cli.php @@ -2,12 +2,6 @@ $row_id) { - $where = $this->db->quoteInto("{$id_name} = ?", $row_id); - $this->db->update($table_name, ['seq' => $rows_seq[$k]], $where); + foreach ($_POST['data'] as $k => $row_id) { + $records[$row_id] = $rows_seq[$k]; + } + + + $is_custom = $records + ? $this->customSequence($resource, $records) + : false; + + + if ( ! $is_custom) { + foreach ($records as $row_id => $sequence) { + $where = $this->db->quoteInto("{$id_name} = ?", $row_id); + $this->db->update($table_name, ['seq' => $sequence], $where); + } } } @@ -1106,4 +1119,55 @@ public function action_workhorse() { echo Alert::danger($e->getMessage()); } } + + + /** + * Проверка модуля на реализацию собственного удаления + * @param string $resource + * @param array $records + * @return bool + * @throws Exception + */ + private function customSequence(string $resource, array $records): bool { + + $mod = explode('xxx', $resource); + $mod = explode("_", $mod[0]); + $controller_name = "Mod" . ucfirst(strtolower($mod[0])) . "Controller"; + + $this->requireController($mod[0], $controller_name); + $controller = new $controller_name(); + + return $controller instanceof Sequence + ? $controller->sequence($resource, $records) + : false; + } + + + /** + * @param string $module_name + * @param string $mod_controller + * @return void + * @throws Exception + */ + private function requireController(string $module_name, string $mod_controller): void { + + $location = $this->getModuleLocation($module_name); //определяем местоположение модуля + $controller_path = $location . "/" . $mod_controller . ".php"; + + if ( ! file_exists($controller_path)) { + throw new Exception(sprintf($this->translate->tr("Модуль не найден: %s"), $mod_controller), 400); + } + + $autoload = $location . "/vendor/autoload.php"; + + if (file_exists($autoload)) { //подключаем автозагрузку если есть + require_once $autoload; + } + + require_once $controller_path; // подлючаем контроллер + + if ( ! class_exists($mod_controller)) { + throw new RuntimeException(sprintf($this->translate->tr("Модуль сломан: %s"), $location)); + } + } } \ No newline at end of file diff --git a/inc/Interfaces/Queue.php b/inc/Interfaces/Queue.php new file mode 100644 index 00000000..9c9673fc --- /dev/null +++ b/inc/Interfaces/Queue.php @@ -0,0 +1,16 @@ +row->checked_row->assign('[ROW_NUMBER]', $num); + } else { + $tpl->row->start_row->assign('[ROW_NUMBER]', $num); } $col = 0; diff --git a/inc/WorkerManager.php b/inc/WorkerManager.php index 0794df31..4daba3d7 100644 --- a/inc/WorkerManager.php +++ b/inc/WorkerManager.php @@ -731,16 +731,24 @@ protected function load_workers() { } if (isset($this->functions['Workhorse'])) { $db = new Db(); - $mods = $db->dataModules->getModuleList(); + $select = $db->dataModules->select()->where("visible='Y'"); + $mods = $db->dataModules->fetchAll($select); + foreach ($mods as $k => $data) { - $location = $this->config['doc_root'] . "/mod/{$data['module_id']}/v{$data['version']}"; + if ($data['is_system'] === "Y") { + $location = __DIR__ . "/../mod/{$data['module_id']}/v{$data['version']}"; + } else { + $location = $this->config['doc_root'] . "/mod/{$data['module_id']}/v{$data['version']}"; + } +// $location = $this->config['doc_root'] . "/mod/{$data['module_id']}/v{$data['version']}"; $name = "Mod" . ucfirst(strtolower($data['module_id'])) . "Worker"; if (!isset($this->functions[$name])) { - $worker = $location . "/{$name}.php"; + $worker = realpath($location) . "/{$name}.php"; if (file_exists($worker)) { + $co = !empty($this->functions[$name]["count"]) ? $this->functions[$name]["count"] : 2; $this->functions[$name] = [ 'name' => 'Workhorse', - 'count' => 2, + 'count' => $co, 'path' => $worker, 'mod' => $data['module_id'], 'path_workhorse' => $this->functions['Workhorse']['path'], @@ -751,6 +759,7 @@ protected function load_workers() { } unset($this->functions['Workhorse']); } + // echo "
";print_r($this->config);echo "";//die; // echo "
";print_r($this->functions);echo "";die; } diff --git a/inc/classes/Emitter.php b/inc/classes/Emitter.php index 4d7f43ee..4817d2ac 100644 --- a/inc/classes/Emitter.php +++ b/inc/classes/Emitter.php @@ -64,15 +64,12 @@ public function sync($module, $event_name, $data): array { public function async($module, $event_name, $data): void { - foreach ($this->subscribers as $mod => $controller) { - $w = $this->workerAdmin->doBackground('Eventer', [ - 'mod' => $mod, - 'location' => $this->getModuleLocation($mod), - 'context' => $module, - 'event' => $event_name, - 'data' => $data - ]); - } + $w = $this->workerAdmin->doBackground('Eventer', [ + 'context' => $module, + 'event' => $event_name, + 'data' => $data + ]); + // $this->log->info(is_array($data) ? json_encode($data) : $data, ['module' => $module, 'event' => $event_name]); } diff --git a/inc/classes/Error.php b/inc/classes/Error.php index 42016ff1..769e5be8 100644 --- a/inc/classes/Error.php +++ b/inc/classes/Error.php @@ -106,7 +106,9 @@ public static function catchException(\Exception $exception): void { self::Exception('Нет такой страницы', 404); } elseif ($message == 'expired') { - setcookie($cnf->session->name, false); + if ($cnf && $cnf?->session?->name) { + setcookie($cnf->session->name, false); + } header("{$_SERVER['SERVER_PROTOCOL']} 403 Forbidden"); die(); } diff --git a/inc/classes/File.php b/inc/classes/File.php index 335725a1..a25a52bf 100644 --- a/inc/classes/File.php +++ b/inc/classes/File.php @@ -159,13 +159,12 @@ public function getContent($table, $id) * @throws \Exception */ public function handleThumb($table, $id) { - $this->getFileData($table, $id); + $this->getThumb($table, $id); $res2 = $this->data; header("Content-type: {$res2['type']}"); header("Content-Disposition: filename=\"{$res2['filename']}\""); - if ( ! empty($res2['hash'])) { $etagHeader = (isset($_SERVER['HTTP_IF_NONE_MATCH']) ? trim($_SERVER['HTTP_IF_NONE_MATCH']) : false); @@ -175,10 +174,19 @@ public function handleThumb($table, $id) { //check if page has changed. If not, send 304 and exit if ($etagHeader == $res2['hash']) { header("HTTP/1.1 304 Not Modified"); - return ''; } } + } + + /** + * @param string $table + * @param int $id + * @return string + */ + public function getThumb(string $table, int $id): string + { + $this->getFileData($table, $id); $quote_table_files = $this->db->quoteIdentifier($table . '_files'); $thumb = $this->db->fetchOne("SELECT `thumb` FROM {$quote_table_files} WHERE id = ?", $id); //Если задан размер тамбнейла или если тамбнейла нет в базе @@ -186,12 +194,13 @@ public function handleThumb($table, $id) { $content = $this->getContent($table, $id); ob_start(); $image = new Image(); - $image->outStringResized($content, $res2['type'], $this->imgWidth, $this->imgHeight); + $image->outStringResized($content, $this->data['type'], $this->imgWidth, $this->imgHeight); $this->content = ob_get_clean(); } else { $this->content = $thumb; } + return $this->content; } diff --git a/inc/classes/Init.php b/inc/classes/Init.php index 13a3f638..bbc483c9 100644 --- a/inc/classes/Init.php +++ b/inc/classes/Init.php @@ -93,33 +93,34 @@ $section = !empty($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : 'production'; - $conf = new Core2\Config($config_origin); - $config = $conf->getData()->merge($conf->readIni($conf_file, $section)); + $conf = new Core2\Config($config_origin); + $system_config = $conf->getData()->merge($conf->readIni($conf_file, $section)); + $conf_d = __DIR__ . "/../../conf.ext.ini"; if (file_exists($conf_d)) { - $config->merge($conf->readIni($conf_d, $section)); + $system_config->merge($conf->readIni($conf_d, $section)); } if (empty($_SERVER['HTTPS'])) { - if (isset($config->system) && ! empty($config->system->https)) { + if (isset($system_config->system) && ! empty($system_config->system->https)) { header('Location: https://' . $_SERVER['SERVER_NAME']); exit(); // TODO нужно убрать } } - $tz = $config->system->timezone; + $tz = $system_config->system->timezone; if (!empty($tz)) { date_default_timezone_set($tz); } - if (!$config) throw new Exception("Unable to load configuration."); + if (!$system_config) throw new Exception("Unable to load configuration."); } catch (Exception $e) { Error::Exception($e->getMessage()); } // отладка приложения -if ($config->debug->on) { +if ($system_config->debug->on) { error_reporting(E_ALL); ini_set('display_errors', 1); } else { @@ -127,43 +128,43 @@ } //проверяем настройки для базы данных -if ($config->database) { - if (empty($config->database->adapter)) { +if ($system_config->database) { + if (empty($system_config->database->adapter)) { Error::Exception('Database adapter is empty!'); } - if (empty($config->database->params->dbname)) { + if (empty($system_config->database->params->dbname)) { Error::Exception('Database name is empty!'); } } //конфиг стал только для чтения -$config->setReadOnly(); +$system_config->setReadOnly(); -if (isset($config->include_path) && $config->include_path) { - set_include_path(get_include_path() . PATH_SEPARATOR . $config->include_path); +if (isset($system_config->include_path) && $system_config->include_path) { + set_include_path(get_include_path() . PATH_SEPARATOR . $system_config->include_path); } //подключаем мультиязычность require_once 'I18n.php'; -$translate = new I18n($config); +$translate = new I18n($system_config); //сохраняем конфиг -Registry::set('config', $config); +Registry::set('config', $system_config); //обрабатываем конфиг ядра $core_conf_file = __DIR__ . "/../../conf.ini"; if (file_exists($core_conf_file)) { - $config = new Core2\Config(); - Registry::set('core_config', $config->readIni($core_conf_file, 'production')); + $core_config = new Core2\Config(); + Registry::set('core_config', $core_config->readIni($core_conf_file, 'production')); } require_once 'Acl.php'; require_once 'Common.php'; require_once 'SSE.php'; + /** - * Class Init * @property Core2\Model\Modules $dataModules */ class Init extends Acl { @@ -173,9 +174,6 @@ class Init extends Acl { */ private $auth; - protected $is_rest = array(); - protected $is_soap = array(); - /** * Общая проверка аутентификации @@ -320,24 +318,28 @@ public function dispatch() { if (isset($route['module'])) { if (isset($route['api']) && $route['api'] === 'openapi') { + if ($route['action'] == 'core2.json') { - define("SERVER", (!empty($_SERVER['HTTPS']) ? 'https://' : 'http://') . $_SERVER['SERVER_NAME'] . DOC_PATH); + define("SERVER", ( ! empty($_SERVER['HTTPS']) ? 'https://' : 'http://') . $_SERVER['SERVER_NAME'] . DOC_PATH); //генерация свагера для общего API require_once "OpenApi.php"; header("Cache-Control: no-cache"); $schema = new \Core2\OpenApi(); - $html = $schema->render(); + $html = $schema->render(); return $html; } + if ($route['action'] == 'sections') { require_once "OpenApiSpec.php"; header('Content-Type: application/json'); $schema = new \Core2\OpenApiSpec(); $this->setupAcl(); + if ( ! empty($route['params'])) { $section = key($route['params']); return json_encode($schema->getSectionSchema($section)); } + return json_encode([ 'sections' => $schema->getSections() ]); } } @@ -352,6 +354,11 @@ public function dispatch() { $sse = new Core2\SSE(); $sse->run(); return ''; + } elseif ($route['module'] === 'change_pass') { + require_once 'Login.php'; + $login = new Login(); + $this->setupSkin(); + return $login->dispatch($route); } } @@ -363,6 +370,9 @@ public function dispatch() { $this->logActivity($logExclude); //TODO CHECK DIRECT REQUESTS except iframes + //Проверка устаревания пароля + $this->checkExpired(); + require_once 'Zend_Session_Namespace.php'; //DEPRECATED require_once 'core2/inc/Interfaces/Delete.php'; require_once 'core2/inc/Interfaces/File.php'; @@ -541,6 +551,39 @@ public function dispatch() { } + /** + * Проверка на устаревание пароля пользователя + * @return void + * @throws Exception + */ + private function checkExpired() + { + + if ($this->config->registry->pass_expired == 'Y') { + if ( ! empty($this->auth->check_expired)) { + return; + } + + if ( ! empty($this->config->registry->pass_expired_exception_user_id)) { + $exception_ids = explode(',', $this->config->registry->pass_expired_exception_user_id); + if (in_array($this->auth->ID, $exception_ids)) { + $this->auth->check_expired = 1; + return; + } + } + + $data_user = $this->dataUsers->getUserById($this->auth->ID); + + if ( + (new DateTime($data_user['date_expired'])) < (new DateTime())) { + header('Location: /change_pass'); + exit; + } + $this->auth->check_expired = 1; + } + } + + /** * проверка модуля на доступность @@ -744,7 +787,7 @@ private function checkToken() { //http basic auth allowed [$login, $password] = explode(':', base64_decode(substr($_SERVER['HTTP_AUTHORIZATION'], 6))); $user = $this->dataUsers->getUserByLogin($login); - if ($user && \Core2\Tool::password_verify_secure($password, (string)$user['u_pass'])) { + if ($user && $user['u_pass'] === Tool::pass_salt(md5($password))) { $auth = new \StdClass(); $auth->LIVEID = 0; diff --git a/inc/classes/LdapAuth.php b/inc/classes/LdapAuth.php index 5c1011e6..7b3d7449 100644 --- a/inc/classes/LdapAuth.php +++ b/inc/classes/LdapAuth.php @@ -199,7 +199,7 @@ public function getLdapInfo(string $login): void { ]); if ( ! $isset_email) { - $where = $this->db->quoteInto('id = ?', $user_id); + $where = $this->db->quoteInto('u_id = ?', $user_id); $this->db->update('core_users', [ 'email' => $data['mail'][0], ], $where); diff --git a/inc/classes/Login.php b/inc/classes/Login.php index d4c80c14..7958636f 100644 --- a/inc/classes/Login.php +++ b/inc/classes/Login.php @@ -4,6 +4,7 @@ require_once 'Templater3.php'; require_once 'Tool.php'; +use _PHPStan_5a70c2d68\Nette\Utils\DateTime; use Laminas\Session\Container as SessionContainer; use Exception; use Templater3; @@ -33,72 +34,101 @@ public function dispatch(array $route) { if (isset($query['core'])) { $uri = $query['core']; //FIXME DEPRECATED } - if ($uri == 'registration') { - $auth = $this->isModuleInstalled('auth'); - if (!$auth) { - throw new Exception($this->_('Модуль регистрации не найден'), 404); - } - if (isset($auth['submodules']['registration']) && $auth['submodules']['registration']['visible'] !== 'Y') { - //субмдуль регистрациивыключен - throw new Exception($this->_('Регистрация недоступна'), 403); - } - $form_html = $this->modAuth->getPageRegistration(); - $tpl = new Templater3(); - $tpl->setTemplate($form_html); - $html = str_replace('', $tpl->render(), $this->getIndex()); - return $html; - } - elseif ($uri == 'registration_complete') { - $auth = $this->isModuleInstalled('auth'); - if (!$auth) { - throw new Exception($this->_('Модуль регистрации не найден'), 404); - } - if (isset($auth['submodules']['restore']) && $auth['submodules']['restore']['visible'] !== 'Y') { - //субмдуль регистрациивыключен - throw new Exception($this->_('Регистрация недоступна'), 403); - } - if (!isset($query['key'])) { - //субмдуль регистрациивыключен - throw new Exception($this->_('Ключ не передан'), 400); - } - $form_html = $this->modAuth->getPageRegistrationComplete($query['key']); - $tpl = new Templater3(); - $tpl->setTemplate($form_html); - $html = str_replace('', $tpl->render(), $this->getIndex()); - return $html; - } - elseif ($uri == 'restore') { - $auth = $this->isModuleInstalled('auth'); - if (!$auth) { - throw new Exception($this->_('Модуль регистрации не найден'), 404); - } - if (isset($auth['submodules']['restore']) && $auth['submodules']['restore']['visible'] !== 'Y') { - //субмдуль регистрациивыключен - throw new Exception($this->_('Восстановление пароля недоступно'), 403); - } - $form_html = $this->modAuth->getPageRestore(); - $tpl = new Templater3(); - $tpl->setTemplate($form_html); - $html = str_replace('', $tpl->render(), $this->getIndex()); - return $html; - } - elseif ($uri == 'restore_complete') { - $auth = $this->isModuleInstalled('auth'); - if (!$auth) { - throw new Exception($this->_('Модуль регистрации не найден'), 404); - } - if (isset($auth['submodules']['restore']) && $auth['submodules']['restore']['visible'] !== 'Y') { - //субмдуль регистрациивыключен - throw new Exception($this->_('Восстановление пароля недоступно'), 403); - } - $form_html = $this->modAuth->getPageRestoreComplete($query['key']); - $tpl = new Templater3(); - $tpl->setTemplate($form_html); - $html = str_replace('', $tpl->render(), $this->getIndex()); - return $html; - } - else { - return $this->getPageLogin(); + + switch ($uri) { + case 'registration': + $auth = $this->isModuleInstalled('auth'); + if ( ! $auth) { + throw new Exception($this->_('Модуль регистрации не найден'), 404); + } + if (isset($auth['submodules']['registration']) && $auth['submodules']['registration']['visible'] !== 'Y') { + //субмдуль регистрациивыключен + throw new Exception($this->_('Регистрация недоступна'), 403); + } + $form_html = $this->modAuth->getPageRegistration(); + $tpl = new Templater3(); + $tpl->setTemplate($form_html); + $html = str_replace('', $tpl->render(), $this->getIndex()); + return $html; + + case 'registration_complete': + $auth = $this->isModuleInstalled('auth'); + if ( ! $auth) { + throw new Exception($this->_('Модуль регистрации не найден'), 404); + } + if (isset($auth['submodules']['restore']) && $auth['submodules']['restore']['visible'] !== 'Y') { + //субмдуль регистрациивыключен + throw new Exception($this->_('Регистрация недоступна'), 403); + } + if ( ! isset($query['key'])) { + //субмдуль регистрациивыключен + throw new Exception($this->_('Ключ не передан'), 400); + } + $form_html = $this->modAuth->getPageRegistrationComplete($query['key']); + $tpl = new Templater3(); + $tpl->setTemplate($form_html); + $html = str_replace('', $tpl->render(), $this->getIndex()); + return $html; + + case 'restore': + $auth = $this->isModuleInstalled('auth'); + if ( ! $auth) { + throw new Exception($this->_('Модуль регистрации не найден'), 404); + } + if (isset($auth['submodules']['restore']) && $auth['submodules']['restore']['visible'] !== 'Y') { + //субмдуль регистрациивыключен + throw new Exception($this->_('Восстановление пароля недоступно'), 403); + } + $form_html = $this->modAuth->getPageRestore(); + $tpl = new Templater3(); + $tpl->setTemplate($form_html); + $html = str_replace('', $tpl->render(), $this->getIndex()); + return $html; + + case 'restore_complete': + $auth = $this->isModuleInstalled('auth'); + if ( ! $auth) { + throw new Exception($this->_('Модуль регистрации не найден'), 404); + } + if (isset($auth['submodules']['restore']) && $auth['submodules']['restore']['visible'] !== 'Y') { + //субмодуль регистрации выключен + throw new Exception($this->_('Восстановление пароля недоступно'), 403); + } + $form_html = $this->modAuth->getPageRestoreComplete($query['key']); + $tpl = new Templater3(); + $tpl->setTemplate($form_html); + $html = str_replace('', $tpl->render(), $this->getIndex()); + return $html; + + case 'change_pass': + + $auth = $this->isModuleInstalled('auth'); + if ( ! $auth) { + throw new Exception($this->_('Модуль регистрации не найден'), 404); + } + + if (isset($auth['submodules']['restore']) && $auth['submodules']['restore']['visible'] !== 'Y') { + //субмдуль регистрациивыключен + throw new Exception($this->_('Восстановление пароля недоступно'), 403); + } + $error_message = ''; + if ( ! empty($_POST['password'])) { + try { + $this->setNewPass($_POST['password']); + return json_encode(['status' => 'success', 'message' => $this->_('Пароль успешно обновлён')]); + } catch (Exception $e) { + return json_encode(['status' => 'error', 'error_message' => $e->getMessage()]); + } + } + + $form_html = $this->modAuth->getPageChangePass($error_message); + $tpl = new Templater3(); + $tpl->setTemplate($form_html); + $html = str_replace('', $tpl->render(), $this->getIndex()); + return $html; + + default: + return $this->getPageLogin(); } } @@ -151,8 +181,7 @@ public function enter(string $login, string $password, string $return_url = null ]; } } - - $this->authLoginPassword($login, $password); + $this->checkLogin($login, $password); return [ 'status' => 'success', @@ -361,7 +390,7 @@ private function auth(array $user): bool { * @return array * @throws Exception */ - private function checkLogin(string $login, string $password): array { + private function checkLogin(string $login, string $password): void { $blockNamespace = new SessionContainer('Block'); @@ -405,12 +434,12 @@ private function checkLogin(string $login, string $password): array { throw new \Exception($this->translate->tr("Неверный пароль")); } - if (Tool::password_needs_upgrade((string)$user['u_pass']) && !empty($user['u_id'])) { - $where = $this->db->quoteInto('u_id = ?', (int)$user['u_id']); - $this->db->update('core_users', [ - 'u_pass' => Tool::password_hash_secure($password), - ], $where); - } +// if (Tool::password_needs_upgrade((string)$user['u_pass']) && !empty($user['u_id'])) { +// $where = $this->db->quoteInto('u_id = ?', (int)$user['u_id']); +// $this->db->update('core_users', [ +// 'u_pass' => Tool::password_hash_secure($password), +// ], $where); +// } $this->auth($user); @@ -435,21 +464,6 @@ private function checkLogin(string $login, string $password): array { } - /** - * Авторизация пользователя через форму - * @param string $login - * @param string $password - * @return void - * @throws \Zend_Db_Exception - * @throws Exception - */ - private function authLoginPassword(string $login, string $password): void { - - $user = $this->checkLogin($login, $password); - $this->auth($user); - } - - /** * Установка контекста выполнения скрипта * @param string $module @@ -657,4 +671,35 @@ private function getSystemFavicon(): array { ]; } + /** + * @param $pass + * @return void + * @throws Exception + */ + private function setNewPass($pass) + { + if (empty($this->auth)) { + throw new Exception($this->_('Пользователь не залогинен')); + } + + $rowUser = $this->modAdmin->dataUsers->fetchRow( + $this->modAdmin->dataUsers->select() + ->where('u_id = ?', $this->auth->ID) + ); + $pass_salt = Tool::pass_salt(trim($pass)); + if ($pass_salt == $rowUser->u_pass) { + throw new Exception($this->_('Пароль не должен отличаться от предыдущего')); + } + $rowUser->u_pass = $pass_salt; + + if ($this->config->registry->pass_expired == 'Y' && //проставлен признак + $this->config->registry->pass_expired_days && // задано кол-во дней на устаревание пароля + intval($this->config->registry->pass_expired_days) > 0) { + $rowUser->date_expired = (new DateTime())->modify("+{$this->config->registry->pass_expired_days} days")->format('Y-m-d H:i:s'); + $rowUser->reg_key = null; + } + $rowUser->save(); + } + + } diff --git a/inc/classes/Menu.php b/inc/classes/Menu.php index e6fd160c..bcf050a9 100644 --- a/inc/classes/Menu.php +++ b/inc/classes/Menu.php @@ -3,6 +3,7 @@ require_once 'Acl.php'; require_once 'Navigation.php'; +require_once 'Templater3.php'; use Exception; use Templater3; @@ -48,8 +49,8 @@ public function getMenu(): string { $tpl_file_menu = Theme::get("menu"); } - $tpl = new Templater3($tpl_file); - $tpl_menu = new Templater3($tpl_file_menu); + $tpl = new \Templater3($tpl_file); + $tpl_menu = new \Templater3($tpl_file_menu); $tpl->assign('{system_name}', $this->getSystemName()); @@ -213,7 +214,7 @@ public function getMenu(): string { case 'profile': if ($tpl_menu->issetBlock('navigate_item_profile')) { $tpl_menu->navigate_item_profile->assign('[MODULE_NAME]', $item['module_name']); - $tpl_menu->navigate_item_profile->assign('[HTML]', $nav->renderNavigateItem($item)); + $tpl_menu->navigate_item_profile->assign('[HTML]', $nav->renderNavigateItem($item, 'profile')); $tpl_menu->navigate_item_profile->reassign(); } break; diff --git a/inc/classes/Navigation.php b/inc/classes/Navigation.php index 1d6a7e33..1b22fe65 100644 --- a/inc/classes/Navigation.php +++ b/inc/classes/Navigation.php @@ -241,11 +241,12 @@ public function setModuleNavigation($name): void { /** - * @param array $navigate_item + * @param array $navigate_item + * @param string|null $position * @return string * @throws \Exception */ - public function renderNavigateItem(array $navigate_item): string { + public function renderNavigateItem(array $navigate_item, string $position = null): string { if (empty($navigate_item['type'])) { return ''; @@ -265,7 +266,11 @@ public function renderNavigateItem(array $navigate_item): string { ? $navigate_item['onclick'] : "if (event.button === 0 && ! event.ctrlKey) load('{$link}');"; - $tpl = new Templater3(Theme::get("html-navigation-link")); + $tpl_file = $position == 'profile' + ? Theme::get("html-navigation-link-profile") + : Theme::get("html-navigation-link"); + + $tpl = new Templater3($tpl_file); $tpl->assign('[TITLE]', ! empty($navigate_item['title']) ? $navigate_item['title'] : ''); $tpl->assign('[ICON]', ! empty($navigate_item['icon']) ? $navigate_item['icon'] : ''); $tpl->assign('[CLASS]', ! empty($navigate_item['class']) ? $navigate_item['class'] : ''); diff --git a/inc/classes/OpenApiSpec.php b/inc/classes/OpenApiSpec.php index 0e54bb7d..44d6efb9 100644 --- a/inc/classes/OpenApiSpec.php +++ b/inc/classes/OpenApiSpec.php @@ -4,6 +4,7 @@ require_once 'Acl.php'; require_once "OpenApi.php"; + use OpenApi\Generator; use OpenApi\SourceFinder; @@ -261,15 +262,6 @@ private function getSchemeModule(string $module_name): array { } - /** - * @param array $schemes - * @return array - */ - private function mergeSchemes(array $schemes): array { - return []; - } - - /** * @param string $filePath * @return bool diff --git a/inc/classes/RedisStreamQueue.php b/inc/classes/RedisStreamQueue.php new file mode 100644 index 00000000..8750be3f --- /dev/null +++ b/inc/classes/RedisStreamQueue.php @@ -0,0 +1,326 @@ +client = $client; + $this->prefix = $prefix; + $this->consumerGroup = $consumerGroup; + $this->consumerName = $consumerName; + } + + /** + * Добавить сообщение в поток + */ + public function push(array $payload, string $queue = 'default'): bool + { + try { + $streamName = $this->getStreamName($queue); + + // 🔧 Все значения должны быть строками! + $fields = [ + 'payload' => json_encode($payload, JSON_THROW_ON_ERROR), + 'created_at' => (string) time(), + 'attempts' => '0' + ]; + + $args = $this->buildXaddCommand($streamName, $fields); + $messageId = $this->client->executeRaw($args); + + return $messageId !== false && $messageId !== null; + } catch (Exception $e) { + error_log("RedisStreamQueue push error: " . $e->getMessage()); + return false; + } + } + + /** + * 🔧 Построить команду XADD вручную + */ + private function buildXaddCommand(string $streamName, array $fields): array + { + $args = ['XADD', $streamName, '*']; + + foreach ($fields as $field => $value) { + // 🔧 Гарантируем что всё - строки + $args[] = (string) $field; + $args[] = (string) $value; + } + + return $args; + } + + /** + * Получить сообщение из потока (с Consumer Group) + */ + public function pop(string $queue = 'default', int $timeout = 1000): ?array + { + try { + $streamName = $this->getStreamName($queue); + + // Создаём Consumer Group если не существует + $this->createConsumerGroup($streamName); + + // 🔧 xreadgroup - правильный синтаксис + $messages = $this->client->xreadgroup( + 'GROUP', $this->consumerGroup, $this->consumerName, + 'COUNT', 1, + 'BLOCK', $timeout, + 'STREAMS', $streamName, '>' + ); + + if (empty($messages) || !isset($messages[$streamName])) { + return null; + } + + $messageData = reset($messages[$streamName]); + $messageId = key($messages[$streamName]); + + // 🔧 Декодируем payload + $payload = is_string($messageData['payload']) + ? json_decode($messageData['payload'], true) + : $messageData['payload']; + + if ($payload === null) { + $payload = $messageData; + } + + $payload['message_id'] = $messageId; + $payload['attempts'] = (int) ($messageData['attempts'] ?? 0) + 1; + + return $payload; + } catch (Exception $e) { + error_log("RedisStreamQueue pop error: " . $e->getMessage()); + return null; + } + } + + /** + * Подтвердить обработку (XACK) + */ + public function acknowledge(string $queue, string $messageId): bool + { + try { + $streamName = $this->getStreamName($queue); + // 🔧 xack ожидает: stream, group, id1, id2, ... + $this->client->xack($streamName, $this->consumerGroup, $messageId); + return true; + } catch (Exception $e) { + return false; + } + } + + /** + * Отклонить сообщение + */ + public function reject(string $queue, string $messageId, bool $requeue = false): bool + { + try { + $streamName = $this->getStreamName($queue); + + if ($requeue) { + // Возвращаем в pending для повторной обработки + $this->client->xclaim( + $streamName, + $this->consumerGroup, + $this->consumerName, + 0, + $messageId, + 'JUSTID' + ); + } else { + // Перемещаем в DLQ stream + $this->moveToDLQ($queue, $messageId); + $this->acknowledge($queue, $messageId); + } + + return true; + } catch (Exception $e) { + return false; + } + } + + /** + * Получить количество pending сообщений + */ + public function getPendingCount(string $queue): int + { + try { + $streamName = $this->getStreamName($queue); + $pending = $this->client->xpending($streamName, $this->consumerGroup); + return (int) ($pending[1] ?? 0); + } catch (Exception $e) { + return 0; + } + } + + /** + * Получить размер потока + */ + public function getQueueSize(string $queue): int + { + try { + $streamName = $this->getStreamName($queue); + $info = $this->client->xinfo('STREAM', $streamName); + + // Находим индекс длины потока + for ($i = 0; $i < count($info); $i += 2) { + if ($info[$i] === 'length') { + return (int) $info[$i + 1]; + } + } + return 0; + } catch (Exception $e) { + return 0; + } + } + + /** + * Обработать зависшие сообщения (recovery) + */ + public function recoverPendingMessages(string $queue, int $minIdleTime = 3600000): int + { + try { + $streamName = $this->getStreamName($queue); + + // Получаем ID зависших сообщений + $pending = $this->client->xpending($streamName, $this->consumerGroup, '-', '+', 100); + + $recovered = 0; + foreach ($pending as $message) { + $idleTime = $message[3] ?? 0; + + if ($idleTime >= $minIdleTime) { + $messageId = $message[0]; + + //_claim_ возвращает сообщение обратно в обработку + $this->client->xclaim( + $streamName, + $this->consumerGroup, + $this->consumerName, + $minIdleTime, + $messageId + ); + $recovered++; + } + } + + return $recovered; + } catch (Exception $e) { + return 0; + } + } + + /** + * Получить Dead Letter сообщения + */ + public function getDLQMessages(string $queue, int $count = 10): array + { + try { + $dlqName = $this->getDLQName($queue); + $messages = $this->client->xrevrange($dlqName, '+', '-', 'COUNT', $count); + + $result = []; + foreach ($messages as $messageId => $data) { + $result[] = [ + 'message_id' => $messageId, + 'payload' => json_decode($data['payload'], true), + 'error' => $data['error'] ?? null, + 'failed_at' => $data['failed_at'] ?? null + ]; + } + + return $result; + } catch (Exception $e) { + return []; + } + } + + /** + * Повторить сообщение из DLQ + */ + public function retryFromDLQ(string $queue, string $messageId): bool + { + try { + $dlqName = $this->getDLQName($queue); + $streamName = $this->getStreamName($queue); + + $messages = $this->client->xrange($dlqName, '-', '+'); + + foreach ($messages as $id => $data) { + if ($id === $messageId) { + // Добавляем обратно в основной поток + $args = $this->buildXaddCommand($streamName, ['payload' => $data['payload'], 'attempts' => 0]); + $this->client->executeRaw($args); + + // Удаляем из DLQ + $this->client->xdel($dlqName, $messageId); + return true; + } + } + + return false; + } catch (Exception $e) { + return false; + } + } + + private function getStreamName(string $queue): string + { + return "{$this->prefix}:{$queue}"; + } + + private function getDLQName(string $queue): string + { + return "{$this->prefix}:{$queue}:dlq"; + } + + private function createConsumerGroup(string $streamName): void + { + try { + $this->client->xgroup('CREATE', $streamName, $this->consumerGroup, '0', 'MKSTREAM'); + } catch (Exception $e) { + // Группа уже существует - игнорируем + } + } + + private function moveToDLQ(string $queue, string $messageId): void + { + try { + $streamName = $this->getStreamName($queue); + $dlqName = $this->getDLQName($queue); + + $messages = $this->client->xrange($streamName, $messageId, $messageId); + + if (!empty($messages)) { + $data = reset($messages); + $args = $this->buildXaddCommand($dlqName, [ + 'payload' => $data['payload'], + 'original_id' => $messageId, + 'failed_at' => (string) time(), + 'error' => 'Manual reject to DLQ' + ]); + $this->client->executeRaw($args); + } + } catch (Exception $e) { + // Игнорируем ошибки DLQ + } + } +} \ No newline at end of file diff --git a/inc/classes/Routing/Route.php b/inc/classes/Routing/Route.php index a57f7354..cab9adf1 100644 --- a/inc/classes/Routing/Route.php +++ b/inc/classes/Routing/Route.php @@ -200,7 +200,7 @@ public function getPathRegexp(): string { foreach ($matches[0] as $key => $match) { $count = 1; $name = $matches['name'][$key]; - $rule = $matches['rule'][$key] ?: '[\d\w_\-]+'; + $rule = $matches['rule'][$key] ?: '[\d]+'; $path = str_replace($match, "(?<{$name}>{$rule})", $path, $count); } } diff --git a/inc/classes/Table.php b/inc/classes/Table.php index ce9bc450..f5535932 100644 --- a/inc/classes/Table.php +++ b/inc/classes/Table.php @@ -63,6 +63,7 @@ abstract class Table extends Acl { protected $max_height = null; protected $is_ajax = false; protected $is_round_calc = false; + protected $is_overflow = false; protected $head_top = false; protected $deleteKey = ''; protected $table = ''; @@ -288,6 +289,14 @@ public function setAddUrl(string $add_url) { } + /** + * @param bool $is_overflow + */ + public function setOverflow(bool $is_overflow): void { + $this->is_overflow = $is_overflow; + } + + /** * @param bool $is_ajax */ @@ -896,10 +905,10 @@ public function toArray(): array { 'recordsTotalMore' => $this->records_total_more, 'recordsPerPageList' => $per_page_list, 'max_height' => $this->max_height, + 'is_overflow' => $this->is_overflow, 'records' => $records, ]; - if ($this->edit_url) { $data['recordsEditUrl'] = $this->edit_url; } @@ -1376,4 +1385,19 @@ private function getPeriodDates(string $period_type, int $period_count): array { 'end' => $date_end ]; } + + + /** + * Удаляет строку с данными, иногда это нужно + * @param int $row_key + * @return bool + */ + public function deleteRow(int $row_key): bool { + + if (isset($this->data_rows[$row_key])) { + unset($this->data_rows[$row_key]); + return true; + } + return false; + } } \ No newline at end of file diff --git a/inc/classes/Table/Db.php b/inc/classes/Table/Db.php index a9d42fe9..1306d7a1 100644 --- a/inc/classes/Table/Db.php +++ b/inc/classes/Table/Db.php @@ -668,7 +668,6 @@ private function fetchDataQuery($query, string $record_class = Row::class): arra switch ($filter_column->getType()) { case self::FILTER_DATE: case self::FILTER_DATETIME: - case self::FILTER_DATE_PERIOD: case self::FILTER_NUMBER: if (strpos($filter_field, 'ADD_SEARCH') !== false) { if ( ! empty($filter_value[0]) || ! empty($filter_value[1])) { @@ -698,6 +697,35 @@ private function fetchDataQuery($query, string $record_class = Row::class): arra } break; + case self::FILTER_DATE_PERIOD: + if (strpos($filter_field, 'ADD_SEARCH') !== false) { + if ( ! empty($filter_value[0]) || ! empty($filter_value[1])) { + $quoted_value1 = $db->quote($filter_value[0]); + $quoted_value2 = $db->quote($filter_value[1]); + + $where = str_replace("ADD_SEARCH1", $quoted_value1, $filter_field); + $where = str_replace("ADD_SEARCH2", $quoted_value2, $where); + + $select->addWhere($where); + } + + } else { + if ( ! empty($filter_value[0]) && empty($filter_value[1])) { + $quoted_value = $db->quote($filter_value[0]); + $select->addWhere("{$filter_field} >= {$quoted_value}"); + + } elseif (empty($filter_value[0]) && ! empty($filter_value[1])) { + $quoted_value = $db->quote("{$filter_value[1]} 23:59:59"); + $select->addWhere("{$filter_field} <= {$quoted_value}"); + + } elseif ( ! empty($filter_value[0]) && ! empty($filter_value[1])) { + $quoted_value1 = $db->quote($filter_value[0]); + $quoted_value2 = $db->quote("{$filter_value[1]} 23:59:59"); + $select->addWhere("{$filter_field} BETWEEN {$quoted_value1} AND {$quoted_value2}"); + } + } + break; + case self::FILTER_DATE_MONTH: if (preg_match('~^[\d]{4}\-[\d]{1,2}$~', $filter_value)) { @@ -1023,19 +1051,4 @@ public function setCachable(bool $is = true): void { $this->cachable = $is; } - - /** - * Удаляет строку с данными - * иногда это нужно - * @param int $i - * @return bool - */ - public function deleteRow(int $i): bool - { - if (isset($this->data_rows[$i])) { - unset($this->data_rows[$i]); - return true; - } - return false; - } } \ No newline at end of file diff --git a/inc/classes/Table/Render.php b/inc/classes/Table/Render.php index 99d08316..05ad96af 100644 --- a/inc/classes/Table/Render.php +++ b/inc/classes/Table/Render.php @@ -78,6 +78,7 @@ public function render(): string { $tpl->assign('[LOCATION]', ! empty($this->table['isAjax']) ? $_SERVER['QUERY_STRING'] . "&__{$this->table['resource']}=ajax" : $_SERVER['QUERY_STRING']); $tpl->assign('[CLASS_MAX_HEIGHT]', ! empty($this->table['max_height']) ? 'coreui-table-limit-height' : ''); $tpl->assign('[STYLE_MAX_HEIGHT]', ! empty($this->table['max_height']) ? "max-height: {$this->table['max_height']}px;" : ''); + $tpl->assign('[STYLE_OVERFLOW]', $this->table['is_overflow'] ? ';overflow:auto' : ''); if ( ! empty($this->table['head_top'])) { $tpl->touchBlock('script_head_top'); diff --git a/inc/classes/class.edit.php b/inc/classes/class.edit.php index b67e145c..2b64af5a 100644 --- a/inc/classes/class.edit.php +++ b/inc/classes/class.edit.php @@ -1640,12 +1640,12 @@ public function makeTable() { if (is_array($options_value)) { foreach ($options_value as $options_value_id => $options_value_title) { - if ( ! empty($options_value_title['value']) && - in_array($options_value_title['value'], $value['default']) - ) { - $options_out[] = $options_value_title['title'] ?? ''; + if ( ! empty($options_value_title['value'])) { + if (in_array($options_value_title['value'], $value['default'])) { + $options_out[] = $options_value_title['title'] ?? ''; + } - } elseif (in_array($options_value_id, $value['default'])) { + } elseif (in_array($options_value_id, $value['default']) && is_string($options_value_title)) { $options_out[] = $options_value_title; } } @@ -1922,7 +1922,7 @@ public function makeTable() { foreach ($datasets as $dataset) { foreach ($item_fields as $item_field) { - if ($item_field['type'] == 'hidden') { + if ( ! empty($item_field['type']) && $item_field['type'] == 'hidden') { continue; } @@ -2179,6 +2179,9 @@ public function makeTable() { if ( ! empty($value['in']['maxFileSize'])) { $options['maxFileSize'] = $value['in']['maxFileSize']; } + if ( ! empty($value['in']['limitConcurrentUploads'])) { + $options['limitConcurrentUploads'] = $value['in']['limitConcurrentUploads']; + } if ( ! empty($value['in']['acceptFileTypes'])) { $ft = str_replace(",", "|", $value['in']['acceptFileTypes']); $options['acceptFileTypes'] = "_FT_"; @@ -2303,11 +2306,13 @@ public function makeTable() { $('#fileupload-{$un}').bind('fileuploadadd', function (e, data) { var acceptFileTypes = /\.($ft)$/i; var fileName = data.originalFiles[0].name || data.originalFiles[0].fileName; + var needTypes = '$ft'.split('|').join(', '); + if (!acceptFileTypes.test(fileName)) { if ($(this).find('.files > tr').length <= 0) { $('#fileupload-$fieldId div.fileupload-buttonbar button.start').addClass('hide'); } - alert('Файл \"' + fileName + '\" имеет некорректное расширение.'); + alert('Файл \"' + fileName + '\" имеет некорректное расширение. Доступные форматы: ' + needTypes); return false; } }); @@ -2335,19 +2340,23 @@ public function makeTable() { } $controlGroups[$cellId]['html'][$key] .= " - $.ajax({ - url: 'index.php?module=$module&action=$action&filehandler={$this->table}&listid=$refid&f=$field', - dataType: 'json', - context: $('#fileupload-{$un}')[0] - }).always(function () { - //$(this).removeClass('fileupload-processing'); - }).done(function (result) { - if (result.files && result.files[0]) { - $('#fileupload-$fieldId div.fileupload-buttonbar button.delete').removeClass('hide'); - $('#fileupload-$fieldId div.fileupload-buttonbar input.toggle').removeClass('hide'); - } - $(this).fileupload('option', 'done').call(this, $.Event('done'), {result: result}); - }); + fetch('index.php?module=$module&action=$action&filehandler={$this->table}&listid=$refid&f=$field', { + credentials: 'same-origin', + headers: {'X-Requested-With': 'XMLHttpRequest'} + }) + .then(function (response) { + return response.json(); + }) + .then(function (result) { + if (result.files && result.files[0]) { + $('#fileupload-$fieldId div.fileupload-buttonbar button.delete').removeClass('hide'); + $('#fileupload-$fieldId div.fileupload-buttonbar input.toggle').removeClass('hide'); + } + $('#fileupload-{$un}').fileupload('option', 'done').call($('#fileupload-{$un}')[0], $.Event('done'), {result: result}); + }) + .catch(function () { + // noop: preserve previous silent behavior + }); }); "; diff --git a/inc/classes/class.list.php b/inc/classes/class.list.php index 7cada26c..0d72e041 100644 --- a/inc/classes/class.list.php +++ b/inc/classes/class.list.php @@ -7,7 +7,7 @@ use Core2\Tool; /** - * Class listTable + * @deprecated use - \Core2\Classes\Table\Db(); */ class listTable extends initList { public $addSum = array(); diff --git a/js/class.list.js b/js/class.list.js index 21ab8804..d35952d1 100644 --- a/js/class.list.js +++ b/js/class.list.js @@ -277,42 +277,51 @@ var listx = { const module = searchParams.get("module"); let action = searchParams.get("action"); if (!action) action = 'index'; - $.ajax({ - method: "DELETE", - dataType: "json", - url: module + "/" + action + "/" + id + "?" + res[1] + "." + res[2] + "=" + val - }).success(function (data) { - if (data && data.error) { - var msg = data.error ? data.error : "Не удалось выполнить удаление"; - $("#main_" + id + "_error").html(msg); - $("#main_" + id + "_error").show(); - } else { - var loc = listx.loc[id]; - if (data) { - if (data.notice) { - CoreUI.notice.create(data.notice); - } - if (data.alert) { - alert(data.alert); - } - if (data.loc) loc = data.loc; + fetch(module + "/" + action + "/" + id + "?" + res[1] + "." + res[2] + "=" + val, { + method: 'DELETE', + credentials: 'same-origin', + headers: {'X-Requested-With': 'XMLHttpRequest'} + }) + .then(function (response) { + if (!response.ok) { + throw new Error('Не удалось выполнить удаление'); } - load(loc, '', container, function () { - if (listx.reloadEvents.length > 0) { - $.each(listx.reloadEvents, function () { - if (this.list_id === id) { - this.func(); - } - }) + return response.json(); + }) + .then(function (data) { + if (data && data.error) { + var msg = data.error ? data.error : "Не удалось выполнить удаление"; + $("#main_" + id + "_error").html(msg); + $("#main_" + id + "_error").show(); + } else { + var loc = listx.loc[id]; + if (data) { + if (data.notice) { + CoreUI.notice.create(data.notice); + } + if (data.alert) { + alert(data.alert); + } + if (data.loc) loc = data.loc; } - preloader.callback(); - }); - } - }).fail(function () { - alert("Не удалось выполнить удаление"); - }).always(function () { - preloader.hide(); - }); + load(loc, '', container, function () { + if (listx.reloadEvents.length > 0) { + $.each(listx.reloadEvents, function () { + if (this.list_id === id) { + this.func(); + } + }) + } + preloader.callback(); + }); + } + }) + .catch(function () { + alert("Не удалось выполнить удаление"); + }) + .finally(function () { + preloader.hide(); + }); } } } else { diff --git a/js/core-login.js b/js/core-login.js index 920ecea9..33156772 100644 --- a/js/core-login.js +++ b/js/core-login.js @@ -19,22 +19,29 @@ CoreLogin.login = function (form) { passValue = hex_md5(passValue); } - $.ajax({ - url: location.pathname + location.search, - method: "POST", - data: { + fetch(location.pathname + location.search, { + method: 'POST', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', + 'X-Requested-With': 'XMLHttpRequest' + }, + body: new URLSearchParams({ login: $('[name=login]', form).val(), password: passValue - } + }).toString() }) - .always (function (jqXHR) { + .then(function (response) { + return response.text(); + }) + .then(function (response) { CoreLogin.loaderHide(); - var response = typeof jqXHR === 'string' ? jqXHR : jqXHR.responseText; var errorMessage = ''; + var data = {}; try { - var data = JSON.parse(response); + data = JSON.parse(response); errorMessage = typeof data.error_message === 'string' ? data.error_message : ''; } catch (err) { @@ -50,6 +57,10 @@ CoreLogin.login = function (form) { location.reload(); } } + }) + .catch(function () { + CoreLogin.loaderHide(); + $('.form-main .text-danger').text('Ошибка. Попробуйте позже, либо обратитесь к администратору'); }); }; diff --git a/js/js.js b/js/js.js index c22d8437..f0048022 100644 --- a/js/js.js +++ b/js/js.js @@ -62,14 +62,25 @@ function logout() { if (confirm('Вы уверены, что хотите выйти?')) { preloader.show(); - $.ajax({url:'index.php?module=admin', data:{"exit":1}, method:'PUT'}) - .done(function (n) { - preloader.hide(); + fetch('index.php?module=admin', { + method: 'PUT', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', + 'X-Requested-With': 'XMLHttpRequest' + }, + body: new URLSearchParams({exit: 1}).toString() + }) + .then(function (response) { + if (!response.ok) { + throw new Error(response.statusText || 'Request failed'); + } + preloader.hide(); window.location = 'index.php'; - - }).fail(function (a,b,t){ + }) + .catch(function (err) { preloader.hide(); - alert("Произошла ошибка: " + a.statusText); + alert("Произошла ошибка: " + err.message); }); } } @@ -342,25 +353,40 @@ var load = function (url, data, id, callback) { locData['loc'] = 'index.php' + url; loc = 'index.php' + url; //DEPRECATED $("#pdfiframe").remove(); - if (xhrs[locData.id]) xhrs[locData.id].abort(); + if (xhrs[locData.id] && typeof xhrs[locData.id].abort === 'function') xhrs[locData.id].abort(); if (locData.data) { //$(locData.id).load('index.php' + url, callback); $(locData.id).load('index.php' + url, locData.data, callback); - //xhrs[locData.id] = $.ajax({url:'index.php' + url, global:false}) + } else { - xhrs[locData.id] = $.ajax({url:'index.php' + url, global:false}) - .done(function (n) { + var controller = new AbortController(); + xhrs[locData.id] = controller; + fetch('index.php' + url, { + method: 'GET', + credentials: 'same-origin', + headers: {'X-Requested-With': 'XMLHttpRequest'}, + signal: controller.signal + }) + .then(function (response) { + if (!response.ok) { + var error = new Error(response.statusText || 'Request failed'); + error.status = response.status; + throw error; + } + return response.text(); + }) + .then(function (n) { $(locData.id).html(n); toAnchor(locData.id); callback(); }) - .fail(function (a,b,t) { - if (a.statusText != 'abort') { - if (!a.status) alert("Превышено время ожидания ответа. Проверьте соединение с Интернет."); - else if (a.status == 500) alert("Ой! Что-то сломалось, подождите пока мы починим."); - else if (a.status == 404) alert("Запрашиваемый ресурс не найден."); - else if (a.status == 403) document.location.reload(); - else alert("Произошла ошибка: " + a.statusText); + .catch(function (err) { + if (err.name !== 'AbortError') { + if (!err.status) alert("Превышено время ожидания ответа. Проверьте соединение с Интернет."); + else if (err.status == 500) alert("Ой! Что-то сломалось, подождите пока мы починим."); + else if (err.status == 404) alert("Запрашиваемый ресурс не найден."); + else if (err.status == 403) document.location.reload(); + else alert("Произошла ошибка: " + err.message); } preloader.hide(); }); diff --git a/mod/admin/Model/Enum.php b/mod/admin/Model/Enum.php index 788a1660..b249bab0 100644 --- a/mod/admin/Model/Enum.php +++ b/mod/admin/Model/Enum.php @@ -14,6 +14,20 @@ class Enum extends \Zend_Db_Table_Abstract { private $_enum = []; + + /** + * Получение записи по Id + * @param int $id + * @return \Zend_Db_Table_Row_Abstract|null + */ + public function getRowById(int $id):? \Zend_Db_Table_Row_Abstract { + + return $this->fetchRow( + $this->select()->where("id = ?", $id) + ); + } + + /** * Добавление записи в справочник * @param string $global_id diff --git a/mod/admin/Model/Users.php b/mod/admin/Model/Users.php index 02539074..fec847a8 100644 --- a/mod/admin/Model/Users.php +++ b/mod/admin/Model/Users.php @@ -56,7 +56,8 @@ public function getUserById($id) { p.middlename, u.is_admin_sw, r.name AS role, - u.role_id + u.role_id, + u.date_expired FROM `core_users` AS u LEFT JOIN core_users_profile AS p ON u.u_id = p.user_id LEFT JOIN core_roles AS r ON r.id = u.role_id @@ -87,7 +88,8 @@ public function getUserByLogin($login) { p.middlename, u.is_admin_sw, r.name AS role, - u.role_id + u.role_id, + u.date_expired FROM `core_users` AS u LEFT JOIN core_users_profile AS p ON u.u_id = p.user_id LEFT JOIN core_roles AS r ON r.id = u.role_id @@ -115,7 +117,8 @@ public function getAllUsers() { p.middlename, u.is_admin_sw, r.name AS role, - u.role_id + u.role_id, + u.date_expired FROM `core_users` AS u LEFT JOIN core_users_profile AS p ON u.u_id = p.user_id LEFT JOIN core_roles AS r ON r.id = u.role_id diff --git a/mod/admin/assets/js/mod.js b/mod/admin/assets/js/mod.js index a7202f3f..fce482e6 100644 --- a/mod/admin/assets/js/mod.js +++ b/mod/admin/assets/js/mod.js @@ -1,14 +1,21 @@ var modules = { 'repo': function (repo_id) { - $.ajax({url:'index.php?module=admin&action=modules&getModsListFromRepo=' + repo_id}) - .done(function(data, textStatus){ - if (textStatus == 'success') { - $("#repo_" + repo_id).html(data); - } - }) - .fail(function (a,b,t){ - $("#repo_" + repo_id).html("Фатальная ошибка!"); - }); + fetch('index.php?module=admin&action=modules&getModsListFromRepo=' + repo_id, { + credentials: 'same-origin', + headers: {'X-Requested-With': 'XMLHttpRequest'} + }) + .then(function(response) { + if (!response.ok) { + throw new Error('Request failed'); + } + return response.text(); + }) + .then(function(data){ + $("#repo_" + repo_id).html(data); + }) + .catch(function () { + $("#repo_" + repo_id).html("Фатальная ошибка!"); + }); }, @@ -18,23 +25,29 @@ var modules = { */ updateTable: function (theme) { - $.ajax({ - url:'index.php?module=admin&action=modules&data=cache_clean', - dataType: "json", - type: "POST", - }) - .done(function(data) { - if (data.hasOwnProperty('status') && data.status === 'success') { - $(".modal .modal-body").html('
Обновление списка
Обновление списка
