События (хуки)
Система событий позволяет разработчикам взаимодействовать с классами ядра HostCMS и вносить изменения в стандартное поведение контроллеров и моделей.
Добавление обработчика события
Добавление обработчика осуществляется методом Core_Event::attach($eventName, $function), первым аргументом передается название события, вторым — название функции, название события регистрозависимо. Если вызываете статичный метод класса, то $function задается в виде массива array('имя-класса', 'статичный-метод').
Методом attach обработчики события добавляются одним за одним и будут вызваны в порядке добавления. Добавить обработчик события в начале очереди можно через Core_Event::attachFirst($eventName, $function).
Дополнительно третьим параметром вы можете передавать в обработчик собственные данные, например, Core_Event::attach($eventName, $function, array('foo' => 'bar')) и получать их в обработчике третьим параметром.
Удаление обработчика события
Удаление обработчика осуществляется методом Core_Event::detach($eventName, $function), первым аргументом передается название события, вторым - название функции. Если вызываете статичный метод класса, то $function задается в виде массива array('имя-класса', 'статичный-метод').
Временное отключение хуков
Методы Core_Event::off($eventName) или Core_Event::disable($eventName) позволяют временно отключить выполнение хуков с заданных названием (например, для избежания рекурсии). Обратное включение выполнения хуков осуществляется методами Core_Event::on() или Core_Event::enable().
Примеры использования хуков
Манипулирование XML
Добавление своего тега в XML всех объектов Shop_Item
1. Пишем класс наблюдателя, размещаем его в modules/shop/item/observer.php
class Shop_Item_Observer
{
static public function onBeforeGetXml($object, $args, $options)
{
// Добавить новый тег, равный price * 2
$object->addXmlTag('myTag', $object->price * 2);
}
}
2. Добавляем наблюдателя в bootstrap.php
// Add shop_item observer
Core_Event::attach('shop_item.onBeforeGetXml', array('Shop_Item_Observer', 'onBeforeGetXml'));
Ограничение выводимых тегов инфоэлемента для значений дополнительного свойства типа инфосистема
class Property_Value_Int_Observer
{
static public $aForbiddenTags = array('shortcut_id', 'shop_tax_id', 'shop_id', 'active', 'sorting', 'path', 'indexing', 'image_small_height', 'image_small_width', 'user_id', 'guid', 'siteuser_group_id', 'siteuser_id', 'shop_seller_id', 'shop_currency_id', 'shop_group_id', 'shop_producer_id', 'shop_producer','shop_measure_id', 'vendorcode', 'description', 'weight', 'seo_title', 'seo_description', 'seo_keywords', 'image_large_height', 'image_large_width', 'yandex_market', 'rambler_pokupki', 'yandex_market_bid', 'yandex_market_cid', 'yandex_market_sales_notes', 'showed', 'deleted', 'date', 'rest');
static public function onBeforeGetXml($object, $args)
{
$oProperty = $object->Property;
// Property type is informationsystem
if ($oProperty->type == 5 && $object->value != 0 && Core::moduleIsActive('informationsystem'))
{
$oInformationsystem_Item = $object->Informationsystem_Item;
foreach (self::$aForbiddenTags as $sForbiddenTag)
{
$oInformationsystem_Item->addForbiddenTag($sForbiddenTag);
}
}
}
}
Core_Event::attach('property_value_int.onBeforeGetXml', array('Property_Value_Int_Observer', 'onBeforeGetXml'));
Добавление в структуре сайта к пункту меню с кодом 123 логина текущего пользователя
class Structure_Observer
{
static public function onBeforeGetXml($object, $args)
{
if ($object->id == 123 && Core::moduleIsActive('siteuser'))
{
$oSiteuser = Core_Entity::factory('Siteuser')->getCurrent();
if ($oSiteuser)
{
$object
// Запрещаем вывод в XML стандартного значения тега name
->addForbiddenTag('name')
// Добавляем свое значение
->addXmlTag('name', $object->name . ' ' . htmlspecialchars($oSiteuser->login));
}
}
}
}
// Add structure observer
Core_Event::attach('structure.onBeforeGetXml', array('Structure_Observer', 'onBeforeGetXml'));
ORM
Добавление обработчиков несуществующих методов модели
Событие формируется по схеме имя_модели.onCallимя_метода, например для метода xxx() необходимо событие "shop_item.onCallxxx", имя модели указывается в нижнем регистре.
Может использоваться для добавления функции обратного вызова для вывода полей в центре администрирования или других целей.
Пример добавления вывода названия производителя в списке товаров.
1. Создаем поле формы центра администрирования, указываем имя producer и тип "Вычисляемое поле".
2. В bootstrap.php добавляем обработчик метода Core_Event::attach('shop_item.onCallxxx', array('Shop_Item_Observer', 'onCallxxx'));
3. Обработчик
class Shop_Item_Observer
{
static public function onCallxxx($object, $args)
{
return $object->Shop_Producer->name;
}
}
Сохранение и чтение несуществующих свойств модели
Событие формируется по схеме имя_модели.onCallимя_свойства, например для несуществующего свойства login необходимо событие "document.onCalllogin".
В bootstrap.php добавляем обработчик метода Core_Event::attach('document.onCalllogin', array('My_Observer', 'onCalllogin'));
Обработчик
class My_Observer
{
/**
* Сохраненные значения отсутствующего свойства
*/
static protected $_values = array();
static public function onCalllogin($object, $args)
{
$pkv = $object->getPrimaryKey();
$modelName = $object->getModelName();
// Write value
if (count($args))
{
self::$_values[$modelName][$pkv] = $args[0];
}
// Read value
elseif (isset(self::$_values[$modelName][$pkv]))
{
return self::$_values[$modelName][$pkv];
}
}
}
Центр администрирования
Скрытие полей форм центра администрирования (для контроллеров с определенным методом _prepareForm())
Генерация стандартной формы редактирования осуществляется методом _prepareForm() контроллера Admin_Form_Action_Controller_Type_Edit. Далее в методе класса потомка, например Shop_Controller_Edit, осуществляется перемещение полей по вкладкам и для корректировки уже этого перемещения необходимо использовать событие Admin_Form_Action_Controller_Type_Edit.onAfterRedeclaredPrepareForm.
Порядок действий:
1. В файле bootstrap.php навешиваем нового наблюдателя на событие:
Core_Event::attach('Admin_Form_Action_Controller_Type_Edit.onAfterRedeclaredPrepareForm', array('Shop_Controller_Edit_Observer', 'onAfterRedeclaredPrepareForm'));
2. Размещаем файл observer.php в директории modules/shop/controller/edit/
Имя файла и директория зависит от имени самого наблюдателя!
3. В этом файле указываем следующий код, который удаляет поле "Описание" для формы редактирования интернет-магазина:
<?php
defined('HOSTCMS') || exit('HostCMS: access denied.');
class Shop_Controller_Edit_Observer
{
static public function onAfterRedeclaredPrepareForm($controller, $args)
{
list($object, $Admin_Form_Controller) = $args;
// Данное событие будет вызываться для всех форм, определяем с каким контроллером работаем
switch (get_class($controller))
{
case 'Shop_Controller_Edit':
$oMainTab = $controller->getTab('main');
$oMainTab->delete($controller->getField('description'));
break;
}
}
}
Скрытие полей форм центра администрирования (для контроллеров без метода _prepareForm())
Порядок действий:
1. В файле bootstrap.php навешиваем нового наблюдателя на событие:
Core_Event::attach('Admin_Form_Action_Controller_Type_Edit.onBeforeExecute', array('Constant_Controller_Edit_Observer', 'onBeforeExecute'));
2. Размещаем файл observer.php в директории modules/constant/controller/edit/
Имя файла и директория зависит от имени самого наблюдателя!
3. В этом файле указываем следующий код, который удаляет поле description для формы редактирования константы:
class Constant_Controller_Edit_Observer
{
static public function onBeforeExecute($controller, $args)
{
list($operation, $Admin_Form_Controller) = $args;
if (is_null($operation))
{
// Данное событие будет вызываться для всех форм, определяем с каким контроллером работаем
switch (get_class($controller))
{
case 'Constant_Controller_Edit':
$oMainTab = $controller->getTab('main');
$oMainTab->delete($controller->getField('description'));
break;
}
}
}
}
Отключение визуального редактора для товаров, информационных элементов и групп определенных магазинов и информационных систем
Core_Event::attach('Admin_Form_Action_Controller_Type_Edit.onAfterRedeclaredPrepareForm', array('My_Observer', 'onAfterRedeclaredPrepareForm'));
class My_Observer
{
static public function onAfterRedeclaredPrepareForm($controller, $args)
{
list($object, $Admin_Form_Controller) = $args;
// Данное событие будет вызываться для всех форм, определяем с каким контроллером работаем
switch (get_class($controller))
{
case 'Shop_Item_Controller_Edit':
case 'Informationsystem_Item_Controller_Edit':
$oMainTab = $controller->getTab('main');
$modelName = $object->getModelName();
// Отключаем для магазина с номером 1 и инфосистем с номерами 5 и 7
if ($modelName == 'shop_item' && in_array($object->shop_id, array(1))
|| $modelName == 'informationsystem_item' && in_array($object->informationsystem_id, array(5, 7)))
{
$controller->getField('description')->wysiwyg(FALSE);
$controller->getField('text')->wysiwyg(FALSE);
}
break;
}
}
}
Добавление дополнительного пункта меню в выпадающее меню центра администрирования
class Observer_Admin_Form_Controller {
public static function onBeforeAddEntity($object, $args)
{
// Форма списка товаров и групп магазина
if ($object->getAdminForm()->id == 65)
{
if (strpos(get_class($args[0]), 'Admin_Form_Entity_Menus') !== FALSE)
{
$aChildren = $args[0]->getChildren();
// Добавляем потомка первому элементу меню
$aChildren[0]->add(
Admin_Form_Entity::factory('Menu')
->name('Мой пункт меню')
->img('/admin/images/page_add.gif')
->href('ссылка')
->onclick('onclick')
);
}
}
}
}
Core_Event::attach('Admin_Form_Controller.onBeforeAddEntity', array('Observer_Admin_Form_Controller', 'onBeforeAddEntity'));
Добавление полей из других таблиц в формы центра администрирования
Форма содержит данные из одного или нескольких Dataset, добавляемых в файле центра администрирования, например, /admin/document/index.php
// Добавляем источник данных контроллеру формы
// Источник данных 1
$oAdmin_Form_Dataset = new Admin_Form_Dataset_Entity(
Core_Entity::factory('Document')
);
// Ограничение источника 1 по родительской группе
$oAdmin_Form_Dataset->addCondition(
array('where' =>
array('document_dir_id', '=', $document_dir_id)
)
)->addCondition(
array('where' =>
array('site_id', '=', CURRENT_SITE)
)
);
// Добавляем источник данных контроллеру формы
$oAdmin_Form_Controller->addDataset(
$oAdmin_Form_Dataset
);
// Показ формы
$oAdmin_Form_Controller->execute();
У метода addDataset() есть событие:
Core_Event::notify('Admin_Form_Controller.onBeforeAddDataset', $this, array($oAdmin_Form_Dataset));
позволяющее навесить обработчик, в котором можно определить, что это за форма, получив у датасета объект через getEntity() и определив его класс через get_class():
Core_Event::attach('Admin_Form_Controller.onBeforeAddDataset', array('My_Observer', 'onBeforeAddDataset'));
class My_Observer {
static public function onBeforeAddDataset($object, $args)
{
list($oAdmin_Form_Dataset) = $args;
$oEntity = $oAdmin_Form_Dataset->getEntity();
switch (get_class($oEntity))
{
case 'Document_Model':
// do smth
break;
}
}
}
После прохождения проверки вы можете добавить новые объединения с другими таблицами. Не забудьте, что для выбираемых значений из объединяемых таблиц должны быть соответствующие поля в моделях, иначе будет ошибка в конструкторе (см. "Сохранение и чтение несуществующих свойств модели". Объединим документы с таблицей пользователей и выведим логин пользователя:
class My_Observer {
static public function onBeforeAddDataset($object, $args)
{
list($oAdmin_Form_Dataset) = $args;
$oEntity = $oAdmin_Form_Dataset->getEntity();
switch (get_class($oEntity))
{
case 'Document_Model':
$oAdmin_Form_Dataset->addCondition(
array('select' =>
array('documents.*', 'users.login')
)
)->addCondition(
array('join' =>
array('users', 'documents.user_id', '=', 'users.id')
)
);
break;
}
}
}
Особое внимание обратите на необходимость указания в select списка выбираемых полей, в нашем случае все поля из documents и только поле login из users.
Пример вывода значения дополнительного свойства списочного типа в таблице товаров
В форму центра администрирования добавляется поле текстового типа с названием dataListValue, тип фильтрации устанавливается HAVING.
class My_Observer {
static public function onBeforeAddDataset($object, $args)
{
list($oAdmin_Form_Dataset) = $args;
$oEntity = $oAdmin_Form_Dataset->getEntity();
switch (get_class($oEntity))
{
case 'Shop_Group_Model':
// Заглушка для групп, чтобы было поле zzz для HAVING-а
$oAdmin_Form_Dataset->addCondition(
array('select' =>
array(array(Core_QueryBuilder::expression('\' \''), 'dataListValue'))
)
);
break;
case 'Shop_Item_Model':
$oAdmin_Form_Dataset->addCondition(
array('select' =>
array(array('list_items.value', 'dataListValue'))
)
)
->addCondition(
array('leftJoin' =>
array('property_value_ints', 'shop_items.id', '=', 'property_value_ints.entity_id',
array(
array('AND' => array('property_value_ints.property_id', '=', 19))
)
)
)
)
->addCondition(
array('leftJoin' =>
array('list_items', 'property_value_ints.value', '=', 'list_items.id')
)
);
break;
}
}
}
Core_Event::attach('Admin_Form_Controller.onBeforeAddDataset', array('My_Observer', 'onBeforeAddDataset'));
Вывод свойства заказа в список заказов
Для вывода значения свойства необходимо добавить в dataset объединение с нужной таблицей, для этого будем использовать событие Admin_Form_Controller.onBeforeAddDataset. Так как предполагается кроме вывода значения свойства еще и его изменение, то вместо использования data-полей, достаточных для вывода информации, мы будем использовать событие onCallxxx у модели shop_order.
Создайте поле PropertyListField, укажите списочный тип и фильтрацию через HAVING.
class Shop_Order_Form_Observer
{
/**
* Идентификатор выводимого свойства заказа
*/
static protected $_propertyId = 139;
static public function onBeforeAddDataset($object, $args)
{
list($oAdmin_Form_Dataset) = $args;
$oEntity = $oAdmin_Form_Dataset->getEntity();
if (get_class($oEntity) == 'Shop_Order_Model' && Core::moduleIsActive('list'))
{
$oAdmin_Form_Dataset
->addCondition(
array('select' => array('shop_orders.*', array('property_value_ints.value', 'PropertyListField')))
)
->addCondition(
array('leftJoin' =>
array('property_value_ints', 'shop_orders.id', '=', 'property_value_ints.entity_id',
array(
array('AND' => array('property_value_ints.property_id', '=', self::$_propertyId))
)
)
)
);
$oProperty = Core_Entity::factory('Property', self::$_propertyId);
if ($oProperty->type == 3 && $oProperty->list_id != 0)
{
$oListItems = $oProperty->List->List_Items;
$oListItems->queryBuilder()
->where('active', '=', 1)
->orderBy('sorting', 'ASC');
$aListItems = $oListItems->findAll();
$aList = array();
$aList[0] = "—";
foreach ($aListItems as $oListItem)
{
$aList[$oListItem->id] = $oListItem->value;
}
$oAdmin_Form_Dataset->changeField('PropertyListField', 'list', $aList);
}
}
}
static public function onCallPropertyListField($object, $args)
{
$entityId = $object->getPrimaryKey();
$read = count($args) == 0;
$oProperty = Core_Entity::factory('Property', self::$_propertyId);
$aProperty_Values = $oProperty->getValues($entityId, FALSE);
$oProperty_Value = Core_Array::get($aProperty_Values, 0);
// Read value
if ($read)
{
return empty($oProperty_Value) ? 0 : $oProperty_Value->value;
}
// Write value
if (is_null($oProperty_Value))
{
$oProperty_Value = $oProperty->createNewValue($entityId);
}
$oProperty_Value->value = $args[0];
$oProperty_Value->save();
}
}
Core_Event::attach('Admin_Form_Controller.onBeforeAddDataset', array('Shop_Order_Form_Observer', 'onBeforeAddDataset'));
Core_Event::attach('shop_order.onCallPropertyListField', array('Shop_Order_Form_Observer', 'onCallPropertyListField'));
Обработка действий формы центра администрирования
Добавление собственных действий в стандартные формы центра администрирования требует возможности их обработки, которая возможна через действие Admin_Form_Controller.onCallxxx, где xxx - название действия.
Пример обработки действия markDeleted формы списка товаров.
class Admin_Form_Controller_Observer
{
static public function onCallmarkDeleted($oAdmin_Form_Controller, $args)
{
list($datasetKey, $oObject, $operation) = $args;
$oAdmin_Form = $oAdmin_Form_Controller->getAdminForm();
// Проверка на GUID нужной формы
if ($oAdmin_Form->guid == '5AC4B52F-EB2E-1978-5232-8C6FDD8625A0')
{
// do smth
$return = TRUE;
$message = Core_Message::get('Сообщение');
$content = 'Текст формы, если нужно вывести только сообщение, то установить в NULL, а $return в FALSE';
return array($return, $message, $content);
}
}
}
Core_Event::attach('Admin_Form_Controller.onCallmarkDeleted', array('Admin_Form_Controller_Observer', 'onCallmarkDeleted'));
Изменение отображения фильтра для поля центра администрирования
В событие Admin_Form_Controller.onBeforeExecute добавляется callback-функция, которая отобразит фильтр:
class Admin_Form_Controller_Observer
{
static public function onBeforeExecute($oAdmin_Form_Controller, $args)
{
$oAdmin_Form = $oAdmin_Form_Controller->getAdminForm();
// Проверка на GUID нужной формы
if ($oAdmin_Form->guid == '5AC4B52F-EB2E-1978-5232-8C6FDD8625A0')
{
// zzz - имя поля
$oAdmin_Form_Controller->addFilter('zzz', array('Admin_Form_Controller_Observer', 'zzzFilter'));
}
}
/**
* Backend function
* @param mixed $value value
* @param Admin_Form_Field $oAdmin_Form_Field
* @return string
*/
static public function zzzFilter($value, $oAdmin_Form_Field, $filterPrefix, $tabName)
{
ob_start();
Core::factory('Core_Html_Entity_Select')
->options(
array(
0 => 'AAA',
1 => 'BBB',
2 => 'CCC'
)
)
->value(intval($value))
->name($filterPrefix . $oAdmin_Form_Field->id)
->execute();
return ob_get_clean();
}
}
Core_Event::attach('Admin_Form_Controller.onBeforeExecute', array('Admin_Form_Controller_Observer', 'onBeforeExecute'));
Интернет-магазин
Ограничение выборки модификаций для товара
Выбираем модификации с остатком на складе больше 0.
static public function onBeforeShowXmlModifications($object, $args)
{
$args[0]->queryBuilder()
->select('shop_items.*')
->leftJoin('shop_warehouse_items', 'shop_warehouse_items.shop_item_id', '=', 'shop_items.id')
->groupBy('shop_items.id')
->having(Core_QueryBuilder::expression('SUM(shop_warehouse_items.count)'), '>', 0);
}
Добавление цен в других валютах в карточке товара
Обработчик размещаем в коде типовой динамчиеской страницы.
// Если идет показ товара
if ($Shop_Controller_Show->item)
{
class Shop_Item_Observer_Prices
{
static public function onBeforeGetXml($object, $args)
{
$oPrices = Core::factory('Core_Xml_Entity')
->name('prices');
$oShop = $object->Shop;
$aShop_Currencies = Core_Entity::factory('Shop_Currency')->findAll();
// Prices
$oShop_Item_Controller = new Shop_Item_Controller();
if (Core::moduleIsActive('siteuser'))
{
$oSiteuser = Core_Entity::factory('Siteuser')->getCurrent();
$oSiteuser && $oShop_Item_Controller->siteuser($oSiteuser);
}
//$oShop_Item_Controller->count($this->_cartQuantity);
//$aPrices = $oShop_Item_Controller->getPrices($object);
$aPrices = $oShop_Item_Controller->calculatePriceInItemCurrency($object->price, $object);
foreach ($aShop_Currencies as $oShop_Currency)
{
$fCurrencyCoefficient = $object->Shop_Currency->id > 0 && $oShop_Currency->id > 0
? Shop_Controller::instance()->getCurrencyCoefficientInShopCurrency(
$object->Shop_Currency, $oShop_Currency
)
: 0;
$oPrices->addEntity(
Core::factory('Core_Xml_Entity')
->name('price')
->addEntity(
Core::factory('Core_Xml_Entity')
->name('value')
->value(
Shop_Controller::instance()->round($aPrices['price_discount'] * $fCurrencyCoefficient)
)
)
->addEntity(
$oShop_Currency
)
);
}
$object->addEntity($oPrices);
}
}
Core_Event::attach('shop_item.onBeforeGetXml', array('Shop_Item_Observer_Prices', 'onBeforeGetXml'));
}
Вывод в XML товара цен для групп пользователей
class Shop_Item_Observer
{
static public function onBeforeGetXml($object, $args)
{
$aShop_Item_Prices = $object->Shop_Item_Prices->findAll();
$object->addEntities(
$aShop_Item_Prices
);
}
}
Core_Event::attach('shop_item.onBeforeGetXml', array('Shop_Item_Observer', 'onBeforeGetXml'));
Включение вывода сопутствующих товаров для модификаций
По умолчанию для модификаций вывод в XML сопутствующих товаров отключен, вы можете включить его через событие shop_item.onBeforeAddModification:
class Shop_Item_Observer
{
static public function onBeforeAddModification($object, $args)
{
$oModification = $args[0];
$oModification->showXmlAssociatedItems(TRUE);
}
}
Core_Event::attach('shop_item.onBeforeAddModification', array('Shop_Item_Observer', 'onBeforeAddModification'));
Вывод сообщения при создании товара с существующим артикулом
1. Класс наблюдателя, размещаем его в modules/shop/item/marking.php
class Shop_Item_Marking
{
static public function onBeforeCreate($object, $args)
{
$oSetObject = $object->Shop->Shop_Items->getByMarking($object->marking);
if(!is_null($oSetObject))
{
Core_Message::show('Товар с таким артикулом существует', 'error');
}
}
}
2. Добавляем наблюдателя в bootstrap.php
Core_Event::attach('shop_item.onBeforeCreate', array('Shop_Item_Marking', 'onBeforeCreate'));
Запрет создания товара с существующим артикулом
1. Класс наблюдателя, размещаем его в modules/shop/item/marking.php
class Shop_Item_Marking
{
static public function onBeforeExecute($controller, $args)
{
list($operation, $Admin_Form_Controller) = $args;
if (!is_null($operation))
{
// Данное событие будет вызываться для всех форм, определяем с каким контроллером работаем
switch (get_class($controller))
{
case 'Shop_Item_Controller_Edit':
$object = $controller->getObject();
$marking = Core_Array::getPost('marking');
$oSetObject = $object->Shop->Shop_Items->getByMarking($marking);
$modelName = $object->getModelName();
if ($modelName == 'shop_item' && !is_null($oSetObject) && $oSetObject->id != $object->id)
{
Core_Message::show('Товар с таким артикулом существует', 'error');
return TRUE;
}
break;
}
}
}
}
2. Добавляем наблюдателя в bootstrap.php
Core_Event::attach('Admin_Form_Action_Controller_Type_Edit.onBeforeExecute', array('Shop_Item_Marking', 'onBeforeExecute'));
Отключение применения скидок для цен пользователей из определенной группы
// Отключение скидки для оптовых цен
class Shop_Discount_Observer
{
static public function onAfterCalculatePrice($object, $args)
{
// ID группы пользователей сайта, для которых не нужно применять скидки
$iSiteuserGroupId = 8;
$oSiteuser = Core_Entity::factory('Siteuser')->getCurrent();
// Пользователь авторизован
if ($oSiteuser)
{
$aSiteuser_Groups = $oSiteuser->Siteuser_Groups->findAll();
foreach ($aSiteuser_Groups as $oSiteuser_Group)
{
if ($oSiteuser_Group->id == $iSiteuserGroupId)
{
$aPrice = $object->getAPrice();
if (isset($aPrice['discount']) && $aPrice['discount'] > 0)
{
$aPrice['price_discount'] += $aPrice['discount'];
$aPrice['discount'] = 0;
$object->setAPrice($aPrice);
}
break;
}
}
}
}
}
Core_Event::attach('Shop_Item_Controller.onAfterCalculatePrice', array('Shop_Discount_Observer', 'onAfterCalculatePrice'));
Округление цен в интернет-магазине
см. страницу.
Отправка письма пользователю и администратору при смене статуса заказа
Решение не зависит от используемой платежной системы. В данном случае можно обойтись и без использования хука, добавив дополнительные проверки в обработчики платежных систем (метод changedOrder($mode)). Вариант с использованием хука:
class Shop_Order_Status_Observer
{
static public function onBeforeChangedOrder($object, $args)
{
$mode = $args[0];
$oShop = $object->getShopOrder()->Shop;
if (in_array($mode, array('changeStatusPaid', 'edit', 'apply')))
{
// Изменился статус заказа
if ($object->getShopOrderBeforeAction()->shop_order_status_id != $object->getShopOrder()->shop_order_status_id)
{
$date_str = Core_Date::sql2datetime($object->getShopOrder()->datetime);
// Тема письма администратору
$object->adminMailSubject(
sprintf($oShop->confirm_admin_subject, $object->getShopOrder()->invoice, $oShop->name, $date_str)
);
// Тема письма пользователю
$object->siteuserMailSubject(
sprintf($oShop->confirm_user_subject, $object->getShopOrder()->invoice, $oShop->name, $date_str)
);
// Установка XSL-шаблонов в соответствии с настройками в узле структуры
$object->setXSLs();
// Отправка писем клиенту и пользователю
$object->send();
}
}
}
}
// Add observer
Core_Event::attach('Shop_Payment_System_Handler.onBeforeChangedOrder', array('Shop_Order_Status_Observer', 'onBeforeChangedOrder'));
Ограничение скидок от суммы заказа, применение только наибольшей расчитанной скидки
В корзине система управления для заказа рассчитывает различные скидки от суммы заказа, в том числе накопительные и скидку по купону. Соответственно в корзине к заказу могут применяться несколько скидок. С помощью события Shop_Purchase_Discount_Controller.onAfterGetDiscounts мы можем ограничить скидки только наибольшей из рассчитанных.
class Shop_Purchase_Discount_Controller_Observer
{
static public function onAfterGetDiscounts($object, $args)
{
// Массив рассчитанных скидок
$aDiscounts = $object->getReturn();
if (count($aDiscounts))
{
// Сумма предыдущей скидки
$fDiscountAmount = 0;
// Возвращаемая скидка
$oReturnDiscount = NULL;
foreach ($aDiscounts as $oShop_Purchase_Discount)
{
// Сумма текущей скидки больше ранее сохраненной
if ($oShop_Purchase_Discount->getDiscountAmount() > $fDiscountAmount)
{
$oReturnDiscount = $oShop_Purchase_Discount;
$fDiscountAmount = $oShop_Purchase_Discount->getDiscountAmount();
}
}
// Заменяем возвращаемый массив скидок на массив с одной (максимальной) скидкой
!is_null($oReturnDiscount) && $object->setReturn(array($oReturnDiscount));
}
}
}
Core_Event::attach('Shop_Purchase_Discount_Controller.onAfterGetDiscounts', array('Shop_Purchase_Discount_Controller_Observer', 'onAfterGetDiscounts'));
Отключение товара при обмене с 1С, у которого остаток равен 0
class Shop_Item_Import_Cml_Controller_Observer
{
static public function onAfterOffersShopItem($object, $args)
{
$oShop_Item = $args[0];
if ($oShop_Item->getRest() == 0)
{
// Отключаем товар
$oShop_Item->active = 0;
$oShop_Item->save();
}
}
}
Core_Event::attach('Shop_Item_Import_Cml_Controller.onAfterOffersShopItem', array('Shop_Item_Import_Cml_Controller_Observer', 'onAfterOffersShopItem'));
Добавления блока <isbn> при экспорте в Яндекс.Маркет. Значение берется из дополнительного свойства товара (с названием "ISBN" тега XML)
Вносим наблюдается в код типовой дин. страницы экспорта в Яндекс.Маркет.
class Shop_Controller_YandexMarket_Observer
{
static public function onAfterOffer($object, $args)
{
$oShop_Item = $args[0];
$linkedObject = Core_Entity::factory('Shop_Item_Property_List', $oShop_Item->shop_id);
$oProperty = $linkedObject->Properties->getByTag_name('ISBN');
if ($oProperty)
{
$aPropertyValues = $oProperty->getValues($oShop_Item->id);
if (isset($aPropertyValues[0]))
{
$object->write('<isbn>' . Core_Str::xml(trim($aPropertyValues[0]->value)) . '</isbn>'. "\n");
}
}
}
}
Core_Event::attach('Shop_Controller_YandexMarket.onAfterOffer', array('Shop_Controller_YandexMarket_Observer', 'onAfterOffer'));
Вставка собственного поля в экспорт CSV магазина на примере вставки названия родительского товара модификации
class Shop_Item_Export_Csv_Observer
{
// Позиция, в которой делать вставку в массив базовых значений товара
static protected $_position = 4;
static public function onGetItemTitles($object, $args)
{
$return = $args[0];
$return = array_merge(
array_slice($return, 0, self::$_position),
array('Название родительского товара для модификации'),
array_slice($return, self::$_position)
);
return $return;
}
static public function onAfterItemBasicData($object, $args)
{
$return = $args[0];
$oShopItem = $args[1];
// Формируем значение поля
$value = $oShopItem->modification_id
? $object->prepareString($oShopItem->Modification->name)
: '';
$return = array_merge(
array_slice($return, 0, self::$_position),
array($value),
array_slice($return, self::$_position)
);
return $return;
}
}
Core_Event::attach('Shop_Item_Export_Csv_Controller.onGetItemTitles', array('Shop_Item_Export_Csv_Observer', 'onGetItemTitles'));
Core_Event::attach('Shop_Item_Export_Csv_Controller.onAfterItemBasicData', array('Shop_Item_Export_Csv_Observer', 'onAfterItemBasicData'));
Удаление товаров после авторизации из корзины пользователя, оставшихся после предыдущего сеанса
class Shop_Cart_Clear
{
static public function onBeforeMoveTemporaryCart($controller, $args)
{
if (Core::moduleIsActive('siteuser'))
{
$oSiteuser = Core_Entity::factory('Siteuser')->getCurrent();
if ($oSiteuser)
{
$aShop_Carts = Core_Entity::factory('Shop_Cart')->getAllBySiteuser_id($oSiteuser->id, FALSE);
foreach ($aShop_Carts as $oShop_Cart)
{
$oShop_Cart->delete();
}
}
}
}
}
Core_Event::attach('Shop_Cart_Controller.onBeforeMoveTemporaryCart', array('Shop_Cart_Clear', 'onBeforeMoveTemporaryCart'));
Сортировка товаров по названию в корзине и заказе
Для сортировки будем использовать собственную функцию _cmp, сравнивающую названия товаров, код вносим в bootstrap.php.
class Shop_Cart_Controller_Observer
{
static public function onAfterGetAll($object, $args, $options)
{
if (count($args[0]))
{
usort($args[0], 'self::_cmp');
return $args[0];
}
}
static protected function _cmp($a, $b)
{
return strcmp($a->Shop_Item->name, $b->Shop_Item->name);
}
}
Core_Event::attach('Shop_Cart_Controller.onAfterGetAll', array('Shop_Cart_Controller_Observer', 'onAfterGetAll'));
HelpDesk
Оповещение о новых запросах (сообщения), поступивших в Helpdesk через почту или с сайта
В bootstrap.php добавляем:
/**
* Helpdesk Message observer
*
* @package HostCMS 6
* @version 6.x
* @author Hostmake LLC
* @copyright © 2005-2012 ООО "Хостмэйк" (Hostmake LLC), https://www.hostcms.ru
*/
class Helpdesk_Message_Observer
{
static public function onAfterCreate($object, $args)
{
if ($object->inbox)
{
// Отправка письма
$message = "Доброе время суток, уважаемый куратор!\n\nВ службу поддержки поступило новое сообщение:\n";
$message .= "Тема: " . ($object->subject ? $object->subject : '<Без темы>') . "\n";
$message .= "Сообщение: " . $object->message . "\n";
$message .= "E-mail: " . $object->Helpdesk_Ticket->email . "\n";
$message .= "Дата: " . Core_Date::sql2datetime($object->datetime) . "\n";
$oCore_Mail_Driver = Core_Mail::instance()
->to(EMAIL_TO)
->from(EMAIL_TO)
->subject('HostCMS Helpdesk: Новое сообщение # ' . $object->id)
->message($message)
->contentType('text/plain')
->send();
}
}
}
// Add observer
Core_Event::attach('helpdesk_message.onAfterCreate', array('Helpdesk_Message_Observer', 'onAfterCreate'));
Поисковая система
Ограничение индексации товара только по названию
class Shop_Item_Observer
{
static public function onAfterIndexing($object, $args)
{
$oSearch_Page = $args[0];
$oSearch_Page->text = $object->name;
}
}
Core_Event::attach('shop_item.onAfterIndexing', array('Shop_Item_Observer', 'onAfterIndexing'));
Документы
Вывода названия документа перед показом версии документа
class Document_Version_Observer
{
static public function onBeforeExecute($object, $args)
{
?><h1><?php echo htmlspecialchars($object->Document->name)?></h1><?php
}
}
// Add document_version observer
Core_Event::attach('document_version.onBeforeExecute', array('Document_Version_Observer', 'onBeforeExecute'));
Замена подстроки при показе статичной страницы
class Document_Version_Observer
{
static public function onBeforeExecute($object, $args)
{
$content = $object->getContent();
$content = str_replace('%%%', DISCONT, $content);
$object->setContent($content);
}
}
// Add document_version observer
Core_Event::attach('document_version.onBeforeExecute', array('Document_Version_Observer', 'onBeforeExecute'));
Структура сайта
Смена файла, подключаемого контроллером для узла структуры сайта (стандартно подключается документ, ТДС или дин. страница)
В обработчике вы можете подменять объект на любой объект, который имеет метод execute() с выполнением нужного подключения файлов.
class Structure_Observer
{
static public function onAfterGetRelatedObjectByType($object, $args)
{
if ($object->id == 1)
{
$args[0] = Core_Entity::factory('Document_Version', 5);
}
}
}
Core_Event::attach('structure.onAfterGetRelatedObjectByType', array('Structure_Observer', 'onAfterGetRelatedObjectByType'));
Почтовые рассылки
Дополнительные подстановки в выпуск почтовой рассылки
Для внесения дополнительных изменений или подстановок в выпуск рассылки добавлены события maillist_fascicle.onBeforeApplyFascicleTemplate, maillist_fascicle.onAfterApplyFascicleTemplate
class Maillist_Fascicle_Observer
{
static public function onAfterApplyFascicleTemplate($object, $args)
{
$oSiteuser = $args[1];
$args[0] = str_replace('{MY_TEXT}', $myValue, $args[0]);
}
}
Core_Event::attach('maillist_fascicle.onAfterApplyFascicleTemplate', array('Maillist_Fascicle_Observer', 'onAfterApplyFascicleTemplate'));
RSS
Добавление в RSS блока <enclosure type="video/x-flv" />
Тег добавляется для информационного элемента, у которого заполнено дополнительное свойство 15. Используется при экспорте в Яндекс.Новости.
class Rss_Observer
{
static public function onBeforeAddItem($object, $args)
{
list($oInformationsystem_Item, $currentItem) = $args;
$oProperty = Core_Entity::factory('Property', 15);
$aProperty_Values = $oProperty->getValues($oInformationsystem_Item->id);
if (count($aProperty_Values))
{
$oProperty_Value = $aProperty_Values[0];
if (strlen($oProperty_Value->value))
{
$currentItem[] = array(
'name' => 'enclosure',
'value' => NULL,
'attributes' => array(
'url' => $currentItem['link'],
'type' => 'video/x-flv'
)
);
$object->setCurrentItem($currentItem);
}
}
}
}
Core_Event::attach('Informationsystem_Controller_Rss_Show.onBeforeAddItem', array('Rss_Observer', 'onBeforeAddItem'));
robots.txt
Подстановки в robots.txt
При использовании нескольких доменов для одного сайта вы можете вносить изменения в robots.txt, используя замены шаблона в нем:
Core_Event::attach('Core_Command_Controller_Robots.onBeforeShowAction', function($oCore_Command_Controller_Robots, $args) {
// Получить установленное значение
$content = $oCore_Command_Controller_Robots->getRobots();
// Получить сайт
/*$oSite = Core_Entity::factory('Site')->getByAlias(Core::$url['host']);
if (!is_null($oSite))
{
// Выполнить подмены на основе сайта
}*/
// Выполнить другие подмены
$content = str_replace('{foo}', 'bar', $content);
// Заменить установленное
$oCore_Command_Controller_Robots->setRobots($content);
});
История изменений
Версия 7.0.7
Добавлены методы Core_Event::off()/Core_Event::disable() и Core_Event::on()/Core_Event::enable().
Версия 6.9.6
Добавлена передача третьего параметра с аргументами для Core_Event::attach($eventName, $function, array('foo' => 'bar')).
Версия 6.9.5
Добавлен метод Core_Event::attachFirst($eventName, $function).
Комментарии
-
Без темы
А где взять список всех возможных событий?
15.06.2016 22:42:05Дмитрий
Без темы
в API для методов, имеющих события, указывается блок Hostcms-event со списком событий.
Без темы
Тоже задавал этот вопрос. Вот что поддержка ответила. Может кому то надо: - "открываете файл в который хотите внедрится и ищете по event"