MODX Revolution - пара костылей для необычных ситуаций

Источник: habrahabr
Agel_Nash

MODX конечно крутая CMF, но порой сталиваешся с такими мелочами, которые напрягают. Я не буду в этом топике плакаться какие все вокруг плохие, а я такой хороший. Возьмем, например, ветку Revolution и препарируем ее. А чтобы вы не потеряли интерес к этой заметке, вот небольшой план статьи: 

  1. Разбор бэкенда
  2. Углубление в настройки
  3. Работа с вложенными чанками
  4. Наблюдаем за работой парсера
  5. Мощный кеш



Удаление параметра из набора параметров


Создаем набор параметров. В него добавляем Х параметров, а потом понимаем, что мы разошлись. Пытаемся найти кнопку удалить… 

В общем у меня ничего не вышло. Пришлось искать другой способ удаления параметра.
Решение проблемы: открываем на редактирование ненужный параметр и полностью копируем все данные другого нужного параметра (якобы пытаясь перезаписать старый параметр новыми данными).
Следствие: Можем перезаписать значение нужного параметра.

Значения ключей в настройках контекстов


Смоделируем такую ситуацию: несколько контекстов, к каждому контексту привязаны свои настройки.
Если в админке несколько сайтов, то как правило в настройках контекста переопределяют такой ключик, как site_name. А теперь представьте, ваша фирма всегда позиционировала свое название какКрутые{Перцы}. 

Как вы думаете, какой результат мы увидим во front-end? Правильно, просто Крутые.
Решение проблемы: вместо скобок использовать специальные символы.
А что делать тем, кто у кого используется 1 шаблон на нескольких контекстах и желает в качестве ключа указать некий набор параметров для JS кода? Там специальные символы не помогут и одним чанком не обойтись.
Тут решений уже два
  • Первое решение: создать свой сниппет и в зависимости от контекста отдавать нужный чанк посредством getChunk.
  • Второе решение: Для каждого контекста создать свой чанк и в шаблоне вместо ключа прописать [[$name_[[*context_key]]]]. Но как бы там нибыло, данная запись выглядит вырвиглазно… 


Работа с вложенными чанками


В MODX еще с давних пор есть такая функция, как
getChunk
(от нее, например, зависит parseChunk). 
public function getChunk($chunkName, array $properties= array ()) {
        $output= '';
        if (array_key_exists($chunkName, $this->sourceCache['modChunk'])) {
            $chunk = $this->newObject('modChunk');
            $chunk->fromArray($this->sourceCache['modChunk'][$chunkName]['fields'], '', true, true);
            $chunk->setPolicies($this->sourceCache['modChunk'][$chunkName]['policies']);
        } else {
            $chunk= $this->getObject('modChunk', array ('name' => $chunkName), true);
            if (!empty($chunk) // $chunk === '0') {
                $this->sourceCache['modChunk'][$chunkName]= array (
                    'fields' => $chunk->toArray(),
                    'policies' => $chunk->getPolicies()
                );
            }
        }
        if (!empty($chunk) // $chunk === '0') {
            $chunk->setCacheable(false);
            $output= $chunk->process($properties);
        }
        return $output;
    }


А теперь давайте создадим простой
сниппет
<?php
ini_set("display_errors",1);
$data='';
$phs=isset($phs)?explode(",",$phs):array();
if(isset($tpl,$phs) && $tpl!='' && count($phs)>0){
 foreach($phs as $item){
     $data.=$modx->parseChunk($tpl,array('item'=>$item,'time'=>time()));
 }
}
return $data;


установим phpthumbof и создадим
чанк
<p><b>[[+time]]</b>: [[+item]] - [[!phpthumbof? &input=`[[+item]]` &options=`&h=150&f=jpg`]]</p>


Сделаем вызов этого всего на странице примерно так: [[!test? &tpl=`test` &phs=`/assets/1.jpg,/assets/2.jpg`]]
Но вот незадача, при первом вызове в сниппет phpthumbof приходит строка [[+item]] и только при всех последующих уже распарсеный плэйсхолдер

Решение: Хранить такие проблемные чанки в файлах. Т.е. поставить галочку статичный и выбрать файл

Еще одно решение от bezumkin: Заменить parseChunk на getChunk.

Наблюдаем за работой парсера


Эксперимент проведем следующим образом: создадим новый документ с шаблоном blank. Содержимое и все остальное оставим пустым. Проверим, что стоит галочка кешировать.
Теперь откроем функцию processElementTags из файла /core/mode/modx/modparser.class.php
public function processElementTags($parentTag, & $content, $processUncacheable= false, $removeUnprocessed= false, $prefix= "[[", $suffix= "]]", $tokens= array (), $depth= 0) {
        $this->_processingTag = true;
        $this->_processingUncacheable = (boolean) $processUncacheable;
        $this->_removingUnprocessed = (boolean) $removeUnprocessed;
        $depth = $depth > 0 ? $depth - 1 : 0;
        $processed= 0;
        $tags= array ();
        /* invoke OnParseDocument event */
        $this->modx->documentOutput = $content;
        $this->modx->invokeEvent('OnParseDocument', array('content' => &$content));
        $content = $this->modx->documentOutput;
        unset($this->modx->documentOutput);
        if ($collected= $this->collectElementTags($content, $tags, $prefix, $suffix, $tokens)) {
            $tagMap= array ();
            foreach ($tags as $tag) {
                $token= substr($tag[1], 0, 1);
                if (!$processUncacheable && $token === '!') {
                    if ($removeUnprocessed) {
                        $tagMap[$tag[0]]= '';
                    }
                }
                elseif (!empty ($tokens) && !in_array($token, $tokens)) {
                    $collected--;
                    continue;
                }
                if ($tag[0] === $parentTag) {

                    $tagMap[$tag[0]]= '';
                    $processed++;
                    continue;
                }
                $tagOutput= $this->processTag($tag, $processUncacheable);
                if (($tagOutput === null // $tagOutput === false) && $removeUnprocessed) {

                    $tagMap[$tag[0]]= '';
                    $processed++;
                }
                elseif ($tagOutput !== null && $tagOutput !== false) {
                    
                    $tagMap[$tag[0]]= $tagOutput;
                    if ($tag[0] !== $tagOutput) $processed++;
                }
            }
            $this->mergeTagOutput($tagMap, $content);
            if ($depth > 0) {
                $processed+= $this->processElementTags($parentTag, $content, $processUncacheable, $removeUnprocessed, $prefix, $suffix, $tokens, $depth);
            }
        }
        $this->_processingTag = false;
        return $processed;
    }


перед строкой
$this->_processingTag = true;
добавим
static $test; echo ++$test."<br />";

Ну и открываем в браузере нашу страницу. о0! число 1..2..3 Спрашивается какого мать вашу… Пустая страница. Что там парсить? Смотрим внимательно исходник
$this->modx->invokeEvent('OnParseDocument', array('content' => &$content));

Таким образом даже на пустой странице мы вызываем 3 раза одно и тоже событие. Мне страшно подумать, что будет с теми сайтами, у которых еще на это событие повешан какой-то код.
artdevue для эксперимента проверил сколько раз будет выполняться данная функция при различных условиях. таким образом мы имеем следующую табличку.
Условие Итераций
[[snippet]] 2
{{$chunk}} 2
Пустая страница 3
[[num?input=`1000`]] 5
[[*pagetitle:num]] 6
[[num?input=`[[*pagetitle]]`]] 7
Несуществующий сниппет 23
Несуществующий чанк 23


Мощный кеш


На одном из рабочих сайтов открыл я папку с кешем и чуть со стула не упал. 1 страница в кеше занимает от 50 до 200 КБ. Нет, это может конечно и нормально, но кеш обновляется при каждом изменении документов. Обновил 1 документ - все закешированные страницы удалились. А если 100 страниц? 1000? Это уже на несколько МБ тянет. А если новости добавляются каждый день?

Страница сайта http://test.interface.ru
Оригинал находится по адресу http://test.interface.ru/home.asp?artId=30618