PageRenderTime 50ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 1ms

/modules/bizproc/lib/automation/helper.php

https://gitlab.com/alexprowars/bitrix
PHP | 728 lines | 611 code | 98 blank | 19 comment | 83 complexity | af83c66f5fac1c7b4983aa978405ac1e MD5 | raw file
  1. <?php
  2. namespace Bitrix\Bizproc\Automation;
  3. use Bitrix\Bizproc\Automation\Engine\DelayInterval;
  4. use Bitrix\Disk;
  5. use Bitrix\Main\Loader;
  6. class Helper
  7. {
  8. const CURRENT_DATE_BASIS = '{=System:Date}';
  9. const CURRENT_DATETIME_BASIS = '{=System:Now}';
  10. protected static $maps;
  11. protected static $documentFields;
  12. public static function prepareUserSelectorEntities(array $documentType, $users, $config = []): array
  13. {
  14. $result = [];
  15. $users = (array)$users;
  16. $documentUserFields = static::getDocumentFields($documentType, 'user');
  17. $documentUserGroups = self::getDocumentUserServiceGroups($documentType);
  18. foreach ($users as $user)
  19. {
  20. if (!is_scalar($user))
  21. continue;
  22. if (mb_substr($user, 0, 5) === "user_")
  23. {
  24. $user = intval(mb_substr($user, 5));
  25. if (($user > 0) && !in_array($user, $result))
  26. {
  27. $userInfo = self::getUserInfo($user);
  28. $result[] = [
  29. 'id' => 'U'.$user,
  30. 'entityId' => $user,
  31. 'name' => htmlspecialcharsBx($userInfo['fullName']),
  32. 'photoSrc' => $userInfo['photoSrc'],
  33. 'url' => $userInfo['url'],
  34. 'entityType' => 'users',
  35. ];
  36. }
  37. }
  38. elseif ($user === 'author' &&
  39. (
  40. isset($documentUserFields['ASSIGNED_BY_ID']) ||
  41. isset($documentUserFields['RESPONSIBLE_ID'])
  42. )
  43. )
  44. {
  45. $responsibleKey = isset($documentUserFields['ASSIGNED_BY_ID']) ? 'ASSIGNED_BY_ID' : 'RESPONSIBLE_ID';
  46. $result[] = array(
  47. 'id' => $documentUserFields[$responsibleKey]['Expression'],
  48. 'entityId' => $documentUserFields[$responsibleKey]['Expression'],
  49. 'name' => htmlspecialcharsBx($documentUserFields[$responsibleKey]['Name']),
  50. 'entityType' => 'bpuserroles'
  51. );
  52. }
  53. elseif (isset($documentUserGroups[$user]))
  54. {
  55. $result[] = array(
  56. 'id' => $user,
  57. 'entityId' => $user,
  58. 'name' => htmlspecialcharsBx($documentUserGroups[$user]),
  59. 'entityType' => 'bpuserroles'
  60. );
  61. }
  62. else
  63. {
  64. $found = false;
  65. foreach ($documentUserFields as $field)
  66. {
  67. if ($user === $field['Expression'] || $user === $field['SystemExpression'])
  68. {
  69. $result[] = array(
  70. 'id' => $field['Expression'],
  71. 'entityId' => $field['Expression'],
  72. 'name' => htmlspecialcharsBx($field['Name']),
  73. 'entityType' => 'bpuserroles'
  74. );
  75. $found = true;
  76. }
  77. }
  78. if (!$found && isset($config['additionalFields']))
  79. {
  80. foreach ($config['additionalFields'] as $field)
  81. {
  82. if ($user === $field['entityId'])
  83. {
  84. $result[] = array(
  85. 'id' => $field['id'],
  86. 'entityId' => $field['entityId'],
  87. 'name' => htmlspecialcharsBx($field['name']),
  88. 'entityType' => 'bpuserroles'
  89. );
  90. }
  91. }
  92. }
  93. }
  94. }
  95. return $result;
  96. }
  97. public static function getResponsibleUserExpression(array $documentType)
  98. {
  99. $documentUserFields = static::getDocumentFields($documentType, 'user');
  100. $result = null;
  101. if (isset($documentUserFields['ASSIGNED_BY_ID']) || isset($documentUserFields['RESPONSIBLE_ID']))
  102. {
  103. $responsibleKey = isset($documentUserFields['ASSIGNED_BY_ID']) ? 'ASSIGNED_BY_ID' : 'RESPONSIBLE_ID';
  104. $result = '{=Document:'.$responsibleKey.'}';
  105. }
  106. elseif (isset($documentUserFields['CREATED_BY']))
  107. {
  108. $result = '{=Document:CREATED_BY}';
  109. }
  110. return $result;
  111. }
  112. /**
  113. * Get disk files information by file ids.
  114. * @param int|array $attachments
  115. * @return array
  116. */
  117. public static function prepareDiskAttachments($attachments)
  118. {
  119. $result = array();
  120. if (!Loader::includeModule('disk'))
  121. return $result;
  122. foreach ((array)$attachments as $attachmentId)
  123. {
  124. $attachmentId = (int)$attachmentId;
  125. if ($attachmentId <= 0)
  126. {
  127. continue;
  128. }
  129. $file = Disk\File::loadById($attachmentId);
  130. if ($file)
  131. {
  132. $result[] = array(
  133. 'id' => $file->getId(),
  134. 'name' => $file->getName(),
  135. 'size' => \CFile::FormatSize($file->getSize()),
  136. 'type' => 'disk'
  137. );
  138. }
  139. }
  140. return $result;
  141. }
  142. /**
  143. * Get files information from document fields.
  144. * @param array $documentType
  145. * @param $files
  146. * @return array
  147. */
  148. public static function prepareFileAttachments(array $documentType, $files)
  149. {
  150. $result = [];
  151. $files = (array)$files;
  152. $documentUserFields = static::getDocumentFields($documentType, 'file');
  153. foreach ($files as $file)
  154. {
  155. if (!is_scalar($file))
  156. continue;
  157. $found = false;
  158. foreach ($documentUserFields as $id => $field)
  159. {
  160. if ($file !== $field['Expression'])
  161. continue;
  162. $found = true;
  163. $result[] = array(
  164. 'id' => $id,
  165. 'expression' => $field['Expression'],
  166. 'name' => $field['Name'],
  167. 'type' => 'file'
  168. );
  169. }
  170. if (!$found && mb_strpos($file, '{') === 0)
  171. {
  172. $result[] = [
  173. 'id' => $file,
  174. 'expression' => $file,
  175. 'name' => $file,
  176. 'type' => 'file'
  177. ];
  178. }
  179. }
  180. return $result;
  181. }
  182. public static function convertExpressions($source, array $documentType, $useTilda = true)
  183. {
  184. $pattern = \CBPActivity::ValueInlinePattern;
  185. [$mapIds, $mapNames, $mapObjectNames] = static::getExpressionsMaps($documentType);
  186. $converter = function ($matches) use ($mapIds, $mapNames, $mapObjectNames, $useTilda)
  187. {
  188. $mods = [];
  189. if ($matches['mod1'])
  190. {
  191. $mods[] = $matches['mod1'];
  192. }
  193. if ($matches['mod2'])
  194. {
  195. $mods[] = $matches['mod2'];
  196. }
  197. $modifiers = ($mods ? ' > ' . implode(',', $mods) : '');
  198. $objectName = $matches['object'];
  199. $fieldId = $matches['field'];
  200. if (in_array($objectName, $mapObjectNames))
  201. {
  202. $key = array_search($fieldId, $mapIds[$objectName]);
  203. if ($key !== false)
  204. {
  205. $fieldName = $mapNames[$objectName][$key];
  206. return '{{' . $fieldName . $modifiers . '}}';
  207. }
  208. }
  209. elseif ($useTilda && $objectName === 'Template')
  210. {
  211. return '{{~*:' . $fieldId . $modifiers . '}}';
  212. }
  213. elseif ($useTilda && $objectName === 'Constant')
  214. {
  215. return '{{~&:' . $fieldId . $modifiers . '}}';
  216. }
  217. elseif ($useTilda && preg_match('/^A[_0-9]+$/', $objectName))
  218. {
  219. return '{{~' . $objectName . ':' . $fieldId . $modifiers . '}}';
  220. }
  221. return $matches[0];
  222. };
  223. return preg_replace_callback($pattern, $converter, $source);
  224. }
  225. protected static function getExpressionsMaps($documentType): array
  226. {
  227. $mapIds = [];
  228. $mapNames = [];
  229. $mapObjectNames = [];
  230. $objectName = \Bitrix\Bizproc\Workflow\Template\SourceType::DocumentField;
  231. [$ids, $names] = static::getFieldsMap($documentType);
  232. $mapIds[$objectName] = $ids;
  233. $mapNames[$objectName] = $names;
  234. $mapObjectNames[] = $objectName;
  235. $objectName = \Bitrix\Bizproc\Workflow\Template\SourceType::GlobalVariable;
  236. [$ids, $names] = static::getGlobalsMap($objectName, $documentType);
  237. $mapIds[$objectName] = $ids;
  238. $mapNames[$objectName] = $names;
  239. $mapObjectNames[] = $objectName;
  240. $objectName = \Bitrix\Bizproc\Workflow\Template\SourceType::GlobalConstant;
  241. [$ids, $names] = static::getGlobalsMap($objectName, $documentType);
  242. $mapIds[$objectName] = $ids;
  243. $mapNames[$objectName] = $names;
  244. $mapObjectNames[] = $objectName;
  245. return [$mapIds, $mapNames, $mapObjectNames];
  246. }
  247. public static function unConvertExpressions($source, array $documentType)
  248. {
  249. $pattern = '/\{\{(?<mixed>[^=].*?)\}\}/is';
  250. [$mapIds, $mapNames, $mapObjectNames] = static::getExpressionsMaps($documentType);
  251. $converter = function ($matches) use ($mapIds, $mapNames, $mapObjectNames)
  252. {
  253. $matches['mixed'] = htmlspecialcharsback($matches['mixed']);
  254. if (mb_strpos($matches['mixed'], '~') === 0)
  255. {
  256. $len = mb_strpos($matches['mixed'], '#');
  257. $expression = ($len === false)
  258. ? mb_substr($matches['mixed'], 1)
  259. : mb_substr($matches['mixed'], 1, $len - 1)
  260. ;
  261. if (mb_strpos($expression, '*:') === 0)
  262. {
  263. $expression = ltrim($expression,'*');
  264. $expression = 'Template' . $expression;
  265. }
  266. if (mb_strpos($expression, '&:') === 0)
  267. {
  268. $expression = ltrim($expression,'&');
  269. $expression = 'Constant' . $expression;
  270. }
  271. return '{=' . trim($expression) . '}';
  272. }
  273. $pairs = explode('>', $matches['mixed']);
  274. $fieldName = '';
  275. $fieldId = '';
  276. $objectName = '';
  277. while (($pair = array_shift($pairs)) !== null)
  278. {
  279. $fieldName .= $fieldName ? '>' . $pair : $pair;
  280. foreach ($mapObjectNames as $object)
  281. {
  282. $key = array_search(trim($fieldName), $mapNames[$object]);
  283. if ($key !== false)
  284. {
  285. $objectName = $object;
  286. $fieldId = $mapIds[$object][$key];
  287. break;
  288. }
  289. }
  290. if ($fieldId !== '')
  291. {
  292. break;
  293. }
  294. }
  295. if (!$fieldId && mb_substr($fieldName, -10) === '_printable')
  296. {
  297. $fieldName = mb_substr($fieldName, 0, -10);
  298. $key = array_search(trim($fieldName), $mapNames['Document']);
  299. if ($key !== false)
  300. {
  301. $objectName = 'Document';
  302. $fieldId = $mapIds['Document'][$key];
  303. $pairs[] = 'printable';
  304. }
  305. }
  306. if ($fieldId)
  307. {
  308. $mods = isset($pairs[0]) ? trim($pairs[0]) : '';
  309. $modifiers = $mods ? ' > ' . $mods : '';
  310. return '{=' . $objectName . ':' . $fieldId . $modifiers . '}';
  311. }
  312. return $matches[0];
  313. };
  314. return preg_replace_callback($pattern, $converter, $source);
  315. }
  316. public static function convertProperties(array $properties, array $documentType, $useTilda = true)
  317. {
  318. foreach ($properties as $code => $property)
  319. {
  320. if (is_array($property))
  321. {
  322. $properties[$code] = self::convertProperties($property, $documentType, $useTilda);
  323. }
  324. else
  325. {
  326. $properties[$code] = static::convertExpressions($property, $documentType, $useTilda);
  327. }
  328. }
  329. return $properties;
  330. }
  331. public static function unConvertProperties(array $properties, array $documentType)
  332. {
  333. foreach ($properties as $code => $property)
  334. {
  335. if (is_array($property))
  336. {
  337. $properties[$code] = self::unConvertProperties($property, $documentType);
  338. }
  339. else
  340. {
  341. $properties[$code] = static::unConvertExpressions($property, $documentType);
  342. }
  343. }
  344. return $properties;
  345. }
  346. /**
  347. * Get document fields for usage in robots designer.
  348. * @param array $documentType Bizproc document type.
  349. * @param null|string $typeFilter
  350. * @return array
  351. */
  352. public static function getDocumentFields(array $documentType, $typeFilter = null)
  353. {
  354. $key = implode('@', $documentType);
  355. if (!isset(static::$documentFields[$key]))
  356. {
  357. $documentService = \CBPRuntime::GetRuntime(true)->getDocumentService();
  358. static::$documentFields[$key] = $documentService->GetDocumentFields($documentType);
  359. }
  360. $resultFields = array();
  361. if (is_array(static::$documentFields[$key]))
  362. {
  363. foreach (static::$documentFields[$key] as $id => $field)
  364. {
  365. if ($field['Type'] === 'UF:boolean')
  366. {
  367. //Mark as bizproc boolean type
  368. $field['Type'] = $field['BaseType'] = 'bool';
  369. }
  370. if ($field['Type'] === 'UF:date')
  371. {
  372. //Mark as bizproc date type
  373. $field['Type'] = $field['BaseType'] = 'date';
  374. }
  375. if ($typeFilter !== null && $field['Type'] !== $typeFilter)
  376. continue;
  377. $field['Name'] = trim($field['Name']);
  378. $resultFields[$id] = array(
  379. 'Id' => $id,
  380. 'Name' => $field['Name'],
  381. 'Type' => $field['Type'],
  382. 'BaseType' => $field['BaseType'] ?? $field['Type'],
  383. 'Expression' => '{{'.$field['Name'].'}}',
  384. 'SystemExpression' => '{=Document:'.$id.'}',
  385. 'Options' => $field['Options'],
  386. 'Multiple' => $field['Multiple'] ?? false,
  387. );
  388. }
  389. }
  390. return $resultFields;
  391. }
  392. private static function getDocumentUserServiceGroups(array $documentType)
  393. {
  394. $documentService = \CBPRuntime::GetRuntime(true)->getDocumentService();
  395. return $documentService->GetAllowableUserGroups($documentType);
  396. }
  397. public static function getDocumentUserGroups(array $documentType): array
  398. {
  399. $docGroups = self::getDocumentUserServiceGroups($documentType);
  400. $groups = [];
  401. if ($docGroups)
  402. {
  403. foreach ($docGroups as $id => $groupName)
  404. {
  405. if (!$groupName || mb_strpos($id, 'group_') === 0)
  406. {
  407. continue;
  408. }
  409. $groups[] = [
  410. 'id' => preg_match('/^[0-9]+$/', $id) ? 'G'.$id : $id,
  411. 'name' => $groupName
  412. ];
  413. }
  414. }
  415. return $groups;
  416. }
  417. protected static function getFieldsMap(array $documentType)
  418. {
  419. $key = implode('@', $documentType);
  420. if (!isset(static::$maps[$key]))
  421. {
  422. $id = $name = [];
  423. $fields = static::getDocumentFields($documentType);
  424. foreach ($fields as $field)
  425. {
  426. $id[] = $field['Id'];
  427. $name[] = $field['Name'];
  428. }
  429. static::$maps[$key] = [$id, $name];
  430. }
  431. return static::$maps[$key];
  432. }
  433. protected static function getGlobalsMap(string $type, array $documentType)
  434. {
  435. switch ($type)
  436. {
  437. case \Bitrix\Bizproc\Workflow\Template\SourceType::GlobalConstant:
  438. $key = 'globals@const@' . implode('@', $documentType);
  439. if (isset(static::$maps[$key]))
  440. {
  441. return static::$maps[$key];
  442. }
  443. $globals = \Bitrix\Bizproc\Workflow\Type\GlobalConst::getAll($documentType);
  444. $visibilityNames = \Bitrix\Bizproc\Workflow\Type\GlobalConst::getVisibilityFullNames($documentType);
  445. break;
  446. case \Bitrix\Bizproc\Workflow\Template\SourceType::GlobalVariable:
  447. $key = 'globals@var@' . implode('@', $documentType);
  448. if (isset(static::$maps[$key]))
  449. {
  450. return static::$maps[$key];
  451. }
  452. $globals = \Bitrix\Bizproc\Workflow\Type\GlobalVar::getAll($documentType);
  453. $visibilityNames = \Bitrix\Bizproc\Workflow\Type\GlobalVar::getVisibilityFullNames($documentType);
  454. break;
  455. default:
  456. return [];
  457. }
  458. $ids = [];
  459. $names = [];
  460. foreach ($globals as $id => $property)
  461. {
  462. $ids[] = $id;
  463. $visibility = $property['Visibility'];
  464. $names[] = $visibilityNames[$visibility] . ': ' . $property['Name'];
  465. }
  466. static::$maps[$key] = [$ids, $names];
  467. return static::$maps[$key];
  468. }
  469. public static function parseDateTimeInterval($interval)
  470. {
  471. $interval = (string)$interval;
  472. $result = array(
  473. 'basis' => null,
  474. 'i' => 0,
  475. 'h' => 0,
  476. 'd' => 0,
  477. 'workTime' => false
  478. );
  479. if (mb_strpos($interval, '=dateadd(') === 0 || mb_strpos($interval, '=workdateadd(') === 0)
  480. {
  481. if (mb_strpos($interval, '=workdateadd(') === 0)
  482. {
  483. $interval = mb_substr($interval, 13, -1); // cut =workdateadd(...)
  484. $result['workTime'] = true;
  485. }
  486. else
  487. {
  488. $interval = mb_substr($interval, 9, -1); // cut =dateadd(...)
  489. }
  490. $arguments = explode(',', $interval);
  491. $result['basis'] = trim($arguments[0]);
  492. $arguments[1] = trim($arguments[1], '"\'');
  493. $result['type'] = mb_strpos($arguments[1], '-') === 0 ? DelayInterval::TYPE_BEFORE : DelayInterval::TYPE_AFTER;
  494. preg_match_all('/\s*([\d]+)\s*(i|h|d)\s*/i', $arguments[1], $matches);
  495. foreach ($matches[0] as $i => $match)
  496. {
  497. $result[$matches[2][$i]] = (int)$matches[1][$i];
  498. }
  499. }
  500. elseif (\CBPDocument::IsExpression($interval))
  501. $result['basis'] = $interval;
  502. $minutes = $result['i'] + $result['h'] * 60 + $result['d'] * 60 * 24;
  503. if ($minutes % 1440 === 0)
  504. {
  505. $result['value'] = $minutes / 1440;
  506. $result['valueType'] = 'd';
  507. }
  508. elseif ($minutes % 60 === 0)
  509. {
  510. $result['value'] = $minutes / 60;
  511. $result['valueType'] = 'h';
  512. }
  513. else
  514. {
  515. $result['value'] = $minutes;
  516. $result['valueType'] = 'i';
  517. }
  518. if (
  519. !$result['value']
  520. && $result['basis'] !== static::CURRENT_DATETIME_BASIS
  521. && \CBPDocument::IsExpression($result['basis'])
  522. )
  523. {
  524. $result['type'] = DelayInterval::TYPE_IN;
  525. }
  526. return $result;
  527. }
  528. public static function getDateTimeIntervalString($interval)
  529. {
  530. if (!$interval['basis'] || !\CBPDocument::IsExpression($interval['basis']))
  531. $interval['basis'] = static::CURRENT_DATE_BASIS;
  532. $days = isset($interval['d']) ? (int)$interval['d'] : 0;
  533. $hours = isset($interval['h']) ? (int)$interval['h'] : 0;
  534. $minutes = isset($interval['i']) ? (int)$interval['i'] : 0;
  535. if (isset($interval['value']) && isset($interval['valueType']))
  536. {
  537. switch ($interval['valueType'])
  538. {
  539. case 'i':
  540. $minutes = (int)$interval['value'];
  541. break;
  542. case 'h':
  543. $hours = (int)$interval['value'];
  544. break;
  545. case 'd':
  546. $days = (int)$interval['value'];
  547. break;
  548. }
  549. }
  550. $add = '';
  551. if (isset($interval['type']) && $interval['type'] == DelayInterval::TYPE_BEFORE)
  552. $add = '-';
  553. if ($days > 0)
  554. $add .= $days.'d';
  555. if ($hours > 0)
  556. $add .= $hours.'h';
  557. if ($minutes > 0)
  558. $add .= $minutes.'i';
  559. $fn = !empty($interval['workTime']) ? 'workdateadd' : 'dateadd';
  560. if ($fn === 'workdateadd' && $add === '')
  561. {
  562. $add = '0d';
  563. }
  564. $worker = '';
  565. if ($fn === 'workdateadd' && isset($interval['worker']))
  566. {
  567. $worker = $interval['worker'];
  568. }
  569. return '='.$fn.'('.$interval['basis'].',"'.$add.'"'.($worker ? ','.$worker : '').')';
  570. }
  571. public static function parseTimeString($time)
  572. {
  573. $pairs = preg_split('#[\s:]+#', $time);
  574. $pairs[0] = (int)$pairs[0];
  575. $pairs[1] = (int)$pairs[1];
  576. if (count($pairs) === 3)
  577. {
  578. if ($pairs[2] == 'pm' && $pairs[0] < 12)
  579. $pairs[0] += 12;
  580. if ($pairs[2] == 'am' && $pairs[0] == 12)
  581. $pairs[0] = 0;
  582. }
  583. return array('h' => $pairs[0], 'i' => $pairs[1]);
  584. }
  585. public static function countAllRobots(array $documentType, array $statuses): int
  586. {
  587. $cnt = 0;
  588. foreach ($statuses as $status)
  589. {
  590. $template = new Engine\Template($documentType, $status);
  591. if ($template->getId() > 0)
  592. {
  593. $cnt += count($template->getRobots());
  594. }
  595. }
  596. return $cnt;
  597. }
  598. private static function getUserInfo($userID, $format = '', $htmlEncode = false)
  599. {
  600. $userID = intval($userID);
  601. if($userID <= 0)
  602. {
  603. return '';
  604. }
  605. $format = strval($format);
  606. if($format === '')
  607. {
  608. $format = \CSite::GetNameFormat(false);
  609. }
  610. $dbUser = \CUser::GetList(
  611. 'id',
  612. 'asc',
  613. array('ID'=> $userID),
  614. array(
  615. 'FIELDS' => array(
  616. 'ID',
  617. 'NAME', 'SECOND_NAME', 'LAST_NAME',
  618. 'LOGIN', 'TITLE', 'EMAIL',
  619. 'PERSONAL_PHOTO'
  620. )
  621. )
  622. );
  623. $user = $dbUser ? $dbUser->Fetch() : null;
  624. return [
  625. 'fullName' => $user ? \CUser::FormatName($format, $user, true, $htmlEncode) : '',
  626. 'photoSrc' => $user ? \CBPViewHelper::getUserPhotoSrc($user) : null,
  627. 'url' => sprintf('/company/personal/user/%s/', $userID),
  628. ];
  629. }
  630. }