Тематика этого топика будет довольно обширная, но опишу все в одном топике, так как все связано. Постараюсь кратко. Для начала о пакетах Любой, кто хоть что-то пытался разработать на MODX-е, знаком с менеджером пакетов (ставил Wayfinder, getResource и т.п.). При этом далеко не все, кто уже что-то пишет под MODX самостоятельно (сниппеты, плагины и т.п.), оформляет свой код в пакеты. А вот это очень и очень зря. Во-первых, часто такой код плохо локализован, разбросан по сайту и мало с чем совместим, он просто есть на сайте и все, но его сложно отделить от сайта и перенести на другой сайт. А это уже и риски того, что при обновлении сайта что-то может сломаться, да еще и потеря собственных наработок. Так что, учитесь оформлять все свои наработки в пакеты. И это совершенно не сложно. Если у вас простой пакет, без каких-то выполняемых при установке скриптов (создания таблиц в базе данных и т.п.), то вы можете пакеты собираться packMan-ом. Там все просто и понятно. Упаковать можно шаблоны, сниппеты, чанки, плагины и другие пакеты, а так же любые директории сайта. Пара рекомендаций.
if($this instanceof Modxsite){ $modxsite = & $this; } else{ $modxsite = & $this->modxsite; } $modxsite->loadProcessor('web.resourceproduct.getdata', 'shopmodx');
class modWebCatalogProductsGetdataProcessor extends modWebResourceproductGetDataProcessor{
public function initialize() {
if(!$this->getProperty('sort')){
$this->setProperty('sort', "{$this->classKey}.menuindex");
$this->setProperty('dir', "ASC");
}
return parent::initialize();
}
protected function prepareCountQuery(xPDOQuery &$query) {
$query = parent::prepareCountQuery($query);
$query->innerJoin('modTemplateVarResource', 'image',
"image.contentid = {$this->classKey}.id and image.tmplvarid=8 and image.value != ''");
$query->where(array(
'deleted' => 0,
'hidemenu' => 0,
'published' => 1,
));
return $query;
}
/*
* Подготавливаем данные товара
*/
public function iterate(array $data) {
$data = parent::iterate($data);
// УРЛ источника картинок:
$imagesUrl = $this->modx->runSnippet('getSourcePath');
foreach($data as & $d){
// Получаем картинку
$d['image'] = $imagesUrl.$d['tvs']['sm_image']['value'];
// Набиваем иконки
$icons = array();
if(!empty($d['tvs']['sm_new']['value'])){$icons[] = 'i-new';}
if(!empty($d['tvs']['sm_stock']['value'])){$icons[] = 'i-stock';}
if(!empty($d['tvs']['sm_discount']['value'])){$icons[] = 'i-discount';}
$d['icons'] = $icons;
}
return $data;
}
}
return 'modWebCatalogProductsGetdataProcessor'; Конечно этот процессор не самый корневой, так как он расширяет shopModx-овый modWebResourceproductGetDataProcessor, а тот в свою очередь расширяет еще несколько процессоров, но это не принципиально, так как для нас это процессор из другого компонента, а в рамках нашего сайта представленный процессор является корневым. Но для полной управляемости нам само собой необходимо это знать и изучить shopModx-процессоры отдельно. Итак, shopModx-овый процессор делает выборку документов товаров, но без каких-то дополнительных условий, то есть не учитывает опубликован документ или нет, удален или нет и т.п. Плюс к этому оформляет все в виде массива данных со всеми TV-шками (довольно подробно об этом писал здесь). Так что же я делаю в своем пользовательском процессоре, расширяющем базовый?
class modWebCatalogProductsGetmodeldataProcessor extends modWebCatalogProductsGetdataProcessor{ protected $modelsIDs = array();
public function initialize() {
if(!$this->getProperty('sort')){
$this->setProperty('sort', "Models.menuindex");
$this->setProperty('dir', "ASC");
}
return parent::initialize();
}
// Готовим запрос на выборку уникальных объектов
protected function PrepareUniqObjectsQuery(xPDOQuery &$query) {
// Группируем по родителям
$query->groupby("{$this->classKey}.parent");
return parent::PrepareUniqObjectsQuery($query);
}
/*
* Подсчитываем количество товаров именно по ID-шникам их предков (то есть именно моделей),
* так как нам нужны уникальные модели
*/
protected function countTotal($className, xPDOQuery &$criteria) {
$count= 0;
if ($query= $this->modx->newQuery($className, $criteria)) {
if (isset($query->query['columns'])) $query->query['columns'] = array();
$query->select(array ("COUNT(DISTINCT {$this->classKey}.parent)"));
if ($stmt= $query->prepare()) {
// print $query->toSQL();
if ($stmt->execute()) {
if ($results= $stmt->fetchAll(PDO::FETCH_COLUMN)) {
$count= reset($results);
$count= intval($count);
}
}
}
}
return $count;
}
public function prepareQueryBeforeCount(xPDOQuery $c){
$c = parent::prepareQueryBeforeCount($c);
$c->innerJoin('modResource', 'Models', "Models.id = {$this->classKey}.parent");
return $c;
}
public function setSelection(xPDOQuery $c) {
$c = parent::setSelection($c);
$c->select(array(
'Models.id as model_id',
'Models.pagetitle as model_title',
));
return $c;
}
} return 'modWebCatalogProductsGetmodeldataProcessor'; Здесь происходит все то же самое, что и в родительском процессоре (то есть те же проверки на опубликованность, наличие картинки и т.п.), но плюс к этому выполняется выборка именно уникальных товаров для уникальных моделей. Конечный массив данных почти не отличается от результата выполнения предыдущего процессора, только в массив данных товара попадут еще ID модели и заголовок модели. Далее у нас задача еще усложняется. Нам надо сделать выборку всех моделей товаров из целого раздела на два три вложенности. И вот еще один процессор: <?php /*
require_once dirname(dirname(FILE)).'/getmodeldata.class.php';
class modWebCatalogProductsBycatalogsectionGetdataProcessor extends modWebCatalogProductsGetmodeldataProcessor{ protected $modelsCount = 0; // Общее число моделей protected $productsCount = 0; // Общее число товаров
/*
* Проверяем наличие переменной ID категории
*/
public function initialize() {
if(!((int)$this->getProperty('categoryid', false))){
return 'Не был указан ID категории';
}
$this->setDefaultProperties(array(
'limit' => 10,
));
return parent::initialize();
}
/*
* Делаем поиск моделей товаров, чтобы в дальнейшем искать конечные товары
*/
public function beforeQuery() {
// Собираем ID-шники моделей только в одной категории
$c = $this->modx->newQuery('modResource');
$c->innerJoin('modResource', 'Parent');
$c->where(array(
'Parent.parent' => (int)$this->getProperty('categoryid'),
'Parent.deleted' => 0,
'Parent.hidemenu' => 0,
'Parent.published' => 1,
'deleted' => 0,
'hidemenu' => 0,
'published' => 1,
'class_key' => 'ShopmodxResourceProductModel',
));
$c->select(array(
"DISTINCT modResource.id"
));
if($c->prepare() AND $c->stmt->execute() AND $rows = $c->stmt->fetchAll(PDO::FETCH_ASSOC)){
foreach($rows as $row){
$this->modelsIDs[] = $row['id'];
}
}
else{
$this->modx->log(xPDO::LOG_LEVEL_ERROR, "Не удалось получить ID-шники моделей товаров");
$this->modx->log(xPDO::LOG_LEVEL_ERROR, print_r($c->stmt->errorInfo(), true));
}
/*
* categoryid
*/
return parent::beforeQuery();
}
protected function prepareCountQuery(xPDOQuery &$query) {
$query = parent::prepareCountQuery($query);
$query->select(array(
'parent'
));
$query->where(array(
'parent:IN' => $this->modelsIDs,
));
// Подсчитываем общее количество моделей и вариантов
$this->countModelsAndVariables($query);
return $query;
}
// Подсчитываем общее количество моделей и вариантов
protected function countModelsAndVariables(xPDOQuery $query){
$c = clone $query;
if (isset($c->query['columns'])) $c->query['columns'] = array();
$c->select(array(
"COUNT(DISTINCT {$this->classKey}.id) as productsCount",
"COUNT(DISTINCT {$this->classKey}.parent) as modelsCount",
));
if ($stmt= $c->prepare()) {
if ($stmt->execute()) {
if ($results= $stmt->fetchAll(PDO::FETCH_ASSOC)) {
if($result = current($results)){
$this->modelsCount = $result['modelsCount'];
$this->productsCount = $result['productsCount'];
}
}
}
}
return;
}
public function prepareQueryAfterCount(xPDOQuery $c) {
$c = parent::prepareQueryAfterCount($c);
$c->innerJoin('modResource', 'vendor', "Models.parent=vendor.id");
return $c;
}
public function setSelection(xPDOQuery $c) {
$c = parent::setSelection($c);
$c->select(array(
'vendor.id as vendor_id',
'vendor.pagetitle as vendor_title',
));
return $c;
}
/*
* Готовим окончательный вывод
*/
public function outputArray(array $array, $count = false) {
$output = parent::outputArray($array, $count);
$output['modelsCount'] = $this->modelsCount; // Выводим общее кол-во моделей
$output['productsCount'] = $this->productsCount; // Выводим общее кол-во товаров
return $output;
}
} return 'modWebCatalogProductsBycatalogsectionGetdataProcessor'; И последняя задачка — выборка из всего раздела, но только ТОПовых товаров. Но это совсем маленький процессор:-) <?php /*
class modWebCatalogProductsBycatalogsectionGetdatatopProcessor extends modWebCatalogProductsBycatalogsectionGetdataProcessor{ protected function prepareCountQuery(xPDOQuery &$query) { $query = parent::prepareCountQuery($query); $query->innerJoin('modTemplateVarResource', 'top', "top.contentid = {$this->classKey}.id and top.tmplvarid=4 and top.value != ''"); return $query; } } return 'modWebCatalogProductsBycatalogsectionGetdatatopProcessor'; Да, это совсем не мало кода. Но если все это писать на сниппетах, то получится еще больше, и на много. Но главное — если, скажем, мне надо будет выводить уже все товары, без учетов наличия в них картинок, я изменю только один (базовый) процессор, и логика поменяется во всех дочерних процессорах. Или если мне надо будет добавить какие-то новые данные в объект товара, то мне тоже достаточно будет сделать это в одном единственном процессоре, а не переписывать каждый в отдельности. Плюс эти процессоры можно вызвать вообще из любого положения, хоть в плагине, хоть в сниппете, хоть в Смарти-шаблоне. Можно вообще на лету получить класс процессора и расширить его, не создавая для этого отдельного файла. Момент второй. Стандарты. Вот это тоже очень важный момент. Сниппеты не имеют никаких стандартов. Там пишется произвольный PHP-код и все (хотя многие туда еще HTML пишет, а еще бывает javascript, SQL и т.п.)) ). Так вот, процессоры — это классы со своими стандартами и со своим набором методов. И shopModx-овые процессоры поддерживают эти стандарты. Так что если вы изучите и поймете базовые процессоры самого MODX-а (и особенно Object-процессоры), то вам будет многое понятно и в сторонних процессорах. Там есть уже прописанные методы проверки прав, подгрузки языковых топиков, обработчики ошибок и т.д. и т.п. Так что процессор на 10 строк в себе может иметь очень и очень большой функционал. А главное — стандартизированный, который к тому же и обратную совместимость обеспечивает.