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

Вариант с использованием 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
bezumkin.ru/sections/tips_and_tricks/2192/#comment-17960

Следующая заметка
HybridAuth Авторизация в контексте mgr
Предыдущая заметка
Динамический title страницы - радуем поисковики
  • 22 ноября 2013, 16:31
  • Clean
  • 2536


Комментарии ()

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

    По моему лучше такие операции делать при создании материала, занеся адрес в отдельное поле.
    1. Василий Наумкин 22 ноября 2013, 18:56 # 0
      Зачем так сложно? Почему бы просто не использовать preg_match?

      Например, вот:
      $img = '';
      if (preg_match('/<img.*?>/', $input, $matches)) {
      	$img = $matches[0];
      }
      return $img;
      1. Clean 22 ноября 2013, 19:49 # 0
        Если ты по поводу парсинга то тут каждому свое.
        Просто xpath заточен именно на это, и он стандартизирован W3C которому и я стараюсь придерживаться.
        По скорости разницы ты не заметишь, хотя можно протестировать =)

        Ну а дополнительная проверка доступности изображения делается на случай, когда выводится пользовательский контент и пользователь например загрузил ссылку на изображение на сторонний ресурс, который сейчас не доступен.
        В итоге получим отсутствия изображения с ошибкой, что не эстетично.
        Тоже самое касается если файл просто удалится с хостинга — лучше не вывести ничего, чем бяку в виду не существующей ссылки на картинку, не так ли? :)
        1. Василий Наумкин 22 ноября 2013, 20:09 # 0
          Одна регулярка отработает быстрее, чем преобразование документа и поиск по DOM.

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

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

          В общем, интересный велосипед, но на мой взгляд ценность очень сомнительна. Лучше уж логи проверять на предмет 404.
          1. Clean 22 ноября 2013, 20:19 # 0
            Можно использовать как угодно.
            По поводу скорости — проведу замер интересно тоже стало.

            У меня это используется не везде конечно, а только на главной для вывода ТОП-5 новостей по определенным параметрам… Поэтому главную портить точно нет смысла.
            А подстраховаться можно, мало ли…
      2. Clean 22 ноября 2013, 19:56 # 0
        p.s вообще парсинг xml/xhtml/html код регулярками это как-то не красиво.
        Вот кажется эта статья на хабре habrahabr.ru/post/114772/ описывает что лучше отказаться от регулярок в пользу xpath Запросов =)
        1. Василий Наумкин 22 ноября 2013, 20:19 # 0
          Поиск первой картинки через преобразование всего документа — вот что некрасиво.

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

          Мой выбор очевиден.
          1. Clean 22 ноября 2013, 20:36 # 0
            скрипты без изменений, кроме ID страницы у меня отработали

            0.20023822784424 -твой
            0.22262501716614 -мой

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

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

            p.s ну я и не пытаюсь его позиционировать как более совершенное решение.Зависит от цели, но на мой взгляд если например потребуется выбрать все картинки с страницы, то мое решение уже будет в плюсе, т.к самое сложное уже позади. по идее
            1. Василий Наумкин 22 ноября 2013, 20:59 # 0
              Видимо, зависит от сложности форматирования. preg_match то пофиг — он не анализирует документ.

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

              s4327.modx-test.com/manager/
              Логин s4327
              Пароль SufmusRqoRnJ
        2. Алексей Карташов 23 ноября 2013, 22:13 # 0
          Идея правильная.
          Только есть ещё один вариант.

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

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

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

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

          Выбирайте :-)
          1. Clean 24 ноября 2013, 00:01 # 0
            Не плохое решение +) Тут зависит уже от нужд.
            Но коли речь зашла о плагинах и TV я бы предпочел вместо TV использовать свойство properties и запихивать уже туда код первой картинки в ресурс.Это и дает существенный прирост в скорости, и вообще выглядит куда красивее, =) Возможно реализую, если появится свободное время. =)
            1. Abu 24 ноября 2013, 00:29 # 0
              Тоже реализовал это через плагин к твшкам.
              <?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();
              1. Abu 24 ноября 2013, 00:49 # 0
                <?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 интересная, надо будет попробовать
            2. Clean 25 ноября 2013, 01:15 # 0
              Вот вариант с 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);
              
              
              }
              
              
              
              1. Николай 26 ноября 2013, 09:13 # 0
                Скрипт конечно полезный, только он не дает сохранить пустой ресурс
                *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»
                1. Clean 26 ноября 2013, 10:39 # 0
                  Спасибо за наводку, не подумал об этом.
                  Вот фикс:
                  <?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);
                  
                  
                  }
                  }
                  
                  1. Алексей Карташов 27 ноября 2013, 01:36 # 0
                    Забыли пропарсить контент 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);
                            /*...*/
                    
                    1. Clean 27 ноября 2013, 10:09 # 0
                      Да, согласен, это полезно т.к действительно в контенте могут быть сниппеты и чанки в которых может выводиться изображение.
                      Но я не стал это добавлять, т.к в моих страницах не предполагается использовать ModX теги в контенте, поскольку он редактируется с фронта, и важно выводить лишь то, что заполнил ручками пользователь.
                      Добавлю в шапку сноску на Ваш пост.
                    2. Николай 24 декабря 2013, 15:46 # 0
                      Подскажите пожалуйста, что нужно изменить, чтобы этот плагин с Tickets начал работать из frontend? Я почему-то был уверен, что он и без изменений заработает, но к сожалению нет.
                      1. Николай 26 декабря 2013, 13:07 # 0
                        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 воспринимает изображение как лежащее на внешнем сервере и отдает заглушку.
                        Посоветуйте как быть с превью…
                2. Clean 26 ноября 2013, 23:40 # 0
                  Василий, хотел бы твое мнение услышать, и мне кажется есть смысл перенести в Тонкости и трюки, т.к на мой взгляд штука очень полезная
                  1. Василий Наумкин 26 ноября 2013, 23:55 # 0
                    У меня доступ в админку закрыт по ip и было лень включать.

                    Перенес.
                    1. Clean 27 ноября 2013, 00:08 # 0
                      А я в свою очередь поправил шапку под последнее актуальное состояние, чтобы не путать людей.
                  Добавление новых комментариев отключено.