Перейти к основному содержимому

Точки расширения

Ядро не знает о сторонних дополнениях — всё подключается снаружи. Приватных лазеек нет: любое дополнение пользуется ровно теми же четырьмя механизмами, что доступны вам.

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Перед каждым процессором, после проверки права MODXprocessor (класс), 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().