This commit is contained in:
root
2025-11-13 19:52:28 +03:00
parent 8aeeb05b7d
commit 807dec3b6c
4646 changed files with 163445 additions and 626017 deletions

View File

@@ -39,7 +39,6 @@ final class TempFile extends EO_TempFile
$chunk->setFile($newFile);
}
$tempFile = null;
if ($chunk->isOnlyOne())
{
// Cloud and local files are processed by CFile::SaveFile.

View File

@@ -29,7 +29,7 @@ class TempFileAgent
$agentName = '\\' . __METHOD__ . '();';
$agents = \CAgent::getList(['ID' => 'DESC'], [
'MODULE_ID' => 'ui',
'NAME' => $agentName,
'=NAME' => $agentName,
]);
if ($agent = $agents->fetch())

View File

@@ -9,6 +9,7 @@ use Bitrix\Main\ORM\Fields;
use Bitrix\Main\ORM\Fields\Relations\Reference;
use Bitrix\Main\ORM\Query\Join;
use Bitrix\Main\Type\DateTime;
use Bitrix\Main\UuidGenerator;
/**
* Class TempFileTable
@@ -50,13 +51,7 @@ class TempFileTable extends Data\DataManager
->configureUnique(true)
->configureNullable(false)
->configureDefaultValue(static function () {
return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
mt_rand(0, 0xffff), mt_rand(0, 0xffff),
mt_rand(0, 0xffff),
mt_rand(0, 0x0fff) | 0x4000,
mt_rand(0, 0x3fff) | 0x8000,
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
);
return UuidGenerator::generateV4();
})
->configureSize(36)
,

View File

@@ -0,0 +1,23 @@
<?php
namespace Bitrix\UI\Infrastructure\Agent;
class AgentBase
{
public static function run()
{
return static::doRun() ? get_called_class() . '::run();' : '';
}
public static function doRun(): bool
{
return false;
}
protected function setExecutionPeriod(int $period): void
{
global $pPERIOD;
$pPERIOD = $period; // some magic to run the agent next time in $periodInSeconds seconds
}
}

View File

@@ -0,0 +1,127 @@
<?php
namespace Bitrix\UI\Infrastructure\Agent\EntityEditorConfig;
use Bitrix\Main\ORM\Fields\Relations\Reference;
use Bitrix\Main\ORM\Query\Join;
use Bitrix\Main\ORM\Query\Query;
use Bitrix\Ui\EntityForm\EntityFormConfigAcTable;
use Bitrix\Ui\EntityForm\EntityFormConfigTable;
use Bitrix\UI\Infrastructure\Agent\AgentBase;
use Bitrix\UI\Integration\HumanResources\DepartmentQueries;
use Bitrix\UI\Integration\HumanResources\HumanResources;
use COption;
class CrmAccessCodesConverterAgent extends AgentBase
{
public const AGENT_DONE_STOP_IT = false;
public const PERIODICAL_AGENT_RUN_LATER = true;
public const LIMIT = 100;
private const MODULE_NAME = 'ui';
public static function doRun(): bool
{
$instance = new self();
if (!HumanResources::getInstance()->isUsed())
{
$instance->setExecutionPeriod(86400);
return self::PERIODICAL_AGENT_RUN_LATER;
}
$instance->setIsConvertingOption();
if ($instance->hasUnconvertedAccessCodes())
{
$isInProgress = $instance->execute();
if ($isInProgress)
{
return self::PERIODICAL_AGENT_RUN_LATER;
}
}
$instance->cleanUp();
return self::AGENT_DONE_STOP_IT;
}
private function setIsConvertingOption(): void
{
COption::SetOptionString(self::MODULE_NAME, HumanResources::IS_CONVERTED_OPTION_NAME, 'N');
}
private function cleanUp(): void
{
COption::RemoveOption(self::MODULE_NAME, HumanResources::IS_CONVERTED_OPTION_NAME);
}
private function hasUnconvertedAccessCodes(): bool
{
$result = $this->getQuery()
->setSelect(['ID'])
->setLimit(1)
->fetch()
;
return (bool)$result['ID'];
}
private function execute(): bool
{
$items = $this->getItems();
$accessCodes = array_map(
static fn(string $code) => str_replace('DR', 'D', $code),
array_unique(array_column($items, 'ACCESS_CODE')),
);
if (empty($accessCodes))
{
return false;
}
$departmentsQueries = DepartmentQueries::getInstance();
$humanResources = HumanResources::getInstance();
foreach ($items as $item)
{
$department = $departmentsQueries->getDepartmentByAccessCode($item['ACCESS_CODE']);
if (!$department)
{
continue;
}
EntityFormConfigAcTable::update($item['ID'], [
'ACCESS_CODE' => $humanResources->buildAccessCode('SNDR', $department->id),
]);
}
return true;
}
private function getItems(): array
{
return $this->getQuery()
->setSelect(['*'])
->setLimit(self::LIMIT)
->fetchAll()
;
}
private function getQuery(): Query
{
return EntityFormConfigAcTable::query()
->registerRuntimeField(
new Reference(
'CONFIG_CATEGORY',
EntityFormConfigTable::class,
Join::on('this.CONFIG_ID', 'ref.ID')
->where('ref.CATEGORY', 'crm')
->where('ref.OPTION_CATEGORY', 'crm.entity.editor'),
['join_type' => Join::TYPE_LEFT],
),
)
->whereLike('ACCESS_CODE', 'DR%')
;
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace Bitrix\UI\Integration\HumanResources;
use Bitrix\HumanResources;
use Bitrix\Main\Loader;
Loader::requireModule('humanresources');
class DepartmentQueries
{
private HumanResources\Service\Container $hrServiceLocator;
public static function getInstance(): self
{
return new self();
}
private function __construct()
{
$this->hrServiceLocator = HumanResources\Service\Container::instance();
}
public function getDepartmentByAccessCode(string $accessCode): ?HumanResources\Item\Node
{
return $this->hrServiceLocator::getNodeRepository()->getByAccessCode($accessCode);
}
}

View File

@@ -0,0 +1,48 @@
<?php
namespace Bitrix\UI\Integration\HumanResources;
use Bitrix\HumanResources\Config\Storage;
use Bitrix\HumanResources\Type\AccessCodeType;
use Bitrix\Main\Loader;
use COption;
final class HumanResources
{
public const IS_CONVERTED_OPTION_NAME = 'entity_editor_config_access_codes_is_converted';
public static function getInstance(): self
{
return new self();
}
private function __construct()
{
}
public function buildAccessCode(string $value, int $nodeId): ?string
{
if ($this->isUsed())
{
return AccessCodeType::tryFrom($value)?->buildAccessCode($nodeId);
}
return null;
}
public function isUsed(): bool
{
return Loader::includeModule('humanresources') && Storage::instance()->isCompanyStructureConverted(false);
}
private function isEntityEditorConfigAccessCodesConverted(): bool
{
return COption::GetOptionString('ui', self::IS_CONVERTED_OPTION_NAME, 'Y') === 'Y';
}
public function isAccessCodesCanBeUsed(): bool
{
return $this->isUsed() && $this->isEntityEditorConfigAccessCodesConverted();
}
}

View File

@@ -1,20 +0,0 @@
<?php
namespace Bitrix\UI\NotificationManager\Helpers;
final class Uuid
{
public static function getV4(): string
{
return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
random_int(0, 0xffff),
random_int(0, 0xffff),
random_int(0, 0xffff),
random_int(0, 0x0fff) | 0x4000,
random_int(0, 0x3fff) | 0x8000,
random_int(0, 0xffff),
random_int(0, 0xffff),
random_int(0, 0xffff)
);
}
}

View File

@@ -14,7 +14,7 @@ use Bitrix\Main\Text\Encoding;
*
* Maximum length of BankName is increased up to 120, standard states it should not be more than 45.
*/
final class FinancialTransactionsRu
class FinancialTransactionsRu
{
public const FORMAT_IDENTIFIER = 'ST';
public const FORMAT_VERSION = '0001';
@@ -237,6 +237,7 @@ final class FinancialTransactionsRu
self::FIELD_BANK_NAME => 120,
self::FIELD_BIC => 9,
self::FIELD_CORRESPONDENT_ACCOUNT => 20,
self::FIELD_PURPOSE => 210,
];
return $maximumFieldLengths[$fieldName] ?? null;

View File

@@ -35,6 +35,8 @@ class BaseButton implements Contract\Renderable
protected $link;
/** @var integer|string */
protected $counter;
/** @var CounterStyle */
protected $counterStyle;
/** @var array */
protected $events = [];
/** @var ButtonAttributes */
@@ -114,6 +116,11 @@ class BaseButton implements Contract\Renderable
$this->setCounter($params['counter']);
}
if (!empty($params['counterStyle']))
{
$this->setCounterStyle($params['counterStyle']);
}
if (!empty($params['id']))
{
$this->setId($params['id']);
@@ -258,9 +265,10 @@ class BaseButton implements Contract\Renderable
if ($counter !== null)
{
$counterStyle = $this->getCounterStyle() ?? CounterStyle::FILLED_ALERT;
$counter = new Counter(
useAirDesign: true,
style: CounterStyle::FILLED_ALERT,
style: $counterStyle,
value: (int)$counter,
);
@@ -642,4 +650,33 @@ class BaseButton implements Contract\Renderable
return $this;
}
/**
* @return $this
*/
public function setCounterStyle(CounterStyle | string $style): self
{
if ($style instanceof CounterStyle)
{
$this->counterStyle = $style;
return $this;
}
$styleFromEnum = CounterStyle::tryFrom($style);
if (!is_null($styleFromEnum))
{
$this->counterStyle = $styleFromEnum;
return $this;
}
$this->counterStyle = CounterStyle::FILLED_ALERT;
return $this;
}
public function getCounterStyle(): ?CounterStyle
{
return $this->counterStyle;
}
}

View File

@@ -120,6 +120,7 @@ trait ProviderWithUserFieldsTrait
'MULTIPLE' => $userFieldInfo['MULTIPLE'],
'MANDATORY' => $userFieldInfo['MANDATORY'],
'SETTINGS' => $userFieldInfo['SETTINGS'] ?? null,
'HELP_MESSAGE' => $userFieldInfo['HELP_MESSAGE'] ?? null,
];
// required for the enum fields to work on mobile

View File

@@ -0,0 +1,95 @@
<?php
namespace Bitrix\Ui\EntityForm\Dto;
use Bitrix\Ui\EntityForm\EO_EntityFormConfig;
use Bitrix\UI\Form\EntityEditorConfigScope;
final class EntityEditorConfigDto
{
public function __construct(
private readonly string $categoryName,
private readonly string $entityTypeId,
private readonly string $configScopeType,
private readonly ?int $userScopeId,
private ?int $userId = null,
private readonly bool $onAdd = false,
private readonly bool $onUpdate = false,
) {
}
public static function fromEntityFormConfig(EO_EntityFormConfig $config): self
{
return new self(
$config->getOptionCategory(),
$config->getEntityTypeId(),
EntityEditorConfigScope::CUSTOM,
$config->getId(),
null,
$config->getOnAdd(),
$config->getOnUpdate(),
);
}
public function setUserId(int $int): self
{
$this->userId = $int;
return $this;
}
public function getConfigScopeType(): string
{
return $this->configScopeType;
}
public function getCategoryName(): string
{
return $this->categoryName;
}
public function getEntityTypeId(): string
{
return $this->entityTypeId;
}
public function getUserScopeId(): ?int
{
return $this->userScopeId;
}
public function getUserId(): ?int
{
return $this->userId;
}
public function getOptionValue(): array
{
return [
'scope' => $this->configScopeType,
'userScopeId' => $this->userScopeId,
'onAdd' => $this->onAdd,
'onUpdate' => $this->onUpdate,
];
}
public function hasOnAdd(): bool
{
return $this->onAdd;
}
public function hasOnUpdate(): bool
{
return $this->onUpdate;
}
public function getModuleIdFromCategory(): ?string
{
if (preg_match('/^([a-zA-Z0-9_]+)\.entity\.editor$/', $this->getCategoryName(), $matches))
{
return $matches[1];
}
return null;
}
}

View File

@@ -34,22 +34,22 @@ class EntityFormConfigTable extends Entity\DataManager
return [
new Entity\IntegerField('ID', [
'autocomplete' => true,
'primary' => true
'primary' => true,
]),
new Entity\StringField('CATEGORY', [
'required' => true,
'size' => 20
'size' => 20,
]),
new Entity\StringField('ENTITY_TYPE_ID', [
'required' => true,
'size' => 60
'size' => 60,
]),
new Entity\StringField('NAME', [
'required' => true,
'size' => 100
'size' => 100,
]),
(new ArrayField('CONFIG'))
->configureSerializeCallback(function ($value){
->configureSerializeCallback(function ($value) {
return EntityFormConfigTable::serialize($value);
})
->configureUnserializeCallback(function ($value) {
@@ -58,17 +58,27 @@ class EntityFormConfigTable extends Entity\DataManager
new Entity\BooleanField('COMMON', [
'values' => ['N', 'Y'],
'required' => true,
'default_value' => 'N'
'default_value' => 'N',
]),
new Entity\BooleanField('AUTO_APPLY_SCOPE', [
'values' => ['N', 'Y'],
'required' => true,
'default_value' => 'N'
'default_value' => 'N',
]),
new Entity\StringField('OPTION_CATEGORY', [
'required' => true,
'size' => 50
])
'size' => 50,
]),
new Entity\BooleanField('ON_ADD', [
'values' => ['N', 'Y'],
'required' => true,
'default_value' => 'Y',
]),
new Entity\BooleanField('ON_UPDATE', [
'values' => ['N', 'Y'],
'required' => true,
'default_value' => 'Y',
]),
];
}
@@ -90,7 +100,7 @@ class EntityFormConfigTable extends Entity\DataManager
{
$value = Emoji::decode($value);
}
}
},
);
}
elseif (is_string($unserialized))
@@ -110,9 +120,9 @@ class EntityFormConfigTable extends Entity\DataManager
{
$value = Emoji::encode($value);
}
}
},
);
return serialize($fieldValue);
}
}
}

View File

@@ -0,0 +1,215 @@
<?php
namespace Bitrix\Ui\EntityForm;
use Bitrix\Main\Config\Configuration;
use Bitrix\Main\Grid\Options;
use Bitrix\Main\Grid\Panel\Snippet;
use Bitrix\Main\Loader;
use Bitrix\Main\Localization\Loc;
use Bitrix\Main\UI\PageNavigation;
use Bitrix\Main\Web\Json;
use Bitrix\UI\Integration\HumanResources\HumanResources;
class FormConfigData
{
private const SETTINGS_ENTITY_FORM_SCOPE_KEY = 'entityFormScope';
private const SETTINGS_FORM_CONFIG_DATA_CLASS_KEY = 'formConfigData';
public function __construct(
protected readonly string $navParamName,
protected readonly string $moduleId,
protected readonly string|int $entityTypeId,
) {
}
public static function getInstance(string $navParamName, string $moduleId, string|int $entityTypeId): static
{
$configuration = Configuration::getInstance($moduleId);
$value = $configuration->get(static::SETTINGS_ENTITY_FORM_SCOPE_KEY);
if (
is_array($value)
&& isset($value[static::SETTINGS_FORM_CONFIG_DATA_CLASS_KEY])
&& Loader::includeModule($moduleId)
&& is_a($value[static::SETTINGS_FORM_CONFIG_DATA_CLASS_KEY], self::class, true)
) {
return new $value[static::SETTINGS_FORM_CONFIG_DATA_CLASS_KEY]($navParamName, $moduleId, $entityTypeId);
}
return new self($navParamName, $moduleId, $entityTypeId);
}
public function prepare(): array
{
$gridId = $this->getGridId();
$grid['GRID_ID'] = $gridId;
$grid['COLUMNS'] = $this->getColumns();
$gridOptions = new Options($gridId);
$navParams = $gridOptions->getNavParams(['nPageSize' => 10]);
$pageSize = (int)$navParams['nPageSize'];
$pageNavigation = new PageNavigation($this->navParamName);
$pageNavigation->allowAllRecords(false)->setPageSize($pageSize)->initFromUri();
$entityTypeId = $this->entityTypeId ?? null;
if ($entityTypeId)
{
$moduleId = $this->moduleId ?? null;
$list = Scope::getInstance()->getAllUserScopes($entityTypeId, $moduleId);
}
else
{
$list = [];
}
$grid['ROWS'] = [];
if (!empty($list))
{
foreach ($list as $scopeId => $scope)
{
$grid['ROWS'][] = [
'data' => $this->prepareRowData($scopeId, $scope, $entityTypeId),
'actions' => $this->getContextActions($scopeId, $scope),
];
}
}
$grid['NAV_PARAM_NAME'] = $this->navParamName;
$grid['CURRENT_PAGE'] = $pageNavigation->getCurrentPage();
$grid['NAV_OBJECT'] = $pageNavigation;
$grid['AJAX_MODE'] = 'Y';
$grid['ALLOW_ROWS_SORT'] = false;
$grid['AJAX_OPTION_JUMP'] = 'N';
$grid['AJAX_OPTION_STYLE'] = 'N';
$grid['AJAX_OPTION_HISTORY'] = 'N';
$grid['AJAX_ID'] = \CAjax::GetComponentID(
'bitrix:main.ui.grid', '', '',
);
$grid['SHOW_PAGESIZE'] = true;
$grid['PAGE_SIZES'] = [
['NAME' => '10', 'VALUE' => '10'], ['NAME' => '20', 'VALUE' => '20'], ['NAME' => '50', 'VALUE' => '50'],
];
$grid['DEFAULT_PAGE_SIZE'] = 20;
$grid['SHOW_ROW_CHECKBOXES'] = true;
$grid['SHOW_CHECK_ALL_CHECKBOXES'] = false;
$grid['SHOW_ACTION_PANEL'] = true;
$snippet = new Snippet();
$grid['ACTION_PANEL'] = [
'GROUPS' => [
'TYPE' => [
'ITEMS' => [
$snippet->getRemoveButton(),
$snippet->getEditButton(),
],
],
],
];
$grid['FILTER'] = $this->getFilter();
return [
'grid' => $grid,
];
}
protected function getGridId(): string
{
return 'editor_scopes';
}
protected function getColumns(): array
{
$columns = [
[
'id' => 'ID',
'name' => 'ID',
'default' => true,
],
[
'id' => 'NAME',
'name' => Loc::getMessage('UI_FORM_CONFIG_SCOPE'),
'default' => true,
'editable' => true,
],
[
'id' => 'USERS',
'name' => Loc::getMessage('UI_FORM_CONFIG_MEMBERS'),
'default' => true,
'width' => 200,
],
[
'id' => 'AUTO_APPLY_SCOPE',
'name' => Loc::getMessage('UI_FORM_CONFIG_AUTO_APPLY_SCOPE'),
'type' => \Bitrix\Main\Grid\Types::GRID_CHECKBOX,
'default' => true,
'editable' => true,
],
];
if (ScopeAccess::getInstance($this->moduleId)->canUseOnAddOnUpdateSegregation())
{
$columns[] = [
'id' => 'ON_ADD',
'name' => Loc::getMessage('UI_FORM_CONFIG_ON_ADD'),
'type' => \Bitrix\Main\Grid\Types::GRID_CHECKBOX,
'default' => true,
'editable' => true,
];
$columns[] = [
'id' => 'ON_UPDATE',
'name' => Loc::getMessage('UI_FORM_CONFIG_ON_UPDATE'),
'type' => \Bitrix\Main\Grid\Types::GRID_CHECKBOX,
'default' => true,
'editable' => true,
];
}
return $columns;
}
protected function getContextActions(int $scopeId, array $scope): array
{
return [];
}
protected function getFilter(): ?array
{
return null;
}
protected function prepareRowData(int|string $scopeId, array $scope, string $entityTypeId): array
{
return [
'ID' => $scopeId,
'NAME' => $scope['NAME'],
'USERS' => $this->getUserField($scopeId, $scope, $entityTypeId),
'AUTO_APPLY_SCOPE' => $scope['AUTO_APPLY_SCOPE'],
'ON_ADD' => $scope['ON_ADD'],
'ON_UPDATE' => $scope['ON_UPDATE'],
];
}
protected function getUserField(int|string $scopeId, array $scope, $entityTypeId): string
{
$params = Json::encode([
'scopeId' => $scopeId,
'members' => $scope['MEMBERS'],
'moduleId' => $this->moduleId,
'entityTypeId' => $entityTypeId,
'useHumanResourcesModule' => HumanResources::getInstance()->isAccessCodesCanBeUsed(),
]);
return <<<HTML
<div class="ui-editor-config" id="ui-editor-config-$scopeId"></div>
<script>
BX.ready(() => { new BX.Ui.Form.ConfigItem({$params}) });
</script>
HTML;
}
}

View File

@@ -11,6 +11,7 @@ use Bitrix\Main\Error;
use Bitrix\Main\Event;
use Bitrix\Main\Loader;
use Bitrix\Main\Localization\Loc;
use Bitrix\Main\ObjectNotFoundException;
use Bitrix\Main\ORM\Data\DeleteResult;
use Bitrix\Main\ORM\Data\UpdateResult;
use Bitrix\Main\ORM\Query\Query;
@@ -18,6 +19,7 @@ use Bitrix\Main\Result;
use Bitrix\Main\Text\HtmlFilter;
use Bitrix\Main\UI\AccessRights\DataProvider;
use Bitrix\Socialnetwork\UserToGroupTable;
use Bitrix\Ui\EntityForm\Dto\EntityEditorConfigDto;
use Bitrix\UI\Form\EntityEditorConfigScope;
use CAccess;
use CUserOptions;
@@ -31,10 +33,12 @@ class Scope
protected const CODE_USER = 'U';
protected const CODE_PROJECT = 'SG';
protected const CODE_DEPARTMENT = 'DR';
protected const CODE_STRUCTURE_DEPARTMENT = 'SNDR';
protected const TYPE_USER = 'user';
protected const TYPE_PROJECT = 'project';
protected const TYPE_DEPARTMENT = 'department';
protected const TYPE_STRUCTURE_NODE = 'structure-node';
protected static array $instances = [];
private static array $userScopeIdsCache = [];
@@ -89,32 +93,21 @@ class Scope
if (!isset($results[$key]))
{
$result = [];
$isAdminForEntity = $moduleId
&& (
($scopeAccess = ScopeAccess::getInstance($moduleId, $this->userId))
&& $scopeAccess->isAdminForEntityTypeId($entityTypeId)
);
$isAdminForEntity = $this->isAdminForEntity($entityTypeId, $moduleId);
if (!$isAdminForEntity)
{
$filter['@ID'] = $this->getScopesIdByUser();
}
$filter['@ENTITY_TYPE_ID'] = ($this->getEntityTypeIdMap()[$entityTypeId] ?? [$entityTypeId]);
if ($excludeEmptyAccessCode)
{
$filter['!=ACCESS_CODE'] = '';
}
$filter = ScopeListFilter::getInstance($moduleId)->prepareFilter($entityTypeId, $isAdminForEntity, $excludeEmptyAccessCode, $this);
if ($isAdminForEntity || !empty($filter['@ID']))
{
$scopes = EntityFormConfigTable::getList([
'select' => [
'ID',
'ENTITY_TYPE_ID',
'NAME',
'AUTO_APPLY_SCOPE',
'ACCESS_CODE' => '\Bitrix\Ui\EntityForm\EntityFormConfigAcTable:CONFIG.ACCESS_CODE',
'ON_ADD',
'ON_UPDATE',
],
'filter' => $filter,
]);
@@ -123,6 +116,9 @@ class Scope
{
$result[$scope['ID']]['NAME'] = HtmlFilter::encode($scope['NAME']);
$result[$scope['ID']]['AUTO_APPLY_SCOPE'] = $scope['AUTO_APPLY_SCOPE'];
$result[$scope['ID']]['ON_ADD'] = $scope['ON_ADD'];
$result[$scope['ID']]['ON_UPDATE'] = $scope['ON_UPDATE'];
$result[$scope['ID']]['ENTITY_TYPE_ID'] = $scope['ENTITY_TYPE_ID'];
if (
$loadMetadata
&& !isset($result[$scope['ID']]['ACCESS_CODES'][$scope['ACCESS_CODE']])
@@ -132,7 +128,7 @@ class Scope
$accessCode = new AccessCode($scope['ACCESS_CODE']);
$member = (new DataProvider())->getEntity(
$accessCode->getEntityType(),
$accessCode->getEntityId()
$accessCode->getEntityId(),
);
$result[$scope['ID']]['ACCESS_CODES'][$scope['ACCESS_CODE']] = $scope['ACCESS_CODE'];
$result[$scope['ID']]['MEMBERS'][$scope['ACCESS_CODE']] = $member->getMetaData();
@@ -149,7 +145,7 @@ class Scope
/**
* This method must return entityTypeId values that correspond to a single CRM entity only.
*/
protected function getEntityTypeIdMap(): array
public function getEntityTypeIdMap(): array
{
return [
'lead_details' => ['lead_details', 'returning_lead_details'],
@@ -171,7 +167,7 @@ class Scope
return $this->userId;
}
private function getScopesIdByUser(): array
public function getScopesIdByUser(): array
{
if (isset(self::$userScopeIdsCache[$this->getUserId()]))
{
@@ -217,8 +213,9 @@ class Scope
{
if ($row = EntityFormConfigTable::getRowById($scopeId))
{
return (is_array($row['CONFIG']) ? $row['CONFIG'] : null);
return is_array($row['CONFIG']) ? $row['CONFIG'] : null;
}
return null;
}
@@ -249,7 +246,18 @@ class Scope
*/
private function removeById(int $id): DeleteResult
{
$scopeObject = EntityFormConfigTable::getById($id)->fetchObject();
if (!$scopeObject)
{
return (new DeleteResult())->addError(new Error('Configuration not found'));
}
$this->removeScopeMembers($id);
$scopeName = $this->getScopeName(EntityEditorConfigDto::fromEntityFormConfig($scopeObject));
$this->removeUsersScopeOptions($id, $scopeObject->getOptionCategory(), $scopeName);
return EntityFormConfigTable::delete($id);
}
@@ -257,23 +265,65 @@ class Scope
* Set user option with config scope type and scopeId if selected custom scope
* @param string $categoryName
* @param string $guid
* @param string $scope
* @param string $configScopeType
* @param int $userScopeId
*/
public function setScope(string $categoryName, string $guid, string $scope, int $userScopeId = 0, ?int $userId = null): void
public function setScope(string $categoryName, string $guid, string $configScopeType, int $userScopeId = 0, ?int $userId = null): void
{
$this->setScopeToUser($categoryName, $guid, $scope, $userScopeId, $userId);
$this->setScopeWithName($categoryName, $guid, $configScopeType, $userScopeId, $userId);
}
public function setCreateScope(string $moduleId, string $categoryName, string $guid, string $configScopeType, int $userScopeId = 0): void
{
$scopeName = self::getScopeNameOnAdd($moduleId, $guid);
$this->setScopeWithName($categoryName, $guid, $configScopeType, $userScopeId, null, $scopeName);
}
public function setEditScope(string $moduleId, string $categoryName, string $guid, string $configScopeType, int $userScopeId = 0): void
{
$scopeName = self::getScopeNameOnUpdate($moduleId, $guid);
$this->setScopeWithName($categoryName, $guid, $configScopeType, $userScopeId, null, $scopeName);
}
private function setScopeWithName(
string $categoryName,
string $guid,
string $configScopeType,
int $userScopeId = 0,
?int $userId = null,
?string $scopeName = null,
): void
{
$scopeObject = EntityFormConfigTable::getById($userScopeId)->fetchObject();
if (!$scopeObject && $configScopeType === EntityEditorConfigScope::CUSTOM)
{
return;
}
$this->setScopeToUser(
new EntityEditorConfigDto(
$categoryName,
$guid,
$configScopeType,
$userScopeId,
$userId,
(bool)$scopeObject?->getOnAdd(),
(bool)$scopeObject?->getOnUpdate(),
),
$scopeName,
);
}
public function setScopeConfig(
string $category,
string $moduleId,
string $entityTypeId,
string $name,
array $accessCodes,
array $config,
array $params = []
)
{
array $params = [],
) {
if (empty($name))
{
$errors['name'] = new Error(Loc::getMessage('FIELD_REQUIRED'));
@@ -293,14 +343,22 @@ class Scope
$this->formatAccessCodes($accessCodes);
$canUseOnAddOunUpdateSegregation = ScopeAccess::getInstance($moduleId)->canUseOnAddOnUpdateSegregation();
$forceSetToUsers = ($params['forceSetToUsers'] ?? 'N') === 'Y';
$availableOnAdd = (!$canUseOnAddOunUpdateSegregation || ($params['availableOnAdd'] ?? 'N') === 'Y');
$availableOnUpdate = (!$canUseOnAddOunUpdateSegregation || ($params['availableOnUpdate'] ?? 'N') === 'Y');
$result = EntityFormConfigTable::add([
'CATEGORY' => $category,
'CATEGORY' => $moduleId,
'ENTITY_TYPE_ID' => $entityTypeId,
'NAME' => $name,
'CONFIG' => $config,
'COMMON' => ($params['common'] ?? 'Y'),
'AUTO_APPLY_SCOPE' => ($params['forceSetToUsers'] ?? 'N'),
'OPTION_CATEGORY' => $params['categoryName']
'AUTO_APPLY_SCOPE' => $forceSetToUsers,
'OPTION_CATEGORY' => $params['categoryName'],
'ON_ADD' => $availableOnAdd,
'ON_UPDATE' => $availableOnUpdate,
]);
if ($result->isSuccess())
@@ -314,19 +372,20 @@ class Scope
]);
}
$forceSetToUsers = ($params['forceSetToUsers'] ?? false);
if (mb_strtoupper($forceSetToUsers) === 'FALSE')
{
$forceSetToUsers = false;
}
Application::getInstance()->addBackgroundJob(
static fn() => Scope::getInstance()->forceSetScopeToUsers($accessCodes, [
'forceSetToUsers' => $forceSetToUsers,
'categoryName' => ($params['categoryName'] ?? ''),
'entityTypeId' => $entityTypeId,
'configId' => $configId,
])
static fn() => Scope::getInstance()->forceSetScopeToUsers(
new EntityEditorConfigDto(
$params['categoryName'] ?? '',
$entityTypeId,
EntityEditorConfigScope::CUSTOM,
$result->getId(),
null,
$availableOnAdd,
$availableOnUpdate,
),
$forceSetToUsers,
$accessCodes,
),
);
return $configId;
@@ -344,78 +403,81 @@ class Scope
{
if ($item['entityId'] === self::TYPE_USER)
{
$accessCodes[$key]['id'] = self::CODE_USER . (int)$accessCodes[$key]['id'];
$accessCodes[$key]['id'] = self::CODE_USER . (int)$item['id'];
}
elseif ($item['entityId'] === self::TYPE_DEPARTMENT)
{
$accessCodes[$key]['id'] = self::CODE_DEPARTMENT . (int)$accessCodes[$key]['id'];
$accessCodes[$key]['id'] = self::CODE_DEPARTMENT . (int)$item['id'];
}
elseif ($item['entityId'] === self::TYPE_PROJECT)
{
$accessCodes[$key]['id'] = self::CODE_PROJECT . (int)$accessCodes[$key]['id'];
$accessCodes[$key]['id'] = self::CODE_PROJECT . (int)$item['id'];
}
else{
elseif ($item['entityId'] === self::TYPE_STRUCTURE_NODE)
{
$accessCodes[$key]['id'] = self::CODE_STRUCTURE_DEPARTMENT . (int)$item['id'];
}
else
{
unset($accessCodes[$key]);
}
}
}
/**
* @param array $accessCodes
* @param array $params
*/
protected function forceSetScopeToUsers(array $accessCodes = [], array $params = []): void
protected function forceSetScopeToUsers(EntityEditorConfigDto $config, bool $forceSetToUsers, array $accessCodes = []): void
{
if ($params['forceSetToUsers'] && $params['categoryName'])
if ($forceSetToUsers && $config->getCategoryName())
{
$codes = [];
foreach ($accessCodes as $ac)
{
$codes[] = $ac['id'];
}
$this->setScopeByAccessCodes(
$params['categoryName'],
$params['entityTypeId'],
EntityEditorConfigScope::CUSTOM,
(int)$params['configId'],
$codes
);
$this->setScopeByAccessCodes($config, $codes);
}
}
/**
* @param string $categoryName
* @param string $guid
* @param string $scope
* @param int $userScopeId
* @param int|null $userId
*/
protected function setScopeToUser(
string $categoryName,
string $guid,
string $scope,
int $userScopeId,
?int $userId = null
): void
protected function setScopeToUser(EntityEditorConfigDto $config, ?string $scopeName = null): void
{
$scope = (isset($scope) ? strtoupper($scope) : EntityEditorConfigScope::UNDEFINED);
$scope = $config->getConfigScopeType() !== null ? strtoupper($config->getConfigScopeType()) : EntityEditorConfigScope::UNDEFINED;
if (EntityEditorConfigScope::isDefined($scope))
if (!EntityEditorConfigScope::isDefined($scope))
{
if ($scope === EntityEditorConfigScope::CUSTOM && $userScopeId)
{
$value = [
'scope' => $scope,
'userScopeId' => $userScopeId,
];
}
else
{
$value = $scope;
}
return;
}
$userId = ($userId ?? false);
CUserOptions::SetOption($categoryName, "{$guid}_scope", $value, false, $userId);
if ($scope === EntityEditorConfigScope::CUSTOM && $config->getUserScopeId())
{
$value = $config->getOptionValue();
}
else
{
$value = $scope;
}
$scopeNamesToApply = [];
if ($scopeName)
{
$scopeNamesToApply[] = $scopeName;
}
else
{
if ($config->hasOnAdd())
{
$scopeNamesToApply[] = self::getScopeNameOnAdd($config->getModuleIdFromCategory(), $config->getEntityTypeId());
}
if ($config->hasOnUpdate())
{
$scopeNamesToApply[] = self::getScopeNameOnUpdate($config->getModuleIdFromCategory(), $config->getEntityTypeId());
}
}
$scopeNamesToApply = array_unique($scopeNamesToApply);
$userId = !is_null($config->getUserId()) ? $config->getUserId() : false;
foreach ($scopeNamesToApply as $scopeNameToApply)
{
CUserOptions::SetOption($config->getCategoryName(), $scopeNameToApply, $value, false, $userId);
}
}
@@ -426,24 +488,80 @@ class Scope
]);
}
public function updateScopeName(int $id, string $name): UpdateResult
public function updateScope(int $scopeId, array $fields): UpdateResult
{
return EntityFormConfigTable::update($id, [
'NAME' => $name,
]);
$scopeObject = EntityFormConfigTable::getById($scopeId)->fetchObject();
if (!$scopeObject)
{
return (new UpdateResult())->addError(new Error('Configuration not found'));
}
$oldScopeName = $this->getScopeName(EntityEditorConfigDto::fromEntityFormConfig($scopeObject));
$canUseOnAddOunUpdateSegregation = ScopeAccess::getInstance($scopeObject->getCategory())->canUseOnAddOnUpdateSegregation();
if (isset($fields['ON_ADD']) && !$canUseOnAddOunUpdateSegregation)
{
$fields['ON_ADD'] = 'Y';
}
if (isset($fields['ON_UPDATE']) && !$canUseOnAddOunUpdateSegregation)
{
$fields['ON_UPDATE'] = 'Y';
}
$result = EntityFormConfigTable::update($scopeId, $fields);
$optionCategory = $scopeObject->getOptionCategory();
$newScopeName = $this->getScopeName(
new EntityEditorConfigDto(
$optionCategory,
$scopeObject->getEntityTypeId(),
EntityEditorConfigScope::CUSTOM,
$scopeObject->getId(),
null,
$fields['ON_ADD'] === 'Y',
$fields['ON_UPDATE'] === 'Y',
),
);
if ($fields['AUTO_APPLY_SCOPE'] === 'Y')
{
Application::getInstance()->addBackgroundJob(static function() use ($scopeId, $optionCategory, $oldScopeName, $newScopeName) {
if ($oldScopeName !== $newScopeName)
{
Scope::getInstance()->removeUsersScopeOptions($scopeId, $optionCategory, $oldScopeName);
}
Scope::getInstance()->setScopeForEligibleUsers($scopeId);
});
}
return $result;
}
/**
* @param int $configId
* @param array $accessCodes
* @return array
*/
public function updateScopeAccessCodes(int $configId, array $accessCodes = []): array
{
$scopeObject = EntityFormConfigTable::getById($configId)->fetchObject();
if (!$scopeObject)
{
return [];
}
$this->removeScopeMembers($configId);
$this->addAccessCodes($configId, $accessCodes);
return $this->getScopeMembers($configId);
$scopeMembers = $this->getScopeMembers($configId);
if ($scopeObject->getAutoApplyScope())
{
Application::getInstance()->addBackgroundJob(
static fn() => Scope::getInstance()->setScopeByAccessCodes(
EntityEditorConfigDto::fromEntityFormConfig($scopeObject),
array_keys($accessCodes),
),
);
}
return $scopeMembers;
}
public function addAccessCodes(int $configId, array $accessCodes): Result
@@ -453,7 +571,8 @@ class Scope
{
$accessCodeItem = EntityFormConfigAcTable::createObject()
->setAccessCode($accessCode)
->setConfigId($configId);
->setConfigId($configId)
;
$accessCodeCollection->add($accessCodeItem);
}
@@ -481,6 +600,7 @@ class Scope
$result[$accessCodeEntity['ACCESS_CODE']] = $member->getMetaData();
}
}
return $result;
}
@@ -497,51 +617,43 @@ class Scope
$connection->query(sprintf(
'DELETE FROM %s WHERE %s',
$connection->getSqlHelper()->quote($entity->getDBTableName()),
Query::buildFilterSql($entity, $filter)
Query::buildFilterSql($entity, $filter),
));
}
public function updateScopeAutoApplyScope(int $id, bool $autoApplyScope): UpdateResult
{
return EntityFormConfigTable::update($id, [
'AUTO_APPLY_SCOPE' => $autoApplyScope ? 'Y' : 'N',
]);
}
private function setScopeToDepartment(
string $categoryName,
string $guid,
string $scope,
int $userScopeId,
int $departmentId
): void
private function setScopeToDepartment(EntityEditorConfigDto $config, int $departmentId): void
{
$userIds = $this->getUserIdsByDepartment($departmentId);
foreach ($userIds as $userId)
{
$this->setScopeToUser($categoryName, $guid, $scope, $userScopeId, $userId);
$config->setUserId($userId);
$this->setScopeToUser($config);
}
}
private function setScopeToSocialGroup(
string $categoryName,
string $guid,
string $scope,
int $userScopeId,
int $socialGroupId
): void
private function setScopeToSocialGroup(EntityEditorConfigDto $config, int $socialGroupId): void
{
$userIds = $this->getUserIdsBySocialGroup($socialGroupId);
foreach ($userIds as $userId)
{
$this->setScopeToUser($categoryName, $guid, $scope, $userScopeId, $userId);
$config->setUserId($userId);
$this->setScopeToUser($config);
}
}
private function setScopeToStructureDepartment(EntityEditorConfigDto $config, mixed $structureDepartmentId): void
{
$userIds = $this->getUserIdsByStructureDepartment($structureDepartmentId);
foreach ($userIds as $userId)
{
$config->setUserId($userId);
$this->setScopeToUser($config);
}
}
public static function handleMemberAddedToDepartment(Event $event): void
{
Application::getInstance()->addBackgroundJob(static function() use ($event)
{
Application::getInstance()->addBackgroundJob(static function() use ($event) {
$member = $event->getParameter('member');
$memberId = $member->entityId;
@@ -549,28 +661,13 @@ class Scope
$scopeType = EntityEditorConfigScope::CUSTOM;
$scopes = Scope::getInstance()->getScopesByDepartment($departmentId, true);
$appliedEntities = [];
foreach ($scopes as $scope)
{
if (!in_array($scope->getEntityTypeId(), $appliedEntities))
{
$appliedEntities[] = $scope->getEntityTypeId();
Scope::getInstance()->setScopeToUser(
$scope->getOptionCategory(),
$scope->getEntityTypeId(),
$scopeType,
$scope->getId(),
$memberId
);
}
}
self::applyScopesToMember($scopes, $scopeType, $memberId);
});
}
public static function handleMemberAddedToSocialGroup(int $id, array $fields): void
{
Application::getInstance()->addBackgroundJob(static function() use ($id, $fields)
{
Application::getInstance()->addBackgroundJob(static function() use ($id, $fields) {
if (!\Bitrix\Main\Loader::includeModule('socialnetwork'))
{
return;
@@ -602,21 +699,7 @@ class Scope
$scopeType = EntityEditorConfigScope::CUSTOM;
$scopes = Scope::getInstance()->getScopesBySocialGroupId($socialGroupId, true);
$appliedEntities = [];
foreach ($scopes as $scope)
{
if (!in_array($scope->getEntityTypeId(), $appliedEntities))
{
$appliedEntities[] = $scope->getEntityTypeId();
Scope::getInstance()->setScopeToUser(
$scope->getOptionCategory(),
$scope->getEntityTypeId(),
$scopeType,
$scope->getId(),
$memberId
);
}
}
self::applyScopesToMember($scopes, $scopeType, $memberId);
});
}
@@ -659,29 +742,24 @@ class Scope
->setSelect(['ACCESS_CODE', 'CONFIG'])
->setFilter($filter)
->setOrder(['CONFIG.ID' => 'DESC'])
->fetchCollection();
->fetchCollection()
;
return $scopes->getConfigList();
}
public function setScopeForEligibleUsers(int $scopeId): void
{
$scope = EntityFormConfigTable::getById($scopeId)->fetchObject();
$scopeObject = EntityFormConfigTable::getById($scopeId)->fetchObject();
if (!$scope)
if (!$scopeObject)
{
return;
}
$accessCodes = $this->getScopeAccessCodesByScopeId($scopeId);
$this->setScopeByAccessCodes(
$scope->getOptionCategory(),
$scope->getEntityTypeId(),
EntityEditorConfigScope::CUSTOM,
$scopeId,
$accessCodes
);
$this->setScopeByAccessCodes(EntityEditorConfigDto::fromEntityFormConfig($scopeObject), $accessCodes);
}
public function getScopeAccessCodesByScopeId(int $scopeId): array
@@ -689,7 +767,8 @@ class Scope
$accessCodes = EntityFormConfigAcTable::query()
->setSelect(['ACCESS_CODE'])
->setFilter(['=CONFIG_ID' => $scopeId])
->fetchCollection();
->fetchCollection()
;
$result = [];
foreach ($accessCodes as $code)
{
@@ -699,49 +778,56 @@ class Scope
return $result;
}
private function setScopeByAccessCodes(
string $categoryName,
string $entityTypeId,
string $scope,
int $scopeId,
array $accessCodes
): void
public static function getScopeNameOnAdd(string $moduleId, string $entityTypeId): string
{
return self::getScopeNameWithSuffix($moduleId, $entityTypeId, '_on_add');
}
public static function getScopeNameOnUpdate(string $moduleId, string $entityTypeId): string
{
return self::getScopeNameWithSuffix($moduleId, $entityTypeId, '_on_update');
}
private static function getScopeNameWithSuffix(string $moduleId, string $entityTypeId, string $suffix): string
{
$scopeName = $entityTypeId . '_scope';
try
{
if (!ScopeAccess::getInstance($moduleId)->canUseOnAddOnUpdateSegregation())
{
return $scopeName;
}
}
catch (ObjectNotFoundException $e)
{
return $scopeName;
}
return $scopeName . $suffix;
}
private function setScopeByAccessCodes(EntityEditorConfigDto $config, array $accessCodes): void
{
$userIdPattern = '/^U(\d+)$/';
$departmentIdPattern = '/^DR(\d+)$/';
$socialGroupIdPattern = '/^SG(\d+)$/';
foreach ($accessCodes as $accessCode)
{
$matches = [];
if (preg_match($userIdPattern, $accessCode, $matches))
if (preg_match('/'. AccessCode::AC_USER . '/', $accessCode, $matches))
{
$this->setScopeToUser(
$categoryName,
$entityTypeId,
$scope,
$scopeId,
$matches[1]
);
$config->setUserId($matches[2]);
$this->setScopeToUser($config);
}
elseif (preg_match($departmentIdPattern, $accessCode, $matches))
elseif (preg_match('/'. AccessCode::AC_ALL_DEPARTMENT . '/', $accessCode, $matches))
{
$this->setScopeToDepartment(
$categoryName,
$entityTypeId,
$scope,
$scopeId,
$matches[1]
);
$this->setScopeToDepartment($config, $matches[2]);
}
elseif (preg_match($socialGroupIdPattern, $accessCode, $matches))
elseif (preg_match('/'. AccessCode::AC_SOCNETGROUP . '/', $accessCode, $matches))
{
$this->setScopeToSocialGroup(
$categoryName,
$entityTypeId,
$scope,
$scopeId,
$matches[1]
);
$this->setScopeToSocialGroup($config, $matches[2]);
}
elseif (preg_match('/'. AccessCode::AC_ALL_STRUCTURE_DEPARTMENT . '/', $accessCode, $matches))
{
$this->setScopeToStructureDepartment($config, $matches[2]);
}
}
}
@@ -753,7 +839,6 @@ class Scope
return [];
}
$userCollection = UserToGroupTable::query()
->setSelect(['USER_ID'])
->setFilter([
@@ -762,9 +847,10 @@ class Scope
UserToGroupTable::ROLE_MODERATOR,
UserToGroupTable::ROLE_USER,
UserToGroupTable::ROLE_OWNER,
]
],
])
->fetchCollection();
->fetchCollection()
;
$userIds = [];
foreach ($userCollection as $user)
@@ -776,15 +862,25 @@ class Scope
}
private function getUserIdsByDepartment(int $departmentId): array
{
return $this->getUserIdsByAccessCode($departmentId, 'DR');
}
private function getUserIdsByStructureDepartment(int $departmentId): array
{
return $this->getUserIdsByAccessCode($departmentId, 'SNDR');
}
private function getUserIdsByAccessCode(int $departmentId, string $code): array
{
$userIds = [];
if (!\Bitrix\Main\Loader::includeModule('humanresources'))
if (!\Bitrix\Main\Loader::includeModule('humanresources') || !in_array($code, ['DR', 'SNDR'], true))
{
return $userIds;
}
$hrServiceLocator = Container::instance();
$accessCode = 'DR' . $departmentId;
$accessCode = $code . $departmentId;
$node = $hrServiceLocator::getNodeRepository()->getByAccessCode($accessCode);
if (!$node)
{
@@ -804,4 +900,202 @@ class Scope
{
return CAccess::GetUserCodesArray($this->userId);
}
private static function applyScopesToMember(array $scopes, string $scopeType, $memberId): void
{
$appliedEntities = [];
/** @var EO_EntityFormConfig $scope */
foreach ($scopes as $scope)
{
if (in_array($scope->getEntityTypeId(), $appliedEntities, true))
{
continue;
}
$appliedEntities[] = $scope->getEntityTypeId();
self::getInstance()->setScopeToUser(
new EntityEditorConfigDto(
$scope->getOptionCategory(),
$scope->getEntityTypeId(),
$scopeType,
$scope->getId(),
$memberId,
$scope->getOnAdd(),
$scope->getOnUpdate(),
),
);
}
}
private function getScopeName(EntityEditorConfigDto $config): string
{
$scopeName = "{$config->getEntityTypeId()}_scope";
if ($config->hasOnAdd() && !$config->hasOnUpdate())
{
return self::getScopeNameOnAdd($config->getModuleIdFromCategory(), $config->getEntityTypeId());
}
if (!$config->hasOnAdd() && $config->hasOnUpdate())
{
return self::getScopeNameOnUpdate($config->getModuleIdFromCategory(), $config->getEntityTypeId());
}
if (!$config->hasOnAdd() && !$config->hasOnUpdate())
{
return "{$scopeName}_disabled";
}
return $scopeName;
}
private function removeUsersScopeOptions(int $configId, string $optionCategory, string $scopeName): void
{
$accessCodes = $this->getScopeAccessCodesByScopeId($configId);
$userIds = [];
foreach ($accessCodes as $accessCode)
{
$matches = [];
if (preg_match(AccessCode::AC_USER, $accessCode, $matches))
{
$userIds = [$matches[1]];
}
elseif (preg_match(AccessCode::AC_ALL_DEPARTMENT, $accessCode, $matches))
{
$userIds = $this->getUserIdsByDepartment($matches[1]);
}
elseif (preg_match(AccessCode::AC_SOCNETGROUP, $accessCode, $matches))
{
$userIds = $this->getUserIdsBySocialGroup($matches[1]);
}
elseif (preg_match(AccessCode::AC_ALL_STRUCTURE_DEPARTMENT, $accessCode, $matches))
{
$userIds = $this->getUserIdsByStructureDepartment($matches[1]);
}
}
foreach ($userIds as $userId)
{
CUserOptions::DeleteOption($optionCategory, $scopeName, false, $userId);
}
}
public function copyScope(int $scopeId, string $entityTypeId): null|int|array
{
$scope = $this->getById($scopeId);
if (!$scope)
{
return null;
}
$name = Loc::getMessage('UI_ENTITY_FORM_SCOPE_COPY', ['#NAME#' => $scope['NAME']]);
$params = [
'common' => $scope['COMMON'],
'forceSetToUsers' => $scope['AUTO_APPLY_SCOPE'],
'categoryName' => $scope['OPTION_CATEGORY'],
'availableOnAdd' => $scope['ON_ADD'],
'availableOnUpdate' => $scope['ON_UPDATE'],
];
$accessCodes = $this->getScopeAccessCodesByScopeId($scopeId);
$newAccessCodes = [];
foreach ($accessCodes as $code)
{
$newAccessCodes[] = $this->unpackAccessCode($code);
}
return $this->setScopeConfig(
$scope['CATEGORY'],
$entityTypeId,
$name,
$newAccessCodes,
$scope['CONFIG'],
$params,
);
}
private function unpackAccessCode(string $accessCode): ?array
{
if (str_starts_with($accessCode, self::CODE_USER))
{
return [
'entityId' => self::TYPE_USER,
'id' => str_replace(self::CODE_USER, '', $accessCode),
];
}
if (str_starts_with($accessCode, self::CODE_PROJECT))
{
return [
'entityId' => self::TYPE_PROJECT,
'id' => str_replace(self::CODE_PROJECT, '', $accessCode),
];
}
if (str_starts_with($accessCode, self::CODE_DEPARTMENT))
{
return [
'entityId' => self::TYPE_DEPARTMENT,
'id' => str_replace(self::CODE_DEPARTMENT, '', $accessCode),
];
}
if (str_starts_with($accessCode, self::CODE_STRUCTURE_DEPARTMENT))
{
return [
'entityId' => self::TYPE_STRUCTURE_NODE,
'id' => str_replace(self::CODE_STRUCTURE_DEPARTMENT, '', $accessCode),
];
}
return null;
}
public function getUserScopesEntityEditor(string $entityTypeId, ?string $moduleId): array
{
$result = [];
$isAdminForEntity = $this->isAdminForEntity($entityTypeId, $moduleId);
$filter = ScopeListFilter::getInstance($moduleId)->prepareEntityEditorFilter($entityTypeId, $isAdminForEntity, $this);
if (!$isAdminForEntity && empty($filter['@ID']))
{
return $result;
}
$scopes = EntityFormConfigTable::getList([
'select' => [
'ID',
'ENTITY_TYPE_ID',
'NAME',
'ACCESS_CODE' => '\Bitrix\Ui\EntityForm\EntityFormConfigAcTable:CONFIG.ACCESS_CODE',
'ON_ADD',
'ON_UPDATE',
],
'filter' => $filter,
]);
foreach ($scopes as $scope)
{
$result[$scope['ID']] = [
'NAME' => HtmlFilter::encode($scope['NAME']),
'AUTO_APPLY_SCOPE' => $scope['AUTO_APPLY_SCOPE'] ?? null,
'ON_ADD' => $scope['ON_ADD'],
'ON_UPDATE' => $scope['ON_UPDATE'],
'ENTITY_TYPE_ID' => $scope['ENTITY_TYPE_ID'],
];
}
return $result;
}
private function isAdminForEntity(string $entityTypeId, ?string $moduleId = null): bool
{
return $moduleId
&& (
($scopeAccess = ScopeAccess::getInstance($moduleId, $this->userId))
&& $scopeAccess->isAdminForEntityTypeId($entityTypeId)
);
}
}

View File

@@ -91,4 +91,9 @@ class ScopeAccess
{
return $this->canAdd();
}
public function canUseOnAddOnUpdateSegregation(): bool
{
return false;
}
}

View File

@@ -0,0 +1,65 @@
<?php
namespace Bitrix\Ui\EntityForm;
use Bitrix\Main\Config\Configuration;
use Bitrix\Main\Loader;
class ScopeListFilter
{
private const SETTINGS_ENTITY_FORM_SCOPE_KEY = 'entityFormScope';
private const SETTINGS_SCOPE_LIST_FILTER_CLASS_KEY = 'scopeListFilter';
public function __construct(
) {
}
public static function getInstance($moduleId): static
{
$configuration = Configuration::getInstance($moduleId);
$value = $configuration->get(static::SETTINGS_ENTITY_FORM_SCOPE_KEY);
if (
is_array($value)
&& isset($value[static::SETTINGS_SCOPE_LIST_FILTER_CLASS_KEY])
&& Loader::includeModule($moduleId)
&& is_a($value[static::SETTINGS_SCOPE_LIST_FILTER_CLASS_KEY], self::class, true)
) {
return new $value[static::SETTINGS_SCOPE_LIST_FILTER_CLASS_KEY]();
}
return new self();
}
public function prepareFilter(string $entityTypeId, bool $isAdminForEntity, bool $excludeEmptyAccessCode, Scope $scope): array
{
$filter = [];
if (!$isAdminForEntity)
{
$filter['@ID'] = $scope->getScopesIdByUser();
}
$filter['@ENTITY_TYPE_ID'] = ($scope->getEntityTypeIdMap()[$entityTypeId] ?? [$entityTypeId]);
if ($excludeEmptyAccessCode)
{
$filter['!=ACCESS_CODE'] = '';
}
return $filter;
}
public function prepareEntityEditorFilter(string $entityTypeId, bool $isAdminForEntity, Scope $scope): array
{
$filter['!=ACCESS_CODE'] = '';
if (!$isAdminForEntity)
{
$filter['@ID'] = $scope->getScopesIdByUser();
}
$filter['@ENTITY_TYPE_ID'] = ($scope->getEntityTypeIdMap()[$entityTypeId] ?? [$entityTypeId]);
return $filter;
}
}

View File

@@ -2,6 +2,7 @@
namespace Bitrix\UI\EntitySelector;
use Bitrix\Main\HttpApplication;
use Bitrix\Main\Loader;
use Bitrix\Main\ModuleManager;
@@ -197,7 +198,9 @@ final class Configuration
}
catch (\ReflectionException $exception)
{
$application = HttpApplication::getInstance();
$exceptionHandler = $application->getExceptionHandler();
$exceptionHandler->writeToLog($exception);
}
return null;

View File

@@ -9,6 +9,11 @@ class EntityEditorConfiguration
protected $categoryName;
protected int $userId;
private const AVAILABLE_SCOPE_TYPES = [
'on_add',
'on_update',
];
public static function canEditOtherSettings(): bool
{
return Main\Engine\CurrentUser::get()->canDoOperation('edit_other_settings');
@@ -59,18 +64,24 @@ class EntityEditorConfiguration
return "{$configID}_opts";
}
public function getScope($configID)
public function getScope($configID, bool $scopeNamePrepared = false)
{
if (!$this->userId)
{
return EntityEditorConfigScope::UNDEFINED;
}
$scopeName = $configID;
if (!$scopeNamePrepared)
{
$scopeName = $this->prepareScopeName($configID);
}
return \CUserOptions::GetOption(
$this->getCategoryName(),
$this->prepareScopeName($configID),
$scopeName,
EntityEditorConfigScope::UNDEFINED,
$this->userId
$this->userId,
);
}
@@ -176,11 +187,12 @@ class EntityEditorConfiguration
//todo check what to do with options for custom scopes
}
}
public function reset($configID, array $params)
public function reset($configID, array $params): void
{
$categoryName = $this->getCategoryName();
$scope = isset($params['scope'])? mb_strtoupper($params['scope']) : EntityEditorConfigScope::UNDEFINED;
$scope = isset($params['scope']) ? mb_strtoupper($params['scope']) : EntityEditorConfigScope::UNDEFINED;
if(!EntityEditorConfigScope::isDefined($scope))
{
$scope = EntityEditorConfigScope::PERSONAL;
@@ -196,22 +208,28 @@ class EntityEditorConfiguration
$categoryName,
$this->prepareName($configID, $scope),
true,
0
0,
);
\CUserOptions::DeleteOption(
$categoryName,
static::prepareOptionsName($configID, $scope),
true,
0
0,
);
}
else
{
$scopeName = $this->prepareScopeName($configID);
if (isset($params['type']) && $this->isAvailableScopeType((string)$params['type']))
{
$scopeName .= '_' . $params['type'];
}
if($forAllUsers)
{
\CUserOptions::DeleteOptionsByName($categoryName, $this->prepareName($configID, $scope));
\CUserOptions::DeleteOptionsByName($categoryName, static::prepareOptionsName($configID, $scope));
\CUserOptions::DeleteOptionsByName($categoryName, $this->prepareScopeName($configID));
\CUserOptions::DeleteOptionsByName($categoryName, $scopeName);
}
elseif ($this->userId)
{
@@ -220,15 +238,16 @@ class EntityEditorConfiguration
\CUserOptions::SetOption(
$categoryName,
$this->prepareScopeName($configID),
$scopeName,
EntityEditorConfigScope::PERSONAL,
false,
$this->userId
$this->userId,
);
}
}
}
public function setScope($configID, $scope)
{
if(!EntityEditorConfigScope::isDefined($scope))
@@ -241,19 +260,38 @@ class EntityEditorConfiguration
\CUserOptions::SetOption($this->getCategoryName(), $this->prepareScopeName($configID), $scope, false, $this->userId);
}
}
public function forceCommonScopeForAll($configID)
public function forceCommonScopeForAll(string $configID, string $moduleId, ?string $type = null): void
{
if(!self::canEditOtherSettings())
$scopeNames = [
$this->prepareScopeName($configID),
];
if ($type === 'on_add')
{
return;
$scopeNames[] = Scope::getScopeNameOnAdd($moduleId, $configID);
}
if ($type === 'on_update')
{
$scopeNames[] = Scope::getScopeNameOnUpdate($moduleId, $configID);
}
$scopeNames = array_unique($scopeNames);
$categoryName = $this->getCategoryName();
\CUserOptions::DeleteOptionsByName(
$categoryName,
$this->prepareName($configID, EntityEditorConfigScope::PERSONAL)
$this->prepareName($configID, EntityEditorConfigScope::PERSONAL),
);
\CUserOptions::DeleteOptionsByName($categoryName, $this->prepareScopeName($configID));
foreach ($scopeNames as $scopeName)
{
\CUserOptions::DeleteOptionsByName($categoryName, $scopeName);
}
}
private function isAvailableScopeType(string $type): bool
{
return in_array($type, self::AVAILABLE_SCOPE_TYPES, true);
}
}

View File

@@ -156,7 +156,7 @@ class FeedbackForm
*/
private function findFormByZone(array $forms): array
{
$zone = $this->getZone();
$zone = $this->getZone() ?? $this->getDefaultZone();
$found = array_filter($forms, static function ($form) use ($zone)
{
return in_array($zone, $form->zones, true);
@@ -219,9 +219,9 @@ class FeedbackForm
$this->currentForm = $form;
}
protected function getZone(): string
protected function getZone(): ?string
{
return Application::getInstance()->getLicense()->getRegion() ?? $this->getDefaultZone();
return Application::getInstance()->getLicense()->getRegion();
}
private function getDefaultZone(): string
@@ -241,9 +241,9 @@ class FeedbackForm
private function isCis(): bool
{
$region = Application::getInstance()->getLicense()->getRegion();
$zone = $this->getZone() ?? '';
return in_array($region, ['ru', 'by', 'kz', 'uz']);
return in_array($zone, ['ru', 'by', 'kz', 'uz']);
}
public static function getCisZones(): array

View File

@@ -0,0 +1,56 @@
<?php
namespace Bitrix\UI\Form;
/**
* The FormProvider class provides a list of partner feedback forms with region zones, form IDs, languages, and security keys.
*
* For displaying partner feedback forms on the client side, use the PartnerForm class (see ui/partnerform/src/PartnerForm.js).
*
* Each form is associated with one or more zones (regions), a language code, and a unique security key.
* The list is intended for localized feedback widgets or CRM forms.
*
* @package Bitrix\UI\Form
*/
class FormProvider
{
/**
* Returns a list of feedback forms with their respective region zones, form IDs, languages, and security keys.
*
* Each form is associated with one or more zones (regions), a language code, and a unique security key.
* These forms are typically used to display localized feedback widgets or CRM forms.
* @return array[]
*/
public function getPartnerFormList(): array
{
$westZones = \Bitrix\UI\Form\FeedbackForm::getWestZones();
return [
['zones' => $westZones, 'id' => 34, 'lang' => 'en', 'sec' => '940xiq'],
['zones' => $westZones, 'id' => 58, 'lang' => 'br', 'sec' => 'db0u10'],
['zones' => $westZones, 'id' => 82, 'lang' => 'la', 'sec' => 'f5cg69'],
['zones' => $westZones, 'id' => 84, 'lang' => 'pl', 'sec' => 'xaq93h'],
['zones' => $westZones, 'id' => 86, 'lang' => 'tr', 'sec' => '6xbruy'],
['zones' => $westZones, 'id' => 88, 'lang' => 'vn', 'sec' => '8g591b'],
['zones' => $westZones, 'id' => 90, 'lang' => 'it', 'sec' => 'mc95rg'],
['zones' => $westZones, 'id' => 92, 'lang' => 'de', 'sec' => 'uokzr7'],
['zones' => $westZones, 'id' => 94, 'lang' => 'fr', 'sec' => 'gyzkzb'],
['zones' => $westZones, 'id' => 96, 'lang' => 'ms', 'sec' => 'zaetk0'],
['zones' => $westZones, 'id' => 108, 'lang' => 'id', 'sec' => '3gs5vj'],
['zones' => $westZones, 'id' => 98, 'lang' => 'sc', 'sec' => '532jfn'],
['zones' => $westZones, 'id' => 100, 'lang' => 'th', 'sec' => '1q4cis'],
['zones' => $westZones, 'id' => 102, 'lang' => 'ja', 'sec' => 'c84t56'],
['zones' => $westZones, 'id' => 104, 'lang' => 'tc', 'sec' => '6s7a1m'],
['zones' => $westZones, 'id' => 110, 'lang' => 'ar', 'sec' => 'zfkgno'],
['zones' => $westZones, 'id' => 106, 'lang' => 'ru', 'sec' => 'j1h7w4'],
['zones' => $westZones, 'id' => 34, 'lang' => 'ua', 'sec' => '940xiq'],
['zones' => $westZones, 'id' => 34, 'lang' => 'kz', 'sec' => '940xiq'],
['zones' => ['ru'], 'id' => 3282, 'lang' => 'ru', 'sec' => '8x81lp'],
['zones' => ['by'], 'id' => 3, 'lang' => 'ru', 'sec' => 'kde6g5'],
['zones' => ['kz'], 'id' => 4, 'lang' => 'kz', 'sec' => 'id3qfm'],
['zones' => ['kz'], 'id' => 2, 'lang' => 'ru', 'sec' => 'po3skw'],
['zones' => ['kz'], 'id' => 6, 'lang' => 'en', 'sec' => 'gozt5o'],
['zones' => ['uz'], 'id' => 7, 'lang' => 'ru', 'sec' => 'xjkuqu'],
];
}
}

View File

@@ -2,44 +2,12 @@
namespace Bitrix\UI\Form;
/* @deprecated use Bitrix\UI\Form\FormProvider instead */
class FormsProvider
{
/* @deprecated use (new Bitrix\UI\Form\FormProvider)->getPartnerFormList() instead */
public static function getForms(): array
{
$westZones = \Bitrix\UI\Form\FeedbackForm::getWestZones();
return [
['zones' => $westZones, 'id' => 34, 'lang' => 'en', 'sec' => '940xiq'],
['zones' => $westZones, 'id' => 58, 'lang' => 'pt', 'sec' => 'db0u10'],
['zones' => $westZones, 'id' => 82, 'lang' => 'es', 'sec' => 'f5cg69'],
['zones' => $westZones, 'id' => 84, 'lang' => 'pl', 'sec' => 'xaq93h'],
['zones' => $westZones, 'id' => 86, 'lang' => 'tr', 'sec' => '6xbruy'],
['zones' => $westZones, 'id' => 88, 'lang' => 'vn', 'sec' => '8g591b'],
['zones' => $westZones, 'id' => 90, 'lang' => 'it', 'sec' => 'mc95rg'],
['zones' => $westZones, 'id' => 92, 'lang' => 'de', 'sec' => 'uokzr7'],
['zones' => $westZones, 'id' => 94, 'lang' => 'fr', 'sec' => 'gyzkzb'],
['zones' => $westZones, 'id' => 96, 'lang' => 'ms', 'sec' => 'zaetk0'],
['zones' => $westZones, 'id' => 108, 'lang' => 'id', 'sec' => '3gs5vj'],
['zones' => $westZones, 'id' => 98, 'lang' => 'sc', 'sec' => '532jfn'],
['zones' => $westZones, 'id' => 100, 'lang' => 'th', 'sec' => '1q4cis'],
['zones' => $westZones, 'id' => 102, 'lang' => 'jp', 'sec' => 'c84t56'],
['zones' => $westZones, 'id' => 104, 'lang' => 'tc', 'sec' => '6s7a1m'],
['zones' => $westZones, 'id' => 110, 'lang' => 'ar', 'sec' => 'zfkgno'],
['zones' => $westZones, 'id' => 106, 'lang' => 'ru', 'sec' => 'j1h7w4'],
['zones' => $westZones, 'id' => 34, 'lang' => 'ua', 'sec' => '940xiq'],
['zones' => $westZones, 'id' => 34, 'lang' => 'kz', 'sec' => '940xiq'],
['zones' => ['ru'], 'id' => 3282, 'sec' => '8x81lp'],
['zones' => ['by'], 'id' => 3, 'sec' => 'kde6g5'],
['zones' => ['kz'], 'id' => 4, 'lang' => 'kz', 'sec' => 'id3qfm'],
['zones' => ['kz'], 'id' => 2, 'lang' => 'ru', 'sec' => 'po3skw'],
['zones' => ['kz'], 'id' => 6, 'lang' => 'en', 'sec' => 'gozt5o'],
['zones' => ['uz'], 'id' => 7, 'sec' => 'xjkuqu'],
];
return (new FormProvider())->getPartnerFormList();
}
public static function getFormsForJS() : string
{
// Convert the array to JSON and replace the quotes around keys for UI.Feedback.Form compatibility
return preg_replace('/"([a-zA-Z0-9_]+)":/', '$1:',\Bitrix\Main\Web\Json::encode(self::getForms()));
}
}

View File

@@ -207,6 +207,7 @@ class Comment
'FROM_USER_ID' => $fromUserId,
'NOTIFY_TYPE' => IM_NOTIFY_FROM,
'NOTIFY_MODULE' => Driver::MODULE_ID,
'NOTIFY_EVENT' => 'mention',
'NOTIFY_TAG' => 'RPA|MESSAGE_TIMELINE_MENTION|' . $id,
'NOTIFY_MESSAGE' => $message,
]);