Еще в мартовском релизе 2012 года появилась поддержка эмулятора (собственных php-обработчиков, smarty-модификаторов, smarty-плагинов) файлов шаблона. С его помощью в отдельно взятый шаблон можно встроить программную php-логику в принципе какой-угодно сложности, и она не будет затираться при обновлениях движка, так как логика относится только к этому шаблону и размещена вне области обновляемых файлов.
Если в папку шаблона поместить файл http://ваш.сайт/design/ваш-шаблон/html/emulator.php, где для нужных вам tpl-файлов шаблона согласно вашей задаче прописать недостающие установки переменных или запросы к базе данных, вы получите желаемый результат.
Сначала приведу общий каркас файла эмулятора, и следом пример решения задачи Руслана и задачи Григория.
<?php
// =======================================================================
/**
* Эмулятор шаблона клиентской стороны сайта
*
* -----------------------------------------------------------------------
*
* Если в шаблоне требуются какие-то специфичные php-обработки, тогда
* эмулятор помещается в виде файла emulator.php в папку шаблона, а именно
* http://ваш.сайт/design/ваш-шаблон/html/emulator.php, в результате
* система шаблонизации конкретно для данного шаблона начинает работать
* под управлением эмулятора. Остальные шаблоны, в которых такой файл
* эмулятора не существует, обрабатываются стандартным образом.
*
* Подробности читайте ниже в описании метода prepare. Также обратите
* внимание на описание метода setSmartyPlugins.
*
* -----------------------------------------------------------------------
*
* @package Impera CMS
* @author AIMatrix
* @copyright Copyright 2012, AIMatrix
* @link http://imperacms.com
*/
// =======================================================================
class TemplateEmulator {
// объект движка
public $cms = null;
// ===================================================================
/**
* Конструктор класса
*
* -------------------------------------------------------------------
*
* @access public
* @param object $cms объект движка
* @return void
*/
// ===================================================================
public function __construct ( & $cms ) {
// запоминаем выход на объект движка
$this->cms = & $cms;
// подключаем наши функции и модификаторы Smarty
$this->setSmartyPlugins($cms);
}
// ===================================================================
/**
* Подключение наших функций и модификаторов в шаблонизатор Smarty
*
* -------------------------------------------------------------------
*
* Здесь выполняем регистрацию наших собственных Smarty-плагинов и
* модификаторов, которые станут доступны во всех файлах шаблона.
*
* Например, если хотим, чтобы в шаблоне можно было использовать
* такой модификатор, скажем my_date, который в файле шаблона
* записывался бы следом за модифицируемой переменной как
*
* {$date = '12 March 2012'}
* {$date|my_date:'d.m.Y'}
*
* то регистрацию этого модификатора нужно было прописать в данном
* методе в виде строк
*
* $cms->smarty->registerPlugin('modifier',
* 'my_date',
* array($this, 'my_date_handler'));
*
* а также добавить метод my_date_handler, где закодировать сам
* обработчик модификатора, например
*
* public function my_date_handler ( $value, $format = '' ) {
* if ($format == '') $format = 'd.m.Y';
* $value = strtotime($value);
* if ($value === -1) return 'Неверная дата!';
* return date($format, $value);
* }
*
* Аналогично прописывается регистрация плагинов (функций), скажем
* мы хотели бы использовать в шаблоне некую функцию, допустим
* записываемую так
*
* {set_header code = 404}
*
* тогда регистрация этой функции будет выглядеть в виде строк
*
* $cms->smarty->registerPlugin('function',
* 'set_header',
* array($this, 'set_header_handler'));
*
* а также добавить метод set_header_handler, где закодировать сам
* обработчик функции, например
*
* public function set_header_handler ( $params, & $smarty ) {
* if (isset($params['code']) {
* switch ($params['code']) {
* case 404:
* header('HTTP/1.0 404 Not Found');
* break;
* }
* }
* return '';
* }
*
* Модификатор всегда возвращает модифицированное значение исходной
* переменной, функция - что вывести на страницу в том месте, где
* была вызвана функция.
*
* -------------------------------------------------------------------
*
* @access protected
* @param object $cms объект движка
* @return void
*/
// ===================================================================
protected function setSmartyPlugins ( & $cms ) {
}
// ===================================================================
/**
* Подготовка к отрисовке файла шаблона
*
* -------------------------------------------------------------------
*
* Принцип работы эмулятора: перед отрисовкой каждого файла шаблона
* движок сначала передает управление в этот метод эмулятора, чтобы
* эмулятор либо дополнил список Smarty-переменных какими-то
* недостающими сведениями, либо отрисовал контент по-своему,
* либо отдал право отрисовки назад шаблонизатору.
*
* Если эмулятор хочет отрисовать контент по-своему, он должен
* сгенерировать этот контент и возвратить во входную переменную $body
*
* Если эмулятор хочет отдать право отрисовки назад в шаблонизатор,
* он должен установить входную переменную $body = null, а на выход
* метода возвратить имя файла, который он приказывает отрисовать. Как
* правило, имя файла будет равным входному имени ($template->name),
* однако может быть и сменено на другое (например, эмулятор
* динамически подготовил какой-то новый или временный tpl-файл и
* просит отрисовать именно его).
*
* Если эмулятор хочет дополнить список Smarty-переменных, он может
* использовать метод $cms->smarty->getTemplateVars('var_name')
* для чтения уже существующей к этому времени Smarty-переменной и
* метод $cms->smarty->assignByRef('new_var_name', $myvar) для
* установки новой переменной. Например хотим на основе содержимого
* переменной title создать другую переменную title2:
*
* $title = $cms->smarty->getTemplateVars('title');
* $title .= ' купить в Украине';
* $cms->smarty->assignByRef('title2', $title);
*
* Необходимо помнить, что новые назначенные Smarty-переменные будут
* доступны и в других файлах шаблона (в данном случае в файле
* index.tpl, так как он рисуется всегда последним, после отрисовки
* прочих tpl-файлов).
*
* -------------------------------------------------------------------
*
* @access public
* @param object $template сведения об имени шаблона:
* ->name = имя файла реальное
* ->impera_name = имя файла
* (без расширения)
* по Impera CMS
* ->standard_name = имя файла
* (без расширения)
* типичное
* @param object $cms объект движка
* @param string $body null (если отрисовку отдаем
* шаблонизатору)
* или сгенерированный нами контент
* @return string имя файла шаблона (если отрисовку
* отдаем шаблонизатору)
*/
// ===================================================================
public function prepare ( $template, & $cms, & $body = null ) {
// мы не хотим генерировать контент сами
$body = null;
// какой файл сейчас обрабатывается?
$name = strtolower($template->name);
switch ($name) {
// общий макет страницы (отрисовывается всегда самым последним)
case 'index.tpl':
case 'page.tpl':
break;
// страница Каталог (как правило, ее делают главной)
case 'catalog.tpl':
case 'page.catalog.tpl':
// здесь будет правка по задаче Григория,
// так как список недавних отзывов нужно
// показать именно на "главной" странице
break;
// страница Список товаров
case 'products.tpl':
case 'page.products.tpl':
// здесь будет правка по задаче Руслана,
// так как ТОП10 товаров нужно показать
// именно на странице категории (списке
// ее товаров)
break;
// страница Товар
case 'product.tpl':
case 'page.product.tpl':
break;
// страница Личный кабинет
case 'account.tpl':
break;
// страница Список статей
case 'articles.tpl':
break;
// страница Статья
case 'article.tpl':
break;
// страница Позвоните мне
case 'callme.tpl':
break;
// страница Корзина
case 'cart.tpl':
break;
// всплывающая плашка "Товар добавлен в корзину"
case 'page.quick_cart.tpl':
break;
// страница Сравнение товаров
case 'compare.tpl':
break;
// всплывающая плашка "Товар добавлен в сравнение"
case 'page.quick_compare.tpl':
break;
// страница Обратная связь
case 'feedback.tpl':
break;
// страница Список медиа файлов
case 'files.tpl':
break;
// страница Медиа файл
case 'file.tpl':
break;
// страница Авторизация
case 'login.tpl':
break;
// страница Забыли пароль?
case 'password_remind.tpl':
break;
// страница Регистрация
case 'registration.tpl':
break;
// блок кнопок листания страниц списка
case 'navigation.htm':
break;
// страница Список новостей
case 'news.tpl':
break;
// страница Новость
case 'news_item.tpl':
break;
// страница Уведомить о наличии
case 'notifyme.tpl':
break;
// страница Оформленный заказ
case 'order.tpl':
break;
// страница Заказ оформлен успешно
case 'order_success.tpl':
break;
// страница Конфигуратор
case 'page.configurator.tpl':
break;
// страница Результаты поиска
case 'search.tpl':
break;
// страница Карта сайта
case 'sitemap.tpl':
break;
// страница Специальная страница
case 'static_page.tpl':
break;
// страница Список складов
case 'stocks.tpl':
break;
// страница Склад
case 'stock.tpl':
break;
// информер для внешних сайтов
case 'informer.tpl':
break;
// скачиваемый прайс-лист
case 'price.tpl':
case 'price2.tpl':
case 'price3.tpl':
case 'price4.tpl':
case 'price5.tpl':
case 'price6.tpl':
case 'price7.tpl':
case 'price8.tpl':
break;
// иначе какой-то другой файл
default:
}
// просим шаблонизатор отрисовать этот файл
// (иначе мы должны были бы поместить в $body свою "отрисовку")
return $template->name;
}
}
return;
?>
Решение для Руслана Как было помечено выше красным цветом, изменению подвергнется соответствующая часть метода prepare, отвечающая за контроль отрисовки страницы списка товаров.
Суть работы следующего кода состоит в том, что мы сначала извлекаем из шаблонизатора состояние переменной $category, где хранится запись о текущей категории. Далее готовим фильтр, учитывая в нем все необходимые нам параметры отбора товаров, в том числе что товары в заданном количестве нужно отобрать только видимые пользователям и непременно из текущей категории (вместе с вложенными ветвями). Причем не забываем учесть в фильтре, что в этот момент страницу может просматривать авторизованный пользователь со своей скидкой и ценовой группой, следовательно цены товаров должны быть автоматически пересчитаны под этого пользователя. Далее выполняем селекцию товаров и передаем результирующий список товаров обратно в шаблонизатор, чтобы они стали доступны в шаблоне через переменную $top10_products.
...
...
// страница Список товаров
case 'products.tpl':
case 'page.products.tpl':
// извлекаем из шаблонизатора переменную текущей категории
$category = $cms->smarty->getTemplateVars('category');
// начинаем настройку фильтра
$items = null;
$filter = new stdClass;
// только просмотренные
$filter->browsed = 1;
// именно из этой категории
$filter->category = & $category;
// только незапрещенные товары
$filter->enabled = 1;
// если это простой (неавторизованный) посетитель, тогда только не скрытые товары
if (!isset($cms->user->user_id)) $filter->hidden = 0;
// результат упорядочить по убыванию просмотров
$filter->sort = SORT_PRODUCTS_MODE_BY_BROWSED;
$filter->sort_direction = SORT_DIRECTION_DESCENDING;
// из результата отобрать первые 10 товаров
$filter->start = 0;
$filter->maxcount = 10;
// если это авторизованный посетитель, в ценах товаров учитывать его скидку и ценовую группу
$filter->discount = isset($cms->user->discount) ? $cms->user->discount : 0;
$filter->price_id = isset($cms->user->price_id) ? $cms->user->price_id : 0;
// выполняем выборку из базы данных
$cms->db->products->get($items, $filter);
// распаковываем поля записей (некоторые поля хранятся в базе в нестандартном виде)
$cms->db->products->unpack_records($items, $filter);
// передаем товары в шаблонизатор
$cms->smarty->assignByRef('top10_products', $items);
break;
...
...
Решение для Григория Похоже по сути действия, только отбираем отзывы о товарах. Настраиваем фильтр так, чтобы отобрать требуемое число недавних постов, для чего записи просим отобрать плоским списком (игнорируя ветвистость ответов) и только промодерированные, то есть разрешенные посты. Результирующий список отправляем обратно в шаблонизатор под именем переменной $top5_comments.
...
...
// страница Каталог (как правило, ее делают главной)
case 'catalog.tpl':
case 'page.catalog.tpl':
// начинаем настройку фильтра
$items = null;
$filter = new stdClass;
// только промодерированные
$filter->enabled = 1;
// результат нужен плоским списком (без ветвистости)
$filter->flatlist = 1;
// порядок записей хотим перевернуть (то есть от новых к старым)
$filter->reverse = 1;
// из результата отобрать первые 5 отзывов
$filter->start = 0;
$filter->maxcount = 5;
// выполняем выборку из базы данных
$cms->db->get_comments($items, $filter);
// передаем отзывы в шаблонизатор
$cms->smarty->assignByRef('top5_comments', $items);
break;
...
...