MODx ckeditor, вставка изображений, thumb3x, fancybox

Снипет для обработки изображений на сайте modx

Опубликовано: / Просмотрено: 7 /


Вот чтобы не говорили, но нейросети реально помогают в рутиных задачах и идеях, когда сам не догадываешься до изящных решений сложных костылей. В чем суть, для 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>

Ответственности не несу.


Теги: