%PDF- %PDF-
Direktori : /home/bitrix/www/bitrix/modules/bizproc/classes/general/ |
Current File : /home/bitrix/www/bitrix/modules/bizproc/classes/general/runtime.php |
<? IncludeModuleLangFile(__FILE__); use \Bitrix\Bizproc\RestActivityTable; /** * Workflow runtime. */ class CBPRuntime { const EXCEPTION_CODE_INSTANCE_NOT_FOUND = 404; const EXCEPTION_CODE_INSTANCE_LOCKED = 423; const REST_ACTIVITY_PREFIX = 'rest_'; private $isStarted = false; /** @var CBPRuntime $instance*/ private static $instance; private static $featuresCache = array(); private $arServices = array( "SchedulerService" => null, "StateService" => null, "TrackingService" => null, "TaskService" => null, "HistoryService" => null, "DocumentService" => null, ); private $arWorkflows = array(); private $arLoadedActivities = array(); private $arActivityFolders = array(); private $workflowChains = array(); /********************* SINGLETON PATTERN **************************************************/ /** * Private constructor prevents from instantiating this class. Singleton pattern. * */ private function __construct() { $this->isStarted = false; $this->arWorkflows = array(); $this->arServices = array( "SchedulerService" => null, "StateService" => null, "TrackingService" => null, "TaskService" => null, "HistoryService" => null, "DocumentService" => null, ); $this->arLoadedActivities = array(); $this->arActivityFolders = array( $_SERVER["DOCUMENT_ROOT"]."/local/activities", $_SERVER["DOCUMENT_ROOT"]."/local/activities/custom", $_SERVER["DOCUMENT_ROOT"].BX_ROOT."/activities/custom", $_SERVER["DOCUMENT_ROOT"].BX_ROOT."/activities/bitrix", $_SERVER["DOCUMENT_ROOT"].BX_ROOT."/modules/bizproc/activities", ); /* Experimental activity autoloader $runtime = $this; spl_autoload_register(function ($name) use ($runtime) { $name = strtolower($name); if (strpos($name, 'cbp') === 0) { $runtime->IncludeActivityFile($name); } }); */ } /** * Static method returns runtime object. Singleton pattern. * * @param bool $autoStart Starts runtime. * @return CBPRuntime */ public static function GetRuntime($autoStart = false) { if (!isset(self::$instance)) { $c = __CLASS__; self::$instance = new $c; } if ($autoStart) { self::$instance->StartRuntime(); } return self::$instance; } public function __clone() { trigger_error('Clone in not allowed.', E_USER_ERROR); } /** * Method checks if feature is enabled. * @param string $featureName Feature name. Checks default if empty. * @return bool */ public static function isFeatureEnabled($featureName = '') { if (!CModule::IncludeModule('bitrix24')) return true; $featureName = (string)$featureName; if ($featureName === '') $featureName = 'bizproc'; if (!isset(static::$featuresCache[$featureName])) static::$featuresCache[$featureName] = \Bitrix\Bitrix24\Feature::isFeatureEnabled($featureName); return static::$featuresCache[$featureName]; } /********************* START / STOP RUNTIME **************************************************/ /** * Public method starts runtime * */ public function StartRuntime() { if ($this->isStarted) return; if ($this->arServices["SchedulerService"] == null) $this->arServices["SchedulerService"] = new CBPSchedulerService(); if ($this->arServices["StateService"] == null) $this->arServices["StateService"] = new CBPStateService(); if ($this->arServices["TrackingService"] == null) $this->arServices["TrackingService"] = new CBPTrackingService(); if ($this->arServices["TaskService"] == null) $this->arServices["TaskService"] = new CBPTaskService(); if ($this->arServices["HistoryService"] == null) $this->arServices["HistoryService"] = new CBPHistoryService(); if ($this->arServices["DocumentService"] == null) $this->arServices["DocumentService"] = new CBPDocumentService(); foreach ($this->arServices as $serviceId => $service) $service->Start($this); $this->isStarted = true; } /** * Public method stops runtime * */ public function StopRuntime() { if (!$this->isStarted) return; /** @var CBPWorkflow $workflow */ foreach ($this->arWorkflows as $key => $workflow) $workflow->OnRuntimeStopped(); foreach ($this->arServices as $serviceId => $service) $service->Stop(); $this->isStarted = false; } /******************* PROCESS WORKFLOWS *********************************************************/ /** * Creates new workflow instance from the specified template. * * @param int $workflowTemplateId - ID of the workflow template * @param array $documentId - ID of the document * @param mixed $workflowParameters - Optional parameters of the created workflow instance * @param array|null $parentWorkflow - Parent Workflow information. * @return CBPWorkflow * @throws CBPArgumentNullException * @throws CBPArgumentOutOfRangeException * @throws Exception * @throws \Bitrix\Main\ArgumentNullException */ public function CreateWorkflow($workflowTemplateId, $documentId, $workflowParameters = array(), $parentWorkflow = null) { $workflowTemplateId = intval($workflowTemplateId); if ($workflowTemplateId <= 0) throw new Exception("workflowTemplateId"); $arDocumentId = CBPHelper::ParseDocumentId($documentId); $limit = \Bitrix\Main\Config\Option::get("bizproc", "limit_simultaneous_processes", "0"); $ignoreLimits = !empty($workflowParameters[CBPDocument::PARAM_IGNORE_SIMULTANEOUS_PROCESSES_LIMIT]); if (!$ignoreLimits && intval($limit) > 0) { if (CBPStateService::CountDocumentWorkflows($documentId) >= $limit) throw new Exception(GetMessage("BPCGDOC_LIMIT_SIMULTANEOUS_PROCESSES", array("#NUM#" => $limit))); } unset($workflowParameters[CBPDocument::PARAM_IGNORE_SIMULTANEOUS_PROCESSES_LIMIT]); if (!$this->isStarted) $this->StartRuntime(); $workflowId = uniqid("", true); if ($parentWorkflow) { $this->addWorkflowToChain($workflowId, $parentWorkflow); if ($this->checkWorkflowRecursion($workflowId, $workflowTemplateId)) throw new Exception(GetMessage("BPCGDOC_WORKFLOW_RECURSION_LOCK")); } $workflow = new CBPWorkflow($workflowId, $this); $loader = CBPWorkflowTemplateLoader::GetLoader(); list($rootActivity, $workflowVariablesTypes, $workflowParametersTypes) = $loader->LoadWorkflow($workflowTemplateId); if ($rootActivity == null) throw new Exception("EmptyRootActivity"); //if (!is_a($rootActivity, "IBPRootActivity")) // throw new Exception("RootActivityIsNotAIBPRootActivity"); foreach(GetModuleEvents("bizproc", "OnCreateWorkflow", true) as $arEvent) ExecuteModuleEventEx($arEvent, array($workflowTemplateId, $documentId, &$workflowParameters)); $workflow->Initialize($rootActivity, $arDocumentId, $workflowParameters, $workflowVariablesTypes, $workflowParametersTypes, $workflowTemplateId); $starterUserId = 0; if (isset($workflowParameters[CBPDocument::PARAM_TAGRET_USER])) { $starterUserId = intval(substr($workflowParameters[CBPDocument::PARAM_TAGRET_USER], strlen("user_"))); } $this->GetService("StateService")->AddWorkflow($workflowId, $workflowTemplateId, $arDocumentId, $starterUserId); $this->arWorkflows[$workflowId] = $workflow; return $workflow; } /** * Returns existing workflow instance by its ID * * @param string $workflowId ID of the workflow instance. * @param bool $silent * @return CBPWorkflow * @throws Exception */ public function GetWorkflow($workflowId, $silent = false) { if (strlen($workflowId) <= 0) throw new Exception("workflowId"); if (!$this->isStarted) $this->StartRuntime(); if (array_key_exists($workflowId, $this->arWorkflows)) return $this->arWorkflows[$workflowId]; $workflow = new CBPWorkflow($workflowId, $this); $persister = CBPWorkflowPersister::GetPersister(); $rootActivity = $persister->LoadWorkflow($workflowId, $silent); if ($rootActivity == null) throw new Exception("Empty root activity"); $workflow->Reload($rootActivity); $this->arWorkflows[$workflowId] = $workflow; return $workflow; } public function onWorkflowStatusChanged($workflowId, $status) { if ( $status === \CBPWorkflowStatus::Completed || $status === \CBPWorkflowStatus::Terminated || $status === \CBPWorkflowStatus::Suspended ) { unset($this->arWorkflows[$workflowId]); } } /******************* SERVICES *********************************************************/ /** * Returns service instance by its code. * * @param mixed $name - Service code. * @return mixed|CBPSchedulerService|CBPStateService|CBPTrackingService|CBPTaskService|CBPHistoryService|CBPDocumentService - Service instance or null if service is not found. */ public function GetService($name) { if (array_key_exists($name, $this->arServices)) return $this->arServices[$name]; return null; } /** * Adds new service to runtime. Runtime should be stopped. * * @param string $name - Service code. * @param CBPRuntimeService $service - Service object. */ public function AddService($name, CBPRuntimeService $service) { if ($this->isStarted) throw new Exception("Runtime is started"); $name = trim($name); if (strlen($name) <= 0) throw new Exception("Service code is empty"); if (!$service) throw new Exception("Service is null"); if (array_key_exists($name, $this->arServices)) throw new Exception("Service is already exists"); $this->arServices[$name] = $service; } /******************* EVENTS ******************************************************************/ /** * Static method transfer event to the specified workflow instance. * * @param mixed $workflowId - ID of the workflow instance. * @param mixed $eventName - Event name. * @param mixed $arEventParameters - Event parameters. */ public static function SendExternalEvent($workflowId, $eventName, $arEventParameters = array()) { $runtime = CBPRuntime::GetRuntime(); $workflow = $runtime->GetWorkflow($workflowId); if ($workflow) { $workflow->SendExternalEvent($eventName, $arEventParameters); } } /******************* UTILITIES ***************************************************************/ /** * Includes activity file by activity code. * * @param string $code - Activity code. */ public function IncludeActivityFile($code) { if (in_array($code, $this->arLoadedActivities)) return true; if (preg_match("#[^a-zA-Z0-9_]#", $code)) return false; if (strlen($code) <= 0) return false; $code = strtolower($code); if (substr($code, 0, 3) == "cbp") $code = substr($code, 3); if (strlen($code) <= 0) return false; if (in_array($code, $this->arLoadedActivities)) return true; $filePath = ""; $fileDir = ""; foreach ($this->arActivityFolders as $folder) { if (file_exists($folder."/".$code."/".$code.".php") && is_file($folder."/".$code."/".$code.".php")) { $filePath = $folder."/".$code."/".$code.".php"; $fileDir = $folder."/".$code; break; } } if (strlen($filePath) > 0) { $this->LoadActivityLocalization($fileDir, $code.".php"); include_once($filePath); $this->arLoadedActivities[] = $code; return true; } if (strpos($code, static::REST_ACTIVITY_PREFIX) === 0) { $code = substr($code, strlen(static::REST_ACTIVITY_PREFIX)); $result = RestActivityTable::getList(array( 'select' => array('ID'), 'filter' => array('=INTERNAL_CODE' => $code) )); $activity = $result->fetch(); eval('class CBP'.static::REST_ACTIVITY_PREFIX.$code.' extends CBPRestActivity {const REST_ACTIVITY_ID = '.($activity? $activity['ID'] : 0).';}'); $this->arLoadedActivities[] = static::REST_ACTIVITY_PREFIX.$code; return true; } return false; } public function GetActivityDescription($code, $lang = false) { if (preg_match("#[^a-zA-Z0-9_]#", $code)) return null; if (strlen($code) <= 0) return null; $code = strtolower($code); if (substr($code, 0, 3) == "cbp") $code = substr($code, 3); if (strlen($code) <= 0) return null; $filePath = ""; $fileDir = ""; foreach ($this->arActivityFolders as $folder) { if (file_exists($folder."/".$code."/".$code.".php") && is_file($folder."/".$code."/".$code.".php")) { $filePath = $folder."/".$code."/.description.php"; $fileDir = $folder."/".$code; break; } } if (strlen($filePath) > 0) { $arActivityDescription = array(); if (file_exists($filePath) && is_file($filePath)) { $this->LoadActivityLocalization($fileDir, ".description.php"); include($filePath); } $arActivityDescription["PATH_TO_ACTIVITY"] = $fileDir; return $arActivityDescription; } if (strpos($code, static::REST_ACTIVITY_PREFIX) === 0) { $code = substr($code, strlen(static::REST_ACTIVITY_PREFIX)); $result = RestActivityTable::getList(array( 'filter' => array('=INTERNAL_CODE' => $code) )); $activity = $result->fetch(); if ($activity) { return $this->makeRestActivityDescription($activity, $lang); } } return null; } private function LoadActivityLocalization($path, $file, $lang = false) { global $MESS; if ($lang === false) $lang = LANGUAGE_ID; $p = $path."/lang/".$lang."/".$file; $defaultLang = \Bitrix\Main\Localization\Loc::getDefaultLang($lang); $pe = $path."/lang/".$defaultLang."/".$file; if (file_exists($p) && is_file($p)) include($p); elseif (file_exists($pe) && is_file($pe)) include($pe); } public function GetResourceFilePath($activityPath, $filePath) { $path = str_replace("\\", "/", $activityPath); $path = substr($path, 0, strrpos($path, "/") + 1); $filePath = str_replace("\\", "/", $filePath); $filePath = ltrim($filePath, "/"); if (file_exists($path.$filePath) && is_file($path.$filePath)) return array($path.$filePath, $path); else return null; } public function ExecuteResourceFile($activityPath, $filePath, $arParameters = array()) { $result = null; $path = $this->GetResourceFilePath($activityPath, $filePath); if ($path != null) { ob_start(); foreach ($arParameters as $key => $value) ${$key} = $value; $this->LoadActivityLocalization($path[1], $filePath); include($path[0]); $result = ob_get_contents(); ob_end_clean(); } return $result; } public function SearchActivitiesByType($type, array $documentType = null) { $type = strtolower(trim($type)); if (strlen($type) <= 0) return false; $arProcessedDirs = array(); foreach ($this->arActivityFolders as $folder) { if ($handle = @opendir($folder)) { while (false !== ($dir = readdir($handle))) { if ($dir == "." || $dir == "..") continue; if (!is_dir($folder."/".$dir)) continue; $dirKey = strtolower($dir); if (array_key_exists($dirKey, $arProcessedDirs)) continue; if (!file_exists($folder."/".$dir."/.description.php")) continue; $arActivityDescription = array(); $this->LoadActivityLocalization($folder."/".$dir, ".description.php"); include($folder."/".$dir."/.description.php"); //Support multiple types $activityType = (array)$arActivityDescription['TYPE']; foreach ($activityType as $i => $aType) $activityType[$i] = strtolower(trim($aType)); if (in_array($type, $activityType, true)) { $arProcessedDirs[$dirKey] = $arActivityDescription; $arProcessedDirs[$dirKey]["PATH_TO_ACTIVITY"] = $folder."/".$dir; if ( isset($arActivityDescription['FILTER']) && is_array($arActivityDescription['FILTER']) && !$this->checkActivityFilter($arActivityDescription['FILTER'], $documentType) ) $arProcessedDirs[$dirKey]['EXCLUDED'] = true; } } closedir($handle); } } if ($type == 'activity') { $arProcessedDirs = array_merge($arProcessedDirs, $this->getRestActivities(false, $documentType)); } if ($type == 'robot_activity') { $arProcessedDirs = array_merge($arProcessedDirs, $this->getRestRobots(false, $documentType)); } if ($type != 'condition') \Bitrix\Main\Type\Collection::sortByColumn($arProcessedDirs, 'NAME'); return $arProcessedDirs; } /** * @param bool $lang Language ID. * @param null|array $documentType Document type. * @return array * @throws \Bitrix\Main\ArgumentException */ public function getRestActivities($lang = false, $documentType = null) { $result = array(); $iterator = RestActivityTable::getList(array( 'filter' => array('=IS_ROBOT' => 'N') )); while ($activity = $iterator->fetch()) { $result[static::REST_ACTIVITY_PREFIX.$activity['INTERNAL_CODE']] = $this->makeRestActivityDescription($activity, $lang, $documentType); } return $result; } /** * @param bool $lang Language ID. * @param null|array $documentType Document type. * @return array * @throws \Bitrix\Main\ArgumentException */ public function getRestRobots($lang = false, $documentType = null) { $result = array(); $iterator = RestActivityTable::getList(array( 'filter' => array('=IS_ROBOT' => 'Y') )); while ($activity = $iterator->fetch()) { $result[static::REST_ACTIVITY_PREFIX.$activity['INTERNAL_CODE']] = $this->makeRestRobotDescription($activity, $lang, $documentType); } return $result; } private function makeRestActivityDescription($activity, $lang = false, $documentType = null) { if ($lang === false) $lang = LANGUAGE_ID; $code = static::REST_ACTIVITY_PREFIX.$activity['INTERNAL_CODE']; $result = array( 'NAME' => '['.RestActivityTable::getLocalization($activity['APP_NAME'], $lang).'] ' .RestActivityTable::getLocalization($activity['NAME'], $lang), 'DESCRIPTION' => RestActivityTable::getLocalization($activity['DESCRIPTION'], $lang), 'TYPE' => 'activity', 'CLASS' => $code, 'JSCLASS' => 'BizProcActivity', 'CATEGORY' => array( 'ID' => 'rest', ), 'RETURN' => array(), //compatibility 'PATH_TO_ACTIVITY' => '' ); if ( isset($activity['FILTER']) && is_array($activity['FILTER']) && !$this->checkActivityFilter($activity['FILTER'], $documentType) ) $result['EXCLUDED'] = true; if (!empty($activity['RETURN_PROPERTIES'])) { foreach ($activity['RETURN_PROPERTIES'] as $name => $property) { $result['RETURN'][$name] = array( 'NAME' => RestActivityTable::getLocalization($property['NAME'], $lang), 'TYPE' => isset($property['TYPE']) ? $property['TYPE'] : \Bitrix\Bizproc\FieldType::STRING ); } } if ($activity['USE_SUBSCRIPTION'] != 'N') $result['RETURN']['IsTimeout'] = array( 'NAME' => GetMessage('BPRA_IS_TIMEOUT'), 'TYPE' => \Bitrix\Bizproc\FieldType::INT ); return $result; } private function makeRestRobotDescription($activity, $lang = false, $documentType = null) { if ($lang === false) $lang = LANGUAGE_ID; $code = static::REST_ACTIVITY_PREFIX.$activity['INTERNAL_CODE']; $result = array( 'NAME' => '['.RestActivityTable::getLocalization($activity['APP_NAME'], $lang).'] ' .RestActivityTable::getLocalization($activity['NAME'], $lang), 'DESCRIPTION' => RestActivityTable::getLocalization($activity['DESCRIPTION'], $lang), 'TYPE' => array('activity', 'robot_activity'), 'CLASS' => $code, 'JSCLASS' => 'BizProcActivity', 'CATEGORY' => array(), 'RETURN' => array(), //compatibility 'PATH_TO_ACTIVITY' => '', 'ROBOT_SETTINGS' => array( 'CATEGORY' => 'other' ) ); if ( isset($activity['FILTER']) && is_array($activity['FILTER']) && !$this->checkActivityFilter($activity['FILTER'], $documentType) ) $result['EXCLUDED'] = true; return $result; } private function checkActivityFilter($filter, $documentType) { $distrName = CBPHelper::getDistrName(); foreach ($filter as $type => $rules) { $found = $this->checkActivityFilterRules($rules, $documentType, $distrName); if ($type == 'INCLUDE' && !$found || $type == 'EXCLUDE' && $found) return false; } return true; } private function checkActivityFilterRules($rules, $documentType, $distrName) { if (!is_array($rules) || CBPHelper::IsAssociativeArray($rules)) $rules = array($rules); foreach ($rules as $rule) { $result = false; if (is_array($rule)) { if (!$documentType) $result = true; else { foreach ($documentType as $key => $value) { if (!isset($rule[$key])) break; $result = $rule[$key] == $value; if (!$result) break; } } } else { $result = (string)$rule == $distrName; } if ($result) return true; } return false; } private function addWorkflowToChain($childId, $parent) { $this->workflowChains[$childId] = $parent; return $this; } private function checkWorkflowRecursion($workflowId, $currentTemplateId) { $templates = array($currentTemplateId); while (isset($this->workflowChains[$workflowId])) { $parent = $this->workflowChains[$workflowId]; if (in_array($parent['templateId'], $templates)) return true; $templates[] = $parent['templateId']; $workflowId = $parent['workflowId']; } return false; } }