Current Path : /var/www/www-root/data/www/monolith-realty.ru/bitrix/modules/main/lib/numerator/ |
Current File : /var/www/www-root/data/www/monolith-realty.ru/bitrix/modules/main/lib/numerator/numerator.php |
<? namespace Bitrix\Main\Numerator; use Bitrix\Main\Entity\ExpressionField; use Bitrix\Main\Entity\Query; use Bitrix\Main\Error; use Bitrix\Main\Event; use Bitrix\Main\Localization\Loc; use Bitrix\Main\Numerator\Generator\Contract\DynamicConfigurable; use Bitrix\Main\Numerator\Generator\Contract\Sequenceable; use Bitrix\Main\Numerator\Generator\Contract\UserConfigurable; use Bitrix\Main\Numerator\Generator\NumberGenerator; use Bitrix\Main\Numerator\Model\NumeratorSequenceTable; use Bitrix\Main\Numerator\Model\NumeratorTable; use Bitrix\Main\Result; Loc::loadMessages(__FILE__); /** * Class Numerator - generates numbers based on config, * is used for creating random, sequential numbers, also may contain prefix, date values etc. * @package Bitrix\Main\Numerator */ class Numerator { private $template; private $type; private $name; /** @var NumberGenerator[] */ private $generators = []; private $code; private $id; const NUMERATOR_DEFAULT_TYPE = 'DEFAULT'; const NUMERATOR_ALL_GENERATORS_TYPE = 'ALL'; /** * @var NumberGeneratorFactory */ static protected $numberGeneratorFactory; /** return empty numerator object with no configuration * @return static */ public static function create() { return new static(); } private function __construct() { } /** * @return NumberGeneratorFactory */ protected static function getNumberGeneratorFactory() { if (static::$numberGeneratorFactory === null) { static::$numberGeneratorFactory = new NumberGeneratorFactory(); } return static::$numberGeneratorFactory; } /** * @param $numeratorType * @return mixed * @throws \Bitrix\Main\ArgumentException * @throws \Bitrix\Main\NotImplementedException * @throws \Bitrix\Main\ObjectPropertyException * @throws \Bitrix\Main\SystemException */ public static function getSettingsFields($numeratorType) { $numeratorsAmount = static::getNextNumeratorNumber($numeratorType); $settings = ['settingsFields' => [], 'settingsWords' => [],]; $settings['settingsFields'][static::getType()] = [ [ 'title' => Loc::getMessage('TITLE_BITRIX_MAIN_NUMERATOR_NUMERATOR_NAME_TITLE'), 'settingName' => 'name', 'type' => 'string', 'default' => Loc::getMessage('NUMERATOR_DEFAULT_NUMERATOR_NAME', ['#NUMBER#' => $numeratorsAmount]), ], [ 'settingName' => 'template', 'type' => 'string', 'title' => Loc::getMessage('TITLE_BITRIX_MAIN_NUMERATOR_NUMERATOR_TEMPLATE_TITLE'), ], ]; $allGeneratorsClasses = static::getNumberGeneratorFactory()->getClasses(); foreach ($allGeneratorsClasses as $class) { /** @var $class NumberGenerator|UserConfigurable */ $isAvailableForAll = $class::getAvailableForType() == static::NUMERATOR_DEFAULT_TYPE; if ($isAvailableForAll || $class::getAvailableForType() == $numeratorType) { if (in_array(UserConfigurable::class, class_implements($class))) { $settings['settingsFields'][$class::getType()] = $class::getSettingsFields(); } $settings['settingsWords'][$class::getType()] = $class::getTemplateWordsSettings(); } } $settings['settingsWords'] = array_merge_recursive($settings['settingsWords'], static::getUserDefinedTemplateWords($numeratorType)); return $settings; } /** For compatibility - users can defined their own type of template generation * @param $numeratorType * @return array */ protected static function getUserDefinedTemplateWords($numeratorType) { $settingsWords = []; $event = new Event('main', 'onBuildNumeratorTemplateWordsList', ['numeratorType' => $numeratorType]); $event->send(); if ($event->getResults()) { $count = 0; foreach ($event->getResults() as $eventResult) { $eventParameters = $eventResult->getParameters(); if (isset($eventParameters['CODE']) && isset($eventParameters['NAME'])) { $settingsWords["UserDefinedVirtualGenerator" . $count++] = [ NumberGenerator::USER_DEFINED_SYMBOL_START . $eventParameters['CODE'] . NumberGenerator::USER_DEFINED_SYMBOL_END => $eventParameters['NAME'], ]; } else { foreach ($eventParameters as $parameters) { if (isset($parameters['CODE']) && isset($parameters['NAME'])) { $settingsWords["UserDefinedVirtualGenerator" . $count++] = [ NumberGenerator::USER_DEFINED_SYMBOL_START . $parameters['CODE'] . NumberGenerator::USER_DEFINED_SYMBOL_END => $parameters['NAME'], ]; } } } } } return $settingsWords; } /** * @param string $type * @param null $sort * @return array * @throws \Bitrix\Main\ArgumentException * @throws \Bitrix\Main\ObjectPropertyException * @throws \Bitrix\Main\SystemException */ public static function getListByType($type = null, $sort = null) { if (is_null($type)) { $type = static::NUMERATOR_DEFAULT_TYPE; } return NumeratorTable::getNumeratorList($type, $sort); } /** Returns numerator related fields from db by its type * (use it in case of only single one exists for the type) * @param string $type * @return array|null * @throws \Bitrix\Main\ArgumentException * @throws \Bitrix\Main\ObjectPropertyException * @throws \Bitrix\Main\SystemException */ public static function getOneByType($type = null) { if (is_null($type)) { $type = static::NUMERATOR_DEFAULT_TYPE; } $numeratorSettings = static::getListByType($type); if ($numeratorSettings && isset($numeratorSettings[0])) { return $numeratorSettings[0]; } return null; } /** returns all template words for creating new numerator depending on its type * @param string $isAvailableByType * @return array * @throws \Bitrix\Main\NotImplementedException */ public static function getTemplateWordsForType($isAvailableByType = null) { if (is_null($isAvailableByType)) { $isAvailableByType = static::NUMERATOR_DEFAULT_TYPE; } $settings = []; $allGeneratorsClasses = static::getNumberGeneratorFactory()->getClasses(); foreach ($allGeneratorsClasses as $class) { /** @var $class NumberGenerator */ $isAllTypesNeeded = $isAvailableByType === static::NUMERATOR_ALL_GENERATORS_TYPE; $isAvailableByDefault = $class::getAvailableForType() == static::NUMERATOR_DEFAULT_TYPE; if ($isAllTypesNeeded || $isAvailableByDefault || $class::getAvailableForType() == $isAvailableByType) { $settings = array_merge($settings, [$class::getType() => $class::getTemplateWordsForParse()]); } } return $settings; } /** * @param $hash */ private function setNumberHashForGenerators($hash) { foreach ($this->generators as $index => $generator) { if ($generator instanceof Sequenceable) { $generator->setNumberHash($hash); } } } /** return next number. If numerator has {NUMBER} in template, * Sequential counter value in database will be updated * If you need next number for preview only, use previewNextNumber * @param string|int $hash - you can reuse one numerator in various cases (for various companies etc.) * by passing different hashes to it. For Sequential number it means using independent counters for every hash * Hash will be ignored here, if it was already set in Load method or via setHash * @return string * @see Numerator::setHash() * @see Numerator::previewNextNumber() */ public function getNext($hash = null) { $this->setNumberHashForGenerators($hash); $nextNumber = $this->template; foreach ($this->generators as $index => $generator) { /** @var $generator NumberGenerator */ $nextNumber = $generator->parseTemplate($nextNumber); } return $nextNumber; } /** * @param $dynamicConfig */ private function setDynamicConfigForGenerators($dynamicConfig) { if ($dynamicConfig !== null) { foreach ($this->generators as $generator) { if ($generator instanceof DynamicConfigurable) { $generator->setDynamicConfig($dynamicConfig); } } } } /** * @param $numId * @param $config - same configuration structure as using via setConfig method * @return \Bitrix\Main\Entity\AddResult|\Bitrix\Main\Entity\UpdateResult|Result * @throws \Bitrix\Main\ArgumentException * @throws \Bitrix\Main\NotImplementedException * @throws \Bitrix\Main\ObjectException * @throws \Bitrix\Main\ObjectPropertyException * @throws \Bitrix\Main\SystemException */ public static function update($numId, $config) { $numerator = static::create(); $config[static::getType()]['idFromDb'] = $numId; $result = $numerator->setNumeratorConfig($config); if ($result->isSuccess()) { $result = $numerator->setGeneratorsConfig($config); if ($result->isSuccess()) { return $numerator->save(); } } return $result; } /** * @param $hashable - object that returns hash string * Used for Numerators containing Sequential number * Hash can be set once, will be ignored here, if it was already set * Typically hash is a string like COMPANY_64, or USER_42 */ public function setHash($hashable) { if ($hashable instanceof Hashable) { $this->setNumberHashForGenerators($hashable->getHash()); } } /** * @param $dynamicConfig - anything (array|object|..) that will be used by DynamicConfigurable generators * of Numerator for parsing template and creating number */ public function setDynamicConfig($dynamicConfig) { $this->setDynamicConfigForGenerators($dynamicConfig); } /** * @return \Bitrix\Main\Entity\AddResult|\Bitrix\Main\Entity\UpdateResult * @throws \Bitrix\Main\ArgumentException * @throws \Bitrix\Main\ObjectException * @throws \Bitrix\Main\ObjectPropertyException * @throws \Bitrix\Main\SystemException */ public function save() { $settingsToStore = $this->getSettings(); $result = NumeratorTable::saveNumerator($this->id, [ 'CODE' => $this->code, 'NAME' => $this->name, 'TEMPLATE' => $this->template, 'TYPE' => $this->type ? $this->type : static::NUMERATOR_DEFAULT_TYPE, 'SETTINGS' => $settingsToStore, ]); if ($result->isSuccess()) { $this->id = $result->getId(); } return $result; } /** * @return array */ private function getSettings() { $settingsToStore = []; foreach ($this->generators as $numberGenerator) { if ($numberGenerator instanceof UserConfigurable) { /** @var UserConfigurable $numberGenerator */ $settingsToStore = array_merge($settingsToStore, [$this->getTypeOfGenerator($numberGenerator) => $numberGenerator->getConfig(),]); } } return $settingsToStore; } /** Load numerator by id * @param $numeratorId * @param $source - optional, numerator dynamicConfig for generating next number, * also can be Hashable ancestor for set up hash for numerator * @return null|static * @throws \Bitrix\Main\ArgumentException * @throws \Bitrix\Main\ObjectPropertyException * @throws \Bitrix\Main\SystemException * @see Numerator::getNext() */ public static function load($numeratorId, $source = null) { if ($config = NumeratorTable::loadSettings($numeratorId)) { $numerator = new static(); $result = $numerator->setConfig($config); if (($result->isSuccess())) { $numerator->setDynamicConfig($source); $numerator->setHash($source); return $numerator; } } return null; } /** * @param $code * @param $source * @return static|null */ public static function loadByCode($code, $source = null) { $id = NumeratorTable::getIdByCode($code); if ($id === null) { return null; } return self::load($id, $source); } /** * @param $id * @return $this|\Bitrix\Main\Entity\DeleteResult|Result * @throws \Exception */ public static function delete($id) { if (!$id) { return (new Result())->addError(new Error('Numerator id is required')); } $result = NumeratorTable::delete((int)$id); if ($result->isSuccess()) { NumeratorSequenceTable::deleteByNumeratorId($id); } return $result; } /** return next number, without updating database value (for numerator with sequential number) * @param null $hash * @return string */ public function previewNextNumber($hash = null) { $this->setNumberHashForGenerators($hash); $nextNumber = $this->template; foreach ($this->generators as $index => $generator) { /** @var $generator NumberGenerator */ $nextNumber = $generator->parseTemplateForPreview($nextNumber); } return $nextNumber; } /** * returns next sequential number, if numerator has sequence, * null if it hasn't * not increases the sequent number * @param string $hash * @return int|null */ public function previewNextSequentialNumber($hash = null) { $this->setNumberHashForGenerators($hash); foreach ($this->generators as $generator) { if ($generator instanceof Sequenceable) { return $generator->getNextNumber($this->id); } } return null; } /** check if numerator has {NUMBER} in template * @return bool */ public function hasSequentialNumber() { foreach ($this->generators as $generator) { if ($generator instanceof Sequenceable) { return true; } } return false; } /** * The only way to affect the NEXT number * - function forces the numerator to start counting with a given number * @param int $nextNumber * @param $whereNumber - old value of next number * @param string $hash * @return Result */ public function setNextSequentialNumber($nextNumber, $whereNumber = null, $hash = null) { $this->setNumberHashForGenerators($hash); foreach ($this->generators as $generator) { if ($generator instanceof Sequenceable) { return $generator->setNextNumber($this->id, $nextNumber, $whereNumber); } } return (new Result())->addError(new Error(Loc::getMessage('NUMERATOR_SET_SEQUENTIAL_IS_IMPOSSIBLE'))); } /** return numerator's configuration with filled in values for every setting * @return array */ public function getConfig() { $selfConfig = [ static::getType() => [ 'name' => $this->name, 'template' => $this->template, 'id' => $this->id, 'code' => $this->code, 'type' => $this->type, ], ]; $generatorConfigs = []; foreach ($this->generators as $generator) { if ($generator instanceof UserConfigurable) { $generatorConfigs[$this->getTypeOfGenerator($generator)] = $generator->getConfig(); } } return $selfConfig + $generatorConfigs; } /** sets configuration for numerator and validates settings * @param $config * @return Result - message that can be shown to an end user * @throws \Bitrix\Main\NotImplementedException */ public function setConfig($config) { $result = $this->setNumeratorConfig($config); if (!$result->isSuccess()) { return $result; }; return $this->setGeneratorsConfig($config); } /** * @param $config * @return Result */ private function setNumeratorConfig($config) { $result = $this->validate($config); if (!$result->isSuccess()) { return $result; }; $this->type = trim($config[static::getType()]['type']); $this->setTemplate($config[static::getType()]['template']); $this->name = trim($config[static::getType()]['name']); if (isset($config[static::getType()]['idFromDb'])) { $this->id = $config[static::getType()]['idFromDb']; } if (array_key_exists('code', $config[static::getType()])) { $code = $config[static::getType()]['code']; if (is_string($code)) { $code = trim($code); } $this->code = (is_string($code) && !empty($code)) ? $code : null; } return $result; } private function createGenerators() { $generatorTypesToCreate = $this->getGeneratorTypesByTemplate(); if ($this->type === static::NUMERATOR_ALL_GENERATORS_TYPE) { return $this->createGeneratorsOfTypes($generatorTypesToCreate); } $factory = static::getNumberGeneratorFactory(); $typesForCurrentNumerator = []; foreach ($generatorTypesToCreate as $index => $generatorType) { $generatorClass = $factory->getClassByType($generatorType); if ($generatorClass::getAvailableForType() === $this->type || $generatorClass::getAvailableForType() === static::NUMERATOR_DEFAULT_TYPE ) { $typesForCurrentNumerator[] = $generatorType; } } return $this->createGeneratorsOfTypes($typesForCurrentNumerator); } /** * @param $config * @return Result * @throws \Bitrix\Main\NotImplementedException */ private function setGeneratorsConfig($config) { $generators = $this->createGenerators(); foreach ($generators as $index => $generator) { $this->addGenerator($generator); } $result = $this->validateGeneratorsConfig($config); if ($result->isSuccess()) { foreach ($this->generators as $generator) { if ($generator instanceof UserConfigurable) { /** @var UserConfigurable $generator */ $generator->setConfig($config[$this->getTypeOfGenerator($generator)] ?? null); } } }; return $result; } /** type string used as key in configuration arrays * @return string */ public static function getType() { return str_replace('\\', '_', static::class); } /** * @param $template */ protected function setTemplate($template) { $this->template = str_replace(["\r\n", "\n"], '', trim($template)); } /** * @return array * @throws \Bitrix\Main\NotImplementedException */ private function getTypeToTemplateWords() { $result = []; $typesToClasses = static::getNumberGeneratorFactory()->getTypeToClassMap(); foreach ($typesToClasses as $type => $class) { /** @var NumberGenerator $class */ $result[$type] = $class::getTemplateWordsForParse(); } return $result; } /** * @return array * @throws \Bitrix\Main\NotImplementedException */ private function getGeneratorTypesByTemplate() { $generatorTypes = []; foreach ($this->getTypeToTemplateWords() as $type => $words) { foreach ($words as $word) { if (mb_stripos($this->template, $word) !== false) { $generatorTypes[$type] = 1; } } } return array_keys($generatorTypes); } /** * @param $generatorTypesToCreate * @return array */ private function createGeneratorsOfTypes($generatorTypesToCreate) { $generators = []; foreach ($generatorTypesToCreate as $key => $type) { if ($generator = static::getNumberGeneratorFactory()->createGeneratorByType($type)) { $generators[] = $generator; } } return $generators; } /** * @param $numeratorConfig * @return Result */ private function validate($numeratorConfig) { $result = new Result(); if (!isset($numeratorConfig[static::getType()])) { $result->addError(new Error('Numerator config is required')); } $numeratorBaseConfig = $numeratorConfig[static::getType()]; if (isset($numeratorBaseConfig['code'])) { if (is_string($numeratorBaseConfig['code']) && !empty($numeratorBaseConfig['code'])) { $idWithSameCode = NumeratorTable::getIdByCode($numeratorBaseConfig['code']); if ($idWithSameCode !== null) { $id = (int)($numeratorBaseConfig['idFromDb'] ?? null); if ($id <= 0 || $idWithSameCode !== $id) { $result->addError(new Error('Another numerator with same code already exists')); } } } elseif (is_string($numeratorBaseConfig['code'])) { $result->addError(new Error('Numerator code should be a non-empty string, if it is provided')); } else { $result->addError(new Error('Numerator code should be a string')); } } if (!(isset($numeratorBaseConfig['name']) && $numeratorBaseConfig['name'])) { $result->addError(new Error(Loc::getMessage('NUMERATOR_VALIDATE_NAME_IS_REQUIRED'))); } $resultTemplate = $this->validateTemplate($numeratorBaseConfig['template']); if ($resultTemplate->getErrors()) { $result->addErrors($resultTemplate->getErrors()); } return $result; } /** * @param string $template * @return Result */ private function validateTemplate($template) { if (!($template && $template != '')) { return (new Result())->addError(new Error(Loc::getMessage('NUMERATOR_VALIDATE_TEMPLATE_IS_REQUIRED'))); } return new Result(); } /** * @param $isAvailableForType * @return int * @throws \Bitrix\Main\ArgumentException * @throws \Bitrix\Main\ObjectPropertyException * @throws \Bitrix\Main\SystemException */ private static function getNextNumeratorNumber($isAvailableForType) { $query = new Query(NumeratorTable::getEntity()); $query->addSelect(new ExpressionField('COUNT', 'COUNT(*)')); $query->where('TYPE', $isAvailableForType); $result = $query->exec()->fetch(); return $result ? $result['COUNT'] + 1 : 1; } /** * @param $config * @return Result */ private function validateGeneratorsConfig($config) { $result = new Result(); foreach ($this->generators as $generator) { if ($generator instanceof UserConfigurable) { $generatorResult = ($generator->validateConfig($config)); if ($generatorResult->getErrors()) { $result->addErrors($generatorResult->getErrors()); } } } return $result; } /** * @param $generator */ private function addGenerator($generator) { $this->generators[] = $generator; } /** * @param $generator * @return string */ private function getTypeOfGenerator($generator) { /** @var NumberGenerator $generatorClass */ $generatorClass = get_class($generator); return $generatorClass::getType(); } public function getId() { return $this->id; } }