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

Вариант с использованием 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

← Предыдущая заметка
Динамический title страницы - радуем поисковики
Следующая заметка →
HybridAuth Авторизация в контексте mgr
Комментарии (23)
Сергей
22.11.2013 18:44

Если выбирается только 1 элемент, то зачем цикл? Если уж объявили переменную $file, то надо использовать до последнего =) Почините код чанка.

По моему лучше такие операции делать при создании материала, занеся адрес в отдельное поле.

bezumkinВасилий Наумкин
22.11.2013 18:56

Зачем так сложно? Почему бы просто не использовать preg_match?

Например, вот:

$img = '';
if (preg_match('/<img.*?>/', $input, $matches)) {
    $img = $matches[0];
}
return $img;
CleanClean
22.11.2013 19:49

Если ты по поводу парсинга то тут каждому свое. Просто xpath заточен именно на это,и он стандартизирован W3C которому и я стараюсь придерживаться. По скорости разницы ты не заметишь, хотя можно протестировать =)

Ну а дополнительная проверка доступности изображения делается на случай, когда выводится пользовательский контент и пользователь например загрузил ссылку на изображение на сторонний ресурс,который сейчас не доступен. В итоге получим отсутствия изображения с ошибкой, что не эстетично. Тоже самое касается если файл просто удалится с хостинга - лучше не вывести ничего, чем бяку в виду не существующей ссылки на картинку, не так ли ? :)

bezumkinВасилий Наумкин
22.11.2013 20:09

Одна регулярка отработает быстрее, чем преобразование документа и поиск по DOM.

Ну и отсутствующая картинка что в превью, что в контенте - одинаково плохо. Или ты предлагаешь и при загрузке тикета так все картинки проверять и прятать?

Ну и подумай о том, что при выводе списка тиектов каждая картинка будет запрашиваться минимум дважды - сначала твой скрипт отработает, а потом юзер загрузит.

В общем, интересный велосипед, но на мой взгляд ценность очень сомнительна. Лучше уж логи проверять на предмет 404.

CleanClean
22.11.2013 20:19

Можно использовать как угодно. По поводу скорости - проведу замер интересно тоже стало.

У меня это используется не везде конечно, а только на главной для вывода ТОП-5 новостей по определенным параметрам..Поэтому главную портить точно нет смысла. А подстраховаться можно,мало ли..

CleanClean
22.11.2013 19:56

p.s вообще парсинг xml/xhtml/html код регулярками это как-то не красиво. Вот кажется эта статья на хабре http://habrahabr.ru/post/114772/ описывает что лучше отказаться от регулярок в пользу xpath Запросов =)

bezumkinВасилий Наумкин
22.11.2013 20:19

Поиск первой картинки через преобразование всего документа - вот что некрасиво.

Твой код, 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;

Мой выбор очевиден.

CleanClean
22.11.2013 20:36

скрипты без изменений,кроме ID страницы у меня отработали

0.20023822784424 -твой 0.22262501716614 -мой

странно..Притом контент был большой

И кстати - у тебя возвращается тег целиком,а у меня адрес. Т.е тебе нужно еще парсить $img для того чтобы достать адрес картинки,а это доп. действия и возможно доп. нагрузка. =)

p.s ну я и не пытаюсь его позиционировать как более совершенное решение.Зависит от цели, но на мой взгляд если например потребуется выбрать все картинки с страницы, то мое решение уже будет в плюсе,т.к самое сложное уже позади. по идее

bezumkinВасилий Наумкин
22.11.2013 20:59

Видимо, зависит от сложности форматирования. preg_match то пофиг - он не анализирует документ.

Вот гляди: http://s4327.modx-test.com/pcre.html - 0.22 сек http://s4327.modx-test.com/xpath.html - 0.51 сек

http://s4327.modx-test.com/manager/ Логин s4327 Пароль SufmusRqoRnJ

Алексей Карташов
23.11.2013 22:13

Идея правильная. Только есть ещё один вариант.

Вот как можно сделать по-другому (просто времени реализовать такое у меня не было): Пишем плагин на событие сохранения документа, который берёт контент этого документа, парсит его modx-парсером, вытаскивает из контента картинку, и вот здесь простой финт - записывает эту картинку в tvшку.

Потом собрать пакет, который автоматом создаст нужную tvшку, и при установке пробежится по уже существующим документам и сделает свою работу. Плагин будет сохранять картинку в tv независимо от того - привязана эта tv-шка к шаблону текущего ресурса или нет (просто создаёт запись в таблице site_tmplvars_content_values (по-моему как-то так она называется)). А уж потом эту tvшку можно привязывать к любому шаблону - превьюшки уже будут на месте.

Но здесь есть один минус - если в контенте документа используются какие-либо сниппеты, благодаря которым выводимый контент динамически меняется независимо от того, сохранялся доумент или нет, то такой способ может не подойти.

Ваша идея - картинка всегда вытягивается из актуального (на момент загрузки страницы с этим сниппетом) контента документа, но работа делается каждый раз при обращении к этой странице (со сниппетом). Моя - делать работу один раз, сохраняя картинку по событию, и везде, где только можно, использовать эту tvшку штатными modx-средствами. Но при наличии страниц с динамичным контентом этот метод может не подойти.

Выбирайте :-)

CleanClean
24.11.2013 00:01

Не плохое решение +) Тут зависит уже от нужд. Но коли речь зашла о плагинах и TV я бы предпочел вместо TV использовать свойство properties и запихивать уже туда код первой картинки в ресурс.Это и дает существенный прирост в скорости, и вообще выглядит куда красивее, =) Возможно реализую, если появится свободное время. =)

RasulAbu
24.11.2013 00:29

Тоже реализовал это через плагин к твшкам.

<?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();
RasulAbu
24.11.2013 00:49

<?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 интересная, надо будет попробовать

CleanClean
25.11.2013 01:15

Вот вариант с 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);


}


susliktНиколай
26.11.2013 09:13

Скрипт конечно полезный,только он не дает сохранить пустой ресурс *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"

CleanClean
26.11.2013 10:39

Спасибо за наводку, не подумал об этом. Вот фикс:

<?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);


}
}
Алексей Карташов
27.11.2013 01:36

Забыли пропарсить контент 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);
        /*...*/
CleanClean
27.11.2013 10:09

Да, согласен, это полезно т.к действительно в контенте могут быть сниппеты и чанки в которых может выводиться изображение. Но я не стал это добавлять,т.к в моих страницах не предполагается использовать ModX теги в контенте, поскольку он редактируется с фронта, и важно выводить лишь то,что заполнил ручками пользователь. Добавлю в шапку сноску на Ваш пост.

susliktНиколай
24.12.2013 15:46

Подскажите пожалуйста, что нужно изменить,чтобы этот плагин с Tickets начал работать из frontend? Я почему-то был уверен, что он и без изменений заработает,но к сожалению нет.

susliktНиколай
26.12.2013 13:07

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

CleanClean
26.11.2013 23:40

Василий, хотел бы твое мнение услышать, и мне кажется есть смысл перенести в Тонкости и трюки, т.к на мой взгляд штука очень полезная

bezumkinВасилий Наумкин
26.11.2013 23:55

У меня доступ в админку закрыт по ip и было лень включать.

Перенес.

CleanClean
27.11.2013 00:08

А я в свою очередь поправил шапку под последнее актуальное состояние, чтобы не путать людей.

futuris
Futuris
26.03.2024 07:39
Страница отдельного поста заработала сразу в том виде, как ты написал.) А вот в ленте постов контент...
bezumkin
Василий Наумкин
20.03.2024 18:21
Volledig!
Андрей
14.03.2024 10:47
Василий! Как всегда очень круто! Моё почтение!
russelgal
russel gal
09.03.2024 17:17
А этот стоило написать хотя бы затем, чтобы получить комментарий от юзера, который ничего не писал ...
inetlover
Александр Наумов
27.01.2024 00:06
Василий, спасибо! Извини, тупанул.
bezumkin
Василий Наумкин
22.01.2024 04:43
Давай-давай!
bezumkin
Василий Наумкин
24.12.2023 11:26
Спасибо!
bezumkin
Василий Наумкин
27.11.2023 02:43
Ура!
bezumkin
Василий Наумкин
25.11.2023 08:30
Vesp тянет 2 зависимости: vesp-frontent для фронта и vesp-core для бэкенда. Их можно обновлять, но э...
bezumkin
Василий Наумкин
22.11.2023 08:09
Отлично, поздравляю!