MODX - Как вывести популярные статьи

Александр Мальцев
Александр Мальцев
9,3K
29
Содержание:
  1. Описание виджета "Популярные статьи"
  2. Необходимые компоненты
  3. Таблицы TicketView и Ticket
  4. Разработка виджета "Популярные статьи"
  5. Комментарии

Урок, на котором рассмотрим, как с помощью сниппетов pdoTools создать виджет, отображающий на сайте 5 самых популярных статей за последние 7 дней.

Описание виджета "Популярные статьи"

Виджет "Популярные статьи" предназначен для отображения на сайте 5 самых просматриваемых страниц за последние 7 дней.

MODX - Виджет "Популярные статьи"
Виджет популярных статей для сайта на MODX

Визуально данный виджет выполним в виде панели (компонента panel) front-end фреймворка Twitter Bootstrap. Содержимое панели организуем в виде нумерованного списка статей в порядке их убывания по количеству просмотров за неделю.

Необходимые компоненты

Рассматривать создание блока "Самое популярное" будем для сайта, в котором статьи организованы на базе компонента Tickets. По умолчанию просмотры тикетов в компоненте Tickets ведутся только для зарегистрированных пользователей. Для того чтобы просмотры регистрировались и для незарегистрированных посетителей, необходимо включить опцию "tickets.count_guests" в настройках.

MODX - Включение параметра "Считать просмотры страниц гостями"
Параметр Ticket, позволяющий учитывать просмотры страниц гостями

Кроме компонента Tickets, потребуется ещё компонент pdoTools. Данный компонент, содержит хороший набор сниппетов. В этой статье будем использовать из этого набора только сниппет pdoResources.

Таблицы TicketView и Ticket

Для создания запросов понадобятся 2 таблицы: TicketView и Ticket.

Первая таблица (TicketView) содержит записи о просмотренных пользователями тикетах. Она состоит из четырёх полей, но для выполнения запроса понадобятся только 2. Первое поле - это parent, оно хранит id просмотренного пользователем тикета. Второе поле - это timestamp, оно предназначено для хранения даты просмотра.

Вторая таблица (Ticket) будет основной. Она понадобится для того чтобы выбрать из неё необходимые ресурсы для которых будем подсчитывать количество просмотров.

Взаимосвязь между таблицами представим на следующей схеме:

MODX - Схема взаимосвязи таблиц Ticket и TicketView
MODX - Схема взаимосвязи таблиц Ticket и TicketView

Разработка виджета "Популярные статьи"

Создание виджета осуществим за 2 шага:

  1. Напишем сниппет getDateWeekAgo, который будет возвращать дату, которая была 7 дней назад.
  2. Выберем и выведем необходимые ресурсы с помощью сниппета pdoResources и getTicket. Сравним время и ресурсы, затрачиваемые ими на обработку.

Создание сниппета getDateWeekAgo

Для создания сниппетов необходимо открыть в левой панели администрирования вкладку "Элементы" и нажать на значок "+" напротив надписи "Сниппеты". В открывшейся странице ввести следующее:

  • в поле "Имя": getDateWeekAgo.
  • в поле "Код сниппета" следующий код:
    <?php
    $formatDate = date('Y-m-d H:i:s');
    $date = new DateTime($formatDate);
    $date->modify("-7 day");
    return $date->format('Y-m-d H:i:s');
MODX - Создание сниппета getDateWeekAgo
MODX - Создание сниппета getDateWeekAgo

Выборка записей с помощью сниппета pdoResources

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

<section class="panel panel-danger">
  <div class="panel-heading">
    <h5 class="panel-title">
      <i class="fa fa-heart"></i> <span title="Самые популярные статьи и уроки за последние 7 дней">Популярные статьи</span>
    </h5>
  </div>
  <div class="panel-body">
    [[!pdoResources?
      &loadModels=`tickets`
      &class=`Ticket`
      &leftJoin=`{
        "TicketView": {
          "class": "TicketView",
          "on": "Ticket.id = TicketView.parent"
        }
      }`
      &parents=`4`
      &where=`{"TicketView.timestamp:>":"[[!getDateWeekAgo]]"}`
      &select=`{
        "Ticket": "Ticket.id as id, Ticket.uri as uri, Ticket.pageTitle as pagetitle",
        "TicketView": "COUNT(TicketView.parent) as countviews"
      }`
      &hideContainers=`1`
      &groupby=`Ticket.id`
      &sortby=`{"countviews":"desc"}`
      &tpl=`@INLINE <p><span class="badge" style="background-color:#cc2929">[[+idx]]</span> <a href="[[+uri]]" style="color: #cc2929; vertical-align: middle;">[[+pagetitle]]</a> <span style="color: #cc2929;"><i class="fa fa-eye" style="vertical-align: middle;"></i><span style="vertical-align: middle;" title="Количество просмотров за последние 7 дней"> [[+countviews]]</span></span></p>`
      &limit=`5`
    ]]  
  </div>
</section>  
MODX - Лог работы pdoResources
MODX - Лог работы pdoResources

Выборка записей с помощью сниппета getTickets

В качестве 2 способа рассмотрим выборку данных и их отображение с помощью сниппета getTickets.

<section class="panel panel-danger">
  <div class="panel-heading">
    <h5 class="panel-title">
      <i class="fa fa-heart"></i> <span title="Самые популярные статьи и уроки за последние 7 дней">Популярные статьи</span>
    </h5>
  </div>
  <div class="panel-body">
    [[!getTickets?
      &leftJoin=`{
        "TicketView": {
          "class": "TicketView",
          "on": "Ticket.id = TicketView.parent"
        }
      }`
      &parents=`4`
      &where=`{"TicketView.timestamp:>":"[[!getDateWeekAgo]]"}`
      &select=`{
        "Ticket": "Ticket.id as id, Ticket.uri as uri, Ticket.pageTitle as pagetitle",
        "TicketView": "COUNT(TicketView.parent) as countviews"
      }`
      &hideContainers=`1`
      &groupby=`Ticket.id`
      &sortby=`{"countviews":"desc"}`
      &tpl=`@INLINE <p><span class="badge" style="background-color:#cc2929">[[+idx]]</span> <a href="[[+uri]]" style="color: #cc2929; vertical-align: middle;">[[+pagetitle]]</a> <span style="color: #cc2929;"><i class="fa fa-eye" style="vertical-align: middle;"></i><span style="vertical-align: middle;" title="Количество просмотров за последние 7 дней"> [[+countviews]]</span></span></p>`
      &limit=`5`
    ]]  
  </div>
</section>  
MODX - Лог работы getTickets
MODX - Лог работы getTickets
Поиск нужных ресурсов вышепредставленные сниппеты осуществляют с помощью параметра &parent. Т.е. для того чтобы настроить виджет "Популярные статьи" на свой сайт, Вам достаточно изменить значение 4 на необходимое.

Сравнение результатов работы сниппетов

Как видно из результатов логов, сниппет pdoResources выбирает данные намного быстрее. Это связано с тем, что в запросе участвуют только нужные таблицы. В то время как сниппет getTickets дополнительно подключает ещё классы (таблицы) TicketsSection, modUser и modUserProfile.

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

  1. Ник
    21 августа 2018, 10:36
    Шеф, помоги!
    1. Ник
      19 августа 2018, 14:32
      Александр, здравствуйте. Делаю по шаблону как у вас, но мне еще нужно, чтобы загружалась картинка на каждую статью из ms2Gallery. Пишу такой вызов:
          [[!pdoResources?
            &loadModels=`tickets,ms2gallery`
            &class=`Ticket`
            &leftJoin=`{
              "TicketView": {
                "class": "TicketView",
                "on": "Ticket.id = TicketView.parent"
              },
      		"miniblog" : {
      			"class" : "msResourceFile",
      			"alias" : "miniblog",
      			"on" : "miniblog.resource_id = Ticket.id AND miniblog.path LIKE \'%/miniblog/\' AND miniblog.rank=0"
      		}
            }`
            &parents=`16`
            &where=`{ "TicketView.timestamp:>":"[[!getDateWeekAgo]]" }`
            &select=`{
              "Ticket": "Ticket.id as id, Ticket.uri as uri, Ticket.pageTitle as pagetitle, Ticket.introtext as introtext, Ticket.publishedon as publishedon",
              "TicketView": "COUNT(TicketView.parent) as countviews",
              "miniblog" : "miniblog.url as thumb"
            }`
            &hideContainers=`1`
            &groupby=`Ticket.id`
            &sortby=`{ "countviews":"desc" }`
            &tpl=`tpl.news_sidebar`
            &limit=`2`
          ]]  
      
      Не работает. На сайте пишет «Array». В логах пишет ошибку:
      (ERROR @ /home/a/ainure3c/krestilnoekruzhevo.ru/public_html/core/components/pdotools/model/pdotools/pdofetch.class.php : 172) [pdoTools] Error 42S22: Unknown column 'Ticket.id' in 'field list'
      Подскажите, как грамотно сделать запрос.
      1. Александр Мальцев
        21 августа 2018, 14:46
        Здравствуйте! Ошибка кроется в этом выражении:
        miniblog.path LIKE \'%/miniblog/\'
        Для вывода лога можно использовать параметр:
        &showLog = `1`
        1. Ник
          21 августа 2018, 16:37
          Александр, поменял на выражение
          miniblog.path LIKE '%/miniblog/'
          Но не работает
          Поставил
          &showLog = `1`
          пишет:
          Notice: Array to string conversion in */core/cache/includes/elements/modsnippet/8.include.cache.php on line 56 Array
          Почитал статью про xpdo:
          https://bezumkin.ru/training/course2/3006/
          Повставлял разные варианты, но безуспешно.

          Если вызывать так:
          {$_modx->runSnippet('!TicketLatest', [
              'action' => 'tickets',
              'parents' => 16,
              'depth' => 0,
              'limit' => 2,
              'loadModels' => 'ms2gallery',
              'tpl' => 'tpl.news_sidebar',
              'nestedChunkPrefix' => 'tickets_',
              'leftJoin' => '{
                      		"miniblog" : {
                      			"class" : "msResourceFile",
                      			"alias" : "miniblog",
                      			"on" : "miniblog.resource_id = Ticket.id AND miniblog.path LIKE \'%/miniblog/\' AND miniblog.rank=0"
                      		}
                      	}',
              'select' => '{ "miniblog" : "miniblog.url as thumb" }',
          ])}
          То картинки отображаются.
          Но в таком вызове выводятся тикеты свежие, а мне как у вас популярные нужны.
          1. Ник
            21 августа 2018, 17:45
            Все, заработало. Еще не вставил класс:
            &class=`Ticket`
            В итоге рабочий вызов:
            [[!pdoResources?
              &loadModels=`tickets,ms2gallery`
              &class=`Ticket`
              &leftJoin=`{
                "TicketView": {
                  "class": "TicketView",
                  "on": "Ticket.id = TicketView.parent"
                },
            	"miniblog" : {
            		"class" : "msResourceFile",
            		"alias" : "miniblog",
            		"on" : "miniblog.resource_id = Ticket.id AND miniblog.path LIKE '%/miniblog/' AND miniblog.rank=0"
            	}
              }`
              &parents=`16`
              &where=`{ "TicketView.timestamp:>":"[[!getDateWeekAgo]]" }`
              &select=`{
                "Ticket": "Ticket.id as id, Ticket.uri as uri, Ticket.pageTitle as pagetitle, Ticket.introtext as introtext, Ticket.publishedon as publishedon",
                "TicketView": "COUNT(TicketView.parent) as countviews",
                "miniblog" : "miniblog.url as thumb"
              }`
              &hideContainers=`1`
              &groupby=`Ticket.id`
              &sortby=`{ "countviews":"desc" }`
              &tpl=`tpl.news_sidebar2`
              &limit=`2`
            ]] 
            
            Шеф, спасибо за наводку!
      2. Антон
        09 июня 2017, 07:52
        Подскажите, а как вывести дату публикации популярных статей? (Созданных через Tickets)
        [[*publishedon:strtotime:date=`%d %B %Y`]]
        выдает разное время, а на главной вообще не показывает.
        В шаблоне:
        [[!pdoResources?
              &loadModels=`tickets`
              &class=`Ticket`
              &includeTVs=`blog-image`
              &leftJoin=`{
                "TicketView": {
                  "class": "TicketView",
                  "on": "Ticket.id = TicketView.parent"
                }
              }`
              &parents=`9`
              &where=`{"TicketView.timestamp:>":"[[!getDateWeekAgo]]"}`
              &select=`{
                "Ticket": "Ticket.id as id, Ticket.uri as uri, Ticket.pageTitle as pagetitle",
                "TicketView": "COUNT(TicketView.parent) as countviews"
              }`
              &hideContainers=`1`
              &groupby=`Ticket.id`
              &sortby=`{"countviews":"desc"}`
              &tpl=`popular-articleTPL`
              &limit=`3`
            ]]
        И чанк:
        <div class="popular-post">
        	<div class="popular-post-image">
        		<a href="[[+uri]]"><img src="[[+tv.blog-image:phpthumbof=`w=75&h=75&zc=1`]]" alt="" /></a>
        	</div>
        	<div class="popular-post-info">
        		<h4><a href="[[+uri]]">[[+pagetitle]]</a></h4>
        		<p>[[*publishedon:strtotime:date=`%d %B %Y`]]</p>
        	</div>
        </div>
        1. Александр Мальцев
          09 июня 2017, 13:51
          1. Добавить в выборку дату публикации (publishedon):
          &select=`{
            "Ticket": "Ticket.id as id, Ticket.uri as uri, Ticket.pageTitle as pagetitle, Ticket.publishedon as publishedon",
            "TicketView": "COUNT(TicketView.parent) as countviews"
          }`
          
          2. В чанке в необходимое место добавить следующий плейсхолдер:
          [[+publishedon:date=`%d %B %Y`]]
          
        2. Sergey
          03 января 2017, 17:01
          Александр, а подскажите каким образом при использовании TicketForm обработать ТВ с типом список(одиночынй выбор) У ТВ в поле «Возможные значения» стоит конструкция @SELECT `name` FROM `modx_migxdb_cityselect`. Т.е. формируем через админку и MIGx список городов. И при формировании Тикета пользователем он должен выбрать из селекта свой город. Вот каким образом это селект вывести в форму тикета?
          1. Кирилл
            16 сентября 2016, 13:01
            Ребят вопрос, а как сделать так чтобы я мог выбирать сам статью и выводить ее в разделе, или на странице? Например селектом, и еще (Но это уже совсем круто) чтобы можно было в поле вбивать текст и статья искалась, чтобы не весь список статей вылазил!
            1. Александр Мальцев
              17 сентября 2016, 13:51
              Можно начать делать так:
              [[!ajaxpage]]
              <div id="id1"></div>
              <div id="id2">
                <select id="myid">
                  [[!pdoResources?
                    &tpl=`@INLINE <option value="[[+id]]">[[+pagetitle]]</option>`
                    &parents=`13`
                  ]]
                </select>
              </div>
              <button id="mybtn">Получить контент ресурса</button>
              <script>
              // после загрузки страницы
              $(function() {
                $('#mybtn').click(function(){
                  var id = $('#myid').val().toString(); 
                  $.post(document.location.href, {action: 'showContent', pageid: id}, function(data) {
                    $('#id1').html(data);
                  });
                return false;
                });
              });
              </script>
              
              Т.е. с помощью pdoResources выводим необходимый список страниц. После выбора необходимой страницы и нажатию на кнопку, запускаем ajax-запрос на сервер, который передаёт php скрипту ([[!ajaxpage]]), расположенному на этой же странице, id ресурса, который мы хотим получить. После получения выводим его в блок, имеющий id=«id1».
              php скрипт [[!ajaxpage]] — это сниппет с именем ajaxpage, который возвращает контент ресурса по его id.
              Код сниппета ajaxpage будет следующим:
              <?php
              if (empty($_SERVER['HTTP_X_REQUESTED_WITH']) || $_SERVER['HTTP_X_REQUESTED_WITH'] != 'XMLHttpRequest') {return;}
              if (empty($_POST['action'])) {return;}
              $res = '';
              switch ($_POST['action']) {
                case 'showContent':
                  $resource = $modx->getObject('modResource',$_POST['pageid']);
                  $res = $resource->get('content');
                  break;
              }
              if (!empty($res)) {
                die($res);
              };
              
            2. Алексей
              01 сентября 2016, 21:24
              Здравствуйте.
              Сделал как у вас — все работает, спасибо.
              Но вот такая проблема (с данной статьей не совсем связанная), — количество просмотров, как будто закешировалось (вывожу [[+views]]. После просмотра новых тикетов показывается (после перезагрузки страницы) один просмотр, последующие визиты на счетчике не отражаются. В настройках tickets «Считать просмотры страниц гостями — да». Количество комментариев отображается корректно, а просмотры не считает.
              Подскажите в чем причина.
              1. Александр Мальцев
                03 сентября 2016, 05:37
                В tickets для подсчёта просмотра ресурсов незарегистрированными пользователями используется не прямой алгоритм, т.е. количество просмотров не увеличится, если пользователь просто перезагрузит страницу. Попробуйте страницу открыть в другом браузере.
                Так же сниппет который извлекает данные из базы должен быть вызван не кэшированным:
                [[!TicketMeta? &tpl=`tpl.Tickets.meta`]]
                
              2. Андрей
                16 апреля 2016, 00:44
                Александр, подскажите пожалуйста, как мне реализовать описанный функционал, если в выборку должны попасть изображения ms2Gallery.
                Делаю так
                [[!getTickets?
                &parents=`7` 
                &depth=`1` 
                &tpl=`tpl.post` 
                &includeContent=`1`
                &limit=`5`
                &hideContainers=`1`
                &loadModels=`ms2gallery`
                &where=`{"TicketView.timestamp:>":"[[!getDateWeekAgo]]"}`
                &leftJoin=`{
                "120x90": {"class":"msResourceFile","alias":"120x90", "on": "120x90.resource_id = Ticket.id AND 120x90.path LIKE '%/120x90/' AND 120x90.rank=0"},
                "TicketView": {"class": "TicketView","on":"Ticket.id = TicketView.parent"}
                }`
                &select=`{"Ticket":"*","120x90":"120x90.url as 120x90" "TicketView": "COUNT(TicketView.parent) as countviews"}`
                &groupby=`Ticket.id`
                &showLog=`0`
                ]]
                Чанк tpl.post
                <div class="media post">
                  <div class="pull-left">
                	<img src="[[+120x90]]" alt="nature" class="img-fluid">
                  </div>
                  <div class="media-body">
                	<a class="post-title" href="blog-single-left">[[+pagetitle]]</a> [[+countviews]]
                	<div class="post-meta">
                	  <a href="#">[[+fullname]]</a> [[+date_ago]]
                	</div>
                  </div>
                </div>
                В выводе не отображаются countviews и изображения. Спасибо
                1. Андрей
                  16 апреля 2016, 03:25
                  Увидел сам. Запятую в селекте не поставил, поэтому вывод не тот был
                2. Алексей
                  15 февраля 2016, 19:50
                  Шеф, надо добавить
                  &hideContainers=`1`
                  А то так разделы/категории попадают в список статей)
                  1. Александр Мальцев
                    17 февраля 2016, 11:25
                    Спасибо, Алексей.
                    У меня в примере не было категорий, поэтому этот момент упустил.
                    1. Алексей
                      17 февраля 2016, 15:18
                      Считай меня сектантом, но лучше Благо-дарить, чем желать спасения)
                      Для этого у тебя есть такие посетители как Я)))
                      Рад помочь хорошему человеку! Благодаря твоему блогу, Я прогрессирую в изучении Модэкса, чему очень рад. Находя такие мелкие недочёты, Я вижу, что Я развиваюсь)
                  2. Алексей
                    13 февраля 2016, 13:28
                    Шеф, вопрос для саморазвития)
                    Таблицу TicketView нашел в бд, а вот вторую таблицу Ticket, которая основная, не могу найти. Откуда вообще такие названия, если в бд они по другому называются? А то не пойму как работают leftJoin и select с такими названиями таблиц)
                    1. Александр Мальцев
                      13 февраля 2016, 13:59
                      Это не совсем таблицы, это название классов.
                      Для компонента Tickets они берутся из файла tickets.mysql.schema.xml.
                      Он находится в каталоге /core/components/tickets/model/schema.
                      Если его открыть, то будет видно, что данный класс — это расширение modResource.
                      <object class="Ticket" extends="modResource">
                      А modResource — это уже стандартный класс MODX. Он берётся уже из файла modx.mysql.schema.xml. Он находится в каталоге /core/model/schema.
                      В этом файле видно что с классом modResource связана таблица site_content.
                      <object class="modResource" table="site_content" extends="modAccessibleSimpleObject" inherit="single">
                      В итоге класс Ticket — это таблица site_content. В статье описано более просто, для того чтобы не вдаваться в подробности. Чтобы было более понятно начинающему разработчику. Т.е. таблица TicketView — просмотры, Ticket — тикеты.
                      1. Алексей
                        13 февраля 2016, 18:25
                        Благодарю! Буду теперь знать что и как)
                        Урока по созданию слайдера с использованием FlexSlider, например, стоит ожидать?)
                        1. Александр Мальцев
                          14 февраля 2016, 15:41
                          Да, можно.
                    2. Аня
                      13 февраля 2016, 12:50
                      Привет Александр.
                      Есть вопрос. Заметила, что у modx revo картинки индексируются как отдельная страница.
                      Это как то влияет на продвижение в поисковиках? Ведь общее количество загруженных страниц получается явно больше, за счет индексации изображений. Есть ли санкции у писковиков для modx за это?))
                      1. Александр Мальцев
                        13 февраля 2016, 13:28
                        Привет.
                        Что-то новенькое )
                        У меня количество страниц равно количеству загруженных страниц.
                      2. Алексей
                        08 февраля 2016, 17:16
                        Шеф, ты как в воду глядел) Я как раз хотел спросить как реализовать популярные статьи)
                        А если нужны вообще популярные, а не за последние 7 дней. То просто делать без сниппета и из кода убрать
                        &where=`{"TicketView.timestamp:>":"[[!getDateWeekAgo]]"}`
                        ?
                        1. Александр Мальцев
                          10 февраля 2016, 12:57
                          Логично :)
                          А зачем спрашиваешь? Если ты сам ответил на свой вопрос.
                          1. Алексей
                            10 февраля 2016, 14:26
                            Шеф, это был уточняющий вопрос, Я не был уверен в своём ответе) Сейчас только буду приступать делать, решил заранее подстраховаться) Видишь, твои уроки не проходят даром.
                            Два вопроса не по теме)
                            Ты вообще никаким редактором в админке не пользуешься?
                            Водяные знаки к картинкам делаешь в каком-то онлайн сервисе или как-то в модексе это дело автоматизировал?
                            1. Александр Мальцев
                              11 февраля 2016, 15:24
                              Предпочитаю чистый HTML код. Поэтому из редакторов использую только Ace.
                              Картинки делаю в GIMP, а именно их обрезку, настройку (если необходимо), наложение водяных знаков и сжатие. Там всё это делается за несколько кликов. В автоматизации данного процесса с помощью php не вижу смысла, т.к. не делаю больше 10 картинок в неделю.
                              1. Алексей
                                14 февраля 2016, 13:07
                                Для чистого кода MarkitUp очень удобен, он чисто теги вставляет. А кроме того в настройках можно заточить теги под Markdown. Но Тексетс не хочет его подхватывать. Так бы было вообще шикарно. Попробуй на досуге MarkitUp для редактирования ресурсов.
                                1. Александр Мальцев
                                  14 февраля 2016, 15:34
                                  Знаю что такое MarkitUp. Но, Ace нравится больше.
                        Войдите, пожалуйста, в аккаунт, чтобы оставить комментарий.