Добавление оглавления к статьям в MODX

Добавление оглавления к статьям в MODX
Содержание:
  1. Сниппет для формирования содержания
  2. Плагин для добавления id к заголовкам
  3. Скрипт на PHP для добавления id к заголовкам ресурсов
  4. Комментарии

В этом теме мы разберём как к статьям и длинным постам на сайте добавить оглавление. Это руководство будет касаться только сайтов, работающих под управлением CMS MODX.

Зачем вообще создавать содержание? Ответ на этот вопрос очень прост. Это нужно для того, чтобы можно было более просто ориентироваться в информации, представленной на той или этой странице сайта, а также очень быстро переходить к интересующим разделам.

Что мы здесь рассмотрим?

  • пример сниппета, который будет генерировать оглавление из заголовков;
  • плагин, через который будем изменять контент статьи добавляя при его сохранении id к заголовкам h2..h4 (если конечно у этих заголовках нет id);
  • php-скрипт для массового обновления контента ресурсов, находящихся в указанных родителях (в большинстве случаев этот скрипт нужно запустить всего один раз для каждого родителя, он добавит id к заголовкам, находящихся в контенте, у которых их нет).

Сниппет для формирования содержания

Пример простого сниппета, который можно использовать для создания содержания из заголовков <h2>. Назовём его, например, createTableOfContents.

MODX сниппет для создания содержания из заголовков

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

<?php
$id = $modx->resource->id;
$output = $modx->cacheManager->get($id, array(xPDO::OPT_CACHE_KEY => 'table_of_contents'));
if (empty($output)) {
  $output = '';
  $url = $modx->makeUrl($id, '', '', 'abs');
  $content = $modx->resource->content;
  preg_match_all('#<h2.*?>(.*?)<\/h2>#', $content, $matches);
  foreach ($matches[0] as $index => $header) {
    preg_match('#.*?id="(.*?)".*?#', $header, $href);
    $output .= $modx->getChunk('tpl.TableOfContents.row', array(
      'index' => $index,
      'href' => $url . '#' . $href[1],
      'target' => '#' . $href[1],
      'text' => $matches[1][$index]
    ));
  }
  $output = $modx->getChunk('tpl.TableOfContents.wrapper', array('output' => $output));
  $modx->cacheManager->set($id, $output, 0, array(xPDO::OPT_CACHE_KEY => 'table_of_contents'));
}
return $output;

Для формирования разметки в этом сниппете используются 2 чанка:

  • tpl.TableOfContents.wrapper;
  • tpl.TableOfContents.row.

Код чанка tpl.TableOfContents.wrapper:

<div class="table-of-contents">
  <div class="table-of-contents__title">Содержание:</div>
  <div class="table-of-contents__items">[[+output]]</div>
</div>

Код чанка tpl.TableOfContents.row:

<a href="[[+href]]" data-target="[[+target]]" data-index="[[+index]]">[[+text]]</a>

Для создания другой разметки, нужно изменить код этих чанков.

Вызов этого сниппета осуществляется следующим образом:

[[createTableOfContents]]

Данный сниппет при первом выполнении сохраняет созданное содержание страницы в кэш (в папку table_of_contents), которое не очищается даже при очистке кэша сайта (оно удаляется при сохранении ресурса в админке).

Сниппет createTableOfContents формирует следующую HTML структуру на странице:

<div class="table-of-contents">
  <div class="table-of-contents__title">Содержание:</div>
  <div class="table-of-contents__items">
    <a href="/post-1#zagolovok-1" data-target="#zagolovok-1" data-index="0">Заголовок 1</a>
    <a href="/post-1#zagolovok-2" data-target="#zagolovok-2" data-index="1">Заголовок 2</a>
    <a href="/post-1#zagolovok-3" data-target="#zagolovok-3" data-index="2">Заголовок 3</a>
    ...
  </div>
</div>

Плагин для добавления id к заголовкам

Добавление id к заголовкам h2 - h4 ресурса при его сохранении выполним посредством плагина. Назовём его, например, addIdToHeaders.

На вкладке «Системные события» установим галочку напротив OnBeforeDocFormSave. Это событие, которые данный плагин будет должен отслеживать.

Событие OnBeforeDocFormSave запускается при нажатии кнопки «Сохранить» в форме редактирования ресурса перед его сохранением.

MODX плагин для добавления id к заголовкам при сохранении ресурса в адмике

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

<?php
switch ($modx->event->name) {
  case 'OnBeforeDocFormSave':
    if ($resource->get('class_key') !== 'Ticket') {
      return;
    }
    $content = $resource->get('content');
    $newContent = preg_replace_callback('#<h[2-4].*?>(.*?)<\/h[2-4]>#', function($matches){
      preg_match('#.*?id="(.*?)".*?#', $matches[0], $header);
      if (!empty($header)) {
        return $matches[0];
      }
      // убираем HTML-теги
      $header = strip_tags($matches[1]);
      // убираем перевод каретки
      $header = str_replace(array('\n', '\r'), ' ', $header);
      // удаляем повторяющие пробелы
      $header = preg_replace('#\s+#', ' ', $header);
      // убираем пробелы в начале и конце строки, а также справа символы :!.?;
      $header = trim(rtrim($header, ':!.?;'));
      // переводим строку в нижний регистр
      $anchor = function_exists('mb_strtolower') ? mb_strtolower($header) : strtolower($header);
      $anchor = strtr($anchor, array('а'=>'a','б'=>'b','в'=>'v','г'=>'g','д'=>'d','е'=>'e','ё'=>'e','ж'=>'j','з'=>'z','и'=>'i','й'=>'y','к'=>'k','л'=>'l','м'=>'m','н'=>'n','о'=>'o','п'=>'p','р'=>'r','с'=>'s','т'=>'t','у'=>'u','ф'=>'f','х'=>'h','ц'=>'c','ч'=>'ch','ш'=>'sh','щ'=>'shch','ы'=>'y','э'=>'e','ю'=>'yu','я'=>'ya','ъ'=>'','ь'=>''));
      // очищаем строку от недопустимых символов
      $anchor = preg_replace('#[^0-9a-z-_ ]#i', '', $anchor);
      // заменяем пробелы знаком минус
      $anchor = str_replace(' ', '-', $anchor);
      return '<h'. $matches[0][2] .' id="' . $anchor . '">' . $header . '</h' . $matches[0][2] . '>';
    }, $content);
    $resource->set('content', $newContent);
    // дополнительно - очистка кеша, содержащего содержание ресурса (например, если кеш храниться в table_of_contents)
    $output = $modx->cacheManager->get($id,array(xPDO::OPT_CACHE_KEY=>'table_of_contents'));
    if (!empty($output)) {
      $modx->cacheManager->delete($id,array(xPDO::OPT_CACHE_KEY=>'table_of_contents'));
    }
    break;
}

Как этот плагин работает?

При сохранении ресурса (в этом примере он выполняет это только для тикетов, т.е. ресурсов у которых class_key имеет значение Ticket) данный плагин получает его контент ($resource->get('content')) и ищет в нём заголовки h2 - h4, которые не имеют id. Далее формирует для каждого из них определённое значение, которое затем устанавливает в качестве id. После этого текущий контент заменяет новым контентом, в котором установлены id для заголовков.

Скрипт на PHP для добавления id к заголовкам ресурсов

Для добавления id к заголовкам h2 - h4 большого количества ресурсов удобно использовать следующий скрипт на PHP:

<?php

require_once 'config.core.php';
require_once MODX_CORE_PATH . 'model/modx/modx.class.php';
$modx = new modX();
$modx->initialize('web');
$modx->getService('error', 'error.modError', '', '');

$pdoTools = $modx->getService('pdoTools');

// массив родителей (их id), ресурсы которых нужно обработать (здесь: 5)
$parent = array(5);
$query = $modx->newQuery('modResource', array(
  'parent:IN' => $parent
));
$resources = $modx->getCollection('modResource', $query);

foreach ($resources as $resource) {
  if ($resource->get('class_key') !== 'Ticket') {
    continue;
  }
  $id = $resource->get('id');
  $pagetitle = $resource->get('pagetitle');
  $content = $resource->get('content');
  // какие были заголовки
  preg_match_all('#<h[2-4].*?>(.*?)<\/h[2-4]>#', $content, $matches);
  echo '<div style="font-weight: bold;">' . $id . ' - ' . $pagetitle . '</div>';
  echo '<pre>';
  echo str_replace('>', '>', str_replace('<', '<', print_r($matches[0], true)));
  echo '</pre>';
  if (empty($matches[0])) {
    continue;
  }
  $newContent = preg_replace_callback('#<h[2-4].*?>(.*?)<\/h[2-4]>#', function ($matches) {
    preg_match('#.*?id="(.*?)".*?#', $matches[0], $header);
    if (!empty($header)) {
      return $matches[0];
    }
    // убираем HTML-теги
    $header = strip_tags($matches[1]);
    // убираем перевод каретки
    $header = str_replace(array('\n', '\r'), ' ', $header);
    // удаляем повторяющие пробелы
    $header = preg_replace('#\s+#', ' ', $header);
    // убираем пробелы в начале и конце строки, а также справа символы :!.?;
    $header = trim(rtrim($header, ':!.?;'));
    // переводим строку в нижний регистр (иногда надо задать локаль)
    $anchor = function_exists('mb_strtolower') ? mb_strtolower($header) : strtolower($header);
    $anchor = strtr($anchor, array('а' => 'a', 'б' => 'b', 'в' => 'v', 'г' => 'g', 'д' => 'd', 'е' => 'e', 'ё' => 'e', 'ж' => 'j', 'з' => 'z', 'и' => 'i', 'й' => 'y', 'к' => 'k', 'л' => 'l', 'м' => 'm', 'н' => 'n', 'о' => 'o', 'п' => 'p', 'р' => 'r', 'с' => 's', 'т' => 't', 'у' => 'u', 'ф' => 'f', 'х' => 'h', 'ц' => 'c', 'ч' => 'ch', 'ш' => 'sh', 'щ' => 'shch', 'ы' => 'y', 'э' => 'e', 'ю' => 'yu', 'я' => 'ya', 'ъ' => '', 'ь' => ''));
    // очищаем строку от недопустимых символов
    $anchor = preg_replace('#[^0-9a-z-_ ]#i', '', $anchor);
    // заменяем пробелы знаком минус
    $anchor = str_replace(' ', '-', $anchor);
    return '<h'. $matches[0][2] .' id="' . $anchor . '">' . $header . '</h' . $matches[0][2] . '>';
  }, $content);
  // какие стали заголовки
  preg_match_all('#<h[2-4].*?>(.*?)<\/h[2-4]>#', $newContent, $matches);
  echo '<div style="font-weight: bold;">';
  echo '<pre>';
  echo str_replace('>', '>', str_replace('<', '<', print_r($matches[0], true)));
  echo '</pre>';
  echo '</div>';
  // сохраняем
  $resource->set('content', $newContent);
  $resource->save();
}

exit();

В этом примере, он выполняет работу только для ресурсов, расположенных в id = 5:

$parent = array(5);

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

Запустить этот php-скрипт можно просто указав в адресной строке браузера URL к нему.

Результат своей работы он выведет следующи образом на страницу:

Результат работы php-скрипта по добавлению id к заголовкам для ресурсов, расположенных в указанных родителях

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

NANO
NANO

Александр как сделать так чтоб список вышел после тега например <hr> внутри [[*content]] в Tickets

Александр Мальцев
Александр Мальцев

Это можно сделать прямо в коде плагина addIdToHeaders. После того как id добавлены к заголовкам, вы можете уже проанализировать контент $newContent и сформировать, используя его HTML-код заголовков. А потом их просто добавить перед $newContent:

// $tableOfContents – HTML-код оглавления
$newContent = $tableOfContents . $newContent;
$resource->set('content', $newContent);
ООО
ООО
Здравствуйте, скрипт открываю в браузере, отдает HTTP ERROR 500
Открываю в Console, ошибка Fatal error: require_once(): Failed opening required 'config.core.php' (include_path='.:/usr/share/php') in /home/b/barano17/lorzdrav.ru/public_html/core/components/console/processors/exec.class.php(24): eval()'d code on line 2
Александр Мальцев
Александр Мальцев
Здравствуйте! Его нужно положить туда, где находиться файл «config.core.php».
andrianov
andrianov
Добрый день. Некоторые заголовки пропускает(не добавляет им id), хотя в оглавлении стоит якорь на заголовок. Когда добавляешь новые заголовки на страницу они не появляются в оглавлении, как это можно сделать?
Александр Мальцев
Александр Мальцев
Здравствуйте! Добавление id к осуществляется только к тем заголовкам, которые соответствуют регулярному выражению:
#<h[2-4].*?>(.*?)<\/h[2-4]>#
Осуществляется это перед сохранением ресурса с помощью плагина. Если id не добавляется, значит заголовок не соответствует указанному регулярному выражению.
Сниппет составляет оглавление только из h2. Если нужно по-другому, то нужно в сниппете это изменить.
andrianov
andrianov
Да, все получилось.
Заголовки, доставшиеся от старого верстальщика работали:
"<\h2 class="square-menu__title">Проводим оценку коммерческой недвижимости<\/h2>"
А мои, нет)
"<\h2 class="square-menu__title">
	Проводим оценку коммерческой недвижимости
<\/h2>"
Спасибо за помощь!