403Webshell
Server IP : 80.87.202.40  /  Your IP : 216.73.216.169
Web Server : Apache
System : Linux rospirotorg.ru 5.14.0-539.el9.x86_64 #1 SMP PREEMPT_DYNAMIC Thu Dec 5 22:26:13 UTC 2024 x86_64
User : bitrix ( 600)
PHP Version : 8.2.27
Disable Function : NONE
MySQL : OFF |  cURL : ON |  WGET : ON |  Perl : ON |  Python : OFF |  Sudo : ON |  Pkexec : ON
Directory :  /home/bitrix/ext_www/rospirotorg.ru/bitrix/modules/main/lib/orm/data/

Upload File :
current_dir [ Writeable] document_root [ Writeable]

 

Command :


[ Back ]     

Current File : /home/bitrix/ext_www/rospirotorg.ru/bitrix/modules/main/lib/orm/data/datamanager.php
<?php

/**
 * Bitrix Framework
 * @package bitrix
 * @subpackage main
 * @copyright 2001-2024 Bitrix
 */

namespace Bitrix\Main\ORM\Data;

use Bitrix\Main;
use Bitrix\Main\ORM\Entity;
use Bitrix\Main\ORM\EntityError;
use Bitrix\Main\ORM\Event;
use Bitrix\Main\ORM\Fields\ExpressionField;
use Bitrix\Main\ORM\Fields\FieldError;
use Bitrix\Main\ORM\Fields\FieldTypeMask;
use Bitrix\Main\ORM\Fields\ScalarField;
use Bitrix\Main\ORM\Objectify\Values;
use Bitrix\Main\ORM\Query\Query;
use Bitrix\Main\ORM\Query\Result as QueryResult;
use Bitrix\Main\Localization\Loc;
use Bitrix\Main\ORM\Data\AddStrategy\Contract\AddStrategy;
use Bitrix\Main\ORM\Query\Filter\ConditionTree as Filter;
use Bitrix\Main\ORM\Objectify\EntityObject;
use Bitrix\Main\ORM\Objectify\Collection;

Loc::loadMessages(__FILE__);

/**
 * Base entity data manager
 */
abstract class DataManager
{
	const EVENT_ON_BEFORE_ADD = "OnBeforeAdd";
	const EVENT_ON_ADD = "OnAdd";
	const EVENT_ON_AFTER_ADD = "OnAfterAdd";
	const EVENT_ON_BEFORE_UPDATE = "OnBeforeUpdate";
	const EVENT_ON_UPDATE = "OnUpdate";
	const EVENT_ON_AFTER_UPDATE = "OnAfterUpdate";
	const EVENT_ON_BEFORE_DELETE = "OnBeforeDelete";
	const EVENT_ON_DELETE = "OnDelete";
	const EVENT_ON_AFTER_DELETE = "OnAfterDelete";

	/** @var Entity[] */
	protected static $entity;

	/** @var EntityObject[] Cache of class names */
	protected static $objectClass;

	/** @var Collection[] Cache of class names */
	protected static $collectionClass;

	/** @var EntityObject[][] Objects that called delete() method themselves */
	protected static $currentDeletingObjects;

	/**
	 * Returns entity object
	 *
	 * @return Entity
	 * @throws Main\ArgumentException
	 * @throws Main\SystemException
	 */
	public static function getEntity()
	{
		$class = static::getEntityClass()::normalizeEntityClass(get_called_class());

		if (!isset(static::$entity[$class]))
		{
			static::$entity[$class] = static::getEntityClass()::getInstance($class);
		}

		return static::$entity[$class];
	}

	public static function unsetEntity($class)
	{
		$class = static::getEntityClass()::normalizeEntityClass($class);

		if (isset(static::$entity[$class]))
		{
			unset(static::$entity[$class]);
		}
	}

	/**
	 * Returns DB table name for entity
	 *
	 * @return string
	 */
	public static function getTableName()
	{
		return null;
	}

	/**
	 * Returns connection name for entity
	 *
	 * @return string
	 */
	public static function getConnectionName()
	{
		return 'default';
	}

	/**
	 * @return string | null
	 */
	public static function getTitle()
	{
		return null;
	}

	/**
	 * Returns class of Object for current entity.
	 *
	 * @return string|EntityObject
	 */
	public static function getObjectClass()
	{
		if (!isset(static::$objectClass[get_called_class()]))
		{
			static::$objectClass[get_called_class()] = static::getObjectClassByDataClass(get_called_class());
		}

		return static::$objectClass[get_called_class()];
	}

	/**
	 * Returns class name (without namespace) of Object for current entity.
	 *
	 * @return string
	 */
	final public static function getObjectClassName()
	{
		$class = static::getObjectClass();
		return substr($class, strrpos($class, '\\') + 1);
	}

	protected static function getObjectClassByDataClass($dataClass)
	{
		$objectClass = static::getEntityClass()::normalizeName($dataClass);

		// make class name more unique
		$namespace = substr($objectClass, 0, strrpos($objectClass, '\\') + 1);
		$className = substr($objectClass, strrpos($objectClass, '\\') + 1);

		$className = static::getEntityClass()::getDefaultObjectClassName($className);

		return $namespace.$className;
	}

	/**
	 * Returns class of Object collection for current entity.
	 *
	 * @return string|Collection
	 */
	public static function getCollectionClass()
	{
		if (!isset(static::$collectionClass[get_called_class()]))
		{
			static::$collectionClass[get_called_class()] = static::getCollectionClassByDataClass(get_called_class());
		}

		return static::$collectionClass[get_called_class()];
	}

	/**
	 * Returns class name (without namespace) of Object collection for current entity.
	 *
	 * @return string
	 */
	final public static function getCollectionClassName()
	{
		$class = static::getCollectionClass();
		return substr($class, strrpos($class, '\\') + 1);
	}

	protected static function getCollectionClassByDataClass($dataClass)
	{
		$objectClass = static::getEntityClass()::normalizeName($dataClass);

		// make class name more unique
		$namespace = substr($objectClass, 0, strrpos($objectClass, '\\') + 1);
		$className = substr($objectClass, strrpos($objectClass, '\\') + 1);

		$className = static::getEntityClass()::getDefaultCollectionClassName($className);

		return $namespace.$className;
	}

	/**
	 * @return EntityObject|string
	 */
	public static function getObjectParentClass()
	{
		return EntityObject::class;
	}

	/**
	 * @return Collection|string
	 */
	public static function getCollectionParentClass()
	{
		return Collection::class;
	}

	/**
	 * @return Query|string
	 */
	public static function getQueryClass()
	{
		return Query::class;
	}

	/**
	 * @return Entity|string
	 */
	public static function getEntityClass()
	{
		return Entity::class;
	}

	/**
	 * @param bool $setDefaultValues
	 *
	 * @return null Actual type should be annotated by orm:annotate
	 * @throws Main\ArgumentException
	 * @throws Main\SystemException
	 */
	final public static function createObject($setDefaultValues = true)
	{
		return static::getEntity()->createObject($setDefaultValues);
	}

	/**
	 * @return null Actual type should be annotated by orm:annotate
	 * @throws Main\ArgumentException
	 * @throws Main\SystemException
	 */
	final public static function createCollection()
	{
		return static::getEntity()->createCollection();
	}

	/**
	 * @see EntityObject::wakeUp()
	 *
	 * @param $row
	 *
	 * @return null Actual type should be annotated by orm:annotate
	 * @throws Main\ArgumentException
	 * @throws Main\SystemException
	 */
	final public static function wakeUpObject($row)
	{
		return static::getEntity()->wakeUpObject($row);
	}

	/**
	 * @see Collection::wakeUp()
	 *
	 * @param $rows
	 *
	 * @return null Actual type should be annotated by orm:annotate
	 * @throws Main\ArgumentException
	 * @throws Main\SystemException
	 */
	final public static function wakeUpCollection($rows)
	{
		return static::getEntity()->wakeUpCollection($rows);
	}

	/**
	 * Returns entity map definition.
	 * To get initialized fields @see \Bitrix\Main\ORM\Entity::getFields() and \Bitrix\Main\ORM\Entity::getField()
	 */
	public static function getMap()
	{
		return array();
	}

	public static function getUfId()
	{
		return null;
	}

	public static function isUts()
	{
		return false;
	}

	public static function isUtm()
	{
		return false;
	}

	/**
	 * @param Query $query
	 *
	 * @return Query
	 */
	public static function setDefaultScope($query)
	{
		return $query;
	}

	/**
	 * @param Entity $entity
	 *
	 * @return null
	 */
	public static function postInitialize(Entity $entity)
	{
		return null;
	}

	/**
	 * Returns selection by entity's primary key and optional parameters for getList()
	 *
	 * @param mixed $primary    Primary key of the entity
	 * @param array $parameters Additional parameters for getList()
	 *
	 * @return QueryResult
	 * @throws Main\ArgumentException
	 * @throws Main\ObjectPropertyException
	 * @throws Main\SystemException
	 */
	public static function getByPrimary($primary, array $parameters = array())
	{
		static::normalizePrimary($primary);
		static::validatePrimary($primary);

		$primaryFilter = array();

		foreach ($primary as $k => $v)
		{
			$primaryFilter['='.$k] = $v;
		}

		if (isset($parameters['filter']))
		{
			$parameters['filter'] = array($primaryFilter, $parameters['filter']);
		}
		else
		{
			$parameters['filter'] = $primaryFilter;
		}

		return static::getList($parameters);
	}

	/**
	 * Returns selection by entity's primary key
	 *
	 * @param mixed $id Primary key of the entity
	 *
	 * @return QueryResult
	 * @throws Main\ArgumentException
	 * @throws Main\ObjectPropertyException
	 * @throws Main\SystemException
	 */
	public static function getById($id)
	{
		return static::getByPrimary($id);
	}

	/**
	 * Returns one row (or null) by entity's primary key
	 *
	 * @param mixed $id Primary key of the entity
	 * @param array $parameters Additional parameters for getList()
	 *
	 * @return array|null
	 * @throws Main\ArgumentException
	 * @throws Main\ObjectPropertyException
	 * @throws Main\SystemException
	 */
	public static function getRowById($id, array $parameters = [])
	{
		$result = static::getByPrimary($id, $parameters);
		$row = $result->fetch();

		return (is_array($row)? $row : null);
	}

	/**
	 * Returns one row (or null) by parameters for getList()
	 *
	 * @param array $parameters Primary key of the entity
	 *
	 * @return array|null
	 * @throws Main\ArgumentException
	 * @throws Main\ObjectPropertyException
	 * @throws Main\SystemException
	 */
	public static function getRow(array $parameters)
	{
		$parameters['limit'] = 1;
		$result = static::getList($parameters);
		$row = $result->fetch();

		return (is_array($row)? $row : null);
	}

	/**
	 * Executes the query and returns selection by parameters of the query. This function is an alias to the Query
	 * object functions
	 *
	 * @param array $parameters An array of query parameters, available keys are:<br>
	 * 		"select" => array of fields in the SELECT part of the query, aliases are possible in the form of
	 *     "alias"=>"field";<br>
	 * 		"filter" => array of filters in the WHERE/HAVING part of the query in the form of
	 *     "(condition)field"=>"value"; also could be an instance of Filter;<br>
	 * 		"group" => array of fields in the GROUP BY part of the query;<br>
	 * 		"order" => array of fields in the ORDER BY part of the query in the form of "field"=>"asc|desc";<br>
	 * 		"limit" => integer indicating maximum number of rows in the selection (like LIMIT n in MySql);<br>
	 * 		"offset" => integer indicating first row number in the selection (like LIMIT n, 100 in MySql);<br>
	 *		"runtime" => array of entity fields created dynamically;<br>
	 * 		"cache => array of cache options:<br>
	 * 			"ttl" => integer indicating cache TTL;<br>
	 * 			"cache_joins" => boolean enabling to cache joins, false by default.
	 * @see Query::filter()
	 *
	 * @return QueryResult
	 * @throws Main\ArgumentException
	 * @throws Main\ObjectPropertyException
	 * @throws Main\SystemException
	 */
	public static function getList(array $parameters = array())
	{
		$query = static::query();

		if(!isset($parameters['select']))
		{
			$query->setSelect(array('*'));
		}

		foreach($parameters as $param => $value)
		{
			switch($param)
			{
				case 'select':
					$query->setSelect($value);
					break;
				case 'filter':
					$value instanceof Filter ? $query->where($value) : $query->setFilter($value);
					break;
				case 'group':
					$query->setGroup($value);
					break;
				case 'order';
					$query->setOrder($value);
					break;
				case 'limit':
					$query->setLimit($value);
					break;
				case 'offset':
					$query->setOffset($value);
					break;
				case 'count_total':
					$query->countTotal($value);
					break;
				case 'runtime':
					foreach ($value as $name => $fieldInfo)
					{
						$query->registerRuntimeField($name, $fieldInfo);
					}
					break;
				case 'data_doubling':
					if($value)
					{
						$query->enableDataDoubling();
					}
					else
					{
						$query->disableDataDoubling();
					}
					break;
				case 'private_fields':
					if($value)
					{
						$query->enablePrivateFields();
					}
					else
					{
						$query->disablePrivateFields();
					}
					break;
				case 'cache':
					$query->setCacheTtl($value["ttl"]);
					if(isset($value["cache_joins"]))
					{
						$query->cacheJoins($value["cache_joins"]);
					}
					break;
				default:
					throw new Main\ArgumentException("Unknown parameter: ".$param, $param);
			}
		}

		return $query->exec();
	}

	/**
	 * Performs COUNT query on entity and returns the result.
	 *
	 * @param array|Filter $filter
	 * @param array $cache An array of cache options
	 * 		"ttl" => integer indicating cache TTL
	 * @return int
	 * @throws Main\ObjectPropertyException
	 * @throws Main\SystemException
	 */
	public static function getCount($filter = array(), array $cache = array())
	{
		$query = static::query();

		// new filter
		$query->addSelect(new ExpressionField('CNT', 'COUNT(1)'));

		if ($filter instanceof Filter)
		{
			$query->where($filter);
		}
		else
		{
			$query->setFilter($filter);
		}

		if(isset($cache["ttl"]))
		{
			$query->setCacheTtl($cache["ttl"]);
		}

		$result = $query->exec()->fetch();

		return (int)$result['CNT'];
	}

	/**
	 * Creates and returns the Query object for the entity
	 *
	 * @return Query
	 * @throws Main\ArgumentException
	 * @throws Main\SystemException
	 */
	public static function query()
	{
		$queryClass = static::getQueryClass();
		return new $queryClass(static::getEntity());
	}

	/**
	 * @param array $data
	 *
	 * @return array
	 * @throws Main\ArgumentException
	 * @throws Main\SystemException
	 */
	protected static function replaceFieldName($data = array())
	{
		$newData = [];
		$entity = static::getEntity();

		foreach ($data as $fieldName => $value)
		{
			$newData[$entity->getField($fieldName)->getColumnName()] = $value;
		}

		return $newData;
	}

	/**
	 * @param       $primary
	 * @param array $data
	 *
	 * @throws Main\ArgumentException
	 * @throws Main\SystemException
	 */
	protected static function normalizePrimary(&$primary, $data = array())
	{
		$entity = static::getEntity();
		$entity_primary = $entity->getPrimaryArray();

		if ($primary === null)
		{
			$primary = array();

			// extract primary from data array
			foreach ($entity_primary as $key)
			{
				/** @var ScalarField $field  */
				$field = $entity->getField($key);
				if ($field->isAutocomplete())
				{
					continue;
				}

				if (!isset($data[$key]))
				{
					throw new Main\ArgumentException(sprintf(
						'Primary `%s` was not found when trying to query %s row.', $key, $entity->getName()
					));
				}

				$primary[$key] = $data[$key];
			}
		}
		elseif (is_scalar($primary))
		{
			if (count($entity_primary) > 1)
			{
				throw new Main\ArgumentException(sprintf(
					'Require multi primary {`%s`}, but one scalar value "%s" found when trying to query %s row.',
					join('`, `', $entity_primary), $primary, $entity->getName()
				));
			}

			$primary = array($entity_primary[0] => $primary);
		}
	}

	/**
	 * @param $primary
	 *
	 * @throws Main\ArgumentException
	 * @throws Main\SystemException
	 */
	protected static function validatePrimary($primary)
	{
		$entity = static::getEntity();
		if (is_array($primary))
		{
			if(empty($primary))
			{
				throw new Main\ArgumentException(sprintf(
					'Empty primary found when trying to query %s row.', $entity->getName()
				));
			}

			$entity_primary = $entity->getPrimaryArray();

			foreach (array_keys($primary) as $key)
			{
				if (!in_array($key, $entity_primary, true))
				{
					throw new Main\ArgumentException(sprintf(
						'Unknown primary `%s` found when trying to query %s row.',
						$key, $entity->getName()
					));
				}
			}
		}
		else
		{
			throw new Main\ArgumentException(sprintf(
				'Unknown type of primary "%s" found when trying to query %s row.', gettype($primary), $entity->getName()
			));
		}

		// primary values validation
		foreach ($primary as $key => $value)
		{
			if (!is_scalar($value) && !($value instanceof Main\Type\Date))
			{
				throw new Main\ArgumentException(sprintf(
					'Unknown value type "%s" for primary "%s" found when trying to query %s row.',
					gettype($value), $key, $entity->getName()
				));
			}
		}
	}

	/**
	 * Checks the data fields before saving to DB. Result stores in the $result object
	 *
	 * @param Result $result
	 * @param mixed  $primary
	 * @param array  $data
	 *
	 * @throws Main\ArgumentException
	 * @throws Main\SystemException
	 */
	public static function checkFields(Result $result, $primary, array $data)
	{
		$entity = static::getEntity();
		//checks required fields
		foreach ($entity->getFields() as $field)
		{
			if ($field instanceof ScalarField && $field->isRequired())
			{
				$fieldName = $field->getName();
				if (
					(empty($primary) && (!isset($data[$fieldName]) || $field->isValueEmpty($data[$fieldName])))
					|| (!empty($primary) && array_key_exists($fieldName, $data) && $field->isValueEmpty($data[$fieldName]))
				)
				{
					$result->addError(new FieldError(
						$field,
						Loc::getMessage("MAIN_ENTITY_FIELD_REQUIRED", array("#FIELD#"=>$field->getTitle())),
						FieldError::EMPTY_REQUIRED
					));
				}
			}
		}

		// checks data - fieldname & type & strlen etc.
		foreach ($data as $k => $v)
		{
			if ($entity->hasField($k))
			{
				$field = $entity->getField($k);

			}
			else
			{
				throw new Main\ArgumentException(sprintf(
					'Field `%s` not found in entity when trying to query %s row.',
					$k, $entity->getName()
				));
			}

			$field->validateValue($v, $primary, $data, $result);
		}
	}

	/**
	 * @param array $fields
	 * @param bool  $setDefaultValues
	 * @param array $primary
	 *
	 * @return EntityObject
	 * @throws Main\ArgumentException
	 * @throws Main\SystemException
	 */
	protected static function convertArrayToObject(&$fields, $setDefaultValues = false, $primary = null)
	{
		// extended data format
		$data = null;

		if (isset($fields["fields"]) && is_array($fields["fields"]))
		{
			$data = $fields;
			$fields = $data["fields"];
		}

		// convert to object
		if (isset($fields['__object']))
		{
			$object = $fields['__object'];
			unset($fields['__object']);
		}
		else
		{
			$entity = static::getEntity();

			/** @var EntityObject $object */
			if ($primary === null)
			{
				$object = $entity->createObject($setDefaultValues);

				foreach ($fields as $fieldName => $value)
				{
					// sometimes data array can be used for storing non-entity data
					if ($entity->hasField($fieldName))
					{
						$object->sysSetValue($fieldName, $value);
					}
				}
			}
			else
			{
				$object = $entity->wakeUpObject($primary);

				foreach ($fields as $fieldName => $value)
				{
					// sometimes data array can be used for storing non-entity data
					if ($entity->hasField($fieldName))
					{
						if ($entity->getField($fieldName) instanceof ScalarField && $entity->getField($fieldName)->isPrimary())
						{
							// ignore old primary
							if (array_key_exists($fieldName, $primary) && $primary[$fieldName] == $value)
							{
								unset($fields[$fieldName]);
								continue;
							}

							// but prevent primary changing
							trigger_error(sprintf(
								'Primary of %s %s can not be changed. You can delete this row and add a new one',
								static::getObjectClass(), Main\Web\Json::encode($object->primary)
							), E_USER_WARNING);

							continue;
						}

						$object->sysSetValue($fieldName, $value);
					}
				}
			}
		}

		// auth context
		if (isset($data['auth_context']))
		{
			$object->authContext = $data['auth_context'];
		}

		return $object;
	}

	/**
	 * @param EntityObject $object
	 * @param $ufdata
	 * @param Result $result
	 */
	protected static function checkUfFields($object, $ufdata, $result)
	{
		global $USER_FIELD_MANAGER, $APPLICATION;

		$userId = ($object->authContext && $object->authContext->getUserId())
			? $object->authContext->getUserId()
			: false;

		if ($object->sysGetState() === Main\ORM\Objectify\State::RAW)
		{
			$ufPrimary = false;
		}
		else
		{
			$ufPrimary = $object->primary;
			$ufPrimary = end($ufPrimary);
		}

		if (!$USER_FIELD_MANAGER->CheckFields($object->entity->getUfId(), $ufPrimary, $ufdata, $userId))
		{
			if (is_object($APPLICATION) && $APPLICATION->getException())
			{
				$e = $APPLICATION->getException();
				$result->addError(new EntityError($e->getString()));
				$APPLICATION->resetException();
			}
			else
			{
				$result->addError(new EntityError("Unknown error while checking userfields"));
			}
		}
	}

	protected static function getAddStrategy(): AddStrategy
	{
		return new Main\ORM\Data\AddStrategy\Insert(static::getEntity());
	}

	/**
	 * Adds row to entity table
	 *
	 * @param array $data An array with fields like
	 * 	array(
	 * 		"fields" => array(
	 * 			"FIELD1" => "value1",
	 * 			"FIELD2" => "value2",
	 * 		),
	 * 		"auth_context" => \Bitrix\Main\Authentication\Context object
	 *	)
	 *	or just a plain array of fields.
	 *
	 * This method uses the default strategy defined in the class.
	 *
	 * @return AddResult Contains ID of inserted row
	 *
	 * @throws \Exception
	 */
	public static function add(array $data)
	{
		return self::sysAddInternal(static::getAddStrategy(), $data);
	}

	/**
	 * @internal For internal system usage only.
	 */
	final protected static function sysAddInternal(
		AddStrategy $strategy,
		array $data,
		bool $ignoreEvents = false,
	): AddResult
	{
		global $USER_FIELD_MANAGER;

		// compatibility
		$fields = $data;

		// prepare entity object for compatibility with new code
		$object = static::convertArrayToObject($fields, true);

		$entity = static::getEntity();
		$result = new AddResult();

		try
		{
			if (!$ignoreEvents)
			{
				static::callOnBeforeAddEvent($object, $fields, $result);
			}

			// actualize old-style fields array from object
			$fields = $object->collectValues(Values::CURRENT, FieldTypeMask::SCALAR);

			// uf values
			$ufdata = $object->collectValues(Values::CURRENT, FieldTypeMask::USERTYPE);

			// check data
			static::checkFields($result, null, $fields);

			// check uf data
			if (!empty($ufdata))
			{
				static::checkUfFields($object, $ufdata, $result);
			}

			// check if there is still some data
			if (empty($fields) && empty($ufdata))
			{
				$result->addError(new EntityError('There is no data to add.'));
			}

			// return if any error
			if (!$result->isSuccess(true))
			{
				return $result;
			}

			if (!$ignoreEvents)
			{
				//event on adding
				self::callOnAddEvent($object, $fields, $ufdata);
			}

			// use save modifiers
			$fieldsToDb = $fields;

			foreach ($fieldsToDb as $fieldName => $value)
			{
				$field = $entity->getField($fieldName);
				if ($field->isPrimary() && $field->isAutocomplete() && is_null($value))
				{
					unset($fieldsToDb[$fieldName]); // postgresql compatibility
					continue;
				}
				$fieldsToDb[$fieldName] = $field->modifyValueBeforeSave($value, $fields);
			}

			$addedData = $strategy->add(static::replaceFieldName($fieldsToDb));
			$id = $addedData->id;

			// build standard primary
			$primary = null;
			$isGuessedPrimary = false;

			if (!empty($id))
			{
				if (!empty($entity->getAutoIncrement()))
				{
					$primary = [$entity->getAutoIncrement() => $id];
					static::normalizePrimary($primary);
				}
				else
				{
					// for those who did not set 'autocomplete' flag but wants to get id from result
					$primary = ['ID' => $id];
					$isGuessedPrimary = true;
				}
			}
			else
			{
				static::normalizePrimary($primary, $fields);
			}

			// fill result
			$result->setPrimary($primary);
			$result->setData($fields + $ufdata);
			$result->setObject($object);

			if (!$isGuessedPrimary)
			{
				foreach ($primary as $primaryName => $primaryValue)
				{
					$object->sysSetActual($primaryName, $primaryValue);
				}
			}

			// save uf data
			if (!empty($ufdata))
			{
				$ufUserId = false;

				if ($object->authContext)
				{
					$ufUserId = $object->authContext->getUserId();
				}

				$USER_FIELD_MANAGER->update($entity->getUfId(), end($primary), $ufdata, $ufUserId);
			}

			if ($addedData->isDBChanged)
			{
				static::cleanCache();
			}

			if (!$ignoreEvents)
			{
				static::callOnAfterAddEvent($object, $fields + $ufdata, $id);
			}
		}
		catch (\Exception $e)
		{
			// check result to avoid warning
			$result->isSuccess();

			throw $e;
		}

		return $result;
	}

	/**
	 * Adds several rows to entity table.
	 *
	 * This method uses the default strategy defined in the class.
	 *
	 * @param      $rows
	 * @param bool $ignoreEvents
	 *
	 * @return AddResult
	 * @throws Main\ArgumentException
	 * @throws Main\SystemException
	 */
	public static function addMulti($rows, $ignoreEvents = false)
	{
		return self::sysAddMultiInternal(
			static::getAddStrategy(),
			(array)$rows,
			(bool)$ignoreEvents,
		);
	}

	/**
	 * @internal For internal system usage only.
	 */
	final protected static function sysAddMultiInternal(
		AddStrategy $strategy,
		array $multiData,
		bool $ignoreEvents = false,
	): AddResult
	{
		if (count($multiData) <= 0)
		{
			return (new AddResult())->addError(new EntityError('There is no data to add.'));
		}
		if (count($multiData) === 1)
		{
			return self::sysAddInternal($strategy, reset($multiData), $ignoreEvents);
		}

		global $USER_FIELD_MANAGER;

		$rows = array_values($multiData);

		// prepare objects
		$objects = [];

		foreach ($rows as $k => &$row)
		{
			$objects[$k] = static::convertArrayToObject($row, true);
		}

		$entity = static::getEntity();
		$result = new AddResult();

		try
		{
			// call onBeforeEvent
			if (!$ignoreEvents)
			{
				foreach ($objects as $k => $object)
				{
					static::callOnBeforeAddEvent($object, $rows[$k], $result);
				}
			}

			// collect array data
			$allFields = [];
			$allUfData = [];

			foreach ($objects as $k => $object)
			{
				// actualize old-style fields array from object
				$allFields[$k] = $object->collectValues(Values::CURRENT, FieldTypeMask::SCALAR);

				// uf values
				$allUfData[$k] = $object->collectValues(Values::CURRENT, FieldTypeMask::USERTYPE);
			}

			// check data and uf
			foreach ($objects as $k => $object)
			{
				$fields = $allFields[$k];
				$ufdata = $allUfData[$k];

				// check data
				static::checkFields($result, null, $fields);

				// check uf data
				if (!empty($ufdata))
				{
					static::checkUfFields($object, $ufdata, $result);
				}

				// check if there is still some data
				if (empty($fields) && empty($ufdata))
				{
					$result->addError(new EntityError('There is no data to add.'));
				}
			}

			// return if any error in any row
			if (!$result->isSuccess(true))
			{
				return $result;
			}

			//event on adding
			if (!$ignoreEvents)
			{
				foreach ($objects as $k => $object)
				{
					$fields = $allFields[$k];
					$ufdata = $allUfData[$k];

					self::callOnAddEvent($object, $fields, $ufdata);
				}
			}

			$allFieldsToDb = [];

			foreach ($allFields as $k => $fields)
			{
				// use save modifiers
				$fieldsToDb = $fields;

				foreach ($fieldsToDb as $fieldName => $value)
				{
					$field = $entity->getField($fieldName);
					$fieldsToDb[$fieldName] = $field->modifyValueBeforeSave($value, $fields);
				}

				$dataReplacedColumn = static::replaceFieldName($fieldsToDb);

				$allFieldsToDb[$k] = $dataReplacedColumn;
			}

			if (!$ignoreEvents && !empty($entity->getAutoIncrement()))
			{
				// change to warning
				trigger_error(
					'Multi-insert doesn\'t work with events as far as we can not get last inserted IDs that we need for the events. '.
					'Insert query was forced to multiple separate queries.',
					E_USER_NOTICE
				);

				$forcedSeparateQueries = true;
				$isDBChanged = false;

				$ids = [];
				foreach ($allFieldsToDb as $key => $dbFields)
				{
					$addedData = $strategy->add($dbFields);
					$ids[$key] = $addedData->id;
					$isDBChanged = $isDBChanged || $addedData->isDBChanged;
				}
			}
			else
			{
				$addedData = $strategy->addMulti($allFieldsToDb);

				$forcedSeparateQueries = false;
				$isDBChanged = $addedData->isDBChanged;
				$ids = null;
			}

			// save uf data
			foreach ($allUfData as $k => $ufdata)
			{
				if (!empty($ufdata))
				{
					$ufUserId = false;

					if ($objects[$k]->authContext)
					{
						$ufUserId = $objects[$k]->authContext->getUserId();
					}

					$USER_FIELD_MANAGER->update($entity->getUfId(), end($primary), $ufdata, $ufUserId);
				}
			}

			if ($isDBChanged)
			{
				static::cleanCache();
			}

			// after event
			if (!$ignoreEvents)
			{
				foreach ($objects as $k => $object)
				{
					$fields = $allFields[$k] + $allUfData[$k];
					$id = $forcedSeparateQueries ? $ids[$k] : null;

					static::callOnAfterAddEvent($object, $fields, $id);
				}
			}
		}
		catch (\Exception $e)
		{
			// check result to avoid warning
			$result->isSuccess();

			throw $e;
		}

		return $result;
	}

	/**
	 * Updates row in entity table by primary key
	 *
	 * @param mixed $primary
	 * @param array $data An array with fields like
	 * 	array(
	 * 		"fields" => array(
	 * 			"FIELD1" => "value1",
	 * 			"FIELD2" => "value2",
	 * 		),
	 * 		"auth_context" => \Bitrix\Main\Authentication\Context object
	 *	)
	 *	or just a plain array of fields.
	 *
	 * @return UpdateResult
	 *
	 * @throws \Exception
	 */
	public static function update($primary, array $data)
	{
		global $USER_FIELD_MANAGER;

		// check primary
		static::normalizePrimary(
			$primary, isset($data["fields"]) && is_array($data["fields"]) ? $data["fields"] : $data
		);
		static::validatePrimary($primary);

		// compatibility
		$fields = $data;

		// prepare entity object for compatibility with new code
		$object = static::convertArrayToObject($fields, false, $primary);

		$entity = static::getEntity();
		$result = new UpdateResult();

		try
		{
			static::callOnBeforeUpdateEvent($object, $fields, $result);

			// actualize old-style fields array from object
			$fields = $object->collectValues(Values::CURRENT, FieldTypeMask::SCALAR);

			// uf values
			$ufdata = $object->collectValues(Values::CURRENT, FieldTypeMask::USERTYPE);

			// check data
			static::checkFields($result, $primary, $fields);

			// check uf data
			if (!empty($ufdata))
			{
				static::checkUfFields($object, $ufdata, $result);
			}

			// check if there is still some data
			if (empty($fields) && empty($ufdata))
			{
				return $result;
			}

			// return if any error
			if (!$result->isSuccess(true))
			{
				return $result;
			}

			static::callOnUpdateEvent($object, $fields, $ufdata);

			// use save modifiers
			$fieldsToDb = $fields;

			foreach ($fieldsToDb as $fieldName => $value)
			{
				$field = $entity->getField($fieldName);
				$fieldsToDb[$fieldName] = $field->modifyValueBeforeSave($value, $fields);
			}

			// save data
			if (!empty($fieldsToDb))
			{
				$connection = $entity->getConnection();
				$helper = $connection->getSqlHelper();

				$tableName = $entity->getDBTableName();

				$dataReplacedColumn = static::replaceFieldName($fieldsToDb);
				$update = $helper->prepareUpdate($tableName, $dataReplacedColumn);

				$replacedPrimary = static::replaceFieldName($primary);
				$id = array();
				foreach ($replacedPrimary as $k => $v)
				{
					$id[] = $helper->prepareAssignment($tableName, $k, $v);
				}
				$where = implode(' AND ', $id);

				$sql = "UPDATE ".$helper->quote($tableName)." SET ".$update[0]." WHERE ".$where;
				$connection->queryExecute($sql, $update[1]);

				$result->setAffectedRowsCount($connection);
			}

			$result->setData($fields + $ufdata);
			$result->setPrimary($primary);
			$result->setObject($object);

			// save uf data
			if (!empty($ufdata))
			{
				$ufUserId = false;

				if ($object->authContext)
				{
					$ufUserId = $object->authContext->getUserId();
				}

				$USER_FIELD_MANAGER->update($entity->getUfId(), end($primary), $ufdata, $ufUserId);
			}

			static::cleanCache();

			// event after update
			static::callOnAfterUpdateEvent($object, $fields + $ufdata);
		}
		catch (\Exception $e)
		{
			// check result to avoid warning
			$result->isSuccess();

			throw $e;
		}

		return $result;
	}

	/**
	 * @param array $primaries
	 * @param array $data
	 * @param bool  $ignoreEvents
	 *
	 * @return UpdateResult
	 * @throws Main\ArgumentException
	 * @throws Main\SystemException
	 */
	public static function updateMulti($primaries, $data, $ignoreEvents = false)
	{
		global $USER_FIELD_MANAGER;

		$entity = static::getEntity();
		$primaries = array_values($primaries);

		/** @var EntityObject[] $objects */
		$objects = [];

		foreach ($primaries as &$primary)
		{
			static::normalizePrimary($primary, $data);
			static::validatePrimary($primary);

			/** @var EntityObject $object */
			$object = $entity->wakeUpObject($primary);

			foreach ($data as $k => $v)
			{
				$object->set($k, $v);
			}

			$objects[] = $object;
		}

		$result = new UpdateResult;

		try
		{
			// before event
			if (!$ignoreEvents)
			{
				foreach ($objects as $object)
				{
					static::callOnBeforeUpdateEvent($object, $data, $result);
				}
			}

			// collect array data
			$allFields = [];
			$allUfData = [];

			foreach ($objects as $k => $object)
			{
				// actualize old-style fields array from object
				$allFields[$k] = $object->collectValues(Values::CURRENT, FieldTypeMask::SCALAR);

				// uf values
				$allUfData[$k] = $object->collectValues(Values::CURRENT, FieldTypeMask::USERTYPE);
			}

			// check data and uf
			foreach ($objects as $k => $object)
			{
				$fields = $allFields[$k];
				$ufdata = $allUfData[$k];

				// check data
				static::checkFields($result, $object->primary, $fields);

				// check uf data
				if (!empty($ufdata))
				{
					static::checkUfFields($object, $ufdata, $result);
				}

				// check if there is still some data
				if (empty($fields) && empty($ufdata))
				{
					$result->addError(new EntityError("There is no data to add."));
				}
			}

			// return if any error in any row
			if (!$result->isSuccess(true))
			{
				return $result;
			}

			//event on adding
			if (!$ignoreEvents)
			{
				foreach ($objects as $k => $object)
				{
					$fields = $allFields[$k];
					$ufdata = $allUfData[$k];

					static::callOnUpdateEvent($object, $fields, $ufdata);
				}
			}

			// prepare sql
			$allSqlData = [];

			foreach ($allFields as $k => $fields)
			{
				// use save modifiers
				$fieldsToDb = $fields;

				foreach ($fieldsToDb as $fieldName => $value)
				{
					$field = $entity->getField($fieldName);
					$fieldsToDb[$fieldName] = $field->modifyValueBeforeSave($value, $fields);
				}

				$dataReplacedColumn = static::replaceFieldName($fieldsToDb);

				$allSqlData[$k] = $dataReplacedColumn;
			}

			// check if rows data are equal
			$areEqual = true;

			$dataSample = $allSqlData[0];
			asort($dataSample);

			if (!empty($allSqlData[0]))
			{

				foreach ($allSqlData as $data)
				{
					asort($data);

					if ($data !== $dataSample)
					{
						$areEqual = false;
						break;
					}
				}

				// save data
				$connection = $entity->getConnection();
				$helper = $connection->getSqlHelper();
				$tableName = $entity->getDBTableName();

				// save data
				if ($areEqual)
				{
					// one query
					$update = $helper->prepareUpdate($tableName, $dataSample);
					$where = [];
					$isSinglePrimary = (count($entity->getPrimaryArray()) == 1);

					foreach ($allSqlData as $k => $data)
					{
						$replacedPrimary = static::replaceFieldName($objects[$k]->primary);

						if ($isSinglePrimary)
						{
							// for single primary IN is better
							$primaryName = key($replacedPrimary);
							$primaryValue = current($replacedPrimary);
							$tableField = $entity->getConnection()->getTableField($tableName, $primaryName);

							$where[] = $helper->convertToDb($primaryValue, $tableField);
						}
						else
						{
							$id = [];

							foreach ($replacedPrimary as $primaryName => $primaryValue)
							{
								$id[] = $helper->prepareAssignment($tableName, $primaryName, $primaryValue);
							}
							$where[] = implode(' AND ', $id);
						}
					}

					if ($isSinglePrimary)
					{
						$where = $helper->quote($entity->getPrimary()).' IN ('.join(', ', $where).')';
					}
					else
					{
						$where = '('.join(') OR (', $where).')';
					}

					$sql = "UPDATE ".$helper->quote($tableName)." SET ".$update[0]." WHERE ".$where;
					$connection->queryExecute($sql, $update[1]);

					$result->setAffectedRowsCount($connection);
				}
				else
				{
					// query for each row
					foreach ($allSqlData as $k => $dataReplacedColumn)
					{
						$update = $helper->prepareUpdate($tableName, $dataReplacedColumn);

						$replacedPrimary = static::replaceFieldName($objects[$k]->primary);

						$id = [];

						foreach ($replacedPrimary as $primaryName => $primaryValue)
						{
							$id[] = $helper->prepareAssignment($tableName, $primaryName, $primaryValue);
						}
						$where = implode(' AND ', $id);

						$sql = "UPDATE ".$helper->quote($tableName)." SET ".$update[0]." WHERE ".$where;
						$connection->queryExecute($sql, $update[1]);

						$result->setAffectedRowsCount($connection);
					}
				}
			}

			// doesn't make sense for multiple rows
			$result->setData($dataSample);

			if (count($allSqlData) == 1)
			{
				$result->setPrimary($objects[0]->primary);
				$result->setObject($objects[0]);
			}

			// save uf data
			foreach ($allUfData as $k => $ufdata)
			{
				if (!empty($ufdata))
				{
					$ufPrimary = $objects[$k]->primary;
					$USER_FIELD_MANAGER->update($entity->getUfId(), end($ufPrimary), $ufdata);
				}
			}

			static::cleanCache();

			// event after update
			if (!$ignoreEvents)
			{
				foreach ($objects as $k => $object)
				{
					$fields = $allFields[$k] + $allUfData[$k];

					static::callOnAfterUpdateEvent($object, $fields);
				}
			}
		}
		catch (\Exception $e)
		{
			// check result to avoid warning
			$result->isSuccess();

			throw $e;
		}

		return $result;
	}

	/**
	 * Deletes row in entity table by primary key
	 *
	 * @param mixed $primary
	 *
	 * @return DeleteResult
	 *
	 * @throws \Exception
	 */
	public static function delete($primary)
	{
		global $USER_FIELD_MANAGER;

		// check primary
		static::normalizePrimary($primary);
		static::validatePrimary($primary);

		$entity = static::getEntity();
		$result = new DeleteResult();

		$entityClass = static::getEntity()->getDataClass();
		$primaryAsString = EntityObject::sysSerializePrimary($primary, static::getEntity());

		$object = !empty(static::$currentDeletingObjects[$entityClass][$primaryAsString])
			? static::$currentDeletingObjects[$entityClass][$primaryAsString]
			: static::wakeUpObject($primary);

		try
		{
			//event before delete
			static::callOnBeforeDeleteEvent($object, $entity, $result);

			// return if any error
			if (!$result->isSuccess(true))
			{
				return $result;
			}

			//event on delete
			static::callOnDeleteEvent($object, $entity);

			// delete
			$connection = $entity->getConnection();
			$helper = $connection->getSqlHelper();

			$tableName = $entity->getDBTableName();

			$replacedPrimary = static::replaceFieldName($primary);
			$id = array();
			foreach ($replacedPrimary as $k => $v)
			{
				$id[] = $helper->prepareAssignment($tableName, $k, $v);
			}
			$where = implode(' AND ', $id);

			$sql = "DELETE FROM ".$helper->quote($tableName)." WHERE ".$where;
			$connection->queryExecute($sql);

			// delete uf data
			if ($entity->getUfId())
			{
				$USER_FIELD_MANAGER->delete($entity->getUfId(), end($primary));
			}

			static::cleanCache();

			//event after delete
			static::callOnAfterDeleteEvent($object, $entity);
		}
		catch (\Exception $e)
		{
			// check result to avoid warning
			$result->isSuccess();

			throw $e;
		}
		finally
		{
			// clean temporary objects
			if (!empty(static::$currentDeletingObjects[$entityClass][$primaryAsString]))
			{
				unset(static::$currentDeletingObjects[$entityClass][$primaryAsString]);
			}
		}

		return $result;
	}

	/**
	 * @param EntityObject $object
	 * @param              $fields
	 * @param              $result
	 */
	protected static function callOnBeforeAddEvent($object, $fields, $result)
	{
		//event before adding
		$event = new Event($object->entity, self::EVENT_ON_BEFORE_ADD, [
			'fields' => $fields,
			'object' => $object,
		]);

		$event->send();
		$event->getErrors($result);
		$event->mergeObjectFields($object);

		//event before adding (modern with namespace)
		$event = new Event($object->entity, self::EVENT_ON_BEFORE_ADD, [
			'fields' => $fields,
			'object' => $object,
		], true);

		$event->send();
		$event->getErrors($result);
		$event->mergeObjectFields($object);
	}

	/**
	 * @param $object
	 * @param $fields
	 * @param $ufdata
	 */
	protected static function callOnAddEvent($object, $fields, $ufdata)
	{
		$event = new Event($object->entity, self::EVENT_ON_ADD, [
			'fields' => $fields + $ufdata,
			'object' => clone $object,
		]);
		$event->send();

		//event on adding (modern with namespace)
		$event = new Event($object->entity, self::EVENT_ON_ADD, [
			'fields' => $fields + $ufdata,
			'object' => clone $object,
		], true);
		$event->send();
	}

	/**
	 * @param EntityObject $object
	 * @param array        $fields
	 * @param int          $id
	 */
	protected static function callOnAfterAddEvent($object, $fields, $id)
	{
		//event after adding
		$event = new Event($object->entity, self::EVENT_ON_AFTER_ADD, [
			'id' => $id,
			'fields' => $fields,
			'object' => clone $object,
		]);
		$event->send();

		//event after adding (modern with namespace)
		$event = new Event($object->entity, self::EVENT_ON_AFTER_ADD, [
			'id' => $id,
			'primary' => $object->primary,
			'fields' => $fields,
			'object' => clone $object,
		], true);
		$event->send();
	}

	/**
	 * @param EntityObject $object
	 * @param              $fields
	 * @param              $result
	 */
	protected static function callOnBeforeUpdateEvent($object, $fields, $result)
	{
		$event = new Event($object->entity, self::EVENT_ON_BEFORE_UPDATE, [
			'id' => $object->primary,
			'fields' => $fields,
			'object' => $object,
		]);

		$event->send();
		$event->getErrors($result);
		$event->mergeObjectFields($object);

		//event before update (modern with namespace)
		$event = new Event($object->entity, self::EVENT_ON_BEFORE_UPDATE, [
			'id' => $object->primary,
			'primary' => $object->primary,
			'fields' => $fields,
			'object' => $object,
		], true);

		$event->send();
		$event->getErrors($result);
		$event->mergeObjectFields($object);
	}

	/**
	 * @param EntityObject $object
	 * @param              $fields
	 * @param              $ufdata
	 */
	protected static function callOnUpdateEvent($object, $fields, $ufdata)
	{
		$event = new Event($object->entity, self::EVENT_ON_UPDATE, [
			'id' => $object->primary,
			'fields' => $fields + $ufdata,
			'object' => clone $object,
		]);
		$event->send();

		//event on update (modern with namespace)
		$event = new Event($object->entity, self::EVENT_ON_UPDATE, [
			'id' => $object->primary,
			'primary' => $object->primary,
			'fields' => $fields + $ufdata,
			'object' => clone $object,
		], true);
		$event->send();
	}

	/**
	 * @param EntityObject $object
	 * @param              $fields
	 */
	protected static function callOnAfterUpdateEvent($object, $fields)
	{
		$event = new Event($object->entity, self::EVENT_ON_AFTER_UPDATE, [
			'id' => $object->primary,
			'fields' => $fields,
			'object' => clone $object,
		]);
		$event->send();

		//event after update (modern with namespace)
		$event = new Event($object->entity, self::EVENT_ON_AFTER_UPDATE, [
			'id' => $object->primary,
			'primary' => $object->primary,
			'fields' => $fields,
			'object' => clone $object,
		], true);
		$event->send();
	}

	/**
	 * @param $object
	 * @param $entity
	 * @param $result
	 */
	protected static function callOnBeforeDeleteEvent($object, $entity, $result)
	{
		$event = new Event($entity, self::EVENT_ON_BEFORE_DELETE, array("id" => $object->primary));
		$event->send();
		$event->getErrors($result);

		//event before delete (modern with namespace)
		$event = new Event($entity, self::EVENT_ON_BEFORE_DELETE, array("id" => $object->primary, "primary" => $object->primary, "object" => clone $object), true);
		$event->send();
		$event->getErrors($result);
	}

	/**
	 * @param $object
	 * @param $entity
	 */
	protected static function callOnDeleteEvent($object, $entity)
	{
		$event = new Event($entity, self::EVENT_ON_DELETE, array("id" => $object->primary));
		$event->send();

		//event on delete (modern with namespace)
		$event = new Event($entity, self::EVENT_ON_DELETE, array("id" => $object->primary, "primary" => $object->primary, "object" => clone $object), true);
		$event->send();
	}

	/**
	 * @param $object
	 * @param $entity
	 */
	protected static function callOnAfterDeleteEvent($object, $entity)
	{
		$event = new Event($entity, self::EVENT_ON_AFTER_DELETE, array("id" => $object->primary));
		$event->send();

		//event after delete (modern with namespace)
		$event = new Event($entity, self::EVENT_ON_AFTER_DELETE, array("id" => $object->primary, "primary" => $object->primary, "object" => clone $object), true);
		$event->send();
	}

	/**
	 * Sets a flag indicating crypto support for a field.
	 *
	 * @param string $field
	 * @param string $table
	 * @param bool   $mode
	 */
	public static function enableCrypto($field, $table = null, $mode = true)
	{
		if($table === null)
		{
			$table = static::getTableName();
		}
		$options = array();
		$optionString = Main\Config\Option::get("main", "~crypto_".$table);
		if($optionString <> '')
		{
			$options = unserialize($optionString, ['allowed_classes' => false]);
		}
		$options[strtoupper($field)] = $mode;
		Main\Config\Option::set("main", "~crypto_".$table, serialize($options));
	}

	/**
	 * Returns true if crypto is enabled for a field.
	 *
	 * @param string $field
	 * @param string $table
	 *
	 * @return bool
	 */
	public static function cryptoEnabled($field, $table = null)
	{
		if($table === null)
		{
			$table = static::getTableName();
		}
		$optionString = Main\Config\Option::get("main", "~crypto_".$table);
		if($optionString <> '')
		{
			$field = strtoupper($field);
			$options = unserialize($optionString, ['allowed_classes' => false]);
			if(isset($options[$field]) && $options[$field] === true)
			{
				return true;
			}
		}
		return false;
	}

	/**
	 * @param EntityObject $object
	 */
	public static function setCurrentDeletingObject($object): void
	{
		$entityClass = static::getEntity()->getDataClass();
		self::$currentDeletingObjects[$entityClass][$object->primaryAsString] = $object;
	}

	/**
	 * Cleans the tablet cache after data modifications.
	 *
	 * @return void
	 */
	public static function cleanCache(): void
	{
		if (static::isCacheable())
		{
			$entity = static::getEntity();
			$entity->cleanCache();
		}
	}

	/**
	 * You can disable cache for the tablet completely.
	 *
	 * @return bool
	 */
	public static function isCacheable(): bool
	{
		return true;
	}

	/*
	An inheritor class can define the event handlers for own events.
	Why? To prevent from rewriting the add/update/delete functions.
	These handlers are triggered in the Bitrix\Main\ORM\Event::send() function
	*/
	public static function onBeforeAdd(Event $event){}
	public static function onAdd(Event $event){}
	public static function onAfterAdd(Event $event){}
	public static function onBeforeUpdate(Event $event){}
	public static function onUpdate(Event $event){}
	public static function onAfterUpdate(Event $event){}
	public static function onBeforeDelete(Event $event){}
	public static function onDelete(Event $event){}
	public static function onAfterDelete(Event $event){}
}

Youez - 2016 - github.com/yon3zu
LinuXploit