Manager.php 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787
  1. <?php
  2. /**
  3. * @copyright Copyright (c) 2016 Morris Jobke <hey@morrisjobke.de>
  4. *
  5. * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
  6. * @author blizzz <blizzz@arthur-schiwon.de>
  7. * @author Christoph Wurst <christoph@winzerhof-wurst.at>
  8. * @author Daniel Kesselberg <mail@danielkesselberg.de>
  9. * @author Joas Schilling <coding@schilljs.com>
  10. * @author Julius Härtl <jus@bitgrid.net>
  11. * @author Morris Jobke <hey@morrisjobke.de>
  12. * @author Roeland Jago Douma <roeland@famdouma.nl>
  13. *
  14. * @license GNU AGPL version 3 or any later version
  15. *
  16. * This program is free software: you can redistribute it and/or modify
  17. * it under the terms of the GNU Affero General Public License as
  18. * published by the Free Software Foundation, either version 3 of the
  19. * License, or (at your option) any later version.
  20. *
  21. * This program is distributed in the hope that it will be useful,
  22. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  23. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  24. * GNU Affero General Public License for more details.
  25. *
  26. * You should have received a copy of the GNU Affero General Public License
  27. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  28. *
  29. */
  30. namespace OCA\WorkflowEngine;
  31. use Doctrine\DBAL\Exception;
  32. use OCP\Cache\CappedMemoryCache;
  33. use OCA\WorkflowEngine\AppInfo\Application;
  34. use OCA\WorkflowEngine\Check\FileMimeType;
  35. use OCA\WorkflowEngine\Check\FileName;
  36. use OCA\WorkflowEngine\Check\FileSize;
  37. use OCA\WorkflowEngine\Check\FileSystemTags;
  38. use OCA\WorkflowEngine\Check\RequestRemoteAddress;
  39. use OCA\WorkflowEngine\Check\RequestTime;
  40. use OCA\WorkflowEngine\Check\RequestURL;
  41. use OCA\WorkflowEngine\Check\RequestUserAgent;
  42. use OCA\WorkflowEngine\Check\UserGroupMembership;
  43. use OCA\WorkflowEngine\Entity\File;
  44. use OCA\WorkflowEngine\Helper\ScopeContext;
  45. use OCA\WorkflowEngine\Service\Logger;
  46. use OCA\WorkflowEngine\Service\RuleMatcher;
  47. use OCP\AppFramework\QueryException;
  48. use OCP\DB\QueryBuilder\IQueryBuilder;
  49. use OCP\EventDispatcher\IEventDispatcher;
  50. use OCP\Files\Storage\IStorage;
  51. use OCP\ICacheFactory;
  52. use OCP\IConfig;
  53. use OCP\IDBConnection;
  54. use OCP\IL10N;
  55. use OCP\ILogger;
  56. use OCP\IServerContainer;
  57. use OCP\IUserSession;
  58. use OCP\WorkflowEngine\Events\RegisterChecksEvent;
  59. use OCP\WorkflowEngine\Events\RegisterEntitiesEvent;
  60. use OCP\WorkflowEngine\Events\RegisterOperationsEvent;
  61. use OCP\WorkflowEngine\ICheck;
  62. use OCP\WorkflowEngine\IComplexOperation;
  63. use OCP\WorkflowEngine\IEntity;
  64. use OCP\WorkflowEngine\IEntityEvent;
  65. use OCP\WorkflowEngine\IManager;
  66. use OCP\WorkflowEngine\IOperation;
  67. use OCP\WorkflowEngine\IRuleMatcher;
  68. use Symfony\Component\EventDispatcher\EventDispatcherInterface as LegacyDispatcher;
  69. use Symfony\Component\EventDispatcher\GenericEvent;
  70. class Manager implements IManager {
  71. /** @var IStorage */
  72. protected $storage;
  73. /** @var string */
  74. protected $path;
  75. /** @var object */
  76. protected $entity;
  77. /** @var array[] */
  78. protected $operations = [];
  79. /** @var array[] */
  80. protected $checks = [];
  81. /** @var IDBConnection */
  82. protected $connection;
  83. /** @var IServerContainer|\OC\Server */
  84. protected $container;
  85. /** @var IL10N */
  86. protected $l;
  87. /** @var LegacyDispatcher */
  88. protected $legacyEventDispatcher;
  89. /** @var IEntity[] */
  90. protected $registeredEntities = [];
  91. /** @var IOperation[] */
  92. protected $registeredOperators = [];
  93. /** @var ICheck[] */
  94. protected $registeredChecks = [];
  95. /** @var ILogger */
  96. protected $logger;
  97. /** @var CappedMemoryCache<int[]> */
  98. protected CappedMemoryCache $operationsByScope;
  99. /** @var IUserSession */
  100. protected $session;
  101. /** @var IEventDispatcher */
  102. private $dispatcher;
  103. /** @var IConfig */
  104. private $config;
  105. private ICacheFactory $cacheFactory;
  106. public function __construct(
  107. IDBConnection $connection,
  108. IServerContainer $container,
  109. IL10N $l,
  110. LegacyDispatcher $eventDispatcher,
  111. ILogger $logger,
  112. IUserSession $session,
  113. IEventDispatcher $dispatcher,
  114. IConfig $config,
  115. ICacheFactory $cacheFactory,
  116. ) {
  117. $this->connection = $connection;
  118. $this->container = $container;
  119. $this->l = $l;
  120. $this->legacyEventDispatcher = $eventDispatcher;
  121. $this->logger = $logger;
  122. $this->operationsByScope = new CappedMemoryCache(64);
  123. $this->session = $session;
  124. $this->dispatcher = $dispatcher;
  125. $this->config = $config;
  126. $this->cacheFactory = $cacheFactory;
  127. }
  128. public function getRuleMatcher(): IRuleMatcher {
  129. return new RuleMatcher(
  130. $this->session,
  131. $this->container,
  132. $this->l,
  133. $this,
  134. $this->container->query(Logger::class)
  135. );
  136. }
  137. public function getAllConfiguredEvents() {
  138. $cache = $this->cacheFactory->createDistributed('flow');
  139. $cached = $cache->get('events');
  140. if ($cached !== null) {
  141. return $cached;
  142. }
  143. $query = $this->connection->getQueryBuilder();
  144. $query->select('class', 'entity')
  145. ->selectAlias($query->expr()->castColumn('events', IQueryBuilder::PARAM_STR), 'events')
  146. ->from('flow_operations')
  147. ->where($query->expr()->neq('events', $query->createNamedParameter('[]'), IQueryBuilder::PARAM_STR))
  148. ->groupBy('class', 'entity', $query->expr()->castColumn('events', IQueryBuilder::PARAM_STR));
  149. $result = $query->execute();
  150. $operations = [];
  151. while ($row = $result->fetch()) {
  152. $eventNames = \json_decode($row['events']);
  153. $operation = $row['class'];
  154. $entity = $row['entity'];
  155. $operations[$operation] = $operations[$row['class']] ?? [];
  156. $operations[$operation][$entity] = $operations[$operation][$entity] ?? [];
  157. $operations[$operation][$entity] = array_unique(array_merge($operations[$operation][$entity], $eventNames ?? []));
  158. }
  159. $result->closeCursor();
  160. $cache->set('events', $operations, 3600);
  161. return $operations;
  162. }
  163. /**
  164. * @param string $operationClass
  165. * @return ScopeContext[]
  166. */
  167. public function getAllConfiguredScopesForOperation(string $operationClass): array {
  168. static $scopesByOperation = [];
  169. if (isset($scopesByOperation[$operationClass])) {
  170. return $scopesByOperation[$operationClass];
  171. }
  172. try {
  173. /** @var IOperation $operation */
  174. $operation = $this->container->query($operationClass);
  175. } catch (QueryException $e) {
  176. return [];
  177. }
  178. $query = $this->connection->getQueryBuilder();
  179. $query->selectDistinct('s.type')
  180. ->addSelect('s.value')
  181. ->from('flow_operations', 'o')
  182. ->leftJoin('o', 'flow_operations_scope', 's', $query->expr()->eq('o.id', 's.operation_id'))
  183. ->where($query->expr()->eq('o.class', $query->createParameter('operationClass')));
  184. $query->setParameters(['operationClass' => $operationClass]);
  185. $result = $query->execute();
  186. $scopesByOperation[$operationClass] = [];
  187. while ($row = $result->fetch()) {
  188. $scope = new ScopeContext($row['type'], $row['value']);
  189. if (!$operation->isAvailableForScope((int) $row['type'])) {
  190. continue;
  191. }
  192. $scopesByOperation[$operationClass][$scope->getHash()] = $scope;
  193. }
  194. return $scopesByOperation[$operationClass];
  195. }
  196. public function getAllOperations(ScopeContext $scopeContext): array {
  197. if (isset($this->operations[$scopeContext->getHash()])) {
  198. return $this->operations[$scopeContext->getHash()];
  199. }
  200. $query = $this->connection->getQueryBuilder();
  201. $query->select('o.*')
  202. ->selectAlias('s.type', 'scope_type')
  203. ->selectAlias('s.value', 'scope_actor_id')
  204. ->from('flow_operations', 'o')
  205. ->leftJoin('o', 'flow_operations_scope', 's', $query->expr()->eq('o.id', 's.operation_id'))
  206. ->where($query->expr()->eq('s.type', $query->createParameter('scope')));
  207. if ($scopeContext->getScope() === IManager::SCOPE_USER) {
  208. $query->andWhere($query->expr()->eq('s.value', $query->createParameter('scopeId')));
  209. }
  210. $query->setParameters(['scope' => $scopeContext->getScope(), 'scopeId' => $scopeContext->getScopeId()]);
  211. $result = $query->execute();
  212. $this->operations[$scopeContext->getHash()] = [];
  213. while ($row = $result->fetch()) {
  214. try {
  215. /** @var IOperation $operation */
  216. $operation = $this->container->query($row['class']);
  217. } catch (QueryException $e) {
  218. continue;
  219. }
  220. if (!$operation->isAvailableForScope((int) $row['scope_type'])) {
  221. continue;
  222. }
  223. if (!isset($this->operations[$scopeContext->getHash()][$row['class']])) {
  224. $this->operations[$scopeContext->getHash()][$row['class']] = [];
  225. }
  226. $this->operations[$scopeContext->getHash()][$row['class']][] = $row;
  227. }
  228. return $this->operations[$scopeContext->getHash()];
  229. }
  230. public function getOperations(string $class, ScopeContext $scopeContext): array {
  231. if (!isset($this->operations[$scopeContext->getHash()])) {
  232. $this->getAllOperations($scopeContext);
  233. }
  234. return $this->operations[$scopeContext->getHash()][$class] ?? [];
  235. }
  236. /**
  237. * @param int $id
  238. * @return array
  239. * @throws \UnexpectedValueException
  240. */
  241. protected function getOperation($id) {
  242. $query = $this->connection->getQueryBuilder();
  243. $query->select('*')
  244. ->from('flow_operations')
  245. ->where($query->expr()->eq('id', $query->createNamedParameter($id)));
  246. $result = $query->execute();
  247. $row = $result->fetch();
  248. $result->closeCursor();
  249. if ($row) {
  250. return $row;
  251. }
  252. throw new \UnexpectedValueException($this->l->t('Operation #%s does not exist', [$id]));
  253. }
  254. protected function insertOperation(
  255. string $class,
  256. string $name,
  257. array $checkIds,
  258. string $operation,
  259. string $entity,
  260. array $events
  261. ): int {
  262. $query = $this->connection->getQueryBuilder();
  263. $query->insert('flow_operations')
  264. ->values([
  265. 'class' => $query->createNamedParameter($class),
  266. 'name' => $query->createNamedParameter($name),
  267. 'checks' => $query->createNamedParameter(json_encode(array_unique($checkIds))),
  268. 'operation' => $query->createNamedParameter($operation),
  269. 'entity' => $query->createNamedParameter($entity),
  270. 'events' => $query->createNamedParameter(json_encode($events))
  271. ]);
  272. $query->execute();
  273. $this->cacheFactory->createDistributed('flow')->remove('events');
  274. return $query->getLastInsertId();
  275. }
  276. /**
  277. * @param string $class
  278. * @param string $name
  279. * @param array[] $checks
  280. * @param string $operation
  281. * @return array The added operation
  282. * @throws \UnexpectedValueException
  283. * @throw Exception
  284. */
  285. public function addOperation(
  286. string $class,
  287. string $name,
  288. array $checks,
  289. string $operation,
  290. ScopeContext $scope,
  291. string $entity,
  292. array $events
  293. ) {
  294. $this->validateOperation($class, $name, $checks, $operation, $scope, $entity, $events);
  295. $this->connection->beginTransaction();
  296. try {
  297. $checkIds = [];
  298. foreach ($checks as $check) {
  299. $checkIds[] = $this->addCheck($check['class'], $check['operator'], $check['value']);
  300. }
  301. $id = $this->insertOperation($class, $name, $checkIds, $operation, $entity, $events);
  302. $this->addScope($id, $scope);
  303. $this->connection->commit();
  304. } catch (Exception $e) {
  305. $this->connection->rollBack();
  306. throw $e;
  307. }
  308. return $this->getOperation($id);
  309. }
  310. protected function canModify(int $id, ScopeContext $scopeContext):bool {
  311. if (isset($this->operationsByScope[$scopeContext->getHash()])) {
  312. return in_array($id, $this->operationsByScope[$scopeContext->getHash()], true);
  313. }
  314. $qb = $this->connection->getQueryBuilder();
  315. $qb = $qb->select('o.id')
  316. ->from('flow_operations', 'o')
  317. ->leftJoin('o', 'flow_operations_scope', 's', $qb->expr()->eq('o.id', 's.operation_id'))
  318. ->where($qb->expr()->eq('s.type', $qb->createParameter('scope')));
  319. if ($scopeContext->getScope() !== IManager::SCOPE_ADMIN) {
  320. $qb->andWhere($qb->expr()->eq('s.value', $qb->createParameter('scopeId')));
  321. }
  322. $qb->setParameters(['scope' => $scopeContext->getScope(), 'scopeId' => $scopeContext->getScopeId()]);
  323. $result = $qb->execute();
  324. $operations = [];
  325. while (($opId = $result->fetchOne()) !== false) {
  326. $operations[] = (int)$opId;
  327. }
  328. $this->operationsByScope[$scopeContext->getHash()] = $operations;
  329. $result->closeCursor();
  330. return in_array($id, $this->operationsByScope[$scopeContext->getHash()], true);
  331. }
  332. /**
  333. * @param int $id
  334. * @param string $name
  335. * @param array[] $checks
  336. * @param string $operation
  337. * @return array The updated operation
  338. * @throws \UnexpectedValueException
  339. * @throws \DomainException
  340. * @throws Exception
  341. */
  342. public function updateOperation(
  343. int $id,
  344. string $name,
  345. array $checks,
  346. string $operation,
  347. ScopeContext $scopeContext,
  348. string $entity,
  349. array $events
  350. ): array {
  351. if (!$this->canModify($id, $scopeContext)) {
  352. throw new \DomainException('Target operation not within scope');
  353. };
  354. $row = $this->getOperation($id);
  355. $this->validateOperation($row['class'], $name, $checks, $operation, $scopeContext, $entity, $events);
  356. $checkIds = [];
  357. try {
  358. $this->connection->beginTransaction();
  359. foreach ($checks as $check) {
  360. $checkIds[] = $this->addCheck($check['class'], $check['operator'], $check['value']);
  361. }
  362. $query = $this->connection->getQueryBuilder();
  363. $query->update('flow_operations')
  364. ->set('name', $query->createNamedParameter($name))
  365. ->set('checks', $query->createNamedParameter(json_encode(array_unique($checkIds))))
  366. ->set('operation', $query->createNamedParameter($operation))
  367. ->set('entity', $query->createNamedParameter($entity))
  368. ->set('events', $query->createNamedParameter(json_encode($events)))
  369. ->where($query->expr()->eq('id', $query->createNamedParameter($id)));
  370. $query->execute();
  371. $this->connection->commit();
  372. } catch (Exception $e) {
  373. $this->connection->rollBack();
  374. throw $e;
  375. }
  376. unset($this->operations[$scopeContext->getHash()]);
  377. $this->cacheFactory->createDistributed('flow')->remove('events');
  378. return $this->getOperation($id);
  379. }
  380. /**
  381. * @param int $id
  382. * @return bool
  383. * @throws \UnexpectedValueException
  384. * @throws Exception
  385. * @throws \DomainException
  386. */
  387. public function deleteOperation($id, ScopeContext $scopeContext) {
  388. if (!$this->canModify($id, $scopeContext)) {
  389. throw new \DomainException('Target operation not within scope');
  390. };
  391. $query = $this->connection->getQueryBuilder();
  392. try {
  393. $this->connection->beginTransaction();
  394. $result = (bool)$query->delete('flow_operations')
  395. ->where($query->expr()->eq('id', $query->createNamedParameter($id)))
  396. ->execute();
  397. if ($result) {
  398. $qb = $this->connection->getQueryBuilder();
  399. $result &= (bool)$qb->delete('flow_operations_scope')
  400. ->where($qb->expr()->eq('operation_id', $qb->createNamedParameter($id)))
  401. ->execute();
  402. }
  403. $this->connection->commit();
  404. } catch (Exception $e) {
  405. $this->connection->rollBack();
  406. throw $e;
  407. }
  408. if (isset($this->operations[$scopeContext->getHash()])) {
  409. unset($this->operations[$scopeContext->getHash()]);
  410. }
  411. $this->cacheFactory->createDistributed('flow')->remove('events');
  412. return $result;
  413. }
  414. protected function validateEvents(string $entity, array $events, IOperation $operation) {
  415. try {
  416. /** @var IEntity $instance */
  417. $instance = $this->container->query($entity);
  418. } catch (QueryException $e) {
  419. throw new \UnexpectedValueException($this->l->t('Entity %s does not exist', [$entity]));
  420. }
  421. if (!$instance instanceof IEntity) {
  422. throw new \UnexpectedValueException($this->l->t('Entity %s is invalid', [$entity]));
  423. }
  424. if (empty($events)) {
  425. if (!$operation instanceof IComplexOperation) {
  426. throw new \UnexpectedValueException($this->l->t('No events are chosen.'));
  427. }
  428. return;
  429. }
  430. $availableEvents = [];
  431. foreach ($instance->getEvents() as $event) {
  432. /** @var IEntityEvent $event */
  433. $availableEvents[] = $event->getEventName();
  434. }
  435. $diff = array_diff($events, $availableEvents);
  436. if (!empty($diff)) {
  437. throw new \UnexpectedValueException($this->l->t('Entity %s has no event %s', [$entity, array_shift($diff)]));
  438. }
  439. }
  440. /**
  441. * @param string $class
  442. * @param string $name
  443. * @param array[] $checks
  444. * @param string $operation
  445. * @param ScopeContext $scope
  446. * @param string $entity
  447. * @param array $events
  448. * @throws \UnexpectedValueException
  449. */
  450. public function validateOperation($class, $name, array $checks, $operation, ScopeContext $scope, string $entity, array $events) {
  451. try {
  452. /** @var IOperation $instance */
  453. $instance = $this->container->query($class);
  454. } catch (QueryException $e) {
  455. throw new \UnexpectedValueException($this->l->t('Operation %s does not exist', [$class]));
  456. }
  457. if (!($instance instanceof IOperation)) {
  458. throw new \UnexpectedValueException($this->l->t('Operation %s is invalid', [$class]));
  459. }
  460. if (!$instance->isAvailableForScope($scope->getScope())) {
  461. throw new \UnexpectedValueException($this->l->t('Operation %s is invalid', [$class]));
  462. }
  463. $this->validateEvents($entity, $events, $instance);
  464. if (count($checks) === 0) {
  465. throw new \UnexpectedValueException($this->l->t('At least one check needs to be provided'));
  466. }
  467. if (strlen((string)$operation) > IManager::MAX_OPERATION_VALUE_BYTES) {
  468. throw new \UnexpectedValueException($this->l->t('The provided operation data is too long'));
  469. }
  470. $instance->validateOperation($name, $checks, $operation);
  471. foreach ($checks as $check) {
  472. if (!is_string($check['class'])) {
  473. throw new \UnexpectedValueException($this->l->t('Invalid check provided'));
  474. }
  475. try {
  476. /** @var ICheck $instance */
  477. $instance = $this->container->query($check['class']);
  478. } catch (QueryException $e) {
  479. throw new \UnexpectedValueException($this->l->t('Check %s does not exist', [$class]));
  480. }
  481. if (!($instance instanceof ICheck)) {
  482. throw new \UnexpectedValueException($this->l->t('Check %s is invalid', [$class]));
  483. }
  484. if (!empty($instance->supportedEntities())
  485. && !in_array($entity, $instance->supportedEntities())
  486. ) {
  487. throw new \UnexpectedValueException($this->l->t('Check %s is not allowed with this entity', [$class]));
  488. }
  489. if (strlen((string)$check['value']) > IManager::MAX_CHECK_VALUE_BYTES) {
  490. throw new \UnexpectedValueException($this->l->t('The provided check value is too long'));
  491. }
  492. $instance->validateCheck($check['operator'], $check['value']);
  493. }
  494. }
  495. /**
  496. * @param int[] $checkIds
  497. * @return array[]
  498. */
  499. public function getChecks(array $checkIds) {
  500. $checkIds = array_map('intval', $checkIds);
  501. $checks = [];
  502. foreach ($checkIds as $i => $checkId) {
  503. if (isset($this->checks[$checkId])) {
  504. $checks[$checkId] = $this->checks[$checkId];
  505. unset($checkIds[$i]);
  506. }
  507. }
  508. if (empty($checkIds)) {
  509. return $checks;
  510. }
  511. $query = $this->connection->getQueryBuilder();
  512. $query->select('*')
  513. ->from('flow_checks')
  514. ->where($query->expr()->in('id', $query->createNamedParameter($checkIds, IQueryBuilder::PARAM_INT_ARRAY)));
  515. $result = $query->execute();
  516. while ($row = $result->fetch()) {
  517. $this->checks[(int) $row['id']] = $row;
  518. $checks[(int) $row['id']] = $row;
  519. }
  520. $result->closeCursor();
  521. $checkIds = array_diff($checkIds, array_keys($checks));
  522. if (!empty($checkIds)) {
  523. $missingCheck = array_pop($checkIds);
  524. throw new \UnexpectedValueException($this->l->t('Check #%s does not exist', $missingCheck));
  525. }
  526. return $checks;
  527. }
  528. /**
  529. * @param string $class
  530. * @param string $operator
  531. * @param string $value
  532. * @return int Check unique ID
  533. */
  534. protected function addCheck($class, $operator, $value) {
  535. $hash = md5($class . '::' . $operator . '::' . $value);
  536. $query = $this->connection->getQueryBuilder();
  537. $query->select('id')
  538. ->from('flow_checks')
  539. ->where($query->expr()->eq('hash', $query->createNamedParameter($hash)));
  540. $result = $query->execute();
  541. if ($row = $result->fetch()) {
  542. $result->closeCursor();
  543. return (int) $row['id'];
  544. }
  545. $query = $this->connection->getQueryBuilder();
  546. $query->insert('flow_checks')
  547. ->values([
  548. 'class' => $query->createNamedParameter($class),
  549. 'operator' => $query->createNamedParameter($operator),
  550. 'value' => $query->createNamedParameter($value),
  551. 'hash' => $query->createNamedParameter($hash),
  552. ]);
  553. $query->execute();
  554. return $query->getLastInsertId();
  555. }
  556. protected function addScope(int $operationId, ScopeContext $scope): void {
  557. $query = $this->connection->getQueryBuilder();
  558. $insertQuery = $query->insert('flow_operations_scope');
  559. $insertQuery->values([
  560. 'operation_id' => $query->createNamedParameter($operationId),
  561. 'type' => $query->createNamedParameter($scope->getScope()),
  562. 'value' => $query->createNamedParameter($scope->getScopeId()),
  563. ]);
  564. $insertQuery->execute();
  565. }
  566. public function formatOperation(array $operation): array {
  567. $checkIds = json_decode($operation['checks'], true);
  568. $checks = $this->getChecks($checkIds);
  569. $operation['checks'] = [];
  570. foreach ($checks as $check) {
  571. // Remove internal values
  572. unset($check['id']);
  573. unset($check['hash']);
  574. $operation['checks'][] = $check;
  575. }
  576. $operation['events'] = json_decode($operation['events'], true) ?? [];
  577. return $operation;
  578. }
  579. /**
  580. * @return IEntity[]
  581. */
  582. public function getEntitiesList(): array {
  583. $this->dispatcher->dispatchTyped(new RegisterEntitiesEvent($this));
  584. $this->legacyEventDispatcher->dispatch(IManager::EVENT_NAME_REG_ENTITY, new GenericEvent($this));
  585. return array_values(array_merge($this->getBuildInEntities(), $this->registeredEntities));
  586. }
  587. /**
  588. * @return IOperation[]
  589. */
  590. public function getOperatorList(): array {
  591. $this->dispatcher->dispatchTyped(new RegisterOperationsEvent($this));
  592. $this->legacyEventDispatcher->dispatch(IManager::EVENT_NAME_REG_OPERATION, new GenericEvent($this));
  593. return array_merge($this->getBuildInOperators(), $this->registeredOperators);
  594. }
  595. /**
  596. * @return ICheck[]
  597. */
  598. public function getCheckList(): array {
  599. $this->dispatcher->dispatchTyped(new RegisterChecksEvent($this));
  600. $this->legacyEventDispatcher->dispatch(IManager::EVENT_NAME_REG_CHECK, new GenericEvent($this));
  601. return array_merge($this->getBuildInChecks(), $this->registeredChecks);
  602. }
  603. public function registerEntity(IEntity $entity): void {
  604. $this->registeredEntities[get_class($entity)] = $entity;
  605. }
  606. public function registerOperation(IOperation $operator): void {
  607. $this->registeredOperators[get_class($operator)] = $operator;
  608. }
  609. public function registerCheck(ICheck $check): void {
  610. $this->registeredChecks[get_class($check)] = $check;
  611. }
  612. /**
  613. * @return IEntity[]
  614. */
  615. protected function getBuildInEntities(): array {
  616. try {
  617. return [
  618. File::class => $this->container->query(File::class),
  619. ];
  620. } catch (QueryException $e) {
  621. $this->logger->logException($e);
  622. return [];
  623. }
  624. }
  625. /**
  626. * @return IOperation[]
  627. */
  628. protected function getBuildInOperators(): array {
  629. try {
  630. return [
  631. // None yet
  632. ];
  633. } catch (QueryException $e) {
  634. $this->logger->logException($e);
  635. return [];
  636. }
  637. }
  638. /**
  639. * @return ICheck[]
  640. */
  641. protected function getBuildInChecks(): array {
  642. try {
  643. return [
  644. $this->container->query(FileMimeType::class),
  645. $this->container->query(FileName::class),
  646. $this->container->query(FileSize::class),
  647. $this->container->query(FileSystemTags::class),
  648. $this->container->query(RequestRemoteAddress::class),
  649. $this->container->query(RequestTime::class),
  650. $this->container->query(RequestURL::class),
  651. $this->container->query(RequestUserAgent::class),
  652. $this->container->query(UserGroupMembership::class),
  653. ];
  654. } catch (QueryException $e) {
  655. $this->logger->logException($e);
  656. return [];
  657. }
  658. }
  659. public function isUserScopeEnabled(): bool {
  660. return $this->config->getAppValue(Application::APP_ID, 'user_scope_disabled', 'no') === 'no';
  661. }
  662. }