Manager.php 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. <?php
  2. /**
  3. * @copyright Copyright (c) 2016 Morris Jobke <hey@morrisjobke.de>
  4. *
  5. * @license GNU AGPL version 3 or any later version
  6. *
  7. * This program is free software: you can redistribute it and/or modify
  8. * it under the terms of the GNU Affero General Public License as
  9. * published by the Free Software Foundation, either version 3 of the
  10. * License, or (at your option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU Affero General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU Affero General Public License
  18. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  19. *
  20. */
  21. namespace OCA\WorkflowEngine;
  22. use OCP\AppFramework\QueryException;
  23. use OCP\DB\QueryBuilder\IQueryBuilder;
  24. use OCP\Files\Storage\IStorage;
  25. use OCP\IDBConnection;
  26. use OCP\IL10N;
  27. use OCP\IServerContainer;
  28. use OCP\WorkflowEngine\ICheck;
  29. use OCP\WorkflowEngine\IManager;
  30. use OCP\WorkflowEngine\IOperation;
  31. class Manager implements IManager {
  32. /** @var IStorage */
  33. protected $storage;
  34. /** @var string */
  35. protected $path;
  36. /** @var array[] */
  37. protected $operations = [];
  38. /** @var array[] */
  39. protected $checks = [];
  40. /** @var IDBConnection */
  41. protected $connection;
  42. /** @var IServerContainer|\OC\Server */
  43. protected $container;
  44. /** @var IL10N */
  45. protected $l;
  46. /**
  47. * @param IDBConnection $connection
  48. * @param IServerContainer $container
  49. * @param IL10N $l
  50. */
  51. public function __construct(IDBConnection $connection, IServerContainer $container, IL10N $l) {
  52. $this->connection = $connection;
  53. $this->container = $container;
  54. $this->l = $l;
  55. }
  56. /**
  57. * @inheritdoc
  58. */
  59. public function setFileInfo(IStorage $storage, $path) {
  60. $this->storage = $storage;
  61. $this->path = $path;
  62. }
  63. /**
  64. * @inheritdoc
  65. */
  66. public function getMatchingOperations($class, $returnFirstMatchingOperationOnly = true) {
  67. $operations = $this->getOperations($class);
  68. $matches = [];
  69. foreach ($operations as $operation) {
  70. $checkIds = json_decode($operation['checks'], true);
  71. $checks = $this->getChecks($checkIds);
  72. foreach ($checks as $check) {
  73. if (!$this->check($check)) {
  74. // Check did not match, continue with the next operation
  75. continue 2;
  76. }
  77. }
  78. if ($returnFirstMatchingOperationOnly) {
  79. return $operation;
  80. }
  81. $matches[] = $operation;
  82. }
  83. return $matches;
  84. }
  85. /**
  86. * @param array $check
  87. * @return bool
  88. */
  89. protected function check(array $check) {
  90. try {
  91. $checkInstance = $this->container->query($check['class']);
  92. } catch (QueryException $e) {
  93. // Check does not exist, assume it matches.
  94. return true;
  95. }
  96. if ($checkInstance instanceof ICheck) {
  97. $checkInstance->setFileInfo($this->storage, $this->path);
  98. return $checkInstance->executeCheck($check['operator'], $check['value']);
  99. } else {
  100. // Check is invalid
  101. throw new \UnexpectedValueException($this->l->t('Check %s is invalid or does not exist', $check['class']));
  102. }
  103. }
  104. /**
  105. * @param string $class
  106. * @return array[]
  107. */
  108. public function getOperations($class) {
  109. if (isset($this->operations[$class])) {
  110. return $this->operations[$class];
  111. }
  112. $query = $this->connection->getQueryBuilder();
  113. $query->select('*')
  114. ->from('flow_operations')
  115. ->where($query->expr()->eq('class', $query->createNamedParameter($class)));
  116. $result = $query->execute();
  117. $this->operations[$class] = [];
  118. while ($row = $result->fetch()) {
  119. $this->operations[$class][] = $row;
  120. }
  121. $result->closeCursor();
  122. return $this->operations[$class];
  123. }
  124. /**
  125. * @param int $id
  126. * @return array
  127. * @throws \UnexpectedValueException
  128. */
  129. protected function getOperation($id) {
  130. $query = $this->connection->getQueryBuilder();
  131. $query->select('*')
  132. ->from('flow_operations')
  133. ->where($query->expr()->eq('id', $query->createNamedParameter($id)));
  134. $result = $query->execute();
  135. $row = $result->fetch();
  136. $result->closeCursor();
  137. if ($row) {
  138. return $row;
  139. }
  140. throw new \UnexpectedValueException($this->l->t('Operation #%s does not exist', [$id]));
  141. }
  142. /**
  143. * @param string $class
  144. * @param string $name
  145. * @param array[] $checks
  146. * @param string $operation
  147. * @return array The added operation
  148. * @throws \UnexpectedValueException
  149. */
  150. public function addOperation($class, $name, array $checks, $operation) {
  151. $this->validateOperation($class, $name, $checks, $operation);
  152. $checkIds = [];
  153. foreach ($checks as $check) {
  154. $checkIds[] = $this->addCheck($check['class'], $check['operator'], $check['value']);
  155. }
  156. $query = $this->connection->getQueryBuilder();
  157. $query->insert('flow_operations')
  158. ->values([
  159. 'class' => $query->createNamedParameter($class),
  160. 'name' => $query->createNamedParameter($name),
  161. 'checks' => $query->createNamedParameter(json_encode(array_unique($checkIds))),
  162. 'operation' => $query->createNamedParameter($operation),
  163. ]);
  164. $query->execute();
  165. $id = $query->getLastInsertId();
  166. return $this->getOperation($id);
  167. }
  168. /**
  169. * @param int $id
  170. * @param string $name
  171. * @param array[] $checks
  172. * @param string $operation
  173. * @return array The updated operation
  174. * @throws \UnexpectedValueException
  175. */
  176. public function updateOperation($id, $name, array $checks, $operation) {
  177. $row = $this->getOperation($id);
  178. $this->validateOperation($row['class'], $name, $checks, $operation);
  179. $checkIds = [];
  180. foreach ($checks as $check) {
  181. $checkIds[] = $this->addCheck($check['class'], $check['operator'], $check['value']);
  182. }
  183. $query = $this->connection->getQueryBuilder();
  184. $query->update('flow_operations')
  185. ->set('name', $query->createNamedParameter($name))
  186. ->set('checks', $query->createNamedParameter(json_encode(array_unique($checkIds))))
  187. ->set('operation', $query->createNamedParameter($operation))
  188. ->where($query->expr()->eq('id', $query->createNamedParameter($id)));
  189. $query->execute();
  190. return $this->getOperation($id);
  191. }
  192. /**
  193. * @param int $id
  194. * @return bool
  195. * @throws \UnexpectedValueException
  196. */
  197. public function deleteOperation($id) {
  198. $query = $this->connection->getQueryBuilder();
  199. $query->delete('flow_operations')
  200. ->where($query->expr()->eq('id', $query->createNamedParameter($id)));
  201. return (bool) $query->execute();
  202. }
  203. /**
  204. * @param string $class
  205. * @param string $name
  206. * @param array[] $checks
  207. * @param string $operation
  208. * @throws \UnexpectedValueException
  209. */
  210. protected function validateOperation($class, $name, array $checks, $operation) {
  211. try {
  212. /** @var IOperation $instance */
  213. $instance = $this->container->query($class);
  214. } catch (QueryException $e) {
  215. throw new \UnexpectedValueException($this->l->t('Operation %s does not exist', [$class]));
  216. }
  217. if (!($instance instanceof IOperation)) {
  218. throw new \UnexpectedValueException($this->l->t('Operation %s is invalid', [$class]));
  219. }
  220. $instance->validateOperation($name, $checks, $operation);
  221. foreach ($checks as $check) {
  222. try {
  223. /** @var ICheck $instance */
  224. $instance = $this->container->query($check['class']);
  225. } catch (QueryException $e) {
  226. throw new \UnexpectedValueException($this->l->t('Check %s does not exist', [$class]));
  227. }
  228. if (!($instance instanceof ICheck)) {
  229. throw new \UnexpectedValueException($this->l->t('Check %s is invalid', [$class]));
  230. }
  231. $instance->validateCheck($check['operator'], $check['value']);
  232. }
  233. }
  234. /**
  235. * @param int[] $checkIds
  236. * @return array[]
  237. */
  238. public function getChecks(array $checkIds) {
  239. $checkIds = array_map('intval', $checkIds);
  240. $checks = [];
  241. foreach ($checkIds as $i => $checkId) {
  242. if (isset($this->checks[$checkId])) {
  243. $checks[$checkId] = $this->checks[$checkId];
  244. unset($checkIds[$i]);
  245. }
  246. }
  247. if (empty($checkIds)) {
  248. return $checks;
  249. }
  250. $query = $this->connection->getQueryBuilder();
  251. $query->select('*')
  252. ->from('flow_checks')
  253. ->where($query->expr()->in('id', $query->createNamedParameter($checkIds, IQueryBuilder::PARAM_INT_ARRAY)));
  254. $result = $query->execute();
  255. while ($row = $result->fetch()) {
  256. $this->checks[(int) $row['id']] = $row;
  257. $checks[(int) $row['id']] = $row;
  258. }
  259. $result->closeCursor();
  260. $checkIds = array_diff($checkIds, array_keys($checks));
  261. if (!empty($checkIds)) {
  262. $missingCheck = array_pop($checkIds);
  263. throw new \UnexpectedValueException($this->l->t('Check #%s does not exist', $missingCheck));
  264. }
  265. return $checks;
  266. }
  267. /**
  268. * @param string $class
  269. * @param string $operator
  270. * @param string $value
  271. * @return int Check unique ID
  272. */
  273. protected function addCheck($class, $operator, $value) {
  274. $hash = md5($class . '::' . $operator . '::' . $value);
  275. $query = $this->connection->getQueryBuilder();
  276. $query->select('id')
  277. ->from('flow_checks')
  278. ->where($query->expr()->eq('hash', $query->createNamedParameter($hash)));
  279. $result = $query->execute();
  280. if ($row = $result->fetch()) {
  281. $result->closeCursor();
  282. return (int) $row['id'];
  283. }
  284. $query = $this->connection->getQueryBuilder();
  285. $query->insert('flow_checks')
  286. ->values([
  287. 'class' => $query->createNamedParameter($class),
  288. 'operator' => $query->createNamedParameter($operator),
  289. 'value' => $query->createNamedParameter($value),
  290. 'hash' => $query->createNamedParameter($hash),
  291. ]);
  292. $query->execute();
  293. return $query->getLastInsertId();
  294. }
  295. }