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

Александр Мальцев
Александр Мальцев
8,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. Олег
    08 августа 2020, 23:25
    Александр, установил HitsPage, все ОК.
    Но возникла проблема.
    MODX «закалена», папка CORE вынесена за корневую директорию.
    А после установки HitsPage в корне система создает вторую папку CORE с файлами HitsPage.
    Как установить компонент вне корня?
    1. Александр Мальцев
      10 августа 2020, 13:06
      Скорее всего при установке компонента путь к папке core прописан «жёстко». Нужно чтобы разработчик компонента поправил путь к папке core так, чтобы он брался из константы MODX_CORE_PATH.
    2. Дмитрий
      20 апреля 2020, 22:29
      Александр, здравствуйте!
      А есть код который просто бы учитывал просмотр определенной ссылки на странице сайта с несколькими такими же, но разными ссылками и количество выводилось бы отдельно каждой ссылки через html код?
      Подскажите пожалуйста.
      1. Александр Мальцев
        24 апреля 2020, 15:12
        Здравствуйте!
        Наиболее простой вариант – это добавить к ссылкам utm метки. Если нужно просто статистика, то можно это выполнять через Google Analytics. Если нужно выводить на странице, то тоже можно попробовать получать эти данные из этого сервиса.
        Ну или пойти по обычному пути, а именно написать свой компонент для MODX, решающий эту задачу. Если для хранения данных подойдет TV, то можете выполнить это через них.
        1. Дмитрий
          24 апреля 2020, 21:31
          Благодарю вас! Видимо да, нужно изучить MODX, но пока что я не имею понятия, что это)
          1. Александр Мальцев
            25 апреля 2020, 15:37
            Да, если требуется функционал которого нет по умолчанию в MODX и готового компонента для этого нет, то без изучения MODX тут уже не обойтись.
      2. kalisto
        19 апреля 2018, 19:42
        И у меня не работает счетчик страниц.
        1. Александр
          09 марта 2017, 12:10
          Добрый день, подсчет заработал, подскажите, пожалуйста, а как вывести популярные статьи из определенной категории?
          1. Александр Мальцев
            09 марта 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 марта 2017, 16:38
              Всё супер! Заработало! Спасибо вам огромное!
          2. Демьян Золин
            28 февраля 2017, 21:57
            1. Создал TV
            2. Создал плагин
            3. в Чанк «Footer» добавил
            [[!+viewsTicket]]
            [[!+viewsSection]]
            [[*views]]
            В результате видно не меняющуюся цифру «0» от этого [[*views]]
            1. Александр Мальцев
              02 марта 2017, 16:36
              Скорее всего, у вас ресурс не является Ticket.
              Если вам необходимо организовать плагин для обычных ресурсов (modDocument), то вам нужно вместо Ticket использовать modDocument:
              // если тип ресурса == modDocument
              if ($class_key == 'modDocument')
              
              Лучше не использовать [[*views]], т.к. он создаст дополнительные запросы к базе данных и увеличит время парсинга страницы. Используйте подготовленный плейсхолдер [[!+viewsTicket]].
              1. Демьян Золин
                03 марта 2017, 01:14
                Верно ресурс не является Ticket.
                // если тип ресурса == modDocument
                if ($class_key == 'modDocument') это исправил.
                [[*views]] убрал, сразу время уменьшилось.
                но это [[!+viewsTicket]] не отображается, может еще что поменять или весь плагин другой нужно?
                1. Александр Мальцев
                  08 марта 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 февраля 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 февраля 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 февраля 2021, 19:11
                        Приношу извинения за выше написанное лучше удалите этот позор нафиг)
                        Всё работает то что вы написали

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