setFromArrayOrDefault('timezone', $config);
$this->setFromArrayOrDefault('start', $config, 1, 'int');
$this->setFromArrayOrDefault('step', $config, 1, 'int');
$this->setFromArrayOrDefault('length', $config, 0, 'int');
$this->setFromArrayOrDefault('padString', $config, '0', 'string');
$this->setFromArrayOrDefault('isDirectNumeration', $config, false, 'bool');
$this->setFromArrayOrDefault('periodicBy', $config);
$this->setFromArrayOrDefault('nowTime', $config, time());
if (isset($config['numeratorId']))
{
$this->lastInvocationTime = $config['lastInvocationTime'] ?? null;
$this->numeratorId = $config['numeratorId'];
if ($this->isDirectNumeration)
{
$this->numberHash = $this->numeratorId;
}
}
else
{
$this->nextNumber = $this->start;
}
}
/** @inheritdoc */
public static function getSettingsFields()
{
$timezonesSettings = static::getTimezoneSettings();
foreach ($timezonesSettings as $index => $timezonesSetting)
{
$timezonesSettings[$index]['settingName'] = $timezonesSetting['name'];
unset($timezonesSettings[$index]['name']);
}
return [
[
'settingName' => 'length', 'type' => 'int', 'default' => 0,
'title' => Loc::getMessage('TITLE_BITRIX_MAIN_NUMERATOR_GENERATOR_SEQUENTNUMBERGENERATOR_LENGTH'),
],
[
'settingName' => 'padString', 'type' => 'string', 'default' => '0',
'title' => Loc::getMessage('TITLE_BITRIX_MAIN_NUMERATOR_GENERATOR_SEQUENTNUMBERGENERATOR_PAD_STRING'),
],
[
'settingName' => 'start', 'type' => 'int', 'default' => 1,
'title' => Loc::getMessage('TITLE_BITRIX_MAIN_NUMERATOR_GENERATOR_SEQUENTNUMBERGENERATOR_START'),
],
[
'settingName' => 'step', 'type' => 'int', 'default' => 1,
'title' => Loc::getMessage('TITLE_BITRIX_MAIN_NUMERATOR_GENERATOR_SEQUENTNUMBERGENERATOR_STEP'),
],
[
'settingName' => 'periodicBy', 'type' => 'array',
'title' => Loc::getMessage('TITLE_BITRIX_MAIN_NUMERATOR_GENERATOR_SEQUENTNUMBERGENERATOR_PERIODICBY'),
'values' => [
[
'settingName' => 'default', 'value' => '',
'title' => Loc::getMessage('TITLE_BITRIX_MAIN_NUMERATOR_GENERATOR_SEQUENTNUMBERGENERATOR_PERIODICBY_DEFAULT'),
],
[
'settingName' => self::DAY, 'value' => self::DAY,
'title' => Loc::getMessage('TITLE_BITRIX_MAIN_NUMERATOR_GENERATOR_SEQUENTNUMBERGENERATOR_PERIODICBY_DAY'),
],
[
'settingName' => self::MONTH, 'value' => self::MONTH,
'title' => Loc::getMessage('TITLE_BITRIX_MAIN_NUMERATOR_GENERATOR_SEQUENTNUMBERGENERATOR_PERIODICBY_MONTH'),
],
[
'settingName' => self::YEAR, 'value' => self::YEAR,
'title' => Loc::getMessage('TITLE_BITRIX_MAIN_NUMERATOR_GENERATOR_SEQUENTNUMBERGENERATOR_PERIODICBY_YEAR'),
],
],
],
[
'settingName' => 'timezone', 'type' => 'array', 'values' => $timezonesSettings,
'title' => Loc::getMessage('TITLE_BITRIX_MAIN_NUMERATOR_GENERATOR_SEQUENTNUMBERGENERATOR_TIMEZONE'),
],
[
'settingName' => 'isDirectNumeration', 'type' => 'boolean',
'title' => Loc::getMessage('TITLE_BITRIX_MAIN_NUMERATOR_GENERATOR_SEQUENTNUMBERGENERATOR_ISDIRECTNUMERATION'),
],
];
}
/** @inheritdoc */
public static function getTemplateWordsSettings()
{
return [
static::getPatternFor(static::TEMPLATE_WORD_NUMBER) =>
Loc::getMessage('BITRIX_MAIN_NUMERATOR_GENERATOR_SEQUENTNUMBERGENERATOR_WORD_NUMBER'),
];
}
/** @inheritdoc */
public function getConfig()
{
return [
'start' => $this->start,
'step' => $this->step,
'length' => $this->length,
'padString' => $this->padString,
'periodicBy' => $this->periodicBy,
'timezone' => $this->timezone,
'isDirectNumeration' => (bool)$this->isDirectNumeration,
];
}
/**
* @param null $numeratorId
* @param bool $createIfEmpty
* @return array
* @throws \Bitrix\Main\ArgumentException
* @throws \Bitrix\Main\DB\SqlQueryException
* @throws \Bitrix\Main\ObjectPropertyException
* @throws \Bitrix\Main\SystemException
*/
protected function getSettings($numeratorId = null, $createIfEmpty = true)
{
if ($numeratorId === null)
{
$numeratorId = $this->numeratorId;
}
$nextNumberSettings = NumeratorSequenceTable::getSettings($numeratorId, $this->getNumberHash());
if (!$nextNumberSettings && $createIfEmpty)
{
$nextNumberSettings = NumeratorSequenceTable::setSettings($numeratorId, $this->getNumberHash(), $this->start, $this->nowTime);
}
return $nextNumberSettings;
}
/**
* @return mixed
*/
private function getNumberHash()
{
if ($this->numberHash === null)
{
$this->setNumberHash($this->numeratorId);
}
return $this->numberHash;
}
/** @inheritdoc */
public function parseTemplate($template)
{
for ($tryouts = 0; $tryouts < 50; $tryouts++)
{
$this->nextNumber = null;
$this->currentNumber = null;
$nextNumberSettings = $this->getSettings();
if (!$nextNumberSettings)
{
continue;
}
$this->lastInvocationTime = $nextNumberSettings['LAST_INVOCATION_TIME'];
$this->calculateNextAndCurrentNumber($nextNumberSettings['NEXT_NUMBER']);
$this->lastInvocationTime = $this->nowTime;
$affectedRows = $this->saveNumeratorSequenceSettings(
$this->numeratorId,
$this->getNumberHash(),
[
'NEXT_NUMBER' => $this->nextNumber,
'LAST_INVOCATION_TIME' => $this->lastInvocationTime,
],
$nextNumberSettings['NEXT_NUMBER']
);
if ($affectedRows == 1)
{
break;
}
}
return $this->replaceNumberInPattern($template);
}
protected function saveNumeratorSequenceSettings($numeratorId, $numberHash, $fields, $whereNextNumber = null)
{
return NumeratorSequenceTable::updateSettings($numeratorId, $numberHash, $fields, $whereNextNumber);
}
/** @inheritdoc */
public static function getTemplateWordsForParse()
{
return [static::getPatternFor(static::TEMPLATE_WORD_NUMBER)];
}
/** @inheritdoc */
public function parseTemplateForPreview($template)
{
$nextNumberSettings = $this->getSettings($this->numeratorId, false);
$this->lastInvocationTime = $nextNumberSettings['LAST_INVOCATION_TIME'] ?? $this->nowTime;
$this->calculateNextAndCurrentNumber($nextNumberSettings['NEXT_NUMBER'] ?? $this->start);
return $this->replaceNumberInPattern($template);
}
private function replaceNumberInPattern($template)
{
$resultNumber = $this->currentNumber;
if ($this->length > 0)
{
$resultNumber = \Bitrix\Main\Text\UtfSafeString::pad($resultNumber, $this->length, $this->padString, STR_PAD_LEFT);
}
return str_replace(static::getPatternFor(static::TEMPLATE_WORD_NUMBER), $resultNumber, $template);
}
/** @inheritdoc */
public function getNextNumber($numeratorId)
{
if (!$numeratorId)
{
return null;
}
$this->numeratorId = $numeratorId;
$nextNumberSettings = $this->getSettings($numeratorId, false);
if ($nextNumberSettings)
{
return $nextNumberSettings['NEXT_NUMBER'];
}
else
{
return $this->start;
}
}
/**
* @return string
*/
public static function getAvailableForType()
{
return 'DEFAULT';
}
/*** @inheritdoc */
public function setNextNumber($numeratorId, $newNumber, $whereNumber)
{
$this->nextNumber = $newNumber;
$sequence = $this->getSettings($numeratorId, false);
if (!$sequence)
{
return (new Result())->addError(new Error(Loc::getMessage('NUMERATOR_UPDATE_SEQUENT_IS_NOT_SET_YET')));
}
$affectedRows = $this->saveNumeratorSequenceSettings(
$numeratorId,
$this->getNumberHash(),
[
'NEXT_NUMBER' => $this->nextNumber,
],
$whereNumber
);
if ($affectedRows == 1)
{
return new Result();
}
return (new Result())->addError(new Error(Loc::getMessage('NUMERATOR_SEQUENT_DEFAULT_INTERNAL_ERROR')));
}
/**
* set current number to its start position if generator is periodic and period has been just changed
*/
private function resetCurrentNumberIfNeeded()
{
if ($this->periodicBy)
{
if ($this->periodicBy == static::YEAR && $this->isHasChanged(static::YEAR))
{
$this->currentNumber = $this->start;
}
if ($this->periodicBy == static::MONTH)
{
if ($this->isHasChanged(static::MONTH) || $this->isSameMonthButDifferentYear())
{
$this->currentNumber = $this->start;
}
}
if ($this->periodicBy == static::DAY)
{
if ($this->isHasChanged(static::DAY)
|| $this->isSameDayButDifferent(static::MONTH)
|| $this->isSameDayButDifferent(static::YEAR))
{
$this->currentNumber = $this->start;
}
}
}
}
/**
* @return bool
*/
private function isSameMonthButDifferentYear()
{
return $this->getLastInvocationUserTime()->format('m') === $this->getNowUserTime()->format('m') && $this->isHasChanged(static::YEAR);
}
/**
* @param $interval
* @return bool
*/
private function isSameDayButDifferent($interval)
{
$isSameDay = $this->getLastInvocationUserTime()->format('d') === $this->getNowUserTime()->format('d');
if ($interval == static::MONTH)
{
return $isSameDay && $this->isHasChanged(static::MONTH);
}
if ($interval == static::YEAR)
{
return $isSameDay && $this->isHasChanged(static::YEAR);
}
return false;
}
/**
* @param $interval
* @return bool
*/
private function isHasChanged($interval)
{
if ($interval == static::MONTH)
{
return $this->getLastInvocationUserTime()->format('m') !== $this->getNowUserTime()->format('m');
}
if ($interval == static::DAY)
{
return $this->getLastInvocationUserTime()->format('d') !== $this->getNowUserTime()->format('d');
}
if ($interval == static::YEAR)
{
return $this->getLastInvocationUserTime()->format('Y') !== $this->getNowUserTime()->format('Y');
}
return false;
}
/**
* @return array
*/
private static function getTimezoneSettings()
{
$timezones = \CTimeZone::GetZones();
$settings = [];
foreach ($timezones as $timezoneValue => $timezoneName)
{
$settings[] = ['name' => $timezoneName, 'value' => $timezoneValue,];
}
return $settings;
}
private function getNowUserTime()
{
return $this->createDateTimeInCurrentTimezone($this->nowTime);
}
private function getLastInvocationUserTime()
{
return $this->createDateTimeInCurrentTimezone($this->lastInvocationTime);
}
private function createDateTimeInCurrentTimezone($timestamp)
{
$dateTime = \DateTime::createFromFormat('U', $timestamp);
if ($this->timezone)
{
$result = $dateTime->setTimezone(new \DateTimeZone($this->timezone));
if ($result === false)
{
$dateTime = \DateTime::createFromFormat('U', $timestamp);
}
}
return $dateTime;
}
/** @inheritdoc */
public function validateConfig($config)
{
$result = new Result();
return $result;
}
/** @inheritdoc */
public function setNumberHash($numberHash)
{
if (!is_string($numberHash) && !is_int($numberHash))
{
return;
}
if ($this->numberHash === null)
{
$this->numberHash = (string)$numberHash;
}
}
private function calculateNextAndCurrentNumber($initNumber)
{
$this->currentNumber = $initNumber;
$this->resetCurrentNumberIfNeeded();
$this->nextNumber = $this->currentNumber + $this->step;
}
}