Точки расширения
Ядро не знает о сторонних дополнениях — всё подключается снаружи. Приватных лазеек нет: любое дополнение пользуется ровно теми же четырьмя механизмами, что доступны вам.
1. События MODX
Регистрируются пакетом как modEvent, поэтому видны в списке событий обычного плагина.
| Событие | Когда | Параметры |
|---|---|---|
prism.OnRegisterProviders | Первое обращение к реестру видео-провайдеров | registry, prism |
prism.OnRegisterStorage | Первое обращение к хранилищу | prism |
prism.OnItemQuery | Перед выборкой элементов | query (xPDOQuery), params (сниппет) или properties (менеджер), context (snippet или manager), resourceIds, scope, prism |
prism.OnPanelConfig | Перед выводом конфига панели | config (по ссылке), resource, page, prism |
prism.OnPanelAssets | После того как бандл панели добавлен на страницу менеджера | controller, config, prism. Здесь дополнение подключает свой бандл: раньше нельзя — он собран на React ядра и при загрузке ищет window.PrismApp |
prism.OnCheckPermission | Перед каждым процессором, после проверки права MODX | processor (класс), permission, allowed (по ссылке; слушатель может только запретить), properties, prism |
prism.OnBeforeUpload | Перед проверкой файла | file, name, resource_id, type |
prism.OnAfterUpload | После создания элемента | item, extra (параметры расширения, переданные при старте загрузки) |
prism.OnBeforeRemove / OnAfterRemove | Вокруг удаления | item |
prism.OnBeforeRender | Перед рендером чанков | items, params |
prism.OnMetadataFetched | После получения метаданных видео | item, meta |
prism.OnAfterUpdate | После правки элемента в панели (поля, флаги) | item, fields (что менялось) |
prism.OnBeforeDownload | Перед отдачей файла через download.php, после проверки ресурса | item, allowed (слушатель может только запретить), request (GET-параметры) |
prism.OnThumbnailParams | Перед рендером превью | params (параметры Glide, по ссылке; размер и формат менять нельзя), preset (определение), name (имя пресета), row, format, dpr |
Отказать в загрузке — бросьте Prism\Upload\UploadException в prism.OnBeforeUpload.
switch ($modx->event->name) {
case 'prism.OnItemQuery':
$scriptProperties['query']->where(['Item.width:>' => 1000]);
break;
}
scope — условие, которое отбирает элементы этих ресурсов (['Item.resource_id:IN' => [...]], для &ids — ['Item.id:IN' => [...]]). Оно применяется к запросу после события. PHP-слушатель (см. ниже) может заменить его в $params['scope'] и тем самым расширить выборку — например, добавить 'OR:Item.id:IN' => [...]. Через query->where() можно только сузить.
2. PHP-слушатели
Быстрее плагина и вызываются раньше него. Удобны из bootstrap.php своего компонента:
$prism = $modx->services->get('prism');
$prism->listen('prism.OnItemQuery', function (array $params) use ($modx): void {
if (($params['context'] ?? '') === 'manager') {
$params['query']->where(['Item.createdby' => $modx->user->get('id')]);
}
});
Панель менеджера: вкладки, кнопки, боковая панель
Бандл ядра отдаёт window.PrismApp с React, ReactDOM, runProcessor, t и registerExtension. Свой бандл собирайте с react/react-dom как externals на PrismApp.React/PrismApp.ReactDOM — иначе на странице будет два React.
window.PrismApp.registerExtension({
name: 'my-ext',
// вкладка рядом с типами элементов
tabs: [{ key: 'stats', label: 'Статистика', component: StatsTab }],
// пункт контекстного меню; `applies` прячет его для неподходящих элементов
itemMenu: [{ key: 'send', label: 'Отправить…', onSelect: (items, ctx) => {…} }],
// кнопка в тулбаре; `pages` — где показывать: 'tab' (вкладка ресурса), 'library' (своя страница)
toolbar: [{ key: 'import', label: 'Импорт', pages: ['tab'], onClick: (ctx) => {…} }],
// панель слева от сетки
sidebar: [{ key: 'tree', pages: ['library'], component: Tree }],
settingsTabs: [{ key: 'my', label: 'Моё', component: MySettings }],
});
Все обработчики получают контекст: config (то, что пришло в window.PrismConfig), refresh() — перезапросить список, notice(text) — показать сообщение, filter и setFilter(patch | null). Фильтр — это плоский объект дополнительных параметров: он уходит в Mgr\Item\GetList как есть, поэтому слушатель prism.OnItemQuery видит его в properties, и в каждую загрузку, начатую при этом фильтре — prism.OnAfterUpload получает его как extra. Так боковая панель с деревом альбомов и фильтрует сетку, и кладёт загруженное в выбранный альбом, не трогая ядро.
Своя страница менеджера с той же панелью: соберите конфиг через Prism\Manager\PanelConfig::build($modx, $prism, null, 'library'), подключите PanelConfig::attach($controller, $prism, $config) и вызовите PrismApp.mount(el, window.PrismConfig). resourceId будет 0; какие элементы показывать, решает ваш слушатель через scope.
3. Своё хранилище
$prism->registerStorageFactory(static function (int $sourceId): ?\Prism\Storage\StorageInterface {
return $sourceId === 5 ? new MyS3Storage($sourceId) : null; // null — оставить локальное
});
Интерфейс: basePath, put, exists, delete, url, absolutePath.
4. Расширение панели
Бандл ядра экспортирует React, поэтому расширение собирается с react и react-dom как externals
на PrismApp.React и PrismApp.ReactDOM:
window.PrismApp.registerExtension({
name: 'my-extra',
tabs: [{ key: 'library', label: 'Медиатека', component: LibraryTab }],
toolbar: [{ key: 'import', label: 'Импорт', types: ['image'], onClick: ({ refresh }) => {} }],
itemMenu: [{ key: 'ai', label: 'AI: описание', onSelect: (items, { notice }) => {} }],
settingsTabs: [{ key: 'my', label: 'Моё', component: MySettings }],
});
Скрипт подключают своим плагином на OnDocFormPrerender после бандла ядра — панель
перерисуется, как только расширение зарегистрируется.
Библиотеки Prism — не ваши
В установленном пакете vendor/ Prism переименован: League\Glide\ServerFactory там называется
Prism\Vendor\League\Glide\ServerFactory, и так со всем, что лежит рядом — Intervention Image,
Flysystem, PSR. Это сделано намеренно: MODX везёт те же библиотеки в других версиях, и без
префикса за них отвечал бы тот автозагрузчик, что зарегистрирован раньше.
Следствие для расширения: не пишите use League\Glide\… в расчёте на автозагрузчик Prism.
В рабочем дереве разработчика такой код найдёт класс, на боевом сайте — нет. Нужен Glide или
Intervention — везите свою зависимость в своём vendor/. Точки расширения выше устроены так, что
этого не требуется: превью делает $prism->thumbnailer(), файлы — $prism->storage().