Вообще вопросом собственного репозитория для MODX-пакетов я задумывался уже давно (да и далеко не только я один), но так как раньше особо пакеты не собирал, особо не искал решения.
Сейчас же, когда у меня есть несколько своих пакетов, которыми я еще и с сообществом делюсь, по многим причинам озадачился созданием собственного репозитория. Гугление не помогло вообще, не нашел ни документации, ни готовых решений (кстати, если кто где-то натыкался на публичный репозиторий для MODX Revolution, дайте ссылку).
В общем решил написать этот репозиторий во что бы то ни стало. Как оказалось, задача вообще не из легких. Не буду сейчас вдаваться в подробности, но на все это дело убил три полных дня, и это совсем не по 8 часов в сутки… Ну да не важно. Главное — репозиторий есть :-) Не все еще в нем дописано (в частности пока не считает количество загрузок), но в целом механизм вполне работающий.
Но, думаю, для многих лучшей новостью будет то, что все это дело я оформил в пакет, и выкладываю в паблик:-) Теперь каждый может создать свой собственный репозиторий:-)
Для тех, кто думает «зачем он мне нужен? я все равно не умею создавать свои пакеты»: не переживайте. На modx.com есть отличный пакет: PackMan, которые позволяет очень быстро и удобно создавать несложные пакетики. В следующем топике я выложу видео как с его помощью создавать свои пакеты.
Особенно этот продукт будет полезен веб-студиям и фрилансерам. Не буду объяснять почему, это очевидно. Заметка: хотя я пока не дописал это, но в следующем релизе будет введен функционал для закрытых репозиториев с доступом по API-ключу. Накатится это дело простым апдейтом, так что можно не ждать, а устанавливать уже то, что есть.
Процесс установки показан на видео, но на всякий случай дублирую УРЛ моего репозитория: rest.modxstore.ru/extras/
UPD: Исходник с билдером на гитхабе: github.com/Fi1osof/modxrepository
UPD 2: Пытаюсь загрузить пакеты на Рево 2.2.4 из своего репозитория, возвращается пустой ответ, если кликнуть сразу пакет из лучших или новейши. MODX в запросе с браузера некорректно передает ID провайдера (В JS это MODx.provider). Если при этом кликнуть в раздел или поиск выполнить, то тогда все ОК.
Полез гуглить документацию по неведомому мне ранее объекту modRestServer (информации о которомя не нашел на rtfm, чему и не удивился вовсе), и наткнулся вот на такой источник: fossies.org/dox/modx-2.2.6-pl/index.html Документация на буржуйском, и само собой имеет минусы, свойственные генерируемой документации, но для тех, кто плотно работает с API MODX, это может быть очень хорошим источником ценных данных. Больше всего я оценил иерархическое представление всех объектов платформы, а так же кликабельные схемы, типа таких: ? А вот эта простая схемка как раз и показывает тесную связь modX и xPDO. ? В общем, я однозначно в закладки это дело…
Эта проблема уже не раз фигурировала: на последних версиях Рево в логи постоянно сыпется ошибка «Could not get table name for class: modFileMediaSource». Обновил сайт до 2.2.6, ошибка не пропала. Кто столкнется с этой проблемой и кому понадобится очень простое и быстрое решение, вот оно: создаем плагин на событие OnHandlerRequest. Чтобы надежней было (чтобы он вызывался раньше других возможных плагинов), ставим индекс очередности -100 (если не знаем где, забиваем на это). В плагин прописываем:
<?php if($modx->context->key == 'mgr') return; $modx->loadClass('sources.modMediaSource');
Все.
Данный топик — ответ вот этому: community.modx-cms.ru/blog/9281.html
Я не буду особо лезть в детали, с какими мы здесь трудностями сталкивается, а хочу только сказать, что даже со средними знаниями php можно обходить многие эти сложности. При этом ваши скрипты будут выполняться гораздо быстрее, и будут они более гибкими в использовании.
Справедливости ради приведу работающий вызов этого сниппета на уровне MODX-синтаксиса.
$output= '[[getImageList? &tvname=`afisha` &tpl=`Tpl` &docid=`338` &limit=`3` &sort=`[{"sortby":"data","sortdir":"ASC"}]` &where=`{"data:>":"[[!today]]"}` ]]';
Заметка автору: [[!today]] в вашем случае должен возвращать полную дату, например 2012-12-18 22:00:00, а не time(). И вам внимательно надо продумать правильное кеширование всего этого.
Самые главные минусу нативного решения: 1) В условие where можно прописать только одно поле. 2) Хоть мы и указываем limit, выборка происходит все равно всех элементов, а потом уже на уровне перебора массива срабатывает limit. Хотя здесь как правило небольшое количество элементов, но этот минус справедлив для многих подобных решений.
А теперь мой скрипт на замену:
<?php if(!$doc_id) {return '';} $output = ''; // Получаем TV по ID документа $q = $modx->newQuery('modTemplateVar'); $q->innerJoin('modTemplateVarResource', 'v', 'v.tmplvarid = modTemplateVar.id'); $q->where(array( 'v.contentid' => $doc_id, 'modTemplateVar.id' => $tv_id, )); $q->limit(1); $q->select(array('modTemplateVar.*', 'v.value')); if(!$tv = $modx->getObject('modTemplateVar', $q)){ return ''; } // Получаем его значение $value = $tv->get('value'); // Получаем массив из JSON-строки $array = $modx->fromJSON($value); // Пробегаемся по всему массиву и фильтруем по дате $data = array(); $now = time(); $i = 0; foreach($array as $r){ $time = strtotime($r['data']); if($time < $now){continue;} $data[$time] = $r; if(++$i == $limit){break;} } // Сортируем массив по ключу (чтобы по дате отсортировать) ksort($data); foreach($data as $r){ $output .= $modx->getChunk($tpl, $r); } return $output;
Да, он чуть сложнее, чем работа на уровне синтаксиса, но если вы освоите API MODX, то вы сможете работать с любыми объектами. Честно скажу, что у меня написание этого сниппета заняло меньше времени, чем изучение сниппета getImageList и выяснение, почему же &where=`[{«data:>»:"[[!today]]"}]` не работает, а &where=`{«data:>»:"[[!today]]"}` работает (пришлось лезть в код модуля и выяснять, что он не умеет принимать массив условий, а только одно условие ключ->значение).
При этом решение на уровне таких сниппетов и более гибкое, и работает быстрее, и кеширование гибкое прописать можно. В общем здесь куча плюсов.
Ребята, сегодня я сотворил бомбу :-))) Может кто-то и не оценит, но это реально классная штука!
Кому лень читать много буков, можно просто демку посмотреть.
В чем его суть? Это пакет, устанавливающий другие пакеты. Звучит просто, но не все так просто, как кажется. Скрипты автоустановки MODX всплывали и раньше. У безумкина это серверные скрипты, выполняющие установку, обновление или удаление MODX. Здесь Илья про свою сборку пишет. Мой пакет работает по другому принципу: он устанавливается как и обычный пакет. Но в него зашита дополнительная логика. В частности сейчас прописана только малая часть того, что я хочу получить в конечном итоге, а именно установка пакетов из списка. Все работает на уровне API MODX, то есть через $modx я и проверяю уже имеющиеся пакеты, установлены они или нет, качаю их, устанавливаю, проверяю ошибки и т.п. К слову, и в этот раз понадобилась вот эта информация, чтобы не обламывался вывод логов. Так вот, преимущество данного способа в том, что во-первых, не надо вообще доступа к серверу иметь, а во-вторых, ставить можно как на чистый MODX, так и уже на работающий с уже установленными пакетами. В перспективе из скрипта будут сразу выполняться всякие настройки системы, создание типовых разделов и т.п.
Забегая немного вперед, скажу, что все это я делаю в рамках своей идеи создания готовых сборок сайтов. Немного разверну мысль: с WordPress я познакомился ровно тогда же, когда и с MODX, а именно в январе-феврале 2009-го. Так как я люблю больше программировать, я уцепился за MODX. Мой же товарищ, не умеющий программировать вообще, но любитель блогосферы, подсел на WP. И хотя я сам ничего не делал с WP, я видел как он легко создает на нем новые сайты и что у него есть реальные посещения и т.п. К чему я это все? К тому, что Да, MODX — это Сила, это практически безграничные возможности, Но: 1) все-таки не так много готовых решений, как хотелось бы, 2) нет вообще стандартов по оформлению сайтов и норм шаблонизации, 3) как следствие — нет готовых сайтов-заготовок вообще. Сайтов на WP создается 100 000 в день. На Livestreet (который гораздо ближе к WP, нежели к MODX) тоже не мало сайтов. При этом на этих сайтах в порядке вещей используются дизайны-клоны. И это нормально. Заходишь на сайт, сразу видишь, что это WP или LS, но если там информация, которая тебя интересует, эти мелочи не берешь во внимание. А главное — простота в установке и обслуживании для тех, кто вообще не умеет программировать. Готовых и понятных решений масса. При чем в MODX тоже пакеты устанавливаются просто, но извините, тот же Wayfinder скачать — это мелочь, его еще воткнуть надо куда положено, и параметры всякие прописать. На тот же LS ставятся плагины, которые как правило не требуют никаких действий от владельца сайта. Скачал плагин, становил его, и тут же на сайте новый функционал, новые кнопочки и т.п. И это классно! Другое дело что дорабатывать это все сложнее, чем в MODX, но эти стандарты позволяют всем, и дизайнерам, и программистам, и конечным сайтовладельцам работать как одна команда. Как раз только вчера читал вот эту статью: www.noupe.com/wordpress/wordpress-or-modx-the-winner-is-73407.html (она на англ. Кто переведет и выложит перевод, респект и уважуха!). Человек пишет сравнительный обзор MODX и WP. И говорит о том же: там есть те плюсы и минусы, а там другие. И нам надо смотреть на сторонние решения и высматривать в них плюсы, и портировать их на MODX.
Что я хочу сделать? Я пока не доработал еще все, но скоро представлю комплексный пакет, который будет создавать правильную структуру сайта с запилом под шаблонизацию. Дело в том, что сейчас модель MODX не оконченная. В частности MODX-шаблоны, это на самом деле не шаблоны, а больше контроллеры, так как несколько шаблонов мы как правило создает для того, чтобы задать связи TV с документами и распределить логику (с этим шаблоном мы ленту новостей заложим, с этим у нас карточка товара формироваться будет, и т.д.). Но это не комплексная шаблонизация сайта. Попробуйте разом сменить дизайн сайта, а лучше попереключать несколько вариантов оформления… В модели, которую я закладываю, как я и сказал, MODX-шаблоны будут выполнять только роль контроллеров. Сама же шаблонизация будет выполняться с использованием различных систем-шаблонизаторов. Пакет modxSmarty я уже собрал (но выложу, когда окончательно его обкатаю и дополню). Он не устанавливает Smarty (так как Smarty уже есть в MODX), но он создает плагин, который подключает Smarty для фронтэнда, а так же прописывает настройки пути до папки шаблонов (скинов), используемого шаблона для сайта и т.п., очищает Smarty-кеш, когда очищается кеш всего сайт. А главное — устанавливает дополнительные функции-плагины для Smarty, которые позволяют работать с MODX-элементами. К примеру, чтобы вызвать тот же Wayfinder, можно будет на уровне Smarty-шаблона прописать {snippet name=«Wayfinder»}. В дальнейшем появятся и другие шаблонизаторы. Первый на очереди — Twig.
Все это не только откроет дорогу для тех программистов, которые не работали раньше с MODX только потому что им не хотелось связываться с парсером MODX, но и позволит привлечь дизайнеров для разработки готовых копируемых тем для MODX-сайтов. И быть может MODX все-таки войдет и в блогосферу…
UPD: Исходники пакета выложу позже, а пока самое интересное из него — резолвер, который и устанавливает пакеты.
<?php if ($object->xpdo) { $modx =& $object->xpdo; // $modelPath = $modx->getOption($pkgName.'.core_path',null,$modx->getOption('core_path').'components/ '.$pkgName.'/').'model/'; switch ($options[xPDOTransport::PACKAGE_ACTION]) { case xPDOTransport::ACTION_INSTALL: case xPDOTransport::ACTION_UPGRADE: if ($modx instanceof modX) { if(!$modx->loadClass('transport.modTransportPackage')){ return $modx->log(modX::LOG_LEVEL_ERROR, "Could not load modTransportPackage class"); } /* * Packages need to install */ $packages = array( 'Wayfinder', 'getResources', 'getPage', 'Ace', 'TinyMCE', 'phpTemplates', 'GoogleSiteMap', ); $modx->log(modX::LOG_LEVEL_INFO, "Trying to install packages: ". implode(", ", $packages). "<br />\n Be patient. it`s take a several moments."); $modx->setLogLevel(modX::LOG_LEVEL_INFO); $LogTarget = $modx->getLogTarget(); $LogTopic = $LogTarget->subscriptions[0]; $messages = array(); foreach($packages as $packageName){ if(!$packageName) continue; if($packageExists = $modx->getObject('modTransportPackage', array( 'package_name' => $packageName, ))){ $messages[] = array( 'level' => modX::LOG_LEVEL_INFO, 'msg' => "Package '{$packageName}' allready exists" , ); /* * Check is installed */ if(!$packageExists->get('installed')){ $messages[] = array( 'level' => modX::LOG_LEVEL_ERROR, 'msg' => "Package '{$packageName}' exists, but not installed. Please, install it manually" , ); } continue; } // $modx->log(modX::LOG_LEVEL_INFO, "Try to get '{$packageName}' package."); $response = $modx->runProcessor('workspace/packages/rest/getlist', array( 'provider' => 1, 'query' => $packageName, )); if($response->isError()){ $messages[] = array( 'level' => modX::LOG_LEVEL_ERROR, 'msg' => $response->getMessage(), ); continue; } if(!$result = json_decode($response->getResponse())){ $messages[] = array( 'level' => modX::LOG_LEVEL_ERROR, 'msg' => "Error while reading results" , ); continue; } /* * Try to find package info */ foreach($result->results as $r){ $package = (array)$r; if($package['name'] == $packageName){ goto downloadPackage; } } /* * Info was not found */ $messages[] = array( 'level' => modX::LOG_LEVEL_ERROR, 'msg' => "Package '{$packageName}' was not found. Please try to install it manually" , ); continue; /* * Download Package */ downloadPackage: $messages[] = array( 'level' => modX::LOG_LEVEL_INFO, 'msg' => "Trying to download '{$packageName}' package" , ); $response = $modx->runProcessor('workspace/packages/rest/download', array( 'provider' => 1, 'info' => "{$package['location']}::{$package['signature']}", )); if($response->isError()){ $messages[] = array( 'level' => modX::LOG_LEVEL_ERROR, 'msg' => "Failed download '{$packageName}'. ".$response->getMessage(), ); continue; } $result = $response->getResponse(); if($result['success'] == 1){ $messages[] = array( 'level' => modX::LOG_LEVEL_INFO, 'msg' => "Package '{$packageName}' downloaded success" , ); goto installPackage; } $messages[] = array( 'level' => modX::LOG_LEVEL_ERROR, 'msg' => "Failed download '{$packageName}'.", ); /* * Installing Package */ installPackage: $messages[] = array( 'level' => modX::LOG_LEVEL_INFO, 'msg' => "Trying to install '{$packageName}' package" , ); $response = $modx->runProcessor('workspace/packages/install', array( 'signature' => $package['signature'], )); if($response->isError()){ $messages[] = array( 'level' => modX::LOG_LEVEL_ERROR, 'msg' => "Failed install '{$packageName}'. ". $response->getMessage(), ); continue; } $messages[] = array( 'level' => modX::LOG_LEVEL_INFO, 'msg' => $response->getMessage() , ); } $modx->registry->setLogging($LogTarget, $LogTopic); // $modx->log(xPDO::LOG_LEVEL_ERROR, '$err'); // print_r($messages); $modx->setLogLevel(modX::LOG_LEVEL_INFO); foreach($messages as $msg){ $modx->log($msg['level'], $msg['msg']); } } break; } } return true;
Не буду особо про него расписывать, потому что не уверен, что этот пакет заинтересует широкие массы, но лично я его делал для себя и буду использовать много где. Это не просто пакет, который инициализирует $modx->smarty. Он в себе еще содержит функции-плагины для смарти, позволяющие вызывать и обрабатывать MODX-элементы непосредственно в Smarty. К примеру: {snippet name=«Wayfinder»} Таким образом непосредственно в Smarty-шаблоне будет выполнен Wayfinder. Или {parser content=«HTML-code [[++site_url]] etc.»} На выходе получим обработанный MODX-парсером контент.
У кого-то возникнет вопрос «Зачем парсить что-то внутри Smarty-шаблона, если все равно на выходе MODX обработает контент?». 1. MODX хранит довольно большой и хитрый кеш. А если у нас включено Smarty-кеширование и мы правильно им пользуемся, то у нас этот сниппет вызовется только один раз, а потом уже у нас просто конечный HTML, и MODX на это не будет напрягаться. 2., 3., 4. и т.д. — много личных причин так делать.
Исходник с билдером: github.com/Fi1osof/modxSmarty Готовый пакет: sourceforge.net/projects/modxsmarty/
Предыстория. (кому не интересно, пропустите)
Дело было еще почти два месяца назад, когда я только столкнулся с MODX 2.2.5 и «классными» процессорами (до этого я целый год был занят с одним из своих проектов на Рево 2.0.8, и с более новыми версиями Ревы вообще не работал. Да, много нового в Рево с тех пор появилось). И вот я экспериментирую с Рево, и первым делом именно с «классными» процессорами (как раз для modLivestreet процессор писал, который должен был расширить базовый процессор создания пользователя). И вот тогда у нас с bezumkin вот такой немаленький диалог состоялся:
Я: Василий, привет! Только вот сегодня пришлось покопаться с процессорами на рево 2.2.5 Вижу, что если сделать по новым стандартам, то не будет работать в старых версиях. А начиная с какой версии идут именно такие «классные» процессоры? Чтобы compare_version сделать.
ОН: С версии 2.2.0 — это основное ее изменение.
Я: Да уж, серьезное такое изменение… Слушай, а у тебя в примерах только расширение базовых процессоров. А расширения системных процессоров не делал? К примеру modUserCreateProcessor? Я просто к тому, что не нашел метода их нативно подгружать. Видимо придется писать
require_once MODX_PROCESSORS_PATH.'security/user/create'; class modTestProcessor extends modObjectCreateProcessor......
Не сталкивался с такой задачей?
ОН: Именно так это и делается — все верное, надо require.
Я: ОК. Хотя считаю это не самый классный вариант. Вот раньше $modx->runSnippet() тупо выполнял нужный нам файл. Сейчас процессоры стали более умные, но получилась другая крайность — изолированность системных процессоров (для расширения). Сейчас в функции $modx->runProcessor добавилось масса проверок на тип процессора, поиск пути и т.п. (именно в плане файловой системы), но и там же сразу выполнение. Так вот я считаю, что было бы очень круто ту логику, которая отвечает за поиск файла процессора, вынести в отдельный метод, который бы вызывался методом $modx->runProcessor(), но и из других мест можно было вызывать, что-то типа $modx->getProcessor или $modx->findProcessor.
ОН: Ты бы документацию почитал, что ли. Путь к процессору указывается — это и в моей заметке есть.
А runSnippet() выполняет нужное — ибо оно всегда лежит в одном и том же месте, в БД.
Я: Тогда можно было бы написать типа
if($modx->getProcessor('security/user/create')){ $response = $modx->runProcessor('test'); }
или сразу в файле процессора test проверять этот процессор, и если косяк, то возвращать ошибку через
$modx->error->failure('Processor not found');

Ты бы документацию почитал, что ли.
Пытался. Что-то она кажется совсем отсталая. (я имею ввиду rtfm.modx.com). Я так понимаю, теперь только исходники на github читать.
Путь к процессору указывается — это и в моей заметке есть. А runSnippet() выполняет нужное — ибо оно всегда лежит в одном и том же месте, в БД.
Да нет, я имею ввиду не базовые процессоры, а прочие. То есть те, классы которых просто так не подгружаются, надо именно runSnippet вызывать. Сейчас код покажу и объясню, что имею ввиду. Вот часть метода $modx->runSnippet();
public function runProcessor($action = '',$scriptProperties = array(),$options = array()) { // ......................................... /* calculate processor file path from options and action */ $isClass = true; // !!! Вот здесь формируется путь к процессору $processorsPath = isset($options['processors_path']) && !empty($options['processors_path']) ? $options['processors_path'] : $this->config['processors_path']; if (isset($options['location']) && !empty($options['location'])) $processorsPath .= ltrim($options['location'],'/') . '/'; $processorFile = $processorsPath.ltrim(str_replace('../', '', $action . '.class.php'),'/'); if (!file_exists($processorFile)) { $processorFile = $processorsPath.ltrim(str_replace('../', '', $action . '.php'),'/'); $isClass = false; } $response = ''; // !!! Если файл есть, то вызываем его if (file_exists($processorFile)) { // .................................. if (empty($processor)) { $processor = new modDeprecatedProcessor($this); } $processor->setPath($processorFile); $processor->setProperties($scriptProperties); $response = $processor->run(); // !!! Иначе ошибка } else { $this->log(modX::LOG_LEVEL_ERROR, "Processor {$processorFile} does not exist; " . print_r($options, true)); } return $response; }
Я же говорю о том, что правильней было бы типа
public function getProcessorFile($action = '',$scriptProperties = array(),$options = array()){ // !!! Вот здесь формируется путь к процессору $processorsPath = isset($options['processors_path']) && !empty($options['processors_path']) ? $options['processors_path'] : $this->config['processors_path']; if (isset($options['location']) && !empty($options['location'])) $processorsPath .= ltrim($options['location'],'/') . '/'; $processorFile = $processorsPath.ltrim(str_replace('../', '', $action . '.class.php'),'/'); if (!file_exists($processorFile)) { $processorFile = $processorsPath.ltrim(str_replace('../', '', $action . '.php'),'/'); $isClass = false; } // !!! Если файл есть, то вызываем его if (file_exists($processorFile)) { // .................................. return $processorFile; // !!! Иначе ошибка } else { $this->log(modX::LOG_LEVEL_ERROR, "Processor {$processorFile} does not exist; " . print_r($options, true)); return false; } } public function runProcessor($action = '',$scriptProperties = array(),$options = array()) { // ......................................... /* calculate processor file path from options and action */ $isClass = true; $response = ''; // !!! Если файл есть, то вызываем его if ($processorFile = $this->getProcessorFile($action = '',$scriptProperties = array(),$options = array())) { // .................................. $className = include_once $processorFile; //.................................... if (!empty($className)) { $c = new $className($this,$scriptProperties); $processor = call_user_func_array(array($c,'getInstance'),array($this,$className,$scriptProperties)); } //.................................... if (empty($processor)) { $processor = new modDeprecatedProcessor($this); } $processor->setPath($processorFile); $processor->setProperties($scriptProperties); $response = $processor->run(); // !!! Иначе ошибка } else { $this->log(modX::LOG_LEVEL_ERROR, "Processor {$processorFile} does not exist; " . print_r($options, true)); } return $response; }
Вот тогда я бы мог попытаться подгрузить процессор, и если не получилось бы, вернул ошибку.
global $modx; if(!$processorFile = $modx->getProcessorFile('security/user/create')){ return false; } include $processorFile; class modTestProcessor extends modUserCreateProcessor{ // ...................... } return 'modTestProcessor';
Он: Нахер все усложнять?
У метода runProcessor указывается любой путь к любым процессорам. Если по этому пути он процессор не найдет — вернет ошибку «processor not found». Я просто не понимаю, зачем ты выдумываешь новые сущности, если уже все есть.
Посмотри любой кастомный компонент — там процессоры лежат в своих директориях. Вот я в miniShop обращаюсь к процессорам из сниппета.
Ты поразбирайся с тем, что есть, прежде чем предлагать новые методы.
Я: Ты меня явно не слышишь. Я не говорю про случай, когда мне надо подгрузить свой процессор. Безусловно проверка на нахождение моего файла прописана в методе $modx->runSnippet(), и если не найдет его, то вернет ошибку. Но я говорю, что хочу, чтобы мой найденный процессор расширял другой объект процессора, и я хочу провести проверку этого процессора. Вот у тебя процессор в твоем Loginza
require MODX_CORE_PATH.'model/modx/processors/security/user/update.class.php'; class LoginzaUpdateProcessor extends modUserUpdateProcessor {
Где у тебя тут проверка на наличие процессора modUserUpdateProcessor? Если твой пакет поставить на чей-то отсталый Рево, у тебя не просто не выполнится процессор, выполнение MODX вообще развалится, потому что require — это критическая операция, и если файл не будет найден, то все, бабах. С другой стороны проверять if(!file_exists(MODX_CORE_PATH.'model/modx/processors/security/user/update.class.php')) тоже не круто, потому что MODX до сих пор поддерживает modDeprecatedProcessor. Потому хотелось бы вызывать такой метод, который бы проверял наличие файла процессора как современного, так и deprecated
Он: Для этого в репозитории указывается минимальная версия для работы компонента. Для более старых компонент не покажется.
Я: Хорошо. Аргумент. А если глупый юзверь удалил этот процессор, или тебе подгрузить надо какой-нибудь кастомный процессор? Понятно дело, что на это можно придумать много чего, но с чем спорить невозможно, так это с тем, что проверки и отладка должны быть всегда, так вот разработчики MODX запихнув все проверки в метод runProcessor оставили эти проверки чисто для себя, и извне подобные проверки уже придется писать самим, а это не круто.
Он: У тебя видно дохера времени свободного.
Я: Нет, не дохера, но во-первых, привык до конца понимать то, с чем имею дело, а во-вторых, привык писать так, чтобы потом было легко обслуживать. Какой смысл писать что-то, не понимая до конца что и как работает. Это как у меня один программист ответил на вопрос «тебе не кажется, что у тебя как-то все долго работает?»: «ну я там написал, а что именно выполняется, не знаю».
Итог
Переношу сегодня сайт на Рево и столкнулся с такой проблемой: открываю документ для редактирования, а сайт разваливается, ошибка 500. В логах ошибка:
PHP Fatal error: Cannot redeclare class modTemplateVarInputRenderText in /var/www /..../public_html/core/model/modx/processors/element/tv/renders/mgr/input /text.class.php on line 10
Ясно-понятно, что проблема с повторной подгрузкой одного и того же файла процессора с описанным классом. Но почему? Кстати, как раз вчера задумался, а как MODX проверяет подгружать файл процессора или нет, чтобы не произошло ошибки? Оказывается, проверка-то совсем не надежная. В этом плане MODX ориентируется только на путь до предполагаемого файла, при чем даже без учета realpath. То есть если симлинки или типа того, и будут переданы разные пути к одному и тому же файлу, то MODX не разберется, и подгрузит второй раз файл, и сайт развалится. Ну да ладно, это к слову. А в данном случае критическая ошибка как раз из-за того, о чем я так много писал выше, то есть из-за отсутствия стандартного метода подгрузки файла процессора до его выполнения.
Из-за того, что нет нормального метода подгрузки файла процессора (с целью объявления нужного нам класса процессора), в объекте modTemplateVar есть вот такой хитрый код:
if (empty($output)) { $p = $this->xpdo->getOption('processors_path').'element/tv/renders /mgr/'.$method.'/'; $className = $method == 'output' ? 'modTemplateVarOutputRenderText' : 'modTemplateVarInputRenderText'; if (!class_exists($className) && file_exists($p.'text.class.php')) { $className = include $p.'text.class.php'; } if (class_exists($className)) { $render = new $className($this); $output = $render->render($value,$params); } }
То есть, давным давно, когда процессоры еще не были объектами, а были простыми php-файлами, и которые имело смысл только вызывать через метод $modx->runProcessor(), с их последующей подгрузкой там же, то ничего и выдумывать не надо было. Когда же процессоры стали «классными» (и не только потому что они превратились в классы, а еще и потому что это реально классная штука получилась), логично стало, что эти объекты захочется подгружать и использовать в различных целях. В данном случае, этот хитрый перец (которые дописывал этот код), решил «А нафига я буду писать лишние строки? Это же все есть вооон в том процессоре. Давай ка я его использую для себя. Но я обязательно пропишу проверку, а вдруг этот класс уже инициализирован, и его подгружать уже нельзя». Да, проверку-то он прописал. А в самом $modx->runProcessor() эту проверку прописали? НЕТ. А главное — они и не могли ее прописать. Проблема в том, что файлы этих классных процессоров возвращают строку с названием выполняемого класса, но совершенно нет стандартов, как этот класс должен называться, что лично мне вообще не понятно. Я обязательно поинтересуюсь, кто был носителем идеи и внедрением «классных» процессоров, но думаю, что не Jason. В xPDO есть система имен файлов и классов в них, а здесь ее нет. В итоге MODX просто не может знать имя вызываемого объекта и решать подгружать файл или нет.
Резюмирую: на самом деле «классные» процессоры — это огромный прорыв. Это реально классная штука. Но они «выросли», они стали больше, чем просто файлы, подгружаемые в $modx->runProcessor(). Они превратились в очень мощные и самостоятельные объекты. О обязательно надо вводить функцию подгрузки этих классов а-ля $modx->loadClass(); А пока что это плюс ко всему, еще и источник головной боли.
P.S. Для тех, кто ждал, как же это фиксится: я решил, что раз уж так все не по религии сделано, то и я не буду из кожи лезть. Тупо в файл процессора прописал так:
if(!class_exists('modTemplateVarInputRenderText')){ class modTemplateVarInputRenderText extends modTemplateVarInputRender { public function getTemplate() { return 'element/tv/renders/input/textbox.tpl'; } } } return 'modTemplateVarInputRenderText';

Так как постоянно слышу вопросы по поводу пользовательских таблиц и объектов в xPDO, делаю репост своей очень старой статьи на хабре. Пишу сразу с дополнениями, чтобы полностью описать что и для чего делается. Хотя та статья писалась давно, но метод до сих пор актуальный, и я им до сих пор часто пользуюсь. Так что всем новичкам советую сразу в избранное добавлять.
Сразу уточню: данный метод максимально подойдет именно новичкам, но тем, которые хоть как-то плавают в SQL, потому что если вы даже не умеете создавать таблички через phpMyAdmin, то сначала следует изучить именно основы MySQL и научиться создавать таблички и писать простейшие SQL-запросы, и только потом уже сюда.
Еще уточнение: многие считают, что правильно создавать вручную XML с описанием БД, а потом уже из нее генерировать конечные объекты. Я с этим спорить не буду. Но это более сложная процедура, и лично мне ни разу не понадобилась. Мы рассмотрим более простой метод: создание своих таблиц сразу в базе данных, а потом генерацию скриптом конечных объектов и XML с описанием модели, в который, кстати, можно уже дописать вручную связи таблиц и сгенерировать уже объекты со связями.
Итак, задача: Создать свои таблицы и иметь возможность формировать к ним запросы через xPDO и API MODX. То есть, к примеру, создать таблицу modx_mypackage_my_table и потом иметь возможность выполнять типа этого:
$obj = $modx->newObject('MyTable'); $obj->set('name', 'Калинка'); $obj->save(); // Или $obj = $modx->getObject('MyTable', array( 'name' => 'Калинка' )); print $obj->get('id');
Думаю, задача всем ясна.
Теперь немного обобщенной теории, что и как нам надо будет сделать. 1. Создаем нужные нам таблицы в базе данных через phpMyAdmin (или кто что использует для работы с БД). Только сразу имейте ввиду, что чтобы было без гемора, префиксы для таблиц надо задавать отличающиеся от MODX-префиксов. К примеру, если у MODX префикс modx_, то задавайте modx_myprefix_ или типа того. 2. Генерируем скриптом конечную модель с файлами-классами. 3. Перед выполнением запросов к объектам или подключаем пакет через $modx->addPackage(), или методом, описанным здесь. 4. Все, можно работать с объектами.
Сама статья с хабра
В данной статье мы разберем следующие вопросы:
  1. Создание пользовательских таблиц для MODx Revolution.
  2. Генерация XML-схемы и php-файлов-классов для xPDO.
  3. Работа с пользовательскими таблицами.
Одна из самых больших сложностей в переходе с Evolution на Revolution — это xPDO ИМХО. Ведь как раньше было просто: залез в мускул через phpMyAdmin, создал табличку, и работаешь с ней, отправляя чистые SQL-запросы через $modx->db. Теперь же любые изменения в базе данных ни сколько не беспокоят $modx, кроме как полный drop таблиц и т.п. Как связаны между собой xPDO и MODx (а точнее $modx), я написал тут. Считаю сложности в работе с пользовательскими таблицами в MODx Revolution — немалым препятствием для многих программистов. Но на самом деле, все не так уж и сложно. Весь рассматриваемый далее алгоритм в трех словах:
  1. Так же через phpMyAdmin (или другой удобный используемый инструмент) создаем нужные нам таблицы
  2. Используя прилагаемый ниже код генерируем необходимые для работы xPDO файлы
  3. Используя метод $modx->addPackage() подгружаем в нужном месте наш новый класс и уже работаем с нашими таблицами через методы $modx->getObject(), $modx->getCollection() и т.п.
Итак, необходимый нам для генерации файлов код (Исходник тут):
<?php /*******************************************************/ /* Конфиги нашей схемы */ /*******************************************************/ // Имя Класса. Так будет потом называться Класс при вызове $modx->addPackage() $obj = 'program'; /* Префикс таблиц. Если префикс не отличается от системного, то можно вообще не указывать. К сожалению, xPDO при генерации не позволяет перечислять имена конкретных таблиц, которые нам нужны, а позволяет отсеять только по префиксу. */ $tablePrefix='modx_program_'; // Папка, где будет записана XML-схема и все файлы создаваемого объекта // Путь к файлам класса вы будете потом прописывать в вызове метода $modx->addPackage(); $Path = dirname(__FILE__).'/model/'; // Файл-схема $Schema = $Path.'/'.$obj.'.mysql.schema.xml'; /*******************************************************/ // Подгружаем основной файл-конфиг сайта или самим придется прописывать все основные настройки require_once dirname(dirname(dirname(__FILE__))).'/core/config/config.inc.php'; // Подружаем основной класс MODx include_once MODX_CORE_PATH . 'model/modx/modx.class.php'; // Инициализируем класс MODx $modx= new modX(); // Инициализируем контекст, если принципиально // $modx->initialize('mgr'); // Устанавливаем настройки логирования // Не обязательно $modx->setLogLevel(modX::LOG_LEVEL_INFO); $modx->setLogTarget(XPDO_CLI_MODE ? 'ECHO' : 'HTML'); // !!! Обязательно! // Подгружаем основной класс-пакер $modx->addPackage('transport.modPackageBuilder', '', false, true); // Указатель типа базы данных (MySQL / MsSQL и т.п.) $manager = $modx->getManager(); // Класс-генератор схем $generator = $manager->getGenerator(); // Генерируем файл-XML // /xpdo/om/mysql/xpdogenerator.class.php // public function writeSchema($schemaFile, $package= '', $baseClass= '', $tablePrefix= '', $restrictPrefix= false) // $tablePrefix - указываем, если хотим только те таблицы, которые начинаются с этого префикса. // $restrictPrefix - указывает true, если хотим получить таблицы только по префиксу $xml= $generator->writeSchema($Schema, $obj, 'xPDOObject', $tablePrefix ,$restrictPrefix=true ); // Создает классы и мапы (php) по схеме xml $generator->parseSchema($Schema, $Path); print "<br /><br />Выполнено";
Заливаем этот файл на сервер, прописываем ему правильно пути к конфигу MODx-а, и директории, где будут созданы сгенерированные файлы. Обращаемся к файлу через браузер. В результате в указанной папке должны быть созданы xml-файл, в котором будут описаны наши таблицы и php-файл. Эти файлы, если очень надо, можно переместить в другую папку, где вы решите хранить свой новый Класс. В дальнейшем эти файлы всегда будут нужны для работы со своими таблицами.
Теперь вы можете работать со своими таблицами. Для того, чтобы лучше понимать механизм взаимодействия xPDO с XML-схемой, а так же понимать, что прописывается в файле-генераторе, и какие объекты получаете на выходе, привожу свою заметку по данному вопросу. Честно сказать, по началу без нее было просто никак…
1. Подключение пакетов и выполнение запросов к схеме
1.1 Выборка записей из таблицы
Первое, что нужно сделать, это разобраться что вообще нужно для того, чтобы обратиться к схеме. Для этого изучим XML-файл. Вот основной блок созданой нами схемы:
<model package="testtbl" baseClass="xPDOObject" platform="mysql" defaultEngine="MyISAM" version="1.1"> <object class="Tbl" table="tbl" extends="xPDOObject"><param name="wmode" value="opaque"></param>
Самое основное, что нам тут понадобится, это:
<model package="testtbl"
значение атрибута package нам нужно, чтобы знать какой пакет нам вообще нужно подгружать. В данном случае это testtbl. Вызывать пакет будем так:
$modx->addPackage( $package, $path, $prefix); // !!! Проверка в функции addPackage не канает, так как проверяется только присутствие названия пакета в принципе и чтобы указанный путь был директорией, и всё.
Рассмотрим внимательней параметры. $package. Название пакета. В нашем случае как раз testtbl $path. Путь к каталогу пакета (в котором есть XML-файл и папка по названию Объекта, в котором все PHP-файлы) $prefix. Если при создании схемы указывался префикс, отличный от дефолтового, то обязательно нужно его указать.
Есть еще одна маленькая хитрость, как точно определить необходимый префикс. Формула: $prefix = $fullTableName$tableName; Тут $fullTableName — это полное имя таблицы базы данных, к которой обращаемся, $tableName — значение атребута table тега <object нашего файла XML
Выполняем запрос:
$result = $modx->getCollection('Tbl'); foreach($result as $row){ print "<br />Next:". $row->get('columnName'); }
1.2 Создание новой записи в таблице
private static function testCreateRows(){ $pkg = 'testtbl'; $modx->addPackage( 'testtbl', $Path , 'modx_kl_test')) ; $row = $modx->newObject('Tbl'); $row->fromArray(array( 'id' => 5 )); return $row->save(); }
Топик пишется в ответ на этот: community.modx-cms.ru/blog/questions/9210.html
Для начала немного теории: Для того, чтобы предоставить доступ только к определенным компонентам, надо сделать несколько вещей: 1. Создать роль с минимальным набором прав, чтобы пользователь в принципе мог заходить в админку (если такая роль у вас еще не создана). назовем эту роль (и группу пользователей) Components. Список прав:
  • frames (чтобы в принципе иметь доступ к админке).
  • home (чтобы иметь доступ к главной странице админки, а то заходя в админку, пользователь будет видеть сообщение об отказе в доступе).
  • logout (чтобы мог выходить из админки).
  • components (доступ к меню компонентов).
  • list, load, view, save (базовые права, которые с большой долей вероятности могут проверяться в компонентах и на уровне xPDO).
2. На странице управления действиями в тех пунктах меню, к которым надо предоставить только выборочный доступ, прописать свои названия проверяемых политик. 3. Создать свои роли с правами доступа к нужным компонентам. 4. Предоставить доступы к контексту mgr данным ролям (группам пользователей). 5. Пользователя внести в группу Components и в те группы, которые дают доступы к нужным компонентам. 6. Если компонент создает свои политики и роли (компонент Quip создает политику, правда эта политика имеет смысл только во фронтэнде), то вносим пользователя и в группу с этими политиками.
Много расписывать не стану, просто видео с примером заснял. Если что не понятно, спрашивайте в комментах.
Я уже писал про свои идеи частичного отказа от MODX-парсера и подключения сторонних шаблонизаторов, которые воплотились в пакет phpTemplates. И сейчас хочу немного поделиться результатами.
Прирост производительности подтвердился опытным путем. Вот две абсолютно идентичные страницы: yley.msk0.ru/ yley.msk0.ru/test/ Первая работает на связке MODX+Smarty с использованием phpTemplates, а вторая на чистом MODX. Повторюсь: шаблон один-в-один, используемые чанки и сниппеты используются одни и те же и сидят они в одинаковых местах. Страницы очень легкие, без динамических элементов и полностью закешированные. Время выполнения: — Чистый MODX: минимальное зафиксированное 0.076 сек. В среднем 0.08 сек. — phpTemplates: минимальное: 0.0489 сек. В среднем 0.05-0.055 сек. ?
Сайт на шаред-хостинге, потому результаты скачут, но факт остается фактом: на чистом MODX такого результата не удалось добиться.