1 плагин - 3 лайвхака

Всем привет, порой работа менеджеров в админке modx — сущий ад. Сегодня поступил заказ с просьбой решить несколько проблем.

Проблема №1:
В tinyMCE все изображения которые вставляются имеют атрибуты width и height, и не имеют нужных классов

Проблема №2:
При загрузки файлов в каталог, где уже есть такое имя, файлы переписывают друг друга, необходимо было сделать добавление префикса для файлов с одинаковым именем

Проблема №3:
Иногда менеджеры грузят неприлично большие изображения, что непосредственно влияет и на скорость загрузки страницы и на общий размер файлов

Что делает плагин:
  1. Транслитерация файлов при загрузке, добавление префикса если файл дублируется
  2. Уменьшает загружаемое изображение до 1200px по ширине
  3. Из поля content достает все img, вырезает у них атрибуты width и height, вместо них подставляет класс img-thumbnail
Под катом — код с комментариями.


Установка


Создаем плагин с любым именем и даем ему 3 события:
  1. OnFileManagerBeforeUpload
  2. OnFileManagerUpload
  3. OnLoadWebDocument
Вставляем туда наш код:
<?php
switch ($modx->event->name) {
  
    //Работа с контентом
    case 'OnLoadWebDocument':
        $content = $modx->resource->content;
        $content = mb_convert_encoding($content, 'HTML-ENTITIES', 'UTF-8'); //исправляем ошибки кодировки
        
        $dom = new DOMDocument;
        $dom->loadHTML($content, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
        $imgs = $dom->getElementsByTagName('img'); //ищем все изображения

        
        foreach ($imgs as $img) {
          //убираем атрибуты width и height, добавляем класс
          $img->removeAttribute('width');
          $img->removeAttribute('height');
          $img->setAttribute('class', 'img-thumbnail');
        }
        
        //компилируем html и устанавливаем
        $html = $dom->saveHTML();
        $modx->resource->set('content', $html);
        
        break;
        
    case 'OnFileManagerBeforeUpload':
        setlocale(LC_ALL, 'ru_RU.utf8'); //фикс для неправильных серверов, иначе не будет работать patchinfo с кириллицей
        $fullPath = $source->getBases()['pathAbsolute'].$directory; //получаем абсолютную дирректорию
        $dir = scandir($fullPath);
        
        foreach ($files as $key => $file) {
          $info = pathinfo($file['name']);
          $name = $info['filename'];
          
          $name = modResource::filterPathSegment($modx, $name); //транслитерация
          
          $extension = $info['extension'];
          $fullname = $name.'.'.$extension;
          //ищем файлы с таким же названием в этой директории
          if (!in_array($fullname, $dir)) {
            $newFullName = $fullname;
          } else {
            $newFullName = $name.'_'.rand(1,999999).'.'.$extension;
          }
          $file['name'] = $newFullName;
          
          $modx->event->params['file'] = $file; //отдаем изменненную переменную file
          $files[$key] = $file;
        }

        $modx->event->params['files'] = $files; //отдаем изменненную переменную files
        
      break;
    case 'OnFileManagerUpload':
        $fullPath = $source->getBases()['pathAbsolute'].$directory;
        foreach ($files as $file) {
          
          if(strripos($file['type'], 'image') === false) { //если не изображение, не запускаем phpThumb
            return ;
          }
          
          $name = $file['name'];
          $pathToImage = $fullPath.$name;
          //Массив параметров для phpThumb
          $params = array(
          	'w' => 1200	,
          );
          $phpThumb = $modx->getService('modphpthumb','modPhpThumb', MODX_CORE_PATH . 'model/phpthumb/', array()); //Подключаем класс phpThumb
          $phpThumb->setSourceFilename($pathToImage);

          foreach ($params as $k => $v) {
          	$phpThumb->setParameter($k, $v);
          }
          
          if ($phpThumb->GenerateThumbnail()) {
          	if (!$phpThumb->renderToFile($pathToImage)) {
          		$modx->log(1, 'Ошибка сохранения изображения в ['.$pathToImage.']');
          	}
          }
          else {
          	$modx->log(1, print_r($phpThumb->debugmessages, 1));
          }

        }

        break;
}
gist.github.com/pavel-one/17db1b5cff8c2713f28976439e05f945

Радуемся!
Всем спасибо за внимание :)
Pavel Zarubin
26 ноября 2018, 17:42
26
725
+19

Комментарии: 18

iWatchYouFromAfar
26 ноября 2018, 19:13
1
+2
Круто! Я в RTE почти всегда забиваю классы для изображений и пользователь может выбирать их. А тут получается как альтернативный вариант, без визивига. Могу дополнить пост, в закромах лежал плагин транслитерации папок.

Работает как с translit, как с yTranslit, так с и любым другим компонентом транслитерации.
switch($modx->event->name) {
  case 'OnFileManagerDirCreate':
  case 'OnFileManagerDirRename':
    $basePath = $source->getBasePath();
    $dirName  = basename($directory);
    
    $name  = array_pop(array_filter(explode(DIRECTORY_SEPARATOR, $directory)));
    $tmpDoc = $modx->newObject('modResource');
    $newName = $tmpDoc->cleanAlias($name);
    
    if(strcmp($name, $newName) === 0) {
        return;
    }
    
    $oldPath = str_replace(realpath($basePath), '', $directory);
    $bases = $source->getBases($oldPath);
    $oldPath = $bases['pathAbsolute'].$oldPath;

    $oldDirectory = $source->fileHandler->make($oldPath);

    if (!($oldDirectory instanceof modDirectory)) {
        return false;
    }
    if (!$oldDirectory->isReadable() || !$oldDirectory->isWritable()) {
        return false;
    }
    
    $newPath = $source->fileHandler->sanitizePath($newName);
    $newPath = $source->fileHandler->postfixSlash($newPath);
    $newPath = dirname($oldPath).'/'.$newPath;

    /* Транслитерация папки */
    if (!$oldDirectory->rename($newPath)) {
        $modx->log(modX::LOG_LEVEL_ERROR, 'Ошибка транслитерации папки!');
        return false;
    }

    break;
}
    Pavel Zarubin
    26 ноября 2018, 19:28
    +2
    Круто) Спасибо, полезно)
    Вообще со стороны tinyMCE это делать конечно более рационально, т.к. не нужно каждый раз при генерации страницы тратить время на лишнюю обработку дома, но как показали тесты, времени это добавляет не много, да и для уже заполненных сайтов удобнее)
Константин
26 ноября 2018, 22:48
0
Павел, а как допустим сделать так, чтобы изображения резались в определенной папке и, к примеру, во всех вложенных папках? Например, у меня есть папка img и в ней несколько папок-дочек, папок-внучек и т.д. и во всех них мне при загрузке изображения нужно отслеживать ширину и обрезать по заданным размерам.
И еще вопрос. Как из одного изображения в тексте статьи нарезать несколько уменьшенных для использования с scrset?
Спасибо.
    Pavel Zarubin
    26 ноября 2018, 23:14
    +1
    Павел, а как допустим сделать так, чтобы изображения резались в определенной папке и, к примеру, во всех вложенных папках
    Просто вот тут: gist.github.com/pavel-one/17db1b5cff8c2713f28976439e05f945#file-plugin-php-L29 необходимо добавить свои проверки директории. К примеру текущий путь через explode разбить на массив и проверять есть ли в пути папки которые вам нужны

    Как из одного изображения в тексте статьи нарезать несколько уменьшенных для использования с scrset?
    Спасибо.
    Тут все немного сложнее, необходимо понять на каком этапе вам нужно резать их, при сохранении ресурса/рендеринге страницы/etc. Но принцип один, вам нужно загнать все содержимое в PHPDomDocument пройтись по всем изображениям, забрать все пути, ну а дальше уже дело техники.
    Код за вас я писать не буду, простите :)
      Константин
      26 ноября 2018, 23:25
      0
      Код за вас я писать не буду, простите :)
      Сложновато пока для меня все это, учитывая мой теперешний уровень знаний. Но за подсказки все равно спасибо!
    Баха Волков
    26 ноября 2018, 23:32
    0
    Павел, а как допустим сделать так, чтобы изображения резались в определенной папке и, к примеру, во всех вложенных папках?
    Я хоть и не Павел ?, но добавлю к подсказке:

    if(strripos($file['type'], 'image') === false) { //если не изображение, не запускаем phpThumb
        return ;
    }
    Вот тут производится проверка расширения файла, идёте и смотрите документацию и увидите, что в событие приходит как раз directory в которой будет хранится путь.

    Ваша задача к данной проверке добавить еще и свою логику.
      Pavel Zarubin
      26 ноября 2018, 23:35
      0
      directory — это директория относительно источника файлов, так что тут не совсем верно, я выше дал ссылку на строку где определяется абсолютная директория
Алексей Соин
27 ноября 2018, 07:57
0
А такой плагин замены img в контенте не сильно нагружает загрузку страницы? Не лучше ли пробежаться по старым ресурсам одним циклом для замены, а для новых ресурсов поставить плагин на сохранение ресурса?
    Pavel Zarubin
    27 ноября 2018, 10:19
    +1
    Без плагина:

    С плагином


    Я думаю ответ очевиден)
Григорий
30 ноября 2018, 13:37
0
Отлично! Спасибо.
Транслитерацию папок тоже добавил
Григорий
02 декабря 2018, 20:13
+1
Вопрос.
foreach ($imgs as $img) {
//убираем атрибуты width и height, добавляем класс
$img->removeAttribute('width');
$img->removeAttribute('height');
$img->setAttribute('class', 'img-thumbnail');
}
Код
$img->setAttribute('class', 'img-thumbnail');
заменяет любой уже присвоенный class.
По логике, правильнее добавлять class, а не зарезать существующие…
    Pavel Zarubin
    02 декабря 2018, 21:48
    0
    Да. В моих реалиях контент менеджеры не проставляют вообще никакие классы изображениям, я выложил код для моих реалий, если бы это было оформлено в компонент и продавалось бы за деньги, я бы подумал о кейсах использования. Если вы считаете что-то в коде нелогичным — выкладывайте свою реализацию, заодно поможете людям с кейсом использования как у вас
Vlad Brise
06 декабря 2018, 21:47
+3
Спасибо большое, как раз нужно было ресайз изображений делать при загрузке :)
Григорий
02 февраля 2019, 20:58
0
Страное дело. Журнал ошибок пишет:
[2019-02-02 20:52:36] (ERROR @ *******/core/cache/includes/elements/modplugin/27.include.cache.php : 14) PHP warning: DOMDocument::loadHTML(): htmlParseEntityRef: expecting ';' in Entity, line: 3
[2019-02-02 20:52:36] (ERROR @ *******/core/cache/includes/elements/modplugin/27.include.cache.php : 14) PHP warning: DOMDocument::loadHTML(): htmlParseEntityRef: expecting ';' in Entity, line: 4
[2019-02-02 20:52:36] (ERROR @ *******/core/cache/includes/elements/modplugin/27.include.cache.php : 14) PHP warning: DOMDocument::loadHTML(): htmlParseEntityRef: expecting ';' in Entity, line: 5
[2019-02-02 20:52:36] (ERROR @ *******/core/cache/includes/elements/modplugin/27.include.cache.php : 14) PHP warning: DOMDocument::loadHTML(): htmlParseEntityRef: expecting ';' in Entity, line: 6
[2019-02-02 20:52:36] (ERROR @ *******/core/cache/includes/elements/modplugin/27.include.cache.php : 14) PHP warning: DOMDocument::loadHTML(): htmlParseEntityRef: expecting ';' in Entity, line: 7
[2019-02-02 20:52:36] (ERROR @ *******/core/cache/includes/elements/modplugin/27.include.cache.php : 14) PHP warning: DOMDocument::loadHTML(): htmlParseEntityRef: expecting ';' in Entity, line: 8
[2019-02-02 20:52:36] (ERROR @ *******/core/cache/includes/elements/modplugin/27.include.cache.php : 14) PHP warning: DOMDocument::loadHTML(): htmlParseEntityRef: expecting ';' in Entity, line: 9
[2019-02-02 20:52:36] (ERROR @ *******/core/cache/includes/elements/modplugin/27.include.cache.php : 14) PHP warning: DOMDocument::loadHTML(): htmlParseEntityRef: expecting ';' in Entity, line: 10
[2019-02-02 20:52:36] (ERROR @ *******/core/cache/includes/elements/modplugin/27.include.cache.php : 14) PHP warning: DOMDocument::loadHTML(): htmlParseEntityRef: expecting ';' in Entity, line: 11
[2019-02-02 20:52:36] (ERROR @ *******/core/cache/includes/elements/modplugin/27.include.cache.php : 14) PHP warning: DOMDocument::loadHTML(): htmlParseEntityRef: expecting ';' in Entity, line: 12
[2019-02-02 20:52:38] (ERROR @ *******/core/cache/includes/elements/modplugin/27.include.cache.php : 14) PHP warning: DOMDocument::loadHTML(): Unexpected end tag : p in Entity, line: 30
[2019-02-02 20:52:39] (ERROR @ *******/core/cache/includes/elements/modplugin/27.include.cache.php : 14) PHP warning: DOMDocument::loadHTML(): Unexpected end tag : p in Entity, line: 30
Файл 27.include.cache.php

<?php
	
//  modx.pro/development/16940
	
switch ($modx->event->name) {
  
    //Работа с контентом
    
   case 'OnLoadWebDocument':
        $content = $modx->resource->content;
        $content = mb_convert_encoding($content, 'HTML-ENTITIES', 'UTF-8'); //исправляем ошибки кодировки
        
        $dom = new DOMDocument;
        $dom->loadHTML($content, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
        $imgs = $dom->getElementsByTagName('img'); //ищем все изображения
        
        foreach ($imgs as $img) {
          //убираем атрибуты width и height, добавляем класс
          $img->removeAttribute('width');
          $img->removeAttribute('height');
         
          // $img->setAttribute('className', 'img-responsive');
          // kalina *** 
          $class = $img->getAttribute( 'class' );
          $img->setAttribute('class', $class . ' img-fluid' );
        }
        
        //компилируем html и устанавливаем
       $html = $dom->saveHTML();
        $modx->resource->set('content', $html);
        
        break;
Что это может быть?
Денис Чубенко
12 июля 2019, 14:59
0
Столкнулся с необычным поведением DOMDocument.
есть код в content
<h2>Назначение стационарных углекислотных резервуаров</h2>
<ul>
<li>Длительное хранение (неограниченный срок) жидкой углекислоты с минимальными потерями продукта.  </li>
<li>Подача углекислоты на линию потребления. </li>
<li>Заправка любых емкостей жидкой углекислотой. </li>
</ul>
<p> </p>
<h2>Выгодные особенности углекислотных резервуаров</h2>
после использования loadHTML и saveHTML для обработки content, достаточно только загрузить данные и сохранить
$content = $modx->resource->content;
        $content = mb_convert_encoding($content, 'HTML-ENTITIES', 'UTF-8'); //исправляем ошибки кодировки
        libxml_use_internal_errors(true);

        $dom = new DOMDocument;
        if ($dom->loadHTML($content, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD)){
                //компилируем html и устанавливаем
                $html = $dom->saveHTML($dom->documentElement);
                $modx->resource->set('content', $html);
        }
        break;
получаю поломанную верстку.
Перемещается окончание заголовка h2 в самый конец. Вот вывод в логи переменной $html
<h2>Назначение стационарных углекислотных резервуаров<ul>
<li>Длительное хранение (неограниченный срок) жидкой углекислоты с минимальными потерями продукта.  </li>
<li>Подача углекислоты на линию потребления. </li>
<li>Заправка любых емкостей жидкой углекислотой. </li>
</ul>
<p> </p>
<h2>Выгодные особенности углекислотных резервуаров</h2></h2>
если при загрузке content убрать ключ — LIBXML_HTML_NOIMPLIED, то все будет корректно, но добавятся теги
<html><body>

Подскажите, что нужно прописать, что бы не ломало верстку и лишние теги не добавлялись?
    Aborrol
    12 июля 2019, 22:25
    0
    Попробуй обернуть все это в какой-нибудь div, сохранится ли ошибка?
      Денис Чубенко
      12 июля 2019, 23:13
      0
      В content поле ни кто дополнительно div блоки прописывать не будет, а значит придется принудительно прописывать лишние элементы. Проще тогда теги body и html удалить.
Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.