| @copyright 2007-2026 (c) AVE.cms | @link https://ave-cms.ru | @version 3.3 */ namespace App\Common; defined('BASEPATH') || die('Direct access to this location is not allowed.'); /** Registry of stable extension points exposed by the framework and modules. */ class HookCatalog { protected static $definitions = array(); protected static $booted = false; public static function register($name, array $definition = array(), $source = 'core') { $name = trim((string) $name); if ($name === '') { return; } self::boot(); $current = isset(self::$definitions[$name]) ? self::$definitions[$name] : array(); self::$definitions[$name] = array_merge(array( 'name' => $name, 'kind' => 'action', 'domain' => self::domain($name), 'description' => '', 'context' => LifecycleEvent::class, 'mutable' => true, 'source' => (string) $source, 'observed' => false, ), $current, $definition, array('name' => $name)); } public static function registerMany(array $definitions, $source = 'module') { foreach ($definitions as $key => $definition) { if (is_string($definition)) { self::register($definition, array(), $source); continue; } if (!is_array($definition)) { continue; } $name = isset($definition['name']) ? $definition['name'] : (is_string($key) ? $key : ''); unset($definition['name']); self::register($name, $definition, $source); } } public static function observe($name, $kind) { self::boot(); if (!isset(self::$definitions[$name])) { self::register($name, array('kind' => (string) $kind, 'description' => 'Динамическое событие'), 'runtime'); } self::$definitions[$name]['observed'] = true; self::$definitions[$name]['kind'] = (string) $kind; } public static function get($name) { self::boot(); return isset(self::$definitions[$name]) ? self::$definitions[$name] : null; } public static function all() { self::boot(); ksort(self::$definitions, SORT_NATURAL | SORT_FLAG_CASE); return array_values(self::$definitions); } protected static function boot() { if (self::$booted) { return; } self::$booted = true; self::registerDefaults(array( 'system.initialize' => 'Инициализация системного runtime завершена', 'module.registered' => 'Дескриптор модуля зарегистрирован', 'module.installed' => 'Модуль установлен', 'module.updated' => 'Модуль обновлен', 'module.enabled' => 'Модуль включен', 'module.disabled' => 'Модуль выключен', 'module.uninstalling' => 'Модуль готовится к удалению', 'module.uninstalled' => 'Модуль удален', 'frontend.request.received' => 'Начало публичного HTTP-запроса', 'frontend.runtime.ready' => 'Публичное окружение и модули загружены', 'frontend.route.resolved' => 'Публичный маршрут разрешен', 'frontend.response.rendering' => 'Ответ подготовлен перед финальной выдачей', 'frontend.response.rendered' => 'Финальный HTML публичного ответа сформирован', 'content.document.loading' => 'Документ готовится к загрузке', 'content.document.loaded' => 'Документ загружен', 'content.document.saving' => 'Документ готовится к сохранению', 'content.document.persisted' => 'Документ записан внутри транзакции', 'content.document.saved' => 'Документ сохранен', 'content.document.created' => 'Документ создан', 'content.document.updated' => 'Документ изменен', 'content.document.published' => 'Документ опубликован', 'content.document.unpublished' => 'Документ снят с публикации', 'content.document.deleting' => 'Документ готовится к удалению', 'content.document.deleted' => 'Документ удален', 'content.document.snapshot_built' => 'JSON-снимок документа перестроен', 'content.rubric.loading' => 'Рубрика готовится к загрузке', 'content.rubric.loaded' => 'Рубрика загружена', 'content.query.loading' => 'Запрос AVE готовится к загрузке', 'content.query.loaded' => 'Запрос AVE загружен', 'content.query.rendering' => 'Запрос AVE готовится к рендеру', 'content.query.rendered' => 'Запрос AVE отрендерен', 'content.template.loading' => 'Шаблон страницы готовится к загрузке', 'content.template.loaded' => 'Шаблон страницы загружен', 'content.template.rendering' => 'Шаблон страницы готовится к выполнению', 'content.template.rendered' => 'Шаблон страницы выполнен', 'content.block.loading' => 'Визуальный блок готовится к загрузке', 'content.block.loaded' => 'Визуальный блок загружен', 'content.block.rendering' => 'Визуальный блок готовится к рендеру', 'content.block.rendered' => 'Визуальный блок отрендерен', 'content.sysblock.loading' => 'Системный блок готовится к загрузке', 'content.sysblock.loaded' => 'Системный блок загружен', 'content.sysblock.rendering' => 'Системный блок готовится к рендеру', 'content.sysblock.rendered' => 'Системный блок отрендерен', 'content.field.registry' => 'Нативные и модульные типы полей зарегистрированы', 'content.field.normalizing' => 'Значение поля готовится к нормализации', 'content.field.normalized' => 'Значение поля нормализовано', 'content.field.validating' => 'Значение поля готовится к проверке', 'content.field.validated' => 'Значение поля проверено', 'content.field.saving' => 'Значение поля готовится к сохранению', 'content.field.saved' => 'Значение поля сохранено', 'content.field.rendering' => 'Поле готовится к рендеру', 'content.field.rendered' => 'Поле отрендерено', 'auth.user.registered' => 'Публичный пользователь зарегистрирован', 'auth.user.registering' => 'Публичный пользователь готовится к регистрации', 'auth.user.authenticated' => 'Публичный пользователь вошел', 'auth.user.logged_out' => 'Публичный пользователь вышел', 'auth.phone.code_sent' => 'Код входа отправлен на телефон', 'auth.account.links' => 'Модули дополняют ссылки личного кабинета', 'auth.account.overview' => 'Модули дополняют обзор личного кабинета', 'commerce.cart.changed' => 'Состав корзины изменен', 'commerce.order.creating' => 'Заказ готовится к созданию', 'commerce.order.created' => 'Заказ создан', 'commerce.order.status_changed' => 'Состояние заказа изменено', 'commerce.payment.completed' => 'Платеж завершен', 'commerce.delivery.quoted' => 'Стоимость доставки рассчитана', 'http.outbound.requesting' => 'Исходящий HTTP-запрос подготовлен', 'http.outbound.responded' => 'Получен ответ на исходящий HTTP-запрос', 'http.outbound.failed' => 'Исходящий HTTP-запрос завершился ошибкой', 'cache.invalidating' => 'Кеш готовится к сбросу', 'cache.invalidated' => 'Кеш сброшен', 'file.upload.validating' => 'Загружаемый файл проверяется перед сохранением', 'file.upload.stored' => 'Загруженный файл сохранён', )); self::$definitions['module.registered']['context'] = 'string'; self::$definitions['module.registered']['mutable'] = false; self::$definitions['file.upload.validating']['kind'] = 'filter'; self::$definitions['file.upload.validating']['context'] = 'array'; self::$definitions['file.upload.stored']['context'] = 'array'; self::$definitions['auth.account.links']['kind'] = 'filter'; self::$definitions['auth.account.links']['context'] = 'array'; self::$definitions['auth.account.overview']['kind'] = 'filter'; self::$definitions['auth.account.overview']['context'] = 'array{user_id:int,preview:bool,extensions:array}'; foreach (array('http.outbound.requesting', 'http.outbound.responded', 'http.outbound.failed') as $name) { self::$definitions[$name]['mutable'] = false; } foreach (array( 'content.document.saving', 'content.document.persisted', 'content.document.saved', 'content.document.created', 'content.document.updated', 'content.document.published', 'content.document.unpublished', ) as $name) { self::$definitions[$name]['context'] = \App\Content\Documents\DocumentSaveEvent::class; } } protected static function registerDefaults(array $definitions) { foreach ($definitions as $name => $description) { self::$definitions[$name] = array( 'name' => $name, 'kind' => 'action', 'domain' => self::domain($name), 'description' => $description, 'context' => LifecycleEvent::class, 'mutable' => true, 'source' => 'core', 'observed' => false, ); } } protected static function domain($name) { $parts = explode('.', (string) $name, 2); return isset($parts[0]) ? $parts[0] : 'application'; } }