Не могу точно сказать как давно в MODX Revolution появился метод addExtensionPackage() (скорее всего начиная с версии 2.2), но могу точно сказать, что на этот метод надо обратить внимание любителям писать свои эддоны.
В чем его суть? Он позволяет указывать MODX-у, какие пакеты подключать при инициализации движка. Это будет более понятно тем, кому приходилось подключать свои пакеты через $modx->addPackage(), чтобы можно было работать со своими объектами через стандартные методы $modx->newObject(), $modx->getObject() и т.п. Просто если вы написали свой пакет, и его классы не находятся в папке core/model/modx/, то просто так к этим объектам нельзя обратиться, необходимо «дать знать» MODX-у, где эти объекты хранятся. Для этого как раз и существует метод $modx->addPackage(). Но в чем проблема? Проблема в том, что приходится каждый раз думать о том, чтобы пакет обязательно был подключен, и для этого как правило создается плагин, срабатывающий по событию onHandleRequest, или кто как изворачивается. С плагином сразу проблема возникает: при запросах на коннекторы просто так не вызываются события, их вызов приходится дописывать. В общем тонкостей довольно много. Но лично я для себя использую в качестве решения большинства этих головняков метод $modx->addExtensionPackage(); Вызывая этот метод с указанием названия своего пакета (класса) и пути до пакета, MODX записывает эти данные в системную настройку extension_packages. То есть таким образом мы выполняем «подключение» пакета, и выполняется это всего один раз. К примеру
$modx->addExtensionPackage('mypackage', '[[++core_path]]components/mypackage/model/');
После того, как вы выполните этот скрипт и данные о пакете появятся в настройке extension_packages, каждый раз при инициализации контекста будет выполняться подключение указанных пакетов. И да, именно возможность указания настроект для конкретных контекстов, позволяет четко управлять, где будут пакеты подключаться, а где нет.
Вообще этот метод описан для кастомных типов документов, но годится для многих случаев. Правда пока очень глубоко не вдавался, но есть подозрения, что он не хочет таким образом подключать пакеты внутри папки /core/model/modx/, в частности пакет sources не хочет подключать никак.
В этот раз постараемся разобраться с методами проверки прав доступов ->hasPermission() и ->checkPolicy(); Я не буду в этом топике рассказывать как в принципе создавать Роли, группы пользователей, настраивать доступы к контекстам и группам ресурсов. Об этом написано не мало. Но так как многие настраивая эти политики (то есть зная где это делается) часто не получают желаемого результата (то есть не всегда понимают как что работает), то в этом топике я опишу принципы проверки прав к контекстам и ресурсам, чтобы каждый всегда мог проверить что и куда у него разрешено и где что работает или нет.
Итак, для начала повторно отметим, что для проверки прав есть два метода: 1. ->checkPolicy() 2. ->hasPermission() И здесь как раз и стоит отметить, что выполняют они на самом деле одно и то же, просто с разными объектами. ->checkPolicy() проверяется на конкретном объекте, к примеру $modx->resource->checkPolicy(), то есть в данном случае мы проверяем права доступов к конкретному объекту (ресурсу). Это мы условно назовем «Локальные политики». А $modx->hasPermission(), как я уже писал ранее, это на самом деле $modx->context->checkPolicy(), то есть проверка прав именно к контексту, так как все так или иначе работает в раках контекста. Это мы и назовем «Глобальные политики».
Сразу приведу пример, чтобы стало понятней. Дано: группа пользователей имеет права доступов load, list, view к контексту web, и к определенной группе ресурсов только load. У нас есть ресурс, который находится в этой группе ресурсов, на которую как мы и сказали, пользователь имеет только права load. Проверяя его права на данном ресурсе $resource->checkPolicy('view'), мы получим отрицательный результат. Все, к этому ресурсу у нас нет доступа. Но, проверяя его глобальные права $modx->hasPermission('view') (то есть $modx->context->checkPolicy('view')), мы получим положительный ответ, так как его права к данному контексту load, list, view.
Сразу отмечу, что в штатной работе фронтенда вам больше понадобятся именно политики локальные, то есть на группы ресурсов, так как именно за счет настроек доступов к различным группам ресурсов и происходит контроль что кому можно показывать, а кому нет. А вот в бэкенде больше проверяются контекстные политики, так как элементарно не имея прав доступа 'frames' к контексту, не получится войти в админку. Так же все меню и т.д. проверяют глобальные политики, и только работая с конкретными объектами (редактирование и просмотр ресурсов, сохранение и т.п.) будут уже проверять локальные политики доступов к конкретным объектам.
Итак, здесь немного разобрались. Теперь необходимо выучить пару правил. 1. «Если не настроено ни одного правила доступа, то разрешено все». 2. «Если настроено хоть одно правило хоть для кого-то, то все, что не разрешено, то запрещено».
Объясню. К примеру, вы создали новый контекст, к которому не настроили ни одного правила доступа, даже для anonymus. В таком случае чтобы вы ни проверяли методом $modx->hasPermission(), у вас всегда будет положительный результат, даже $modx->hasPermission('sdfsdfweeefwe wef we dfs'); Но если вы к этому контексту пропишите права для группы анонимусов load only, то все остальные группы пользователей, для которых не настроены права к этому контексту, потеряют все права к этому контексту, даже группа Админов (не забываем, что принципиально в MODX Revolution нет разницы между группами, есть только настройки прав. Даже анонимусы могут иметь больше прав, чем у админов, если так прописаны политики доступов). Так вот, если проверить права в данном контексте анонимусов $modx->hasPermission('load'), то мы получим положительный результат. Если пользователь авторизуется и для его группы не настроены политики доступов к данному контексту, то данный пользователь не будет иметь к контексту вообще никаких доступов.
Заметка: как раз по ходу экспериментов с доступами, удалил для группы Админов права на доступ к контексту web, и потерял на этот контекст все права, так как доступ к контексту все еще имели анонимусы. Вот если бы все права на контекст были удалены, то тогда бы доступ был. Пришлось переключаться в SUDO и восстанавливать доступ.
То же самое касается и ресурсов. Если ресурс не входит ни в одну группу ресурсов, или к этим группам ресурсов не настроено никаких политик доступов, то всем пользователям будет все разрешено.
Теперь научимся проверять, какие политики настроены, а какие нет (чтобы не гадать, все ли у нас ОК). Вариант 1: надо проверить настроены ли какие политики доступов к контексту. Пишем такой код:
print '<pre>'; $modx->switchContext($context_name); $policy = $modx->context->findPolicy(); print_R($policy);
Если вы проверяете доступы в текущем контексте, то можно без $modx->switchContext($context_name); Выполним его. Если политики не настроены для контекста вообще, то мы увидим пустой массив. Если настроены, то увидим типа этого:
Array( [modAccessContext] => Array ( [web] => Array ( [0] => Array ( [principal] => 0 [authority] => 9999 [policy] => Array ( [load] => 1 ) ) [1] => Array ( [principal] => 2 [authority] => 9999 [policy] => Array ( [load] => 1 [list] => 1 [view] => 1 [save] => 1 [remove] => 1 [copy] => 1 [view_unpublished] => 1 ) ) ) ) )
Здесь мы видим, что к контексту web прописаны следующие политики: 1. Для группы пользователей 0(анонимусы) (principal — группа пользователей) даны права (policy) только load. 2. Для группы пользователей 2 прописаны load, list, view и еще 4 политики. Таким образом только пользователи данных групп будут иметь соответствующие доступы к контексту web. Все остальные группы пользователей не будут иметь права вообще. Уточню, что это глобальные политики, проверяемые через $modx->hasPermission().
Так же давайте отметим, что корень этого массива имеет ключ modAccessContext. Это очень важно, так как именно в этом объекте (в таблице modx_access_context), а не в самом объекте modContext, прописаны политики доступов к контексту (точнее связь контекст — группа пользователей — ранг — политика). Прописано это в методе modContext::findPolicy()
public function findPolicy($context = '') { $policy = array(); $enabled = true; $context = !empty($context) ? $context : $this->xpdo->context->get('key'); if (!is_object($this->xpdo->context) || $context === $this->xpdo->context->get('key')) { $enabled = (boolean) $this->xpdo->getOption('access_context_enabled', null, true); } elseif ($this->xpdo->getContext($context)) { $enabled = (boolean) $this->xpdo->contexts[$context]->getOption('access_context_enabled', true); } if ($enabled) { if (empty($this->_policies) || !isset($this->_policies[$context])) { $c = $this->xpdo->newQuery('modAccessContext'); $c->leftJoin('modAccessPolicy','Policy'); $c->select(array( 'modAccessContext.id', 'modAccessContext.target', 'modAccessContext.principal', 'modAccessContext.authority', 'modAccessContext.policy', 'Policy.data', )); $c->where(array( 'modAccessContext.principal_class' => 'modUserGroup', 'modAccessContext.target' => $this->get('key'), )); $c->sortby('modAccessContext.target,modAccessContext.principal,modAccessContext.authority,modAccessContext.policy'); $acls = $this->xpdo->getCollection('modAccessContext',$c); foreach ($acls as $acl) { $policy['modAccessContext'][$acl->get('target')][] = array( 'principal' => $acl->get('principal'), 'authority' => $acl->get('authority'), 'policy' => $acl->get('data') ? $this->xpdo->fromJSON($acl->get('data'), true) : array(), ); } $this->_policies[$context] = $policy; } else { $policy = $this->_policies[$context]; } } return $policy; }
Кстати, очень интересный факт: как видите, в этой функции прописана проверка системной настройки access_context_enabled. То есть если в настройках системы указать access_context_enabled = false, то политики безопасности к контексту вообще не будут проверяться. Это может быть очень удобно тогда, когда надо дать полные права на контекст какому-нибудь отдельному пользователю (указав в его настройках этот параметр), или отключить временно проверки для определенного контекста (дебаг или типа того). Но интересно еще и описание данной настройки на русском языке:
Включает или отключает проверку прав доступа к контекстам. ВАЖНО: Если эта настройка установлена в «Нет», то все политики доступа к контекстам будут игнорироваться! Не отключайте проверку прав доступа к контекстам для всей системы или для контекста «mgr», иначе вы отключите доступ к интерфейсу управления сайтом.
То есть говорит, что не отключайте эту настройку особенно для контекста mgr, а то отключится доступ к этому контексту. На самом же деле не доступ отключится, а отключится проверка доступов, и наоборот, пользователь получит неограниченный доступ к этому контексту (если она авторизован). При чем он сможет отредактировать любой документ (если тот не находится в группе ресурсов, к которой этот пользователь не имеет доступа), сможет создать любой сниппет и т.д. В общем получит полный root.
Вариант 2: проверить доступ к конкретному ресурсу. Пишем такой код:
print '<pre>'; $resource = $modx->getObject('modResource', $id); $policy = $resource->findPolicy(); print_R($policy);
Если проверяете текущий документ, то можно просто
print '<pre>'; $policy = $modx->resource->findPolicy(); print_R($policy);
Опять-таки, если политики доступа не настроены, то получите пустой массив. Если настроены, то увидите типа этого:
Array ( [modAccessResourceGroup] => Array ( [1] => Array ( [0] => Array ( [principal] => 1 [authority] => 0 [policy] => Array ( [load] => 1 [list] => 1 [view] => 1 ) ) ) ) )
Здесь принцип такой же, как и с контекстами. principal — группа пользователей, authority — ранг, policy — массив разрешенных политик.
Так же видим, что все политики хранятся в объекте modAccessResourceGroup (таблица access_resource_groups).
Вот наверно и все, что нужно знать о политиках.
P.S. Вот еще пара заметок на тему политик доступов:
Сейчас столкнулся вот с какой проблемой: решил переместить один из документов на уровень выше, но MODX вернул ошибку сохранения документа. Это было довольно странно, так как этот документ я создал несколько минут назад, и я его легко мог редактировать и сохранять. Проблемы возникала именно при попытке пересортировать дерево. Полез ковырять системный процессор… Оказывается проблема была вовсе не в доступе к этому ресурсу, а в доступе к другому ресурсу, который находился в группе ресурсов, на которую я не имел прав save. Но так как при пересортировке дерева MODX пытается сохранить все задействованные документы (у всех же в этом уровне ранг меняется), и он проверяет права на сохранение каждого ресурса, то он и вернул ошибку, и не сохранил мой ресурс. Конечно плохо, что ошибка общая, типа «Не удалось выполнить сохранение». Было бы лучше, если бы он сразу сообщал к какому именно ресурсу нет доступа. Это бы очень упросило понимание проблемы. В общем в таком случае необходимо донастроить права.
Мы уже разговаривали о кешировании и парсинге здесь и здесь. И вот еще небольшая порция информации для размышления. Что мы имеем? Небольшой сайт-визитку с каталогом. На главную выводится помимо парочки менюшек еще и главный источник проблем в области производительности — галерея товаров с использованием пакета Gallery. В принципе у нас главная не меняется, потому у нас стоит задача максимально закешировать документ, чтобы вообще летало. Посмотрим какой производительности можно добиться, применяя кеширование.
Сразу вкратце обрисую схему работы документа: в шаблоне только один исполняющий сниппет, который подгружает PHP-файл и там уже происходит основная работа. То есть по идее, если мы сделаем этот сниппет кешируемый, у нас должно получиться максимальное кеширование. Посмотрим.
Вариант 1. Документ не кушируемый вообще. Генерация страницы 760-900 мсек.
Вариант 2. Документ кешируемый, сниппет не кешируемый. Генерация 630-700 мсек.
Вариант 3. Сниппет полностью кешируемый. Генерация 217-275 мсек. Итак, у нас полное кеширование корневого сниппета. А генерация 200+ мсек. В чем проблема? Проблема в том, что внутри этого сниппета могут встречаться другие некешируемые элементы. Но даже если эти элементы кешируемые, в кеше документа все равно не будет сплошного HTML-кода, а будет массив кешируемых элементов с их значениями, и все равно при каждой загрузке страницы MODX-парсер будет пробегаться по этим элементам и заменять их на их значения. Вот часть кеша этой страницы:
'[[$webim.output]]' => '<div style="margin: 0 0 0 16px;"> <!-- webim button --><a href="/webim/client.php?locale=ru" target="_blank" onclick="if(navigator.userAgent.toLowerCase().indexOf(\'opera\') != -1 && window.event.preventDefault) window.event.preventDefault();this.newWindow = window.open(\'/webim/client.php?locale=ru&url=\'+escape(document.location.href)+ \'&referrer=\'+escape(document.referrer), \'webim\', \'toolbar=0,scrollbars=0,location=0,status=1,menubar=0,width=640,height=480,resizable =1\');this.newWindow.focus();this.newWindow.opener=window;return false;"> <img src="/webim/button.php?i=webim&lang=ru" border="0" width="163" height="61" alt=""/> </a><!-- / webim button --> </div>', '[[~9]]' => 'chugunnie_radiatory.html',
Может не очень видно, но это ассоциативный массив с двумя ключами: '[[$webim.output]]' и '[[~9]]'. То есть как мы не старались, у нас все равно документ закеширован не идеально. Но нам есть еще что сказать…
Вариант 4. Парсим полученный сниппетом контент. Итак, прежде чем в сниппете выполнить return $output, выполняем следующее: дописываем перед return парсинг имеющегося контента.
$maxIterations= intval($modx->getOption('parser_max_iterations', null, 10)); $modx->parser->processElementTags('', $output, true, false, '[[', ']]', array(), $maxIterations); $modx->parser->processElementTags('', $output, true, true, '[[', ']]', array(), $maxIterations);
Таким образом мы уже на уровне сниппета обработаем все MODX-элементы в полученном контенте, и в кеш страницы запишется в качестве значения нашего сниппета чисто конечный HTML-код. И что мы имеем на выходе? Генерация страницы 84-100 мсек. Много или мало? Для сравнения если прописать в index.php exit сразу перед выполнением $modx->handleRequest();, то есть MODX проинициализируется, но не начнет еще обрабатывать запрос и генерить страницу, это время составит 60-70 мсек.
Но даже это еще не все. Я уже писал про свои шаблоны, в которых будет выполняться чистый php-код. То, как MODX работает сейчас, хочу я или нет, но хотя бы один сниппет MODX-парсер вынужден обработать, который прописан в шаблоне. Выполнение же php-кода на уровне шаблона позволит получать из пользовательского кеша полный HTML-код страницы, и отдавать этот код сразу браузеру, не дожидаясь работы MODX-парсера. Скорее всего даже здесь это добавит еще как минимум 10 мсек. экономии. Так что работа в этом направлении продолжается.
Очень прошу подключиться к дискуссии знатоков шаблонизатора Twig. Да и знатоки MODX Revolution тоже мимо не проходите;-)
Утром я писал про свои наработки в области «MODX без парсера» и «Статические шаблоны с PHP-кодом». И вот сегодня эта история получила продолжение.
Общался с Райном Трешем об этом, и он очень заинтересовался данным решением. При этом он написал следующее:
this almost begs the question (but I think I know) why are you using MODX CMS at all then, and not just using xPDO + Smarty? 1) Content editor UI 2) User system/ACLs 3) Media Sources 4) others???
Типа того, что
Тогда почему ты используешь MODX целеком, а не только связку xPDO + Smarty? Но я скорее всего знаю почему: 1) Пользовательские интерфейсы 2) Использование системы прав доступов 3) Источники файлов и прочее
Да, Райн все правильно понял, и я не раз говорил о всех этих прелестях MODX Revolution, которые мне очень нравятся и я не планирую MODX менять на что-то другое. НО: мне нравится именно админка Ревы (своей полной управляемостью, расширяемостью, Ajax-ностью и т.п.), и своим API, с продуманной архитектурой, которую можно бесконечно расширять как угодно. При этом меня мало вдохновляет разработка на основе MODX-парсера. Хотя это вполне не слабый инструмент, все-таки ИМХО pure PHP гораздо лучше. И именно поэтому я привязал LivestreetCSM к MODX, и совершенно не стесняюсь этого, а наоборот очень даже рад, что MODX мне позволяет это делать, позволяет с легкостью прикрутить сторонние решения. Кстати, и Райн сказал, отвечая на свой же вопрос про то, где я планирую использовать свое решение «MODX без парсера + Smarty»
OK … do tell :) [19:30:13] Ryan Thrash: thought I think I know where this is going: for integrations! [19:30:17] Ryan Thrash: like Livestreet
То есть
А, я знаю: для интеграций. Как с Livestreet
В общем Райн сказал, что мне следует пообщаться с Andrew Smith (не знаю точно его роли в MODX, но я так понял, у них плотный с ним контакт), что у него тоже есть наработки в этом направлении. Он использует MODX в связке с шаблонизатором Twig. Райн познакомил меня с Andrew, и мы немного пообщались. Но решение Andrew выполняется на уровне плагина, так как он не нашел способа вклиниться жестко в ядро, но так, чтобы не затрагивать кода самого ядра. Я вчера думал на счет реализации на уровне плагина, но идею отмел, как противоречащую религии. Учитывая то, что в емейл-переписке в копии стоял Jay Gilmore, история обещает получить дальнейшее продолжение. Райн имеет идею реализовать такую фишку, чтобы к MODX-у можно было подключать какие угодно сторонние шаблонизаторы (в первую очередь Twig). Конечно само подключение шаблонизатора и раньше было доступно через $modx->getService() (как и подключается Smarty), но так как до этого времени невозможно было использовать чистый PHP-код в шаблонах MODX, то сама идея рушилась. Сейчас появились варианты. По задумке Райна это не только позволит расширить функционал MODX-а, но и откроет дорогу для вхождения программистов из сторонних проектов, таких Expression Engine и Django.
Вопрос знатокам Twig-а: на сколько это удобный и мощный шаблонизатор, и какую они видят перспективу в связке MODX Revolution + Twig?
Почти 8 часов утра. За трое суток поспал часов восемь, но не могу уснуть и не поделиться своими идеями (которые пол-ночи проверял на работоспособность, и она подтвердилась).
Сразу скажу, что материал может послужить поводом для серьезных холиваров, но очень хотелось бы, чтобы споры если и будут, то чтобы были конструктивными, так как то, что я замыслил, для многих может показаться просто кощунством, особенно для тех, кто не представляет для себя программирования на MODX без использования MODX-тегов. Я ни в коем случае не хочу сказать, что на данное решение всем надо переходить поголовно, но всерьез считаю, что это серьезный прорыв, так как сожет не только на порядок снизить системные требования к хостингу, но и серьезно прибавить производительности. Правда для этого потребуется полное программирование на PHP без использования синтаксиса MODX (конечно будут места для маневров, но в целом расчет на отказ от парсера).
Итак, с описания моего личного отношения к разработке на MODX, и что мне не нравится.
Уже довольно давно я практически не использую MODX-тегов. То есть вообще минимум сторонних компонентов, минимум чанков и т.д. А есть сайт, который я полностью напрограммировал на Смарти, и единственное что там было для парсинга, это [[*pagetitle]] и т.п., то есть все то, что легко можно было закинуть в Смарти, но было видимо лень. Так вот, не смотря на то, что парсить было практически нечего, само собой MODX в любом случае подключал свой парсер, и парсил весь выплевываемый контент, даже если это несколько мегабайт sitemap.xml, и там вообще нет тегов. То есть это бессмысленная трата ресурсов.
Другая вещь, которая мне очень не нравится, это невозможность использования PHP-кода напрямую в MODX-шаблонах. Даже если у меня сайт на Смарти, и у меня вся логика прописана в PHP, я все равно должен в шаблон воткнуть хотя бы один сниппет, который был бы обработан парсером, и только после того, как был бы полностью обработан объект modResourse. Очень меня расстраивал тот факт, что в шаблонах нельзя сразу писать PHP-код.
Плюс ко всему MODX-парсер — это самый большой источник уязвимостей MODX, так как для воздействия на него из вне только то и надо, что суметь скормить ему строку.
Так же я считаю не идеальной систему шаблонов в MODX, то точнее какой-то не комплексной или не доработанной (сложно слова подобрать). Объясню: я уже говорил, что MODX-шаблоны выполняют две задачи (связь шаблон-TV и индивидуальное оформление страницы). Но шаблон не является инструментом общего оформления всего сайта (то есть темой, скином). Как в том же Livestreet — есть папка шаблонов (скинов), в конфиге указал только название нужного шаблона, и все, весь сайт поменялся. Закинул новый шаблон, и опять полностью сменил оформление сайта. В MODX этого нет. У меня постоянное ощущение того, что к шаблонам не хватает еще одного объекта: темы. То есть грубо говоря должна быть Тема, а создаваемые шаблоны просто привязываются к нужной теме и все. То есть конечное оформление происходит именно в теме, а не в шаблоне от head и до подвала. (К слову, у меня есть альтернативная идея завязывать шаблоны на категориях, чтобы документы, имеющие разные шаблоны, но одной группы, на выходе прогонялись через некий конечный шабло-тему. Но сейчас речь не совсем об этом). В общем лично для себя я увидел выход именно в использовании Smarty на уровне MODX, тем более, что она там стоит по умолчанию, и ее просто надо задействовать для своих целей. Но если мы используем Смарти, и пишем на чистом PHP, то зачем нам MODX-парсер и бездумный парсинг конечного кода? Как бы то ни было, это все-таки регулярные выражения, плюс лишние объекты и действия. В общем я всерьез задумался над тем, насколько вообще реально отключить MODX-парсер, но так, чтобы код не затронуть. И я нашел такое решение (саму теорию сейчас выставляю на обсуждение, а готовый пакет выпущу на днях, когда все как следует обкатаю и соберу).
Итак, парсер — это объект modParcer. Он не является неотъемлемой частью MODX-а. В админке он инициализируется сразуу же в index.php (метод $modx->getPaser()), во фронтенде он инициализируется в объекте modResponse, а в коннекторах он вообще не инициализируется (запросы обрабатываются объектами modConnectorRequest и modConnectorResponse, и там парсер не инициализируется). Но MODX позволяет нам указать свой собственный класс-обработчик запросов, в котором мы как раз и может убрать весь лишний код, связанный с инициализацией парсера и самим парсингом. Уверен, для крупных проектов это может быть очень действующим способом снизить нагрузку. Но это не убивает парсер. Если в каком-то исключительном случае будет не обойтись без использования парсера, то выход всегда есть. Можно создать сниппет, который будет работать примерно как getPage, то есть в него будет передаваться имя вызываемого объекта, его тип и параметры. В этом сниппете уже будет вызываться парсер MODX и прежде чем вернуть конечный контент, он будет парситься локально в этом сниппете.
Другая идея связана как раз с тем, чтобы заставить шаблоны обрабатывать чистый PHP-код. Вот тут все оказалось гораздо сложнее, но решение все равно нашлось, и это очень радует. К сожалению нельзя просто так заставить modTemplate обрабатывать PHP, да и modResource-у это не очень нравится (если документ кешируемый, то полученный конечный код из шаблона все равно присваивается документу и кешируется, и в следующий раз PHP-код уже не выполняется). Но здесь в помощь пользовательские ресурсы. Конечный пакет так же выпущу чуть позже, а пока краткая теория: 1. Создаем свой объект шаблона, расширяющий modTemplate. В метод process() прописываем проверку, что если это не статический шаблон, то выполнять родительский process(), а иначе глобалим $modx (чтобы был виден «внутри» кода шаблона), и инклюдим файл. При чем вообще не страшно, если там HTML или HTML в перемешку с PHP. Проблем не возникает, все тоже самое, что и просто проинклюдить php-файл. 2. Создаем пользовательский ресурс, расширяющий modResource, со всеми положенными MySQL-классами. В map-классе этого объекта нам только и надо что сделать, так это переопределить класс шаблона вместо modTemplate. 3. Чуть-чуть переписываем функцию process() этого объекта и все. Исполнение PHP-кода в шаблонах готово. Остается только добавить наш новый тип ресурсов в меню выбора типа ресурса. В общем все будет понятней, когда будет готов пакет. Но в целом это решение все равно дает понять, что не все идеально. Ведь нам только и надо было, что создать новый класс шаблона. Вот класс ресурса можно менять через интерфейс, а класс шаблона нет. Предусматривается только modTemplate. А подгрузка шаблона происходит через метод modResource::getTemplate(), в котором четко прописано $this->getOne('Template'). И здесь не предусматривается смена класса шаблона. Так что скорее всего хотя я и выпущу пакет для тех, кому будет интересно, да и так, по мелочи использовать, но в серьезных проектах скорее всего буду править методы process в modTemplate и modResource. Там поправить надо будет всего строчек пять кода, но это обеспечит наилучший эффект.
UPD: Идея получила продолжение в виде пакета phpTemplates. http://modxclub.ru/blog/111.html
Итак, предлагаю всем, кому интересно и кто силен в php, установить и обкатать phpTemplates.
Источники: sourceforge.net/projects/modxphptemplate/ (готовый пакет) github.com/Fi1osof/phptemplates
Этот пакет устанавливает в систему новый тип ресурсов (phpTemplateResource) и новый тип шаблонов (phpTemplate). Это решение позволяет в статических MODX-шаблонах писать не только HTML-код и MODX-теги, но и php-код, то есть подгружает такие шаблоны как обычный php-файл. Это позволяет непосредственно в шаблоне писать чистый php-код с использованием API MODX и без него, использовать сторонние шаблонизаторы (такие как Smarty) и так далее.
Обратная сторона медали (пока до конца не исследована, потому и надо как можно больше тестов): данное решение направлено на то, чтобы как можно меньше использовать MODX-парсер (в перспективе вообще отказаться), и как можно больше писать на чистом php. Велика вероятность того, что при использовании большого числа MODX-тегов производительность может понизиться. Так же при использовании таких шаблонов управление кешем придется полностью брать на себя, то есть использовать $modx->cacheManager, кеширование шаблонизаторов и т.п. Но в руках опытного программиста это стать очень гибким инструментом.
P.S. Отдельное спасибо proxyfabio за участие в разработке пакета!
UPD: Пакет phpTemplates теперь доступен в официальном репозитории.
Данный урок больше всего будет полезен именно новичкам, особенно тем, у кого либо вообще нет опыта разработки на MODX, либо очень маленький опыт.
В первом «сравнительном» уроке я постарался перечислить все элементы (объекты), присутствующие в MODX Revolution (хотя вряд ли смог все перечислить). В этом уроке мы постараемся классифицировать их, чтобы понять, что для чего нужно.
Сразу определюсь, под каким углом мы будем все это рассматривать: мы постараемся разобраться, для разработки на каком уровне будет достаточно знать только HTML, не имея опыта программирования на PHP, а на каком уровне без PHP уже обойтись будет нельзя. Забегая вперед скажу, что довольно много можно сделать без знания PHP, и даже такие объекты, как сниппеты и плагины, которые в принципе содержат в себе PHP-код, и то можно использовать без знания PHP, если использовать именно сторонние решения, и есть достаточно внятная документация по использованию.
Итак, для начала попробуем просто своими словами определить два уровня разработки:
  • Просто сборка сайтов из готовых компонентов.
  • Индивидуальный сайт, со своим уникальным функционалом.
Уточню: в данной статье будет больше материала именно для первого уровня разработки.
Так что же нам дает MODX Revolution, как среда разработки сайтов? А дает он многое. На нем можно создавать как простые сайты-визитки, так и очень серьезные проекты. И в зависимости от того, какие стоят задачи, для разработки своего проекта можно будет ограничиться даже набором стандартных готовых решений (типа Wayfinder, getResources и т.п.), а где-то уже потребуются серьезные знания и опыт веб-разработок, но зато даже в базовой сборке MODX Revolution способен дать инструментарий для разработки веб-разработчикам любого уровня. Главное только понимать что для чего нужно.
Пойдем по порядку.

Ресурсы (modResource)

По сути основа любой страницы сайта. Содержит все основные параметры страницы (краткий и длинный заголовок, описание, пользовательский контент и т.п.). Здесь вообще все просто, разобраться даже блондинка сможет (с подсказками), которая хотя бы в ворде работала. ?
В настройках страницы можно указать (или увидеть, если уже указано) родительский ресурс, даты публикации и отмены публикации, является ли страница контейнером и т.п. ?
Так же можно указать тип документа. По умолчанию это HTML, но можно указать и другой тип, к примеру XML. ?
Тип документа влияет на то, какие заголовки будут отправлены сервером браузеру, суффикс страницы в адресной строке (если используется ЧПУ) и т.п. Помимо стандартных типов документов, можно создать сколько угодно своих. ?
?
?
В третьей вкладке управления ресурсом можно указать к каким группам ресурсов данная страница относится. ?
Сейчас никаких групп ресурсов не создано, потому и выбрать нечего. Более детально группы ресурсов разберем в одном из последующих уроков. Скажу только, что это напрямую связано с распределением прав доступов к странице. Можно создать группы ресурсов, разрешить, к примеру, просмотр их только зарегистрированным пользователям, отнести страницу к данной группе ресурсов, и уже эту страницу увидят только зарегистрированные пользователи.

Шаблоны (modTemplate)

С шаблонами все просто, и все сложно. Простое расскажу сейчас, для объяснения более сложного посвящу отдельный урок. Шаблоны выполняют две задачи: 1. Собственно, оформление выводимой на сайте страницы. То есть шаблон содержит HTML-код и чаще всего прописанные в нем MODX-элементы (чанки, сниппеты, TV-параметры, плейсхолдеры и т.п.). 2. Страницам могут назначаться пользовательские дополнительные параметры (TV-параметры. Только это не относится к телевизорам, это Template Vars). Так вот создаваемые TV-параметры назначаются нужным шаблонам, и тогда, выбирая в редакторе страницы шаблон, к которому привязаны TV-параметры, появляется вкладка с дополнительными полями. ?
?
Но странице так же может быть указан пустой шаблон. ?
В таком случае на странице будет выведен только контент данной страницы, без какого-либо оформления. Это надо при создании таких ресурсов, как CSS, JS, XML и т.п.

Синтаксис MODX, чанки, сниппеты и прочие элементы

Это не заголовок элементов MODX (просто раз уж мы перечисляем элементы MODX, то на всякий случай уточню). Раз уж материал для совсем новичков, то сразу дам очень краткое описание синтаксиса MODX как раз на примере кода базового шаблона, скрин которого представлен выше. Это очень важно для нас, так как в то время, как ресурсы и шаблоны не являются встраиваемыми объектами MODX (то есть они не имеют какого-то своего обозначения, которое можно было бы воткнуть в HTML-код, как и нельзя в один шаблон воткнуть другой шаблон), большинство рассматриваемых далее элементов будут встраиваемые, и составляют основу синтаксиса MODX.
Итак, посмотрим код шаблона:
<html> <head> <title>[[++site_name]] - [[*pagetitle]]</title> <base href="[[++site_url]]" /> </head> <body> [[*content]] </body> </html>
Здесь мы видим знакомые нам HTML-теги, типа head, title и т.п., а так же видим конструкции, заключенные в двойные квадратные скобки. Как раз это и есть элементы (теги) MODX. Все теги перечислины на этой странице официальной документации. ?
Коротко: все MODX-элементы заключаются в двойные квадратные скобки. Когда MODX-парсер обрабатывает код генерируемый страницы, он ищет свои теги. Найдя тег, он его обрабатывает, и заменяет этот тег результатом выполнения этого объекта. Тип MODX-объекта определяют специальные зарезервированные символы (если они есть. Если нет, то по умолчанию объект — сниппет). Какие символы?
+ Плейсхолдер (переменная, которая может быть заменена ранее присвоенным значением.)
++ Системный плейсхолдер. Такие плейсхолдеры хранят значения системных переменных. К примеру в настройках системы указывается название сайта. Эта системная переменная — site_name. Если где-то вставить системный плейсхолдер [[++site_name]], то на выходе в коде мы получим как раз значение этой переменной. Другая системная переменная site_url хранит полный адрес сайта, включая протокол (http, https и т.п.).
* Параметр страницы. Описанный выше объект modResource имеет ряд зарезервированных параметров, содержащих значения этих полей. К примеру Заголовок страницы содержится в параметре pagetitle, длинный заголовок в параметре longtitle. То есть для того, чтобы вывести заголовок страницы в конечный код, мы просто в нужном нам месте прописываем [[*pagetitle]] По всем названиям полей страницы, можно получить подсказки, просто наведя курсор мыши на нужное поле. ?
Кстати, дополнительные поля имеют такое же обозначение, как параметры страницы, то есть тоже обозначаются звездочкой.
$ Чанки (блоки HTML-кода)
~ Ссылки на MODX-ресурсы (страницы). К примеру, чтобы получить ссылку на страницу с ID 5, достаточно просто прописать [[~5]]. MODX обработает этот тег и сформирует ссылку с учетом настроек системы (учитывая используется ли ЧПУ или нет, является ли документ контейнером и т.д.)
% Значение словаря.
Как я уже сказал выше, сниппеты не имеют спецсимвола, то есть они просто заключены в квадратные скобки. К примеру [[my_snippet]]. Что такое сниппет? Это объект, содержащий блок PHP кода.
Можно использовать вложенные теги. К примеру [[~[[++site_start]] ]] вернет ссылку на главную страницу. Здесь произойдет двойной вызов. MODX найдет тег-ссылку ~, но для формирования конечного значения обработает системный настройку [[++site_start]], которая хранит ID главной страницы, и передаст ее значние. То есть если site_start имеет значение 1, то по сути мы получим [[~1 ]]. Но конструкция [[~[[++site_start]] ]] хороша тем, что в настройках мы можем поменять ID главной страницы, а MODX все равно сгенерирует актуальную ссылку.
! Флаг, указывающий, что данный элемент не кешируемый. Используется для чанков, сниппетов и TV-параметров. Плейсхолдеры не бывают кешируемыми. Подробней о кешировании и примерами читаем здесь.
В общем все это довольно обширный материал, и мы его постепенно изучим в последующих уроках, но основы понять важно.
Итак, двинемся дальше.

Параметры TV (modTemplateVar)

Дополнительные поля. Очень полезный элемент. Хотя ресурсы имеют все важные поля (заголовок, дата создания, контент и т.п.), часто этого бывает не достаточно. К примеру мы хотим на сайте сделать новостную ленту, и хотим, чтобы в списке новостей выводилась аннотация новости и картинка-превью. Аннотация есть ( [[*introtext]] ), а вот картинки-превью нет. Но это не беда, такое поле легко создаться.
TV-параметрам так же будет посвящен отдельный урок.

Чанки (modChunk)

Объекты, содержащие HTML-код. Очень полезный элемент шаблонизации. К примеру у вас в проекте три шаблона, которые имеют одинаковые блоки кода (head со всем его содержимым, шапка, подвал и т.п.). Вот чтобы не плодить код в шаблонах, и более централизовано управлять оформлением страниц, повторяющиеся куски HTML-кода можно записать в чанки, и такие чанки уже использовать в нужных нам шаблонах.

Сниппеты (modSnippet)

Объекты, содержащие PHP-код. Эти элементы так же можно использовать без знания PHP. К примеру сформировать меню, вызвав сниппет [[Wayfinder]]. Но вот для того, чтобы изменить сниппет, или создать свой, уже понадобится знание PHP.

Наборы параметров (modPropertySet)

Крайне полезная штука. Подробней читаем здесь.
Все, написал уже много буков, хватит на сегодня. Все перечисленное позволит клепать сайты-визитки на потоке, практически не требуя знания PHP и внутренней структуры MODX. Если в Эво хотя бы при установке готовых компонентов приходилось хоть что-то делать (разархивировать, закинуть куда надо, часто что-то прописать), то в Рево практически все делается через интерфейс.
Более детально все разбирать будем в последующих уроках. В дальнейшем буду меньше мучать картинками, больше выкладывая видео.
СущностьОписание MODX EvolutionMODX Revolution

Базовые элементы

Категории++ РесурсыMODX-документы++ ШаблоныШаблоны для оформления документов++ Параметры TVДополнительные параметры для шаблонов (TV — это не телевизор, о чем кстати и значек в админке Рево намекает, а Template Variable).
Позволяет создавать дополнительные поля для документов++ ЧанкиБлоки HTML-кода++ СниппетыБлоки PHP-кода++ ПлагиныБлоки PHP-кода, выполняемые по системным событиям.
Позволяют вклиниться в логику движка и отдельных компонентов.++ МодулиРасширения MODX. Могут использоваться не только как расширения оформления и функционала админки, но и как единое хранилище пользовательских переменных для связанных элементов, таких как чанки, сниппеты, ресурсы и т.п.+- Наборы параметровНаборы параметров можно передавать в качестве аргумента элементам, таким как чанк или сниппет, и значения набора параметров будут переданы этому элементу. Только надо иметь ввиду, что для этого набор параметров должен быть связан с этим элементом. В данном плане суть набора параметров напоминает суть Модулей в Evolution, только имеет преимущество в том, что одному элементу может быть привязано сразу несколько наборов параметров, и на уровне синтаксиса эти наборы можно переключать. В Эво с модулями такой фокус не пройдет.-+ КонтекстыКонтексты позволяют создавать несколько сайтов на одном движке. Вообще у контекстов очень много сфер преминения, не буду сейчас все расписывать, это тема для отдельного топика.-+ ДействияПо сути это элементы оформления админки. Можно самому переиначить все пункты меню, скрыть ненужные пункты, создать свои, ограничить права и т.д. Роль Действий в Эво выполняют Модули, с той разницей, что в Эво Модули — это чисто пользовательские элементы, а в Рево Действия — это в общем. То есть в Эво вы не сможете через админку удалить все элементы и пункты меню админки, а в Рево можно.-+ Пространства именПозволяют создавать более обособленные модули, что улучшает управляемость отдельными компонентами всей Системы-+ Источники файловПозволяют в одной системе создать сколь угодно источников файлов, что позволяет для отдельных компоненов сразу указывать где хранить пользовательские файлы и какие пути до них формировать.
На сегодня Рево нативно поддерживает два типа источников файлов: 1. Файловая система 2. Хранилища Amazon S3-+ Группы ресурсовПозволяет объединять ресурсы в отдельные группы. Это удобно в том плане, что можно настроить доступы к отдельным группам ресурсов.++ Web-пользователиВ Эво простые пользователи и пользователи-менеджеры, это отдельные сущности со своими таблицами. Для этого даже отдельно есть «Управление веб-пользователями» и «Управление менеджерами», а так же «Права веб-пользователей» и «Права менеджеров». Не очень круто… В Рево есть только Пользователи. Какие права имеют каждый пользователь в отдельности или группа пользователей, определяют настройки политик безопасности.++ Менеджеры+- Шаблоны политик доступовПозволяют создать «заготовки» для политик доступов. В Эво это частично присутствует, так как для создания пользовательских Ролей (наборов политик доступов) уже есть один общий шаблон политик, но во-первых, его нельзя редактировать через админку, а во-вторых, нельзя создать свой шаблон. В Рево это все можно делать.+-+ Политики доступаНаборы прав на те или иные доступы. В Эво и Рево это имеет несколько различающийся смысл. В Рево Политика доступа — это пока еще только набор тех или иных прав. Политика как бы не привязывается к конкретному пользователю. Политики проявляются только тогда, когда проверяются права доступа к тому или иному объекту, но на основе данных «Группа пользователя», «Ранг» и «Роль» (в общем сейчас в два предложения не уложиться и сейчас это все будет слишком запутанно. Обязательно все это рассмотрим потом в отдельной теме). Так вот, а в Эво это работает по другому. В Эво создается Роль (что по сути является Политикой доступов), и в данной Роли уже прописываются все права, какие будет иметь пользователь с данной Ролью. При чем когда вы редактируете пользователя, для него вы не указываете Роль, вы указываете только группу пользователя. Это и есть его права. При чем для Менеджеров необходимо создавать свои группы пользователей отдельно. И веб-пользователей нельзя поместить в группу менеджеров, и наоборот. Конечно, если быть еще более точным, то веб-пользователям в Эво как бы и вообще нельзя дать доступы. Можно только определить доступ отдельных групп пользователей к отдельным группам ресурсов, но определить какой группе веб-пользователей можно сохранять документы, а какие нет — нельзя. И еще уйма ограничений.+-+ Группы пользователейВ Эво группы пользователей несут только две функции: 1) Сгруппировать пользователей, просто для структуры. 2) Определить к каким группам ресурсов будет иметь пользователь группы. На этом все. В Рево все гораздо серьезней. Опять-таки, этот вопрос вынесем в отдельный топик. Скажу только, что пользователь с одной и той же Ролью может находиться в разных Группах пользователей, и иметь разные права, скажем, в зависимости от того, к какому контексту идет обращение. Так же Группа пользователя (в купе с настройками политик) будет определять уровни доступов к Контекстам, Группам ресурсов, Категориям элементов и источникам файлов. К слову, более мощной системы политик безопасности, чем в Рево, я не встречал ни в одном движке.++

Элементы управления

Управление политиками безопасности++ Управление пользователями++ Управление системными настройками++ Управление пакетамиВ Эво нет такого инструмента, как Система пакетов. То есть нельзя устанавливать пакеты, удалять и т.п. Все приходится делать вручную.-+ Управление словарямиВ Эво тоже есть словари, но только на файлах. Управления словарями через админку там нет.-+
В данном топике рассмотрим различные примеры вызова сниппетов, и как это сказывается на кешировании.
Для начала сразу уточним, что есть два метода вызова сниппетов: 1. Прописать MODX-тег, типа [[snippet]] — кешируемый сниппет, или [[!snippet]] — некешируемый сниппет. 2. Вызвать выполнение сниппета через API MODX, то есть $modx->runSnippet('snippet');
Теперь рассмотрим, в чем здесь разница и как это влияет на кеширование.
Принципиально именно в процессе выполнения сниппетов программно, или на уровне синтаксиса, разницы нет. Это именно если говорить про выполнение кода сниппета, и если не брать во внимание кеширование и выполнение кешированных элементов. Если же говорить про кеширование, то сразу забегая вперед, скажу, что разница есть, если мы хотим кешировать результат, так как метод $modx->runSnippet() — это операция без учета кеша, то есть результат вызываемого таким образом сниппета всегда будет не из кеша, а реально выполняемый.
Но для того, чтобы правильно все это понимать, надо правильно понимать работу парсера и механизма кеширования MODX в целом. И главное, что здесь стоит уяснить, это то, что конечная обработка происходит только тогда, когда полностью сформирован документ, а не на уровне «когда парсер добрался до элемента, тогда его сразу и выполнил».
Объясню на примерах.
Для начала создадим два сниппета. snippet1
return $modx->runSnippet('snippet2');
и snippet2
return "<br />".time();
То есть при вызове первого сниппета, он будет вызывать второй сниппет и возвращать его результат, а snippet2 будет возвращать результат функции time(). Таким образом тогда, когда snippet2 будет возвращать некешированный результат, мы будем видеть актуальное время, а если результат будет кешированный, то при обновлении страницы время меняться не будет. Только не забудьте для тестовой страницы указать, что документ кешируемый, а то результат всегда будет некешированный.
Итак, сниппеты создали, теперь в контент документа пишем
[[snippet1]] [[snippet2]]
Сохраняем, и несколько раз обновляем страницу. Что мы видим? Каждый раз результат один и тот же, то есть оба сниппета возвращают кешированный результат.
1352834413 1352834413

(у вас будут другие цифры).
Почему так происходит? Ведь в сниппете snippet1 мы прописали вызов snippet2, и мы говорили о том, что это некешируемая операция. Чтобы получить ответ, нам следует глянуть кеш страницы (кеш каждого документа находится в папке core/cache/resource/{context}(по умолчанию это web)/resources/). ?
И что мы там видим?
<?php return array ( 'resourceClass' => 'modDocument', 'resource' => array ( 'id' => 1, 'type' => 'document', .................... 'content' => '[[snippet1]] [[snippet2]]',

То есть контент этой страницы — два кешируемых сниппета. Смотрим в этом же кеш-файле, что закешировано для этих сниппетов.
'elementCache' => array ( '[[*pagetitle]]' => 'Home', '[[snippet1]]' => '<br />1352834413', '[[snippet2]]' => '<br />1352834413', ),
То есть для этих сниппетов уже закеширован конечный результат, и этот результат будет выведен на страницу. И не зависимо от того, что snippet1 содержал в себе некешируемый вызов сниппета, сам сниппет snippet1 кешируемый, и его результат был закеширован. Как результат: при обновлении страницы мы видим одно и то же.
Теперь пропишем в контент это
[[!snippet1]] [[snippet2]]
То есть теперь у нас snippet1 будет некешируемый. Результат: при обновлении страницы в первой строке дата обновляется. Логично, так как у нас первый сниппет некешируемый. Посмотрим кеш документа:
'elementCache' => array ( '[[*pagetitle]]' => 'Home', '[[snippet2]]' => '<br />1352835034', ),
Как видим, первый сниппет вообще не закешировался.
А теперь рассмотрим вот такой вариант: 1. Создадим новый сниппет snippet3 и пропишем в нем следующее:
return '[[!snippet2]]';
А в контент пропишем
[[snippet1]] [[snippet2]] [[snippet3]]
То есть все 3 сниппета кешируемые. При этом обновим несколько раз страницу, и что видим?: в третьей строчке результат обновляется. Почему? Смотрим кеш.
'elementCache' => array ( '[[*pagetitle]]' => 'Home', '[[snippet1]]' => '<br />1352835306', '[[snippet2]]' => '<br />1352835306', '[[snippet3]]' => '[[!snippet2]]', ),
То есть кеш-содержимое третьего сниппета — некешируемый сниппет snippet2. Потому на выходе каждый раз при обновлении страница повторно будет вызываться snippet2.
Завершим последним примером. Создадим snippet4 и пропишем в него это:
$output = "<br />".time(); $output .= $modx->runSnippet('snippet2'); $output .= "[[!snippet2]]"; return $output;
А в контент пропишем только [[snippet4]], чтобы не городить вывод. Обновляем несколько раз страницу, и видим, что только третья строка обновляется. Что в кеше?
'elementCache' => array ( '[[*pagetitle]]' => 'Home', '[[snippet4]]' => '<br />1352835673<br />1352835673[[!snippet2]]', ),
То есть MODX-парсер закешировал все в кешируемом сниппете, кроме тега некешируемого сниппета.
Таким образом при создании всех своих шаблонов, чанков и т.п. думайте о том, что у вас в итоге попадет в кеш, так как использование большого числа некешируемых элементов внутри пусть даже и кешируемых элементов неизбежно приведет к увеличению нагрузки.