Выбор первых картинок из контента

Вариант с использованием properties. Используется в плейсхолдере например так
<img   src="[[+properties.first_img_src.0]]" >
Плагин нужно выставить для события OnBeforeDocFormSave
Плагин перезаписывает последнюю актуальную картинку на момент сохранения ресурса, под словом актуальную я имею в виду то, что делается проверка на корректность файла, и если он действительно существует, то только тогда произойдет действие.
Если картинки не найдено — то будет записан null
{"first_img_src":null}
если контент вообще пустой -то ничего не произойдет вообще.
Код можно как угодно доработать, зависит от логики реализации.
Код плагина:
<?php
if ($modx->event->name == 'OnBeforeDocFormSave') {

    //get current content for resource
    $content = $resource->getContent();
    if (!empty($content)){
    //get first image in content
    $doc = new DOMDocument();
    $doc->loadHTML($content);
    $xml = simplexml_import_dom($doc);
    $images = $xml->xpath('//img');
    foreach ($images as $img) {
        //also avaliable in array  $img['alt'] и $img['title]
        $file_headers = @get_headers($img['src']);
        if ($file_headers[0] != 'HTTP/1.1 404 Not Found') {
            $first_img_src = $img['src'];
            break;
        }
    }

    //get properties
    $properties = $resource->get('properties');

    //here you can check for empty $first_img_src and set default noImage photo

    //save or override first_img_src
    $properties['first_img_src'] = $first_img_src;
    $resource->set('properties', $properties);

}
}
В комментариях предлагается использовать так же парсер MODX http://bezumkin.ru/sections/tips\_and\_tricks/2192/#comment-17960

23 комментария

Если выбирается только 1 элемент, то зачем цикл? Если уж объявили переменную $file, то надо использовать до последнего =) Почините код чанка.
По моему лучше такие операции делать при создании материала, занеся адрес в отдельное поле.
Василий Наумкин
Зачем так сложно? Почему бы просто не использовать preg_match?
Например, вот:
$img = '';
if (preg_match('/<img.*?>/', $input, $matches)) {
    $img = $matches[0];
}
return $img;
Если ты по поводу парсинга то тут каждому свое. Просто xpath заточен именно на это,и он стандартизирован W3C которому и я стараюсь придерживаться. По скорости разницы ты не заметишь, хотя можно протестировать =)
Ну а дополнительная проверка доступности изображения делается на случай, когда выводится пользовательский контент и пользователь например загрузил ссылку на изображение на сторонний ресурс,который сейчас не доступен. В итоге получим отсутствия изображения с ошибкой, что не эстетично. Тоже самое касается если файл просто удалится с хостинга - лучше не вывести ничего, чем бяку в виду не существующей ссылки на картинку, не так ли ? :)
Василий Наумкин
Одна регулярка отработает быстрее, чем преобразование документа и поиск по DOM.
Ну и отсутствующая картинка что в превью, что в контенте - одинаково плохо. Или ты предлагаешь и при загрузке тикета так все картинки проверять и прятать?
Ну и подумай о том, что при выводе списка тиектов каждая картинка будет запрашиваться минимум дважды - сначала твой скрипт отработает, а потом юзер загрузит.
В общем, интересный велосипед, но на мой взгляд ценность очень сомнительна. Лучше уж логи проверять на предмет 404.
Можно использовать как угодно. По поводу скорости - проведу замер интересно тоже стало.
У меня это используется не везде конечно, а только на главной для вывода ТОП-5 новостей по определенным параметрам..Поэтому главную портить точно нет смысла. А подстраховаться можно,мало ли..
p.s вообще парсинг xml/xhtml/html код регулярками это как-то не красиво. Вот кажется эта статья на хабре http://habrahabr.ru/post/114772/ описывает что лучше отказаться от регулярок в пользу xpath Запросов =)
Василий Наумкин
Поиск первой картинки через преобразование всего документа - вот что некрасиво.
Твой код, 100 итераций - 0.47 сек

$time = microtime(1);

$img = '';
for ($i = 1; $i <= 100; $i++) {
    if ($res = $modx->getObject('Ticket', 2184)) {
        $input = $res->get('content');

        $doc = new DOMDocument();
        $doc->loadHTML($input);
        $xml = simplexml_import_dom($doc);
        $images = $xml->xpath('//img');
        $img = current($images);
        $img = $img['src'];
    }
}

echo microtime(1) - $time;die;
Мой код, 100 итераций - 0.28 сек
$time = microtime(1);

$img = '';
for ($i = 1; $i <= 100; $i++) {
    if ($res = $modx->getObject('Ticket', 2184)) {
        $input = $res->get('content');

        if (preg_match('/<img.*?>/', $input, $matches)) {
            $img = $matches[0];
        }
    }
}

echo microtime(1) - $time;die;
Мой выбор очевиден.
скрипты без изменений,кроме ID страницы у меня отработали
0.20023822784424 -твой 0.22262501716614 -мой
странно..Притом контент был большой
И кстати - у тебя возвращается тег целиком,а у меня адрес. Т.е тебе нужно еще парсить $img для того чтобы достать адрес картинки,а это доп. действия и возможно доп. нагрузка. =)
p.s ну я и не пытаюсь его позиционировать как более совершенное решение.Зависит от цели, но на мой взгляд если например потребуется выбрать все картинки с страницы, то мое решение уже будет в плюсе,т.к самое сложное уже позади. по идее
Василий Наумкин
Видимо, зависит от сложности форматирования. preg_match то пофиг - он не анализирует документ.
http://s4327.modx-test.com/manager/ Логин s4327 Пароль SufmusRqoRnJ
Алексей Карташов
Идея правильная. Только есть ещё один вариант.
Вот как можно сделать по-другому (просто времени реализовать такое у меня не было): Пишем плагин на событие сохранения документа, который берёт контент этого документа, парсит его modx-парсером, вытаскивает из контента картинку, и вот здесь простой финт - записывает эту картинку в tvшку.
Потом собрать пакет, который автоматом создаст нужную tvшку, и при установке пробежится по уже существующим документам и сделает свою работу. Плагин будет сохранять картинку в tv независимо от того - привязана эта tv-шка к шаблону текущего ресурса или нет (просто создаёт запись в таблице site_tmplvars_content_values (по-моему как-то так она называется)). А уж потом эту tvшку можно привязывать к любому шаблону - превьюшки уже будут на месте.
Но здесь есть один минус - если в контенте документа используются какие-либо сниппеты, благодаря которым выводимый контент динамически меняется независимо от того, сохранялся доумент или нет, то такой способ может не подойти.
Ваша идея - картинка всегда вытягивается из актуального (на момент загрузки страницы с этим сниппетом) контента документа, но работа делается каждый раз при обращении к этой странице (со сниппетом). Моя - делать работу один раз, сохраняя картинку по событию, и везде, где только можно, использовать эту tvшку штатными modx-средствами. Но при наличии страниц с динамичным контентом этот метод может не подойти.
Выбирайте :-)
Не плохое решение +) Тут зависит уже от нужд. Но коли речь зашла о плагинах и TV я бы предпочел вместо TV использовать свойство properties и запихивать уже туда код первой картинки в ресурс.Это и дает существенный прирост в скорости, и вообще выглядит куда красивее, =) Возможно реализую, если появится свободное время. =)
Тоже реализовал это через плагин к твшкам.
<?php
//OnDocFormSave
$TvValue = $modx->getOption('TV','firstImage');
$def_img = 'assets/images/default.png';
$img = $resource->GetTVValue($TvValue);
$text = $resource->content;

if(empty($img)) {
  if (preg_match_all( '|<img.*?src=[\'"](.*?)[\'"].*?>|i',$text , $matches )!=0)
    {
        $img = $matches[1][0];
    }
    else{
    $img = $def_img;
    }
}

$resource->setTVValue($TvValue,$img);
$resource->save();

<?php
//OnDocFormSave
$TvValue = $modx->getOption('TV',$scriptProperties,'firstImage');
$def_img = 'assets/images/default.png';
$img = $resource->GetTVValue($TvValue);
$text = $resource->content;

if(empty($img)) {
  if (preg_match_all( '|<img.*?src=[\'"](.*?)[\'"].*?>|i',$text , $matches )!=0)
    {
        $img = $matches[1][0];
    }
    else{
    $img = $def_img;
    }
}

$resource->setTVValue($TvValue,$img);
$resource->save();
сорри так, без scriptProperties не работает =) идея сохранять в properties интересная, надо будет попробовать
Вот вариант с properties. Используется в плейсхолдере например так
  <img   src="[[+properties.first_img_src.0]]" >
Плагин нужно выставить для события OnBeforeDocFormSave
Плагин перезаписывает последнюю актуальную картинку на момент сохранения ресурса. Если картинки не найдено - то будет записан null
{"first_img_src":null}
Код можно как угодно доработать, зависит от логики реализации.
Я думаю это самый практичный из приведенных выше вариантов работы,в плане скорости,т.к мы не тратим ничего на выходе.

<?php
if ($modx->event->name == 'OnBeforeDocFormSave') {

    //get current content for resource
    $content = $resource->getContent();
    //get first image in content
    $doc = new DOMDocument();
    $doc->loadHTML($content);
    $xml = simplexml_import_dom($doc);
    $images = $xml->xpath('//img');
    foreach ($images as $img) {
        //also avaliable in array  $img['alt'] и $img['title]
        $file_headers = @get_headers($img['src']);
        if ($file_headers[0] != 'HTTP/1.1 404 Not Found') {
            $first_img_src = $img['src'];
            break;
        }
    }

    //get properties
    $properties = $resource->get('properties');

    //here you can check for empty $first_img_src and set default noImage photo

    //save or override first_img_src
    $properties['first_img_src'] = $first_img_src;
    $resource->set('properties', $properties);

}
Николай
Скрипт конечно полезный,только он не дает сохранить пустой ресурс *2168705 FastCGI sent in stderr: "PHP message: PHP Fatal error: Call to a member function xpath() on a non-object in www/core/cache/includes/elements/modplugin/6.include.cache.php on line 15"
Спасибо за наводку, не подумал об этом. Вот фикс:
<?php
if ($modx->event->name == 'OnBeforeDocFormSave') {

    //get current content for resource
    $content = $resource->getContent();
    if (!empty($content)){
    //get first image in content
    $doc = new DOMDocument();
    $doc->loadHTML($content);
    $xml = simplexml_import_dom($doc);
    $images = $xml->xpath('//img');
    foreach ($images as $img) {
        //also avaliable in array  $img['alt'] и $img['title]
        $file_headers = @get_headers($img['src']);
        if ($file_headers[0] != 'HTTP/1.1 404 Not Found') {
            $first_img_src = $img['src'];
            break;
        }
    }

    //get properties
    $properties = $resource->get('properties');

    //here you can check for empty $first_img_src and set default noImage photo

    //save or override first_img_src
    $properties['first_img_src'] = $first_img_src;
    $resource->set('properties', $properties);

}
}
Алексей Карташов
Забыли пропарсить контент modx-парсером:

    //get current content for resource
    $content = $resource->getContent();
    if (!empty($content)){
        // parse modx tags
        $maxIterations = (integer) $modx->getOption('parser_max_iterations', null, 10);
        $modx->getParser()->processElementTags('', $content, false, false, '[[', ']]', array(), $maxIterations);
        $modx->getParser()->processElementTags('', $content, true, true, '[[', ']]', array(), $maxIterations);

        //get first image in content
        $doc = new DOMDocument();
        $doc->loadHTML($content);
        /*...*/
Да, согласен, это полезно т.к действительно в контенте могут быть сниппеты и чанки в которых может выводиться изображение. Но я не стал это добавлять,т.к в моих страницах не предполагается использовать ModX теги в контенте, поскольку он редактируется с фронта, и важно выводить лишь то,что заполнил ручками пользователь. Добавлю в шапку сноску на Ваш пост.
Николай
Подскажите пожалуйста, что нужно изменить,чтобы этот плагин с Tickets начал работать из frontend? Я почему-то был уверен, что он и без изменений заработает,но к сожалению нет.
Николай
up! Если создавать тикет из бэкенда,то плагин записывает в базу строку
{"disable_jevix":"0","process_tags":"0","first_img_src":{"0":"uploaded\/7\/e\/2\/7e27047f9e9aa0d51e7817a123687344.jpg"}}
, что позволяет потом обернуть ее например в phptrumbon и делать превью. Если же создавать тикет из фронтенда,то плагин записывает в базу строку
{"first_img_src":{"0":"http:\/\/site.ru\/uploaded\/7\/e
\/2\/7e27047f9e9aa0d51e7817a123687344.jpg"}}
и phptrumbon воспринимает изображение как лежащее на внешнем сервере и отдает заглушку. Посоветуйте как быть с превью...
Василий, хотел бы твое мнение услышать, и мне кажется есть смысл перенести в Тонкости и трюки, т.к на мой взгляд штука очень полезная
Василий Наумкин
У меня доступ в админку закрыт по ip и было лень включать.
Перенес.
А я в свою очередь поправил шапку под последнее актуальное состояние, чтобы не путать людей.
bezumkin.ru
Личный сайт Василия Наумкина
Прямой эфир
Василий Наумкин
22.11.2024, 03:33:54
Спасибо!
inna
06.11.2024, 15:47:13
Да. Все работает. Спасибо.
Василий Наумкин
01.07.2024, 11:56:41
Да, верно, именно так. А в контроллере, скорее всего, ловить данные методом post.
Василий Наумкин
26.06.2024, 09:38:15
О, точно, вылезает если не залогинен. Спасибо, исправил!
Василий Наумкин
09.04.2024, 04:45:01
> Ошибка 500 Это не похоже на ошибку Nginx, это скорее всего ошибка PHP - надо смотреть его логи. ...
Василий Наумкин
20.03.2024, 21:21:52
Volledig!
Андрей
14.03.2024, 13:47:10
Василий! Как всегда очень круто! Моё почтение!
russel gal
09.03.2024, 20:17:18
> А этот стоило написать хотя бы затем, чтобы получить комментарий от юзера, который ничего не писал...
Александр Наумов
27.01.2024, 03:06:18
Василий, спасибо! Извини, тупанул.
Василий Наумкин
22.01.2024, 07:43:20
Давай-давай!