Счетчик посещений страницы в MODX

Александр Мальцев
Александр Мальцев
9K
19
Содержание:
  1. Создание плагина по учёту количества просмотров
  2. Сниппет для вывода самых популярных статей
  3. Комментарии

Статья, в которой рассмотрим процесс создания плагина по учёту количества просмотров страниц (тикетов) в CMS MODX Revolution. Плагин будет напоминать собой компонент HitsPage. Но в отличие от него он будет считать уникальные просмотры ресурсов (посредством технологии COOKIE), а также предоставлять плейсхолдеры для вывода количества просмотров на страницах. Кроме этого создадим ещё сниппет для вывода 5 самых популярных статей сайта.

Создание плагина по учёту количества просмотров

Процесс создания плагина, с помощью которого будем считать просмотры страниц, выполним посредством 2 действий:

  1. Создадим TV поле для ресурсов. В данное поле будем записывать просмотры страницы.
  2. Напишем плагин, который будет записывать в данное TV поле просмотры.

Создание TV поля для хранения счётчика просмотров

Создадим дополнительное поле (TV-параметр) со следующими характеристиками:

  • имя: views;
  • подпись: Количество просмотров;
  • тип ввода: Число;
  • значение по умолчанию: 0;
  • тип вывода: По умолчанию;
  • доступно для шаблонов: отметить галочками необходимые шаблоны (которые используются для вывода тикетов и разделов).
MODX Revo - Создание дополнительного поля views

В разделе TV-поле views используется для хранения числа просмотров всех его ресурсов. Данное поле позволит вывести количество просмотров всех его дочерних ресурсов без суммирования (т.е. без использования в запросе агрегатной функции SUM).

Создание плагина для обновления TV-поля views

Разработаем в CMS MODX Revolution плагин (например, с именем viewsCount) по учёту количества просмотров статей (тикетов).

Тикет – это расширение стандартного типа ресурса modDocument. Данные типы ресурсов (тикет и раздел для тикетов) можно создавать только после установки дополнения CMS MODX Revolution Tickets.

Код плагина будет выполняться при наступлении системного события OnLoadWebDocument. Данное событие наступает после загрузки документа, но до обработки MODX тегов. Для получения текущего ресурса в плагине будем использовать конструкцию $modx-resource.

Код плагина viewsCount:

<?php
if ($modx->event->name == 'OnLoadWebDocument') {
  // id дополнительного поля views
  $id_tv = 2;  
  // получим ресурс
  $resource = $modx->resource;
  // получим тип ресурса
  $class_key = $resource->get('class_key');

  // если тип ресурса == Ticket
  if ($class_key == 'Ticket') {
    // получим id ресурса
    $id = $resource->get('id');
    // получим tv поле
    $tv_ticket = $modx->getObject('modTemplateVarResource',array(
      'tmplvarid' => $id_tv,
      'contentid' => $id
    ));

    // если не существует COOKIE viewticket
    if (!isset($_COOKIE['viewticket'])) {

      // если tv поле является объектом
      if (is_object($tv_ticket)) {
        // получаем его значение
        $views_ticket = $tv_ticket->get('value');
        // увеличиваем количество просмотров на 1
        $views_ticket++;
        // сохраняем количество просмотров в таблицу
        $tv_ticket->set('value',$views_ticket);
        // сохраняем
        $tv_ticket->save();        
      } else {
        // устанавливаем количество просмотров равным 1
        $views_ticket = 1;
        // создаём новый объект modTemplateVarResource
        $tv_ticket = $modx->newObject('modTemplateVarResource');
        // установливаем значение поля tmplvarid
        $tv_ticket->set('tmplvarid',$id_tv);
        // установливаем значение поля contentid       
        $tv_ticket->set('contentid',$id);
        // устанавливаем значение поля value
        $tv_ticket->set('value',$views_ticket);
        // сохраняем
        $tv_ticket->save();
      }      

      // получаем родительский ресурс      
      if ($section = $resource->getOne('Parent')) {
        $id_section = $section->get('id');
        $tv_section = $modx->getObject('modTemplateVarResource',array(
         'tmplvarid' => $id_tv,
         'contentid' => $id_section
        ));
        if (is_object($tv_section)) {
          // получаем его значение
          $views_section = $tv_section->get('value');
          // увеличиваем количество просмотров на 1
          $views_section++;
          // сохраняем количество просмотров в таблицу
          $tv_section->set('value',$views_section);
          // сохраняем
          $tv_section->save();
        } else {
          // устанавливаем количество просмотров секции равным 1
          $views_section = 1;
          // создаём новый объект modTemplateVarResource
          $tv_section = $modx->newObject('modTemplateVarResource');
          // установливаем значение поля tmplvarid
          $tv_section->set('tmplvarid',$id_tv);
          // установливаем значение поля contentid       
          $tv_section->set('contentid',$id_section);
          // устанавливаем значение поля value
          $tv_section->set('value',$views_section);
          // сохраняем
          $tv_section->save();          
        }
      }        
      // отправка COOKIE viewticket
      setcookie('viewticket', '1',time() + (86400 * 365),'/'.$resource->get('uri')); 
    } else {
      $views_ticket = $tv_ticket->get('value');
    }
    // устанавливаем плейсхолдер viewsTicket
    $modx->setPlaceholder('viewsTicket', $views_ticket);
  } 

  if ($class_key == 'TicketsSection') {
    // получим id ресурса
    $id = $resource->get('id');
    // получим tv поле
    $tv_section = $modx->getObject('modTemplateVarResource',array(
      'tmplvarid' => $id_tv,
      'contentid' => $id
    ));
    // если tv поле является объектом
    if (is_object($tv_section)) {
      // получаем его значение
      $views_section = $tv_section->get('value');
      // устанавливаем плейсхолдер viewsTicket
      $modx->setPlaceholder('viewsSection', $views_section);
    }
  }
}

Для того чтобы плагин отслеживал данное событие, его необходимо отметить флажком на вкладке "Системные события".

Установка событий, которые должен отслеживать плагин viewsCount

Для того чтобы счётчик посещений не увеличивался при просмотре некоторого ресурса одним и тем же пользователем будем использовать COOKIE. Отправку куки будем осуществлять посредством функции setcookie, а её проверку - с помощью ассоциативного php массива $_COOKIE.

Кроме этого с помощью этого плагина будем ещё устанавливать плейсхолдер, с помощью которого можно будет сразу вывести просмотры текущего ресурса или раздела на экран:

  • [[!+viewsTicket]] (для тикета).
  • [[!+viewsSection]] (для раздела с тикетами).

Плейсхолер необходимо вызывать не кэшированным.

Сниппет для пересчёта значений полей views разделов

Если по каким-то причинам вам необходимо пересчитать количество просмотров секций (разделов с тикетами), то можете воспользоваться следующим сниппетом:

<?php
$tv_id = 2;
$sections = $modx->getIterator('modResource', array('class_key' => 'TicketsSection'));
foreach ($sections as $section) {
  $tv = $modx->getObject('modTemplateVarResource', array(
    'tmplvarid' => $section->get('id'),
    'contentid' => $tv_id
  ));
  if ($tv) {
    $tickets = $modx->getIterator('modResource', array('class_key' => 'Ticket','parent' => $section->get('id')));
    $views = 0;
    foreach ($tickets as $ticket) {
      $tv_ticket = $modx->getObject('modTemplateVarResource', array(
        'tmplvarid' => $ticket->get('id'),
        'contentid' => $tv_id
      ));
      if ($tv_ticket) {
        $views += $tv_ticket->get('value');
      }
    }
    $tv->set('value',$views);
    $tv->save();
  }
}

Для выполнения этого сниппета (например, countViewsSection) необходимо поместить его вызов в любой ресурс:

[[!countViewsSection]]

После открытия ресурса в браузере значение TV полей разделов views пересчитаются.

Сниппет для вывода самых популярных статей

Создадим сниппет для вывода самых просматриваемых статей (тикетов) на сайте. Работа сниппета осуществляется на значениях TV-поля views (в примере id поля равно 2). Для снижения нагрузки, результат работы сниппета будем сохранять в кэш на 3 часа. Оформление результатов будем выполнять с помощью чанка.

Код сниппета topTicketsView:

<?php
// попробуем получить содержимое кэша по ключу topticketview
$output = $modx->cacheManager->get('topticketview');
// если кэш не найден, то...
if (empty($output)) {
  // создадим новый запрос, используя объект modTemplateVarResource
  $query = $modx->newQuery('modTemplateVarResource');
  // присоединяем к нему объект modResource
  $query->innerJoin('modResource','Resource');
  // добавляем к запросу условие
  $query->where(array(
    'modTemplateVarResource.tmplvarid'=>'2',
    'Resource.class_key' => 'Ticket'
  ));
  // выполним сортировку по TV (для преобразования строки в беззнаковое число используем mysql функцию CAST)
  $query->sortby('CAST(modTemplateVarResource.value as unsigned)',DESC);
  // укажем поля для выборки
  $query->select(array(
    'Resource.pagetitle as pagetitle',
    'Resource.uri as uri',
    'modTemplateVarResource.value as countviews'
  ));
  // ограничим выборку 5 полями
  $query->limit(5);
  // подготовим и выполним запрос (используем PDO)
  $query->prepare();
  $query->stmt->execute();
  $rows = $query->stmt->fetchAll(PDO::FETCH_ASSOC);
  // переменная для хранения результата
  $output = '';
  // номер записи (для использования в чанке)
  $idx = 0;
  // переберём все записи
  foreach ($rows as $row) {
    $idx++;
    $row['idx'] = $idx;
    // оформлять результат будем с помощью чанка tplTopViewTickets
    $output .= $modx->getChunk('tplTopViewTickets',$row);
  }
  // сохраним результат на 3 часа в кэше (для сокращения нагрузки)
  $modx->cacheManager->set('topticketview',$output,10800);
}
// возвратим результат
return $output;

Чанк tplTopViewTickets:

<li>
  <a href="[[+uri]]">[[+pagetitle]]</a> (Просмотры: [[+countviews]])
</li>

Вызов сниппета topTicketsView в шаблоне:

<section>
  Популярные статьи:
  <ol>
    [[!topTicketsView]]    
  </ol>
</section>

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

Пример дизайна секции самых просматриваемых ресурсов на сайте

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

  1. Y2507
    Y2507
    19.12.2021, 14:38
    Добрый день!
    Такой вопрос в тикете выводится количество просмотров, а как вывести в раздел с тикетами там не работает, переделал как вы сделали новый плагин viewsResource,

    Заметил что у вас на сайте тоже странный подсчет, в разделе с тикетами 1.7K а если зайти в статью то там 101
    1. Y2507
      Y2507
      19.12.2021, 14:37
      Добрый день!
      Такой вопрос в тикете выводится количество просмотров, а как вывести в раздел с тикетами там не работает, переделал как вы сделали новый плагин viewsResource,

      Заметил что у вас на сайте тоже странный подсчет, в разделе с тикетами 1.7K а если зайти в статью то там 101
      1. Олег
        Олег
        08.08.2020, 23:25
        Александр, установил HitsPage, все ОК.
        Но возникла проблема.
        MODX «закалена», папка CORE вынесена за корневую директорию.
        А после установки HitsPage в корне система создает вторую папку CORE с файлами HitsPage.
        Как установить компонент вне корня?
        1. Александр Мальцев
          Александр Мальцев
          10.08.2020, 13:06
          Скорее всего при установке компонента путь к папке core прописан «жёстко». Нужно чтобы разработчик компонента поправил путь к папке core так, чтобы он брался из константы MODX_CORE_PATH.
        2. Дмитрий
          Дмитрий
          20.04.2020, 22:29
          Александр, здравствуйте!
          А есть код который просто бы учитывал просмотр определенной ссылки на странице сайта с несколькими такими же, но разными ссылками и количество выводилось бы отдельно каждой ссылки через html код?
          Подскажите пожалуйста.
          1. Александр Мальцев
            Александр Мальцев
            24.04.2020, 15:12
            Здравствуйте!
            Наиболее простой вариант – это добавить к ссылкам utm метки. Если нужно просто статистика, то можно это выполнять через Google Analytics. Если нужно выводить на странице, то тоже можно попробовать получать эти данные из этого сервиса.
            Ну или пойти по обычному пути, а именно написать свой компонент для MODX, решающий эту задачу. Если для хранения данных подойдет TV, то можете выполнить это через них.
            1. Дмитрий
              Дмитрий
              24.04.2020, 21:31
              Благодарю вас! Видимо да, нужно изучить MODX, но пока что я не имею понятия, что это)
              1. Александр Мальцев
                Александр Мальцев
                25.04.2020, 15:37
                Да, если требуется функционал которого нет по умолчанию в MODX и готового компонента для этого нет, то без изучения MODX тут уже не обойтись.
          2. kalisto
            kalisto
            19.04.2018, 19:42
            И у меня не работает счетчик страниц.
            1. Александр
              Александр
              09.03.2017, 12:10
              Добрый день, подсчет заработал, подскажите, пожалуйста, а как вывести популярные статьи из определенной категории?
              1. Александр Мальцев
                Александр Мальцев
                09.03.2017, 15:18
                Добрый день, Александр.
                В этом случае необходимо немного доработать сниппет topTicketsView:
                <?php
                $parent = $modx->getOption('parent', $scriptProperties, 0);
                $output = $modx->cacheManager->get('topticketview-'.$parent);
                if (empty($output)) {
                  $query = $modx->newQuery('modTemplateVarResource');
                  $query->innerJoin('modResource','Resource');
                  $query->where(array(
                    'modTemplateVarResource.tmplvarid'=>'2',
                    'Resource.class_key' => 'Ticket'
                  ));
                  if ($parent != 0) {
                    $query->where(array(
                      'Resource.parent' => $parent
                    ));  
                  }
                  $query->sortby('CAST(modTemplateVarResource.value as unsigned)',DESC);
                  $query->select(array(
                    'Resource.pagetitle as pagetitle',
                    'Resource.uri as uri',
                    'modTemplateVarResource.value as countviews'
                  ));
                  $query->limit(5);
                  $query->prepare();
                  $query->stmt->execute();
                  $rows = $query->stmt->fetchAll(PDO::FETCH_ASSOC);
                  $output = '';
                  $idx = 0;
                  foreach ($rows as $row) {
                    $idx++;
                    $row['idx'] = $idx;
                    $output .= $modx->getChunk('tpl.top.views.tickets',$row);
                  }
                  $modx->cacheManager->set('topticketview-'.$parent,$output,10800);
                }
                return $output;
                
                Теперь при вызове сниппета нам доступен параметр parent. Если его не устанавливать, то выборка не будет ограничиваться. А если ему указать id категории, то выбор тикетов будет ограничиваться ей.
                <!-- Самые популярные ресурсы на сайте -->
                [[!topTicketsView]]
                <!-- Самые популярные ресурсы в секции, имеющей id = 15
                [[!topTicketsView? &parent=`15`]]
                
                1. Александр
                  Александр
                  09.03.2017, 16:38
                  Всё супер! Заработало! Спасибо вам огромное!
              2. Демьян Золин
                Демьян Золин
                28.02.2017, 21:57
                1. Создал TV
                2. Создал плагин
                3. в Чанк «Footer» добавил
                [[!+viewsTicket]]
                [[!+viewsSection]]
                [[*views]]
                В результате видно не меняющуюся цифру «0» от этого [[*views]]
                1. Александр Мальцев
                  Александр Мальцев
                  02.03.2017, 16:36
                  Скорее всего, у вас ресурс не является Ticket.
                  Если вам необходимо организовать плагин для обычных ресурсов (modDocument), то вам нужно вместо Ticket использовать modDocument:
                  // если тип ресурса == modDocument
                  if ($class_key == 'modDocument')
                  
                  Лучше не использовать [[*views]], т.к. он создаст дополнительные запросы к базе данных и увеличит время парсинга страницы. Используйте подготовленный плейсхолдер [[!+viewsTicket]].
                  1. Демьян Золин
                    Демьян Золин
                    03.03.2017, 01:14
                    Верно ресурс не является Ticket.
                    // если тип ресурса == modDocument
                    if ($class_key == 'modDocument') это исправил.
                    [[*views]] убрал, сразу время уменьшилось.
                    но это [[!+viewsTicket]] не отображается, может еще что поменять или весь плагин другой нужно?
                    1. Александр Мальцев
                      Александр Мальцев
                      08.03.2017, 15:29
                      Немного изменил плагин, теперь он предназначен для подсчёта любых ресурсов у которых есть TV поле views (указание какое TV-поле views имеет id осуществляется посредством переменной $id_tv).
                      <?php
                      if ($modx->event->name == 'OnLoadWebDocument') {
                        // id дополнительного поля views
                        $id_tv = 2;
                        // получим ресурс
                        $resource = $modx->resource;
                        // проверить есть ли у ресурса (его шаблона) TV с указанным id
                        $isTV = $modx->getObject('modTemplateVarTemplate',array(
                          'tmplvarid' => $id_tv,
                          'templateid' => $resource->get('template')
                        ));
                        // если TV нет, то завершаем работу
                        if (!is_object($isTV)) {
                          return;
                        }
                        // получим id ресурса
                        $id = $resource->get('id');
                        // получим url
                        $url = $modx->makeUrl($id,'','',-1);
                      
                        // получим tv поле
                        $tv_resource = $modx->getObject('modTemplateVarResource',array(
                          'tmplvarid' => $id_tv,
                          'contentid' => $id
                        ));
                        // получим id главной страницы
                        $id_start = $modx->getOption('site_start');
                        
                        // если не существует COOKIE viewresource и viewmainresource
                        if ((!isset($_COOKIE['viewresource']) && ($id!=$id_start)) || (!isset($_COOKIE['viewmainresource'])) && ($id==$id_start) ) {
                          // если tv поле является объектом
                          if (is_object($tv_resource)) {
                            // получаем его значение
                            $views_resource = $tv_resource->get('value');
                            // увеличиваем количество просмотров на 1
                            $views_resource++;
                            // сохраняем количество просмотров в таблицу      
                            $resource->setTVValue(2, $views_resource);
                          } else {
                            // устанавливаем количество просмотров равным 1
                            $views_resource = 1;
                            // сохраняем
                            $resource->setTVValue(2,$views_resource);
                          }      
                          // отправка COOKIE
                          if ($id==$id_start) {
                            setcookie('viewmainresource', '1',time() + (86400 * 365),'/');       
                          } else {
                            setcookie('viewresource', '1',time() + (86400 * 365),'/'.$url);
                          }
                        } else {
                          $views_resource = $tv_resource->get('value');
                        }
                        // устанавливаем плейсхолдер viewsTicket
                        $modx->setPlaceholder('viewsResource', $views_resource);
                      }
                      
                      Имя плейсхолдера изменил на более логичное viewsResource.
                      1. Максим
                        Максим
                        21.02.2021, 18:12
                        if (!is_object($isTV)) {
                            return;
                          }
                        почему-то вот из-за этого дальше не отрабатывает ничего, но даже если убираем это вроде всё начинает работать, само поле views начинает нормально считаться но на вывод через viewsResource выводиться всегда главной страницы причём счётчик увеличивается каждый раз когда обновляешь страницу прмчём на рандомное значение от 2 до 5.
                        Не могу никак понять как в getPlaceholder вывести значение именно той новости что нужна
                        вывод идёт через pdotools, используется collection
                        		{$_modx->runSnippet('!pdoPage', [
                        			'tpl' => '@FILE chunks/header_news.tpl',
                        			'parents' => 2,
                        			'limit' => '1',
                        			'includeTVs' => 'imageMajor, headindex, views',
                        			'processTVs' => 1,
                        			'where' => '{"headindex":1}'
                        
                        		])}
                        в чанке указываю так
                        {$_pls["tv.views"]} - выводится нужное значение из конкретной новост
                        {$modx->getPlaceholder('viewsResource')} - выводится значение той странице где расположен чанк вывода то есть главной страницы
                        [[+viewsResource]] - тоже самое что и предыдущее 
                        itchief.ru/assets/uploadify/1/b/8/1b88e5cd21a6af93445a6ac206dcd79a.png
                        1. Максим
                          Максим
                          21.02.2021, 18:39
                          В таком варианте хотя бы работает, но через viewsResource выводится значение текущей страницы, даже если поле шаблону не задано, приходится выводить через тв поле views что как я понимаю не очень хорошо
                          <?php
                          if ($modx->event->name == 'OnLoadWebDocument') {
                              // id дополнительного поля views
                              $id_tv = 6;
                              // получим ресурс
                              $resource = $modx->resource;
                              $id = $resource->get('id');
                              // проверить есть ли у ресурса (его шаблона) TV с указанным id
                              $isTV = $modx->getObject('modTemplateVarTemplate',array(
                                  'tmplvarid' => $id_tv,
                                  'templateid' => $id
                              ));
                          
                              // получим url
                              $url = $modx->makeUrl($id,'','',-1);
                          
                              // получим tv поле
                              $tv_resource = $modx->getObject('modTemplateVarResource',array(
                                  'tmplvarid' => $id_tv,
                                  'contentid' => $id
                              ));
                              
                              // получим id главной страницы
                              $id_start = $modx->getOption('site_start');
                          
                              // если не существует COOKIE viewresource и viewmainresource'
                              if (!isset($_COOKIE['viewresource'])) {
                                  // если tv поле является объектом
                                  if (is_object($tv_resource)) {
                                      // получаем его значение
                                      $views_resource = $tv_resource->get('value');
                                      // увеличиваем количество просмотров на 1
                                      $views_resource++;
                                      // сохраняем количество просмотров в таблицу
                                      $tv_resource->set('value',$views_resource);
                                      $tv_resource->save();   
                                  } else {
                                      // устанавливаем количество просмотров равным 1
                                      $views_resource = 100;
                                      // сохраняем
                                      $tv_resource->$tv_resource->get('value');
                                      $tv_resource->save();   
                                  }
                                  // отправка COOKIE
                                  if ($id==$id_start) {
                                      setcookie('viewmainresource', '1',time() + (86400 * 365),'/');
                                  } else {
                                      setcookie('viewresource', '1',time() + (86400 * 365),'/'.$url);
                                  }
                              } else {
                                  $views_resource = $tv_resource->get('value');
                              }
                              // устанавливаем плейсхолдер viewsTicket
                              $modx->setPlaceholder('viewsResource', $views_resource);
                          }
                          1. Максим
                            Максим
                            28.02.2021, 19:11
                            Приношу извинения за выше написанное лучше удалите этот позор нафиг)
                            Всё работает то что вы написали

                            просто кроме $id_tv = 2; надо было поменять $resource->setTVValue(2,$views_resource); и тут
                            2 на нужный id tv
                        2. Демьян Золин
                          Демьян Золин
                          10.03.2017, 15:28
                          В плагин viewsCount поместил вышеприведенный код.
                          В чанк «Footer» добавил [[!+viewsResource]].
                          На странице не отображается, даже 0 и ноль не меняется в менеджере ресурса.
                          1. alex1
                            alex1
                            21.12.2017, 13:19
                            тоже самое сделал, вообще в ошибку 500 падает
                  Войдите, пожалуйста, в аккаунт, чтобы оставить комментарий.