События (хуки)

Система событий позволяет разработчикам взаимодействовать с классами ядра 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. Создаем поле формы центра администрирования для источника 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;
   }
}

Сохранение и чтение несуществующих свойств модели

С версии 6.8.1 добавлены пользовательские data-атрибуты.

Событие формируется по схеме имя_модели.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'));

История изменений

Версия 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 со списком событий.

    20.06.2016 09:36:36
    hostcms

    Без темы

    Тоже задавал этот вопрос. Вот что поддержка ответила. Может кому то надо: - "открываете файл в который хотите внедрится и ищете по event"

    20.04.2018 11:24:38
    Atlantis
    Atlantis

    Без темы

    Что-то не можем мы найти этого в API

    06.10.2016 20:31:30
    2wind