Вот чтобы не говорили, но нейросети реально помогают в рутиных задачах и идеях, когда сам не догадываешься до изящных решений сложных костылей. В чем суть, для MODx платформы я использую редактор текста ckeditor. В нем есть вставка изображений. Собственно можно там указать только путь и размеры (ну и alt). Картинка вставится, но в идеале хотелось бы, чтобы это работало и через УЛЬТРАклассный компонент Thumb3x (который превращает миниатюры с webp и более новый, чем thumbson) и еще при этом в fancybox открывалось.
Ну и для этого я, в свое время, реализовывал через плагин сохранения документа, который парсит img и меняет на снипет Thumb3x. Однако же это неудобно, если редактор сайта, сотрудник, откроет материал. Тогда с помощью тех же нейронок обратный парсер. Итого сохранили - переписался код, открыли - переписался код.
Но на днях платный популярный для программирования ИИ подсказал, что можно сделать сниппет, который будет вызываться при вызове content и делать это уже по сути при рендере страницы. и тогда плагины не нужны, администратор сайта видит классические изображения и в целом можно придумать даже теги Nofancybox, если не нужно открывать через соотв библиотеку, просто в alt пишем {nofancybox} и изображение не будет увеличиваться (например если нужно вставить логотипы) и даже {nothumb} - если не задействуем. Попробовал - работает. Поэтому поделюсь здесь. Хотя уверен, что у каждого свое решение и можно в приницпе делать тем же плагином, но мне этот подход прям понравился.
называем снипет, например ContentImages
<?php
$html = $input ?? '';
if (trim($html) === '') {
return $html;
}
libxml_use_internal_errors(true);
$dom = new DOMDocument('1.0', 'UTF-8');
$loaded = $dom->loadHTML(
'<?xml encoding="utf-8" ?><div id="content-root">' . $html . '</div>',
LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD
);
if (!$loaded) {
libxml_clear_errors();
return $html;
}
$xpath = new DOMXPath($dom);
$images = $xpath->query('//img');
/*
* Уникальная группа Fancybox для текущего ресурса.
*/
$resourceId = $modx->resource
? (int)$modx->resource->get('id')
: 0;
$fancyboxGroup = 'gallery-' . $resourceId;
foreach ($images as $img) {
/** @var DOMElement $img */
$src = trim($img->getAttribute('src'));
if ($src === '') {
continue;
}
/*
* ---------------------------------------------------------
* Управляющие маркеры в alt:
*
* {nofancybox} — Thumb3x есть, Fancybox нет
* {nothumb} — Thumb3x нет, Fancybox есть
* {raw} — изображение вообще не обрабатываем
*
* Маркеры можно комбинировать:
* {nothumb}{nofancybox} Описание изображения
* ---------------------------------------------------------
*/
$alt = trim($img->getAttribute('alt'));
$noFancybox = false;
$noThumb = false;
$raw = false;
while (preg_match('/^\{(nofancybox|nothumb|raw)\}\s*/i', $alt, $match)) {
switch (strtolower($match[1])) {
case 'nofancybox':
$noFancybox = true;
break;
case 'nothumb':
$noThumb = true;
break;
case 'raw':
$raw = true;
break;
}
$alt = preg_replace(
'/^\{' . preg_quote($match[1], '/') . '\}\s*/i',
'',
$alt,
1
);
}
/*
* В итоговый HTML возвращаем уже чистый alt,
* без служебных маркеров.
*/
$img->setAttribute('alt', $alt);
/*
* {raw}
*
* Ничего больше с изображением не делаем.
* Сам управляющий маркер из alt уже удалён.
*/
if ($raw) {
continue;
}
/*
* Запоминаем оригинальное изображение.
* Именно оно будет открываться в Fancybox.
*/
$originalSrc = $src;
/*
* ---------------------------------------------------------
* Thumb3x
* ---------------------------------------------------------
*/
if (!$noThumb) {
/*
* Если в CKEditor задана высота —
* генерируем превью по ней.
*
* Ширину специально не передаём,
* чтобы не кропать изображение.
*/
$height = (int)$img->getAttribute('height');
if ($height > 0) {
$options = 'h=' . $height . ',f=webp';
} else {
/*
* Если размеры в CKEditor не заданы,
* ограничиваем изображение по ширине.
*/
$options = 'w=1200,f=webp';
}
$thumb = $modx->runSnippet('Thumb3x', [
'input' => $src,
'options' => $options,
]);
if (!empty($thumb)) {
$img->setAttribute('src', $thumb);
}
}
/*
* Lazy loading.
*
* Если loading уже был задан вручную,
* не перезаписываем его.
*/
if (!$img->hasAttribute('loading')) {
$img->setAttribute('loading', 'lazy');
}
/*
* ---------------------------------------------------------
* Fancybox
* ---------------------------------------------------------
*/
if ($noFancybox) {
continue;
}
/*
* Если CKEditor уже поместил изображение внутрь ссылки,
* не создаём вложенный <a>.
*/
if (
$img->parentNode instanceof DOMElement
&& strtolower($img->parentNode->tagName) === 'a'
) {
continue;
}
$link = $dom->createElement('a');
/*
* В Fancybox открываем оригинал,
* а не уменьшенную копию Thumb3x.
*/
$link->setAttribute('href', $originalSrc);
$link->setAttribute('data-fancybox', $fancyboxGroup);
/*
* alt одновременно используем как подпись Fancybox.
* Если alt пустой — data-caption не создаём.
*/
if ($alt !== '') {
$link->setAttribute('data-caption', $alt);
}
/*
* Оборачиваем <img> в созданную ссылку.
*/
$parent = $img->parentNode;
if ($parent) {
$parent->replaceChild($link, $img);
$link->appendChild($img);
}
}
/*
* Забираем содержимое нашего технического контейнера,
* не возвращая сам <div id="content-root">.
*/
$root = $dom->getElementById('content-root');
if (!$root) {
libxml_clear_errors();
return $html;
}
$output = '';
foreach ($root->childNodes as $child) {
$output .= $dom->saveHTML($child);
}
libxml_clear_errors();
return $output;
и в шаблоне выводим как
*content:ContentImages
Относитесь всегда к коду от ИИ с осторожностью, какой бы продвинутой модель не была. Из простого - я бы переменные $root не называл бы. ну такое. Здесь нет про nofancybox и т.д. а fancybox этим примером вызывать так
<script>
document.addEventListener('DOMContentLoaded', function () {
Fancybox.bind('[data-fancybox^="gallery-"]', {});
});
</script>
Ответственности не несу.