Adding your functionality to UMI.CMS using event handlers
In the UMI.CMS site management system, the initial separation is laid out on the main engine of the site, which is not touched by the web developer (and which is overwritten when the system is updated), and additional (custom) functionality that the site developer already adapts for himself: his own design templates, macros (PHP functions called from templates), native modules, if necessary.
However, when developing your site, there are situations when you need to change an existing site’s functionality:
In this case, you have to either edit the system code of the engine (which immediately adds problems when updating the CMS), or use the built-in event functionality. In the documentation or on third-party resources, this issue is considered, however, in my opinion, is not detailed enough. This article is an attempt to gather together information about working with events in UMI.CMS, and also based on an example to show how, with the help of event processing, you can expand the functionality of the system.
According to the documentation in UMI.CMS there are two types of event handlers:
When an event occurs, all handlers assigned to it (both system and user) are called. In addition (this is not written in the documentation), system event handlers are executed after user-defined ones. This should be taken into account when developing your functionality.
Events can be raised before the object changes (the so-called before mode ) and after the change of the object ( after mode ). Not all events can be either “before” or “after”, which ones, it is necessary to look at the link below.
A list of internal events can be found in the documentation . It must be remembered that during actions on the same object in different places different event handlers will be called. This causes some inconvenience:
Suppose, for example, we are developing an online store based on UMI, and we need to perform some actions when changing the status of the order (for example, send additional emails, put some other fields in the order, etc., depending on the status). We look where we can "catch" events of change of the order status:
Thus, in order to take into account all the places where the order status changes, you will have to write 4 handlers.
Let us dwell in more detail on the system event systemModifyPropertyValue . I did not find any mention of it in the documentation, I saw its call only when analyzing the system code, but it is quite difficult to manage without it in the described situation, since the site administrator (manager) can change the order data in two ways:
And if you use only the systemModifyObject event handler , you will have to explain to all future site administrators that they should in no case change the order data from the list of orders, but only need to go into each order and change something there. Which, of course, is very inconvenient and leaves a great opportunity to make a mistake.
The systemModifyPropertyValue system event has the following parameters:
This event handler can be used not only when editing the list of orders in the Internet Shop module, but also in other similar lists of objects in the UMI.CMS admin panel.
How an event handler is assigned can be seen, for example, here or here . But I want to show a more complex example: how to use the creation of a data import event handler to add the missing functionality to UMI - to teach the system to import optional product properties.
Let, for example, we develop an online store selling t-shirts. We have an established 1C “Trade Management”, in which we are going to keep records of goods and orders. In 1C, all the necessary nomenclature of goods is established and it is desirable that it is uploaded to the site with minimal actions.
The specifics of selling such products is such that we have, for example, the Dolce T-shirt model, which has specific units for sale:
That is, many positions of this model, differing in color and size (and possibly price, for certain combinations). In terms of 1C, these are “nomenclature characteristics”. And from the point of view of the online store, we want to have one page of the Dolce T-shirt model, on which the buyer could choose a color and size and create an order with them.
From the point of view of 1C, everything seems simple. We put a checkmark "characteristics" in the settings of the nomenclature. We first unload the goods to disk, look at the resulting XML file (offers.xml), we have these suggestions, rejoice and do the upload to the site. And here we understand that we rejoiced early. The products themselves were added, but their characteristics (that a particular T-shirt has a dozen offers with different combinations of colors and sizes) - no.
In the UMI system, the functionality we need is implemented using optional properties . That is, at first glance, everything is there. However, after further digging in the sources and documentation, it turns out that in the current version of UMI (2.8.6), the “Data Exchange” module does not support the import of optional properties. So we will add the necessary functionality on our own.
Features of import from 1C to UMI are described in the documentation . To add your functionality when importing data, you need to modify the import template /xsl/import/custom/commerceML2.xsl, and also add your import event handler.
Modify the import template:
Using this template, a separate directory is created (the Reference for the "Characteristics" field), in which all the variants of characteristics that have arrived from 1C are written. In addition, the type for the catalog object (product) is modified to add optional properties there. Thus, the characteristics themselves we have already uploaded to the site, it remains only to write a code that would compare the product characteristics.
Add your event handler. To do this, create a custom_events.php file with the code in the / classes / modules / exchange folder
Thus, we set that both when creating and when updating an element (product page), the onImportElement method will be called upon import. We will write the code for this method in the __custom.php file:
In the above code, after creating or adding an item (checking for after), we read the item’s properties from XML and, if there are optional properties, add the corresponding option properties to the item from the reference book “Reference for the Characteristics field”.
After adding the specified code and re-uploading the goods to the site, we see that each product has positions, each with its own color and size. That is, the indicated task is available, and now we have the UMI.CMS system able to import from 1C the optional properties of the goods.
Thus, events in UMI is a very powerful tool, and with the help of competently added event handlers, you can significantly expand the functionality of the site, without changing a single line of CMS system code and preserving the possibility of system updates.
However, when developing your site, there are situations when you need to change an existing site’s functionality:
- add your own logic for importing data from XML;
- perform some actions when importing data;
- perform some actions when creating or modifying an order;
- perform some scheduled actions;
- … and so on.
In this case, you have to either edit the system code of the engine (which immediately adds problems when updating the CMS), or use the built-in event functionality. In the documentation or on third-party resources, this issue is considered, however, in my opinion, is not detailed enough. This article is an attempt to gather together information about working with events in UMI.CMS, and also based on an example to show how, with the help of event processing, you can expand the functionality of the system.
According to the documentation in UMI.CMS there are two types of event handlers:
- system - these are predefined handlers that are prescribed during module development. These handlers are written in the events.php file, which lies in the module directory. For modules included in the UMI.CMS package, this file cannot be changed.
- custom - these handlers should be in the custom_events.php file in the module directory.
When an event occurs, all handlers assigned to it (both system and user) are called. In addition (this is not written in the documentation), system event handlers are executed after user-defined ones. This should be taken into account when developing your functionality.
Events can be raised before the object changes (the so-called before mode ) and after the change of the object ( after mode ). Not all events can be either “before” or “after”, which ones, it is necessary to look at the link below.
A list of internal events can be found in the documentation . It must be remembered that during actions on the same object in different places different event handlers will be called. This causes some inconvenience:
Suppose, for example, we are developing an online store based on UMI, and we need to perform some actions when changing the status of the order (for example, send additional emails, put some other fields in the order, etc., depending on the status). We look where we can "catch" events of change of the order status:
- status change when an order is created by a site user - the order-status-changed event of the Emarket module;
- status change from the admin panel on the order details page - systemModifyObject system event ;
- change of status from the admin panel on the list of all orders page - system event systemModifyPropertyValue ;
- status change when importing it through XML (for example, when unloading from 1C) - the exchangeOnUpdateObject event .
Thus, in order to take into account all the places where the order status changes, you will have to write 4 handlers.
Let us dwell in more detail on the system event systemModifyPropertyValue . I did not find any mention of it in the documentation, I saw its call only when analyzing the system code, but it is quite difficult to manage without it in the described situation, since the site administrator (manager) can change the order data in two ways:
- on the main page of the "Online Store" module in the list of all orders - UMI allows you to edit the status and other order data directly in this list;
- in the window of detailed information about the order, which opens by clicking on the order.
And if you use only the systemModifyObject event handler , you will have to explain to all future site administrators that they should in no case change the order data from the list of orders, but only need to go into each order and change something there. Which, of course, is very inconvenient and leaves a great opportunity to make a mistake.
The systemModifyPropertyValue system event has the following parameters:
- entity - a reference to an object whose property is changing;
- property - name of the property being changed;
- oldValue - old value of the property;
- newValue - the new value of the property;
This event handler can be used not only when editing the list of orders in the Internet Shop module, but also in other similar lists of objects in the UMI.CMS admin panel.
How an event handler is assigned can be seen, for example, here or here . But I want to show a more complex example: how to use the creation of a data import event handler to add the missing functionality to UMI - to teach the system to import optional product properties.
Let, for example, we develop an online store selling t-shirts. We have an established 1C “Trade Management”, in which we are going to keep records of goods and orders. In 1C, all the necessary nomenclature of goods is established and it is desirable that it is uploaded to the site with minimal actions.
The specifics of selling such products is such that we have, for example, the Dolce T-shirt model, which has specific units for sale:
- T-shirt Dolce White, 40 size;
- T-shirt Dolce White, 48 size;
- T-shirt Dolce Red, 44 size;
- … and so on.
That is, many positions of this model, differing in color and size (and possibly price, for certain combinations). In terms of 1C, these are “nomenclature characteristics”. And from the point of view of the online store, we want to have one page of the Dolce T-shirt model, on which the buyer could choose a color and size and create an order with them.
From the point of view of 1C, everything seems simple. We put a checkmark "characteristics" in the settings of the nomenclature. We first unload the goods to disk, look at the resulting XML file (offers.xml), we have these suggestions, rejoice and do the upload to the site. And here we understand that we rejoiced early. The products themselves were added, but their characteristics (that a particular T-shirt has a dozen offers with different combinations of colors and sizes) - no.
In the UMI system, the functionality we need is implemented using optional properties . That is, at first glance, everything is there. However, after further digging in the sources and documentation, it turns out that in the current version of UMI (2.8.6), the “Data Exchange” module does not support the import of optional properties. So we will add the necessary functionality on our own.
Features of import from 1C to UMI are described in the documentation . To add your functionality when importing data, you need to modify the import template /xsl/import/custom/commerceML2.xsl, and also add your import event handler.
Modify the import template:
Import template
<xsl:templatematch="Классификатор">
...
<!-- описание типа для справочника вариантов характеристик футболок --><typeid="charaсteristics-kinds"title='Справочник для поля "Характеристики"'parent-id="root-guides-type"guide="guide"><base/><fieldgroups><groupname="charaсteristics_kinds"><fieldname="1c_id"title="Идентификатор в 1С"visible="visible"><typename="Строка"data-type="string"/></field><fieldname="color"title="Цвет"field-type-id="3"visible="visible"required="required" ><typeid="3"name="Строка"data-type="string"/></field><fieldname="size"title="Размер"field-type-id="3"visible="visible"required="required" ><typeid="3"name="Строка"data-type="string"/></field></group></fieldgroups></type><!-- тип для товара --><typeid="shirts"title='1C: Футболки'parent-id="root-catalog-object-type"><basemodule="catalog"method="object">Объект каталога</base><fieldgroups>
...
<!-- Опционные свойства --><groupname="optioned_properties"title="Опционные свойства"><fieldname="charaсteristics"title="Характеристики"visible="visible"guide-id="charakteristics-kinds"><typename="Составное"data-type="optioned"multiple="multiple" /></field></group></fieldgroups></type>
...
</xsl:template><!-- шаблон для описания товара - указываем, что страницы товара будут создаваться с нашим типом --><xsl:templatematch="Товары/Товар">
...
<pageid="{Ид}"parentId="{$group_id}"type-id="shirts">
...
</page>
...
</xsl:template><!-- Предложения --><xsl:templatematch="ПакетПредложений"><meta><source-name>commerceML2</source-name></meta><objects><xsl:apply-templatesselect="Предложения/Предложение"mode="objects"/></objects><pages><xsl:apply-templatesselect="Предложения/Предложение"mode="items"/></pages></xsl:template><!-- описание объектов справочников --><xsl:templatematch="Предложения/Предложение"mode="objects"><objectid="{substring-after(Ид,'#')}"name="{Наименование}"type-id="charakteristics-kinds"><properties><groupname="charaсteristics_kinds"><propertyname="1c_id"type="string"is-public="1"visible="visible"><title>Идентификатор в 1С</title><value><xsl:value-ofselect="Ид" /></value></property><xsl:apply-templatesselect="ХарактеристикиТовара/ХарактеристикаТовара[Наименование = 'Цвет']"mode="color" /><xsl:apply-templatesselect="ХарактеристикиТовара/ХарактеристикаТовара[Наименование = 'Размер']"mode="size" /></group></properties></object></xsl:template><xsl:templatematch="ХарактеристикиТовара/ХарактеристикаТовара"mode="color" ><propertyname="color"type="string"is-public="1"visible="visible"><title><xsl:value-ofselect="Наименование" /></title><value><xsl:value-ofselect="Значение" /></value></property></xsl:template><xsl:templatematch="ХарактеристикиТовара/ХарактеристикаТовара"mode="size" ><propertyname="size"type="string"is-public="1"visible="visible"><title><xsl:value-ofselect="Наименование" /></title><value><xsl:value-ofselect="Значение" /></value></property></xsl:template><!-- описание страниц, ссылающихся на эти объекты --><xsl:templatematch="Предложения/Предложение"mode="items"><pageid="{substring-before(Ид,'#')}"update-only="1"><properties><groupname="optioned_properties"title="Опционные свойства"><propertyname="charaсteristics"type="optioned"is-public="1"visible="visible"><title>Характеристики</title><value><optionint="{Количество}"float="{Цены/Цена/ЦенаЗаЕдиницу}"><objectid="{substring-after(Ид,'#')}"name="{Наименование}"type-id="charakteristics-kinds" /></option></value></property></group></properties></page></xsl:template>
Using this template, a separate directory is created (the Reference for the "Characteristics" field), in which all the variants of characteristics that have arrived from 1C are written. In addition, the type for the catalog object (product) is modified to add optional properties there. Thus, the characteristics themselves we have already uploaded to the site, it remains only to write a code that would compare the product characteristics.
Add your event handler. To do this, create a custom_events.php file with the code in the / classes / modules / exchange folder
<?phpnew umiEventListener("exchangeOnUpdateElement", "exchange", "onImportElement");
new umiEventListener("exchangeOnAddElement", "exchange", "onImportElement");
?>
Thus, we set that both when creating and when updating an element (product page), the onImportElement method will be called upon import. We will write the code for this method in the __custom.php file:
Event handler code
/**
* Обработчик события импорта страницы товара из 1С
* @param e - ссылка на экземпляр события
*/publicfunctiononImportElement($e){
if($e->getMode() == "after") {
//добавляем опционные свойства$this->addOptionedProperties($e);
}
}
/**
* Добавляет в случае необходимости опционные свойства. Этот функционал отсутствует в оригинальном
* импортере UMI
* @param e - ссылка на экземпляр события
*/functionaddOptionedProperties($e){
$hierarchy = umiHierarchy::getInstance();
$element = $e->getRef('element');
if (!$element instanceof umiHierarchyElement
|| $element->getMethod() != 'object') {
//это не страница товараreturnfalse;
}
$object_id = $element->objectId;
//XML DOM node с данными данного товара
$element_info = $e->getParam('element_info');
$properties = $element_info->getElementsByTagName('property');
$propertiesSize = $properties->length;
$types = umiObjectTypesCollection::getInstance();
//обрабатываем все свойства товара из XMLforeach($properties as $key => $info) {
$old_name = $info->getAttribute('name');
//получаем внутреннее имя свойства
$name = self::translateName($old_name);
$nl = $info->getElementsByTagName("value");
if (!$nl->length) {
//не найдено значение свойства в XMLcontinue;
}
$value_node = $nl->item(0);
//получаем ссылку на соответствующий тип данных и соответствующее поле товара
$type_id = ($element instanceof umiHierarchyElement) ? $element->getObjectTypeId() : $element->getTypeId();
$type = umiObjectTypesCollection::getInstance()->getType($type_id);
$field_id = $type->getFieldId($name, false);
$field = umiFieldsCollection::getInstance()->getField($field_id);
if (!$field instanceof umiField) {
continue;
}
switch($field->getDataType()) {
//нам надо обработать только опционные свойства, так как остальные уже обработаны движком UMIcase"optioned":
//storing old settings
$oldForce = umiObjectProperty::$USE_FORCE_OBJECTS_CREATION;
umiObjectProperty::$USE_FORCE_OBJECTS_CREATION = false;
//находим справочник, на который ссылается поле
$objectsCollection = umiObjectsCollection::getInstance();
$guideItems = $objectsCollection->getGuidedItems($field->getGuideId());
$options = $value_node->getElementsByTagName("option");
$items = Array();
foreach($options as $option) {
//в поле int у нас хранится число товаров на складе соответствующего цвета и размера
$int = $option->hasAttribute("int") ? $option->getAttribute("int") : null;
//в поле float у нас хранится цена товара
$float = $option->hasAttribute("float") ? $option->getAttribute("float") : null;
$objects = $option->getElementsByTagName("object");
foreach($objects as $object) {
$objectId = $object->hasAttribute("id") ? $object->getAttribute("id") : null;
$objectName = $object->hasAttribute("name") ? $object->getAttribute("name") : null;
$objectTypeId = $object->hasAttribute("type-id") ? $object->getAttribute("type-id") : null;
//создаем опционное свойства
$item = Array();
$item["int"] = (int)$int;
$item["float"] = (float)$float;
$item["varchar"] = $objectName;
//находим объект, на который ссылается propertyforeach($guideItems as $key => $value) {
if($value == $objectName) {
//ссылка на id объекта справочника должна быть именно типа int, иначе не работает
$item["rel"] = (int)$key;
break;
}
}
$items[] = $item;
}
}
//обновим поле объекта
$entityId = $element->getId();
if($element instanceof umiHierarchyElement) {
$entityId = $element->getObject()->getId();
}
$pageObject = $objectsCollection->getObject($entityId);
//мы должны добавить объекты к уже существующим, если они есть
$existingItems = $pageObject->getValue($name);
$newItems = Array();
if($existingItems) {
//добавляем те объекты из имеющихся, которых нет в новыхforeach($existingItems as $existingItem) {
$found = false;
foreach($items as $item) {
if($item["rel"] == $existingItem["rel"]) {
$found = true;
break;
}
}
if(!$found) {
$newItems[] = $existingItem;
}
}
}
//теперь добавим все новыеforeach($items as $item) {
$newItems[] = $item;
}
$pageObject->setValue($name, $newItems);
$pageObject->commit();
//restoring settings
umiObjectProperty::$USE_FORCE_OBJECTS_CREATION = $oldForce;
break;
}
}
}
/**
* Оригинальный UMI-шный метод из кода модуля импорта данных
*/protectedstaticfunctiontranslateName($name){
$name = umiHierarchy::convertAltName($name, "_");
$name = umiObjectProperty::filterInputString($name);
if(!strlen($name)) $name = '_';
$name = substr($name, 0, 64);
return $name;
}
In the above code, after creating or adding an item (checking for after), we read the item’s properties from XML and, if there are optional properties, add the corresponding option properties to the item from the reference book “Reference for the Characteristics field”.
After adding the specified code and re-uploading the goods to the site, we see that each product has positions, each with its own color and size. That is, the indicated task is available, and now we have the UMI.CMS system able to import from 1C the optional properties of the goods.
Thus, events in UMI is a very powerful tool, and with the help of competently added event handlers, you can significantly expand the functionality of the site, without changing a single line of CMS system code and preserving the possibility of system updates.