RuleMatcher.php 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
  5. * SPDX-License-Identifier: AGPL-3.0-or-later
  6. */
  7. namespace OCA\WorkflowEngine\Service;
  8. use OCA\WorkflowEngine\Helper\LogContext;
  9. use OCA\WorkflowEngine\Helper\ScopeContext;
  10. use OCA\WorkflowEngine\Manager;
  11. use OCP\AppFramework\QueryException;
  12. use OCP\Files\Storage\IStorage;
  13. use OCP\IL10N;
  14. use OCP\IServerContainer;
  15. use OCP\IUserSession;
  16. use OCP\WorkflowEngine\ICheck;
  17. use OCP\WorkflowEngine\IEntity;
  18. use OCP\WorkflowEngine\IEntityCheck;
  19. use OCP\WorkflowEngine\IFileCheck;
  20. use OCP\WorkflowEngine\IManager;
  21. use OCP\WorkflowEngine\IOperation;
  22. use OCP\WorkflowEngine\IRuleMatcher;
  23. use RuntimeException;
  24. class RuleMatcher implements IRuleMatcher {
  25. /** @var array */
  26. protected $contexts;
  27. /** @var array */
  28. protected $fileInfo = [];
  29. /** @var IOperation */
  30. protected $operation;
  31. /** @var IEntity */
  32. protected $entity;
  33. /** @var string */
  34. protected $eventName;
  35. public function __construct(
  36. protected IUserSession $session,
  37. protected IServerContainer $container,
  38. protected IL10N $l,
  39. protected Manager $manager,
  40. protected Logger $logger,
  41. ) {
  42. }
  43. public function setFileInfo(IStorage $storage, string $path, bool $isDir = false): void {
  44. $this->fileInfo['storage'] = $storage;
  45. $this->fileInfo['path'] = $path;
  46. $this->fileInfo['isDir'] = $isDir;
  47. }
  48. public function setEntitySubject(IEntity $entity, $subject): void {
  49. $this->contexts[get_class($entity)] = [$entity, $subject];
  50. }
  51. public function setOperation(IOperation $operation): void {
  52. if ($this->operation !== null) {
  53. throw new RuntimeException('This method must not be called more than once');
  54. }
  55. $this->operation = $operation;
  56. }
  57. public function setEntity(IEntity $entity): void {
  58. if ($this->entity !== null) {
  59. throw new RuntimeException('This method must not be called more than once');
  60. }
  61. $this->entity = $entity;
  62. }
  63. public function setEventName(string $eventName): void {
  64. if ($this->eventName !== null) {
  65. throw new RuntimeException('This method must not be called more than once');
  66. }
  67. $this->eventName = $eventName;
  68. }
  69. public function getEntity(): IEntity {
  70. if ($this->entity === null) {
  71. throw new \LogicException('Entity was not set yet');
  72. }
  73. return $this->entity;
  74. }
  75. public function getFlows(bool $returnFirstMatchingOperationOnly = true): array {
  76. if (!$this->operation) {
  77. throw new RuntimeException('Operation is not set');
  78. }
  79. return $this->getMatchingOperations(get_class($this->operation), $returnFirstMatchingOperationOnly);
  80. }
  81. public function getMatchingOperations(string $class, bool $returnFirstMatchingOperationOnly = true): array {
  82. $scopes[] = new ScopeContext(IManager::SCOPE_ADMIN);
  83. $user = $this->session->getUser();
  84. if ($user !== null && $this->manager->isUserScopeEnabled()) {
  85. $scopes[] = new ScopeContext(IManager::SCOPE_USER, $user->getUID());
  86. }
  87. $ctx = new LogContext();
  88. $ctx
  89. ->setScopes($scopes)
  90. ->setEntity($this->entity)
  91. ->setOperation($this->operation);
  92. $this->logger->logFlowRequests($ctx);
  93. $operations = [];
  94. foreach ($scopes as $scope) {
  95. $operations = array_merge($operations, $this->manager->getOperations($class, $scope));
  96. }
  97. if ($this->entity instanceof IEntity) {
  98. /** @var ScopeContext[] $additionalScopes */
  99. $additionalScopes = $this->manager->getAllConfiguredScopesForOperation($class);
  100. foreach ($additionalScopes as $hash => $scopeCandidate) {
  101. if ($scopeCandidate->getScope() !== IManager::SCOPE_USER || in_array($scopeCandidate, $scopes)) {
  102. continue;
  103. }
  104. if ($this->entity->isLegitimatedForUserId($scopeCandidate->getScopeId())) {
  105. $ctx = new LogContext();
  106. $ctx
  107. ->setScopes([$scopeCandidate])
  108. ->setEntity($this->entity)
  109. ->setOperation($this->operation);
  110. $this->logger->logScopeExpansion($ctx);
  111. $operations = array_merge($operations, $this->manager->getOperations($class, $scopeCandidate));
  112. }
  113. }
  114. }
  115. $matches = [];
  116. foreach ($operations as $operation) {
  117. $configuredEvents = json_decode($operation['events'], true);
  118. if ($this->eventName !== null && !in_array($this->eventName, $configuredEvents)) {
  119. continue;
  120. }
  121. $checkIds = json_decode($operation['checks'], true);
  122. $checks = $this->manager->getChecks($checkIds);
  123. foreach ($checks as $check) {
  124. if (!$this->check($check)) {
  125. // Check did not match, continue with the next operation
  126. continue 2;
  127. }
  128. }
  129. $ctx = new LogContext();
  130. $ctx
  131. ->setEntity($this->entity)
  132. ->setOperation($this->operation)
  133. ->setConfiguration($operation);
  134. $this->logger->logPassedCheck($ctx);
  135. if ($returnFirstMatchingOperationOnly) {
  136. $ctx = new LogContext();
  137. $ctx
  138. ->setEntity($this->entity)
  139. ->setOperation($this->operation)
  140. ->setConfiguration($operation);
  141. $this->logger->logRunSingle($ctx);
  142. return $operation;
  143. }
  144. $matches[] = $operation;
  145. }
  146. $ctx = new LogContext();
  147. $ctx
  148. ->setEntity($this->entity)
  149. ->setOperation($this->operation);
  150. if (!empty($matches)) {
  151. $ctx->setConfiguration($matches);
  152. $this->logger->logRunAll($ctx);
  153. } else {
  154. $this->logger->logRunNone($ctx);
  155. }
  156. return $matches;
  157. }
  158. /**
  159. * @param array $check
  160. * @return bool
  161. */
  162. public function check(array $check) {
  163. try {
  164. $checkInstance = $this->container->query($check['class']);
  165. } catch (QueryException $e) {
  166. // Check does not exist, assume it matches.
  167. return true;
  168. }
  169. if ($checkInstance instanceof IFileCheck) {
  170. if (empty($this->fileInfo)) {
  171. throw new RuntimeException('Must set file info before running the check');
  172. }
  173. $checkInstance->setFileInfo($this->fileInfo['storage'], $this->fileInfo['path'], $this->fileInfo['isDir']);
  174. } elseif ($checkInstance instanceof IEntityCheck) {
  175. foreach ($this->contexts as $entityInfo) {
  176. [$entity, $subject] = $entityInfo;
  177. $checkInstance->setEntitySubject($entity, $subject);
  178. }
  179. } elseif (!$checkInstance instanceof ICheck) {
  180. // Check is invalid
  181. throw new \UnexpectedValueException($this->l->t('Check %s is invalid or does not exist', $check['class']));
  182. }
  183. return $checkInstance->executeCheck($check['operator'], $check['value']);
  184. }
  185. }