32 private $isStarted =
false;
34 private static $instance;
35 private static $featuresCache = [];
37 private $services = [];
38 private $debugServices = [];
39 private $workflows = [];
41 private $loadedActivities = [];
43 private $activityFolders = [];
44 private $workflowChains = [];
52 private function __construct()
54 $this->workflows =
array();
55 $this->services =
array(
56 "SchedulerService" =>
null,
57 "StateService" =>
null,
58 "TrackingService" =>
null,
59 "TaskService" =>
null,
60 "HistoryService" =>
null,
61 "DocumentService" =>
null,
62 "AnalyticsService" =>
null,
63 "UserService" =>
null,
65 $this->loadedActivities =
array();
66 $this->activityFolders =
array(
67 $_SERVER[
"DOCUMENT_ROOT"].
"/local/activities",
68 $_SERVER[
"DOCUMENT_ROOT"].
"/local/activities/custom",
70 $_SERVER[
"DOCUMENT_ROOT"].BX_ROOT.
"/activities/bitrix",
71 $_SERVER[
"DOCUMENT_ROOT"].BX_ROOT.
"/modules/bizproc/activities",
82 if (!isset(self::$instance))
85 self::$instance =
new $c;
86 self::$instance->startRuntime();
89 return self::$instance;
94 trigger_error(
'Clone in not allowed.', E_USER_ERROR);
101 return $this->GetService(
$matches[1].
'Service');
114 $featureName = (string)$featureName;
115 if ($featureName ===
'')
117 $featureName =
'bizproc';
120 if (!isset(static::$featuresCache[$featureName]))
122 static::$featuresCache[$featureName] = (
124 || \Bitrix\Bitrix24\Feature::isFeatureEnabled($featureName)
128 return static::$featuresCache[$featureName];
139 if ($this->isStarted)
145 foreach ($serviceManager->getAllServiceNames() as $serviceName)
147 $compatibleServiceName = mb_strtoupper($serviceName[0]) . mb_substr($serviceName, 1);
148 if (!$this->services[$compatibleServiceName])
150 $this->services[$compatibleServiceName] = $serviceManager->getService($serviceName);
152 if (!isset($this->debugServices[$compatibleServiceName]) && $serviceManager->hasDebugService($serviceName))
154 $this->debugServices[$compatibleServiceName] = $serviceManager->getDebugService($serviceName);
157 $this->services[$compatibleServiceName]->start($this);
160 $this->isStarted =
true;
169 if (!$this->isStarted)
174 foreach ($this->services as $serviceId =>
$service)
179 $this->isStarted =
false;
197 public function createWorkflow($workflowTemplateId, $documentId, $workflowParameters =
array(), $parentWorkflow =
null)
199 $workflowTemplateId = intval($workflowTemplateId);
200 if ($workflowTemplateId <= 0)
202 throw new Exception(
"workflowTemplateId");
205 $arDocumentId = CBPHelper::ParseDocumentId($documentId);
207 $limit = \Bitrix\Main\Config\Option::get(
"bizproc",
"limit_simultaneous_processes",
"0");
208 $ignoreLimits = !empty($workflowParameters[CBPDocument::PARAM_IGNORE_SIMULTANEOUS_PROCESSES_LIMIT]);
209 if (!$ignoreLimits && intval($limit) > 0)
211 if (CBPStateService::CountDocumentWorkflows($documentId) >= $limit)
213 throw new Exception(
GetMessage(
"BPCGDOC_LIMIT_SIMULTANEOUS_PROCESSES", [
"#NUM#" => $limit]));
216 unset($workflowParameters[CBPDocument::PARAM_IGNORE_SIMULTANEOUS_PROCESSES_LIMIT]);
218 if (!$this->isStarted)
220 $this->StartRuntime();
227 $this->addWorkflowToChain($workflowId, $parentWorkflow);
228 if ($this->checkWorkflowRecursion($workflowId, $workflowTemplateId))
230 throw new Exception(
GetMessage(
"BPCGDOC_WORKFLOW_RECURSION_LOCK"));
234 $workflow =
new CBPWorkflow($workflowId, $this);
239 [$rootActivity, $workflowVariablesTypes, $workflowParametersTypes] = $loader->LoadWorkflow($workflowTemplateId);
240 foreach ($workflowParametersTypes as $parameterName => $parametersProperty)
242 if (!array_key_exists($parameterName, $workflowParameters))
244 $workflowParameters[$parameterName] = $parametersProperty[
'Default'] ??
null;
250 throw new Exception(
"EmptyRootActivity");
253 foreach(
GetModuleEvents(
"bizproc",
"OnCreateWorkflow",
true) as $arEvent)
255 ExecuteModuleEventEx($arEvent, [$workflowTemplateId, $documentId, &$workflowParameters, $workflowId]);
258 $workflow->initialize($rootActivity, $arDocumentId, $workflowParameters, $workflowVariablesTypes, $workflowParametersTypes, $workflowTemplateId);
261 if (isset($workflowParameters[CBPDocument::PARAM_TAGRET_USER]))
263 $starterUserId = (int)CBPHelper::stripUserPrefix($workflowParameters[CBPDocument::PARAM_TAGRET_USER]);
266 $this->getStateService()->addWorkflow($workflowId, $workflowTemplateId, $arDocumentId, $starterUserId);
268 $this->workflows[$workflowId] = $workflow;
275 $complexDocumentId = CBPHelper::ParseDocumentId($documentId);
277 $workflowId = $workflowParameters[CBPDocument::PARAM_PRE_GENERATED_WORKFLOW_ID] ?? static::generateWorkflowId();
280 $loader = CBPWorkflowTemplateLoader::GetLoader();
282 [$rootActivity, $workflowVariablesTypes, $workflowParametersTypes] = $loader->LoadWorkflow(
$templateId);
284 if (is_null($rootActivity))
286 throw new Exception(
'Empty root activity');
289 foreach(
GetModuleEvents(
"bizproc",
"OnCreateWorkflow",
true) as $arEvent)
294 $workflow->Initialize(
298 $workflowVariablesTypes,
299 $workflowParametersTypes,
304 if (isset($workflowParameters[CBPDocument::PARAM_TAGRET_USER]))
306 $starterUserId = intval(mb_substr($workflowParameters[CBPDocument::PARAM_TAGRET_USER], mb_strlen(
"user_")));
310 ->GetService(
"StateService")
311 ->AddWorkflow($workflowId,
$templateId, $complexDocumentId, $starterUserId)
314 $this->workflows[$workflowId] = $workflow;
329 if ($workflowId ==
'')
332 if (!$this->isStarted)
333 $this->StartRuntime();
335 if (array_key_exists($workflowId, $this->workflows))
336 return $this->workflows[$workflowId];
339 $rootActivity = $workflow->getPersister()->LoadWorkflow($workflowId, $silent);
341 if ($rootActivity ==
null)
343 throw new Exception(
"Empty root activity");
346 $workflow->reload($rootActivity);
348 $this->workflows[$workflowId] = $workflow;
359 return array_key_exists($workflowId, $this->workflows);
364 return $this->workflows;
369 if (WorkflowInstanceTable::isDebugWorkflow($workflowId))
385 unset($this->workflows[$workflowId]);
389 public function onDocumentDelete(
array $documentId): void
392 foreach ($this->workflows as $workflow)
394 if (CBPHelper::isEqualDocument($documentId, $workflow->getDocumentId()))
396 $workflow->abandon();
403 return uniqid(
"",
true);
416 if (array_key_exists(
$name, $this->services))
418 return $this->services[
$name];
426 if (array_key_exists(
$name, $this->debugServices))
428 return $this->debugServices[
$name];
442 if ($this->isStarted)
443 throw new Exception(
"Runtime is started");
447 throw new Exception(
"Service code is empty");
451 if (array_key_exists(
$name, $this->services))
452 throw new Exception(
"Service is already exists");
468 $runtime = CBPRuntime::GetRuntime();
469 $workflow = $runtime->getWorkflow($workflowId);
474 $documentExists =
false;
478 $documentService = $runtime->getDocumentService();
479 $documentExists = $documentService->isDocumentExists($workflow->getDocumentId());
482 if (!$stateExists || !$documentExists)
484 $workflow->terminate();
489 $workflow->sendExternalEvent($eventName, (
array)$arEventParameters);
502 if (in_array(
$code, $this->loadedActivities))
505 if (preg_match(
"#[^a-zA-Z0-9_]#",
$code))
511 if (mb_substr(
$code, 0, 3) ==
"cbp")
515 if (in_array(
$code, $this->loadedActivities))
520 foreach ($this->activityFolders as $folder)
522 if (file_exists($folder.
"/".
$code.
"/".
$code.
".php") && is_file($folder.
"/".
$code.
"/".
$code.
".php"))
524 $filePath = $folder.
"/".
$code.
"/".
$code.
".php";
525 $fileDir = $folder.
"/".
$code;
532 $this->LoadActivityLocalization($fileDir,
$code.
".php");
533 include_once($filePath);
534 $this->loadedActivities[] =
$code;
538 if (mb_strpos(
$code, static::REST_ACTIVITY_PREFIX) === 0)
540 $code = mb_substr(
$code, mb_strlen(static::REST_ACTIVITY_PREFIX));
542 'select' =>
array(
'ID'),
546 eval(
'class CBP'.static::REST_ACTIVITY_PREFIX.
$code.
' extends CBPRestActivity {const REST_ACTIVITY_ID = '.(
$activity?
$activity[
'ID'] : 0).
';}');
547 $this->loadedActivities[] = static::REST_ACTIVITY_PREFIX.$code;
556 if (preg_match(
"#[^a-zA-Z0-9_]#",
$code))
562 if (mb_substr(
$code, 0, 3) ==
"cbp")
569 foreach ($this->activityFolders as $folder)
571 if (file_exists($folder.
"/".
$code.
"/".
$code.
".php") && is_file($folder.
"/".
$code.
"/".
$code.
".php"))
573 $filePath = $folder.
"/".
$code.
"/.description.php";
574 $fileDir = $folder.
"/".
$code;
581 $arActivityDescription =
array();
582 if (file_exists($filePath) && is_file($filePath))
584 $this->LoadActivityLocalization($fileDir,
".description.php");
587 $arActivityDescription[
"PATH_TO_ACTIVITY"] = $fileDir;
589 return $arActivityDescription;
592 if (mb_strpos(
$code, static::REST_ACTIVITY_PREFIX) === 0)
594 $code = mb_substr(
$code, mb_strlen(static::REST_ACTIVITY_PREFIX));
628 foreach(
$description[
'ADDITIONAL_RESULT'] as $propertyKey)
630 if (isset(
$activity[
'Properties'][$propertyKey]) && is_array(
$activity[
'Properties'][$propertyKey]))
632 foreach (
$activity[
'Properties'][$propertyKey] as $id => $prop)
642 private function loadActivityLocalization(
$path, $file,
$lang =
false)
649 $path = str_replace(
"\\",
"/", $activityPath);
652 $filePath = str_replace(
"\\",
"/", $filePath);
653 $filePath = ltrim($filePath,
"/");
655 if (file_exists(
$path.$filePath) && is_file(
$path.$filePath))
664 $path = $this->GetResourceFilePath($activityPath, $filePath);
669 foreach ($arParameters as
$key => $value)
672 $this->LoadActivityLocalization(
$path[1], $filePath);
688 $arProcessedDirs = [];
689 foreach ($this->activityFolders as $folder)
691 if (is_dir($folder) &&
$handle = opendir($folder))
699 if (!is_dir($folder.
"/".
$dir))
703 $dirKey = mb_strtolower(
$dir);
704 if (array_key_exists($dirKey, $arProcessedDirs))
708 if (!file_exists($folder.
"/".
$dir.
"/.description.php"))
713 $arActivityDescription = [];
714 $this->LoadActivityLocalization($folder.
"/".
$dir,
".description.php");
715 include($folder.
"/".
$dir.
"/.description.php");
718 $activityType = (
array)$arActivityDescription[
'TYPE'];
719 foreach ($activityType as
$i => $aType)
721 $activityType[
$i] = mb_strtolower(trim($aType));
724 if (in_array(
$type, $activityType,
true))
726 $arProcessedDirs[$dirKey] = $arActivityDescription;
727 $arProcessedDirs[$dirKey][
"PATH_TO_ACTIVITY"] = $folder.
"/".
$dir;
729 isset($arActivityDescription[
'FILTER']) && is_array($arActivityDescription[
'FILTER'])
733 $arProcessedDirs[$dirKey][
'EXCLUDED'] =
true;
742 if (
$type ==
'activity')
744 $arProcessedDirs = array_merge($arProcessedDirs, $this->
getRestActivities(
false, $documentType));
747 if (
$type ==
'activity' ||
$type ==
'robot_activity')
749 $arProcessedDirs = array_merge($arProcessedDirs, $this->
getRestRobots(
false, $documentType));
752 if (
$type !==
'condition')
757 return $arProcessedDirs;
770 'filter' =>
array(
'=IS_ROBOT' =>
'N'),
775 $result[static::REST_ACTIVITY_PREFIX.$activity[
'INTERNAL_CODE']] = $this->makeRestActivityDescription(
$activity,
$lang, $documentType);
791 'filter' => [
'=IS_ROBOT' =>
'Y'],
792 'cache' => [
'ttl' => 3600],
797 $result[static::REST_ACTIVITY_PREFIX.$activity[
'INTERNAL_CODE']] = $this->makeRestRobotDescription(
$activity,
$lang, $documentType);
805 $pos = mb_strpos($stream,
";");
806 $usedActivities = mb_substr($stream, 0, $pos);
807 $stream = mb_substr($stream, $pos + 1);
809 foreach (explode(
',', $usedActivities) as $activityCode)
811 $this->IncludeActivityFile($activityCode);
814 $classesList = array_map(
819 $this->loadedActivities
823 if (in_array(
'cbpstateactivity', $classesList))
825 $classesList[] = CBPStateEventActivitySubscriber::class;
827 if (in_array(
'cbplistenactivity', $classesList))
829 $classesList[] = CBPListenEventActivitySubscriber::class;
832 $classesList[] = \CBPWorkflow::class;
833 $classesList[] = \CBPRuntime::class;
834 $classesList[] = DebugWorkflow::class;
835 $classesList[] = Bizproc\BaseType\Value\Date::class;
836 $classesList[] = Bizproc\BaseType\Value\DateTime::class;
837 $classesList[] = Main\Type\Date::class;
838 $classesList[] = Main\Type\DateTime::class;
839 $classesList[] = Main\Web\Uri::class;
840 $classesList[] = \DateTime::class;
841 $classesList[] = \DateTimeZone::class;
843 return unserialize($stream, [
'allowed_classes' => $classesList]);
846 private function makeRestActivityDescription(
$activity,
$lang =
false, $documentType =
null)
851 $code = static::REST_ACTIVITY_PREFIX.$activity[
'INTERNAL_CODE'];
853 'NAME' =>
'['.RestActivityTable::getLocalization(
$activity[
'APP_NAME'],
$lang).
'] '
855 'DESCRIPTION' => RestActivityTable::getLocalization(
$activity[
'DESCRIPTION'],
$lang),
856 'TYPE' =>
'activity',
858 'JSCLASS' =>
'BizProcActivity',
864 'PATH_TO_ACTIVITY' =>
'',
869 && !$this->checkActivityFilter(
$activity[
'FILTER'], $documentType)
873 if (!empty(
$activity[
'RETURN_PROPERTIES']))
878 'NAME' => RestActivityTable::getLocalization($property[
'NAME'],
$lang),
879 'TYPE' => isset($property[
'TYPE']) ? $property[
'TYPE'] : \
Bitrix\
Bizproc\FieldType::STRING,
883 if (
$activity[
'USE_SUBSCRIPTION'] !=
'N')
886 'TYPE' => \Bitrix\Bizproc\FieldType::INT,
892 private function makeRestRobotDescription(
$activity,
$lang =
false, $documentType =
null)
897 $code = static::REST_ACTIVITY_PREFIX.$activity[
'INTERNAL_CODE'];
899 'NAME' =>
'['.RestActivityTable::getLocalization(
$activity[
'APP_NAME'],
$lang).
'] '
901 'DESCRIPTION' => RestActivityTable::getLocalization(
$activity[
'DESCRIPTION'],
$lang),
902 'TYPE' =>
array(
'activity',
'robot_activity'),
904 'JSCLASS' =>
'BizProcActivity',
910 'PATH_TO_ACTIVITY' =>
'',
911 'ROBOT_SETTINGS' =>
array(
912 'CATEGORY' =>
'other',
922 if (!empty(
$activity[
'RETURN_PROPERTIES']))
927 'NAME' => RestActivityTable::getLocalization($property[
'NAME'],
$lang),
928 'TYPE' => $property[
'TYPE'] ?? \Bitrix\Bizproc\FieldType::STRING,
929 'OPTIONS' => $property[
'OPTIONS'] ??
null,
933 if (
$activity[
'USE_SUBSCRIPTION'] !==
'N')
937 'TYPE' => \Bitrix\Bizproc\FieldType::INT,
946 $distrName = CBPHelper::getDistrName();
949 if (
$type ===
'MIN_API_VERSION')
951 $minApiVersion = (int)$rules;
952 if ($minApiVersion > self::ACTIVITY_API_VERSION)
960 $found = $this->checkActivityFilterRules($rules, $documentType, $distrName);
961 if ((
$type ===
'INCLUDE' && !$found) || (
$type ===
'EXCLUDE' && $found))
970 private function checkActivityFilterRules($rules, $documentType, $distrName)
972 if (!is_array($rules) || CBPHelper::IsAssociativeArray($rules))
977 foreach ($rules as $rule)
988 foreach ($documentType as
$key => $value)
990 if (!isset($rule[
$key]))
1004 $result = (string)$rule == $distrName;
1015 private function addWorkflowToChain($childId, $parent)
1017 $this->workflowChains[$childId] = $parent;
1021 private function checkWorkflowRecursion($workflowId, $currentTemplateId)
1023 $templates =
array($currentTemplateId);
1024 while (isset($this->workflowChains[$workflowId]))
1026 $parent = $this->workflowChains[$workflowId];
1027 if (in_array($parent[
'templateId'], $templates))
1029 $templates[] = $parent[
'templateId'];
1030 $workflowId = $parent[
'workflowId'];
static includeModule($moduleName)
static loadLanguageFile($file, $language=null, $normalize=true)
static sortByColumn(array &$array, $columns, $callbacks='', $defaultValueIfNotSetValue=null, $preserveKeys=false)
if(!defined('SITE_ID')) $lang