Passed
Push — master ( 2187f8...15d39c )
by Joas
11:45 queued 12s
created

Manager::validateOperation()   B

Complexity

Conditions 8
Paths 7

Size

Total Lines 35
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 19
c 0
b 0
f 0
nc 7
nop 6
dl 0
loc 35
rs 8.4444
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016 Morris Jobke <[email protected]>
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
22
namespace OCA\WorkflowEngine;
23
24
use Doctrine\DBAL\DBALException;
25
use OC\Cache\CappedMemoryCache;
26
use OCA\WorkflowEngine\Check\FileMimeType;
27
use OCA\WorkflowEngine\Check\FileName;
28
use OCA\WorkflowEngine\Check\FileSize;
29
use OCA\WorkflowEngine\Check\FileSystemTags;
30
use OCA\WorkflowEngine\Check\RequestRemoteAddress;
31
use OCA\WorkflowEngine\Check\RequestTime;
32
use OCA\WorkflowEngine\Check\RequestURL;
33
use OCA\WorkflowEngine\Check\RequestUserAgent;
34
use OCA\WorkflowEngine\Check\UserGroupMembership;
35
use OCA\WorkflowEngine\Entity\File;
36
use OCA\WorkflowEngine\Helper\ScopeContext;
37
use OCA\WorkflowEngine\Service\RuleMatcher;
38
use OCP\AppFramework\QueryException;
39
use OCP\DB\QueryBuilder\IQueryBuilder;
40
use OCP\Files\Storage\IStorage;
41
use OCP\IDBConnection;
42
use OCP\IL10N;
43
use OCP\ILogger;
44
use OCP\IServerContainer;
45
use OCP\IUserSession;
46
use OCP\WorkflowEngine\ICheck;
47
use OCP\WorkflowEngine\IComplexOperation;
48
use OCP\WorkflowEngine\IEntity;
49
use OCP\WorkflowEngine\IEntityEvent;
50
use OCP\WorkflowEngine\IManager;
51
use OCP\WorkflowEngine\IOperation;
52
use OCP\WorkflowEngine\IRuleMatcher;
53
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
54
use Symfony\Component\EventDispatcher\GenericEvent;
55
56
class Manager implements IManager {
57
58
	/** @var IStorage */
59
	protected $storage;
60
61
	/** @var string */
62
	protected $path;
63
64
	/** @var object */
65
	protected $entity;
66
67
	/** @var array[] */
68
	protected $operations = [];
69
70
	/** @var array[] */
71
	protected $checks = [];
72
73
	/** @var IDBConnection */
74
	protected $connection;
75
76
	/** @var IServerContainer|\OC\Server */
77
	protected $container;
78
79
	/** @var IL10N */
80
	protected $l;
81
82
	/** @var EventDispatcherInterface */
83
	protected $eventDispatcher;
84
85
	/** @var IEntity[] */
86
	protected $registeredEntities = [];
87
88
	/** @var IOperation[] */
89
	protected $registeredOperators = [];
90
91
	/** @var ICheck[] */
92
	protected $registeredChecks = [];
93
94
	/** @var ILogger */
95
	protected $logger;
96
97
	/** @var CappedMemoryCache */
98
	protected $operationsByScope = [];
99
100
	/** @var IUserSession */
101
	protected $session;
102
103
	/**
104
	 * @param IDBConnection $connection
105
	 * @param IServerContainer $container
106
	 * @param IL10N $l
107
	 */
108
	public function __construct(
109
		IDBConnection $connection,
110
		IServerContainer $container,
111
		IL10N $l,
112
		EventDispatcherInterface $eventDispatcher,
113
		ILogger $logger,
114
		IUserSession $session
115
	) {
116
		$this->connection = $connection;
117
		$this->container = $container;
118
		$this->l = $l;
119
		$this->eventDispatcher = $eventDispatcher;
120
		$this->logger = $logger;
121
		$this->operationsByScope = new CappedMemoryCache(64);
122
		$this->session = $session;
123
	}
124
125
	public function getRuleMatcher(): IRuleMatcher {
126
		return new RuleMatcher($this->session, $this->container, $this->l, $this);
127
	}
128
129
	public function getAllConfiguredEvents() {
130
		$query = $this->connection->getQueryBuilder();
131
132
		$query->selectDistinct('class')
133
			->addSelect('entity', 'events')
134
			->from('flow_operations')
135
			->where($query->expr()->neq('events', $query->createNamedParameter('[]'), IQueryBuilder::PARAM_STR));
136
137
		$result = $query->execute();
138
		$operations = [];
139
		while($row = $result->fetch()) {
140
			$eventNames = \json_decode($row['events']);
141
142
			$operation = $row['class'];
143
			$entity =  $row['entity'];
144
145
			$operations[$operation] = $operations[$row['class']] ?? [];
146
			$operations[$operation][$entity] = $operations[$operation][$entity] ?? [];
147
148
			$operations[$operation][$entity] = array_unique(array_merge($operations[$operation][$entity], $eventNames));
149
		}
150
		$result->closeCursor();
151
152
		return $operations;
153
	}
154
155
	public function getAllOperations(ScopeContext $scopeContext): array {
156
		if(isset($this->operations[$scopeContext->getHash()])) {
157
			return $this->operations[$scopeContext->getHash()];
158
		}
159
160
		$query = $this->connection->getQueryBuilder();
161
162
		$query->select('o.*')
163
			->from('flow_operations', 'o')
164
			->leftJoin('o', 'flow_operations_scope', 's', $query->expr()->eq('o.id', 's.operation_id'))
165
			->where($query->expr()->eq('s.type', $query->createParameter('scope')));
166
167
		if($scopeContext->getScope() === IManager::SCOPE_USER) {
168
			$query->andWhere($query->expr()->eq('s.value', $query->createParameter('scopeId')));
169
		}
170
171
		$query->setParameters(['scope' => $scopeContext->getScope(), 'scopeId' => $scopeContext->getScopeId()]);
172
		$result = $query->execute();
173
174
		$this->operations[$scopeContext->getHash()] = [];
175
		while ($row = $result->fetch()) {
176
			if(!isset($this->operations[$scopeContext->getHash()][$row['class']])) {
177
				$this->operations[$scopeContext->getHash()][$row['class']] = [];
178
			}
179
			$this->operations[$scopeContext->getHash()][$row['class']][] = $row;
180
		}
181
182
		return $this->operations[$scopeContext->getHash()];
183
	}
184
185
	public function getOperations(string $class, ScopeContext $scopeContext): array {
186
		if (!isset($this->operations[$scopeContext->getHash()])) {
187
			$this->getAllOperations($scopeContext);
188
		}
189
		return $this->operations[$scopeContext->getHash()][$class] ?? [];
190
	}
191
192
	/**
193
	 * @param int $id
194
	 * @return array
195
	 * @throws \UnexpectedValueException
196
	 */
197
	protected function getOperation($id) {
198
		$query = $this->connection->getQueryBuilder();
199
		$query->select('*')
200
			->from('flow_operations')
201
			->where($query->expr()->eq('id', $query->createNamedParameter($id)));
202
		$result = $query->execute();
203
		$row = $result->fetch();
204
		$result->closeCursor();
205
206
		if ($row) {
207
			return $row;
208
		}
209
210
		throw new \UnexpectedValueException($this->l->t('Operation #%s does not exist', [$id]));
211
	}
212
213
	protected function insertOperation(
214
		string $class,
215
		string $name,
216
		array $checkIds,
217
		string $operation,
218
		string $entity,
219
		array $events
220
	): int {
221
		$query = $this->connection->getQueryBuilder();
222
		$query->insert('flow_operations')
223
			->values([
224
				'class' => $query->createNamedParameter($class),
225
				'name' => $query->createNamedParameter($name),
226
				'checks' => $query->createNamedParameter(json_encode(array_unique($checkIds))),
227
				'operation' => $query->createNamedParameter($operation),
228
				'entity' => $query->createNamedParameter($entity),
229
				'events' => $query->createNamedParameter(json_encode($events))
230
			]);
231
		$query->execute();
232
233
		return $query->getLastInsertId();
234
	}
235
236
	/**
237
	 * @param string $class
238
	 * @param string $name
239
	 * @param array[] $checks
240
	 * @param string $operation
241
	 * @return array The added operation
242
	 * @throws \UnexpectedValueException
243
	 * @throws DBALException
244
	 */
245
	public function addOperation(
246
		string $class,
247
		string $name,
248
		array $checks,
249
		string $operation,
250
		ScopeContext $scope,
251
		string $entity,
252
		array $events
253
	) {
254
		$this->validateOperation($class, $name, $checks, $operation, $entity, $events);
255
256
		$this->connection->beginTransaction();
257
258
		try {
259
			$checkIds = [];
260
			foreach ($checks as $check) {
261
				$checkIds[] = $this->addCheck($check['class'], $check['operator'], $check['value']);
262
			}
263
264
			$id = $this->insertOperation($class, $name, $checkIds, $operation, $entity, $events);
265
			$this->addScope($id, $scope);
266
267
			$this->connection->commit();
268
		} catch (DBALException $e) {
269
			$this->connection->rollBack();
270
			throw $e;
271
		}
272
273
		return $this->getOperation($id);
274
	}
275
276
	protected function canModify(int $id, ScopeContext $scopeContext):bool {
277
		if(isset($this->operationsByScope[$scopeContext->getHash()])) {
278
			return in_array($id, $this->operationsByScope[$scopeContext->getHash()], true);
279
		}
280
281
		$qb = $this->connection->getQueryBuilder();
282
		$qb = $qb->select('o.id')
283
			->from('flow_operations', 'o')
284
			->leftJoin('o', 'flow_operations_scope', 's', $qb->expr()->eq('o.id', 's.operation_id'))
285
			->where($qb->expr()->eq('s.type', $qb->createParameter('scope')));
286
287
		if($scopeContext->getScope() !== IManager::SCOPE_ADMIN) {
288
			$qb->where($qb->expr()->eq('s.value', $qb->createParameter('scopeId')));
289
		}
290
291
		$qb->setParameters(['scope' => $scopeContext->getScope(), 'scopeId' => $scopeContext->getScopeId()]);
292
		$result = $qb->execute();
293
294
		$this->operationsByScope[$scopeContext->getHash()] = [];
295
		while($opId = $result->fetchColumn(0)) {
296
			$this->operationsByScope[$scopeContext->getHash()][] = (int)$opId;
297
		}
298
		$result->closeCursor();
299
300
		return in_array($id, $this->operationsByScope[$scopeContext->getHash()], true);
301
	}
302
303
	/**
304
	 * @param int $id
305
	 * @param string $name
306
	 * @param array[] $checks
307
	 * @param string $operation
308
	 * @return array The updated operation
309
	 * @throws \UnexpectedValueException
310
	 * @throws \DomainException
311
	 * @throws DBALException
312
	 */
313
	public function updateOperation(
314
		int $id,
315
		string $name,
316
		array $checks,
317
		string $operation,
318
		ScopeContext $scopeContext,
319
		string $entity,
320
		array $events
321
	): array {
322
		if(!$this->canModify($id, $scopeContext)) {
323
			throw new \DomainException('Target operation not within scope');
324
		};
325
		$row = $this->getOperation($id);
326
		$this->validateOperation($row['class'], $name, $checks, $operation, $entity, $events);
327
328
		$checkIds = [];
329
		try {
330
			$this->connection->beginTransaction();
331
			foreach ($checks as $check) {
332
				$checkIds[] = $this->addCheck($check['class'], $check['operator'], $check['value']);
333
			}
334
335
			$query = $this->connection->getQueryBuilder();
336
			$query->update('flow_operations')
337
				->set('name', $query->createNamedParameter($name))
338
				->set('checks', $query->createNamedParameter(json_encode(array_unique($checkIds))))
339
				->set('operation', $query->createNamedParameter($operation))
340
				->set('entity', $query->createNamedParameter($entity))
341
				->set('events', $query->createNamedParameter(json_encode($events)))
342
				->where($query->expr()->eq('id', $query->createNamedParameter($id)));
343
			$query->execute();
344
			$this->connection->commit();
345
		} catch (DBALException $e) {
346
			$this->connection->rollBack();
347
			throw $e;
348
		}
349
		unset($this->operations[$scopeContext->getHash()]);
350
351
		return $this->getOperation($id);
352
	}
353
354
	/**
355
	 * @param int $id
356
	 * @return bool
357
	 * @throws \UnexpectedValueException
358
	 * @throws DBALException
359
	 * @throws \DomainException
360
	 */
361
	public function deleteOperation($id, ScopeContext $scopeContext) {
362
		if(!$this->canModify($id, $scopeContext)) {
363
			throw new \DomainException('Target operation not within scope');
364
		};
365
		$query = $this->connection->getQueryBuilder();
366
		try {
367
			$this->connection->beginTransaction();
368
			$result = (bool)$query->delete('flow_operations')
369
				->where($query->expr()->eq('id', $query->createNamedParameter($id)))
370
				->execute();
371
			if($result) {
372
				$qb = $this->connection->getQueryBuilder();
373
				$result &= (bool)$qb->delete('flow_operations_scope')
374
					->where($qb->expr()->eq('operation_id', $qb->createNamedParameter($id)))
375
					->execute();
376
			}
377
			$this->connection->commit();
378
		} catch (DBALException $e) {
379
			$this->connection->rollBack();
380
			throw $e;
381
		}
382
383
		if(isset($this->operations[$scopeContext->getHash()])) {
384
			unset($this->operations[$scopeContext->getHash()]);
385
		}
386
387
		return $result;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $result also could return the type integer which is incompatible with the documented return type boolean.
Loading history...
388
	}
389
390
	protected function validateEvents(string $entity, array $events, IOperation $operation) {
391
		try {
392
			/** @var IEntity $instance */
393
			$instance = $this->container->query($entity);
394
		} catch (QueryException $e) {
395
			throw new \UnexpectedValueException($this->l->t('Entity %s does not exist', [$entity]));
396
		}
397
398
		if(!$instance instanceof IEntity) {
0 ignored issues
show
introduced by
$instance is always a sub-type of OCP\WorkflowEngine\IEntity.
Loading history...
399
			throw new \UnexpectedValueException($this->l->t('Entity %s is invalid', [$entity]));
400
		}
401
402
		if(empty($events)) {
403
			if(!$operation instanceof IComplexOperation) {
404
				throw new \UnexpectedValueException($this->l->t('No events are chosen.'));
405
			}
406
			return;
407
		}
408
409
		$availableEvents = [];
410
		foreach ($instance->getEvents() as $event) {
411
			/** @var IEntityEvent $event */
412
			$availableEvents[] = $event->getEventName();
413
		}
414
415
		$diff = array_diff($events, $availableEvents);
416
		if(!empty($diff)) {
417
			throw new \UnexpectedValueException($this->l->t('Entity %s has no event %s', [$entity, array_shift($diff)]));
418
		}
419
	}
420
421
	/**
422
	 * @param string $class
423
	 * @param string $name
424
	 * @param array[] $checks
425
	 * @param string $operation
426
	 * @throws \UnexpectedValueException
427
	 */
428
	public function validateOperation($class, $name, array $checks, $operation, string $entity, array $events) {
429
		try {
430
			/** @var IOperation $instance */
431
			$instance = $this->container->query($class);
432
		} catch (QueryException $e) {
433
			throw new \UnexpectedValueException($this->l->t('Operation %s does not exist', [$class]));
434
		}
435
436
		if (!($instance instanceof IOperation)) {
0 ignored issues
show
introduced by
$instance is always a sub-type of OCP\WorkflowEngine\IOperation.
Loading history...
437
			throw new \UnexpectedValueException($this->l->t('Operation %s is invalid', [$class]));
438
		}
439
440
		$this->validateEvents($entity, $events, $instance);
441
442
		$instance->validateOperation($name, $checks, $operation);
443
444
		foreach ($checks as $check) {
445
			try {
446
				/** @var ICheck $instance */
447
				$instance = $this->container->query($check['class']);
448
			} catch (QueryException $e) {
449
				throw new \UnexpectedValueException($this->l->t('Check %s does not exist', [$class]));
450
			}
451
452
			if (!($instance instanceof ICheck)) {
453
				throw new \UnexpectedValueException($this->l->t('Check %s is invalid', [$class]));
454
			}
455
456
			if (!empty($instance->supportedEntities())
457
				&& !in_array($entity, $instance->supportedEntities())
458
			) {
459
				throw new \UnexpectedValueException($this->l->t('Check %s is not allowed with this entity', [$class]));
460
			}
461
462
			$instance->validateCheck($check['operator'], $check['value']);
463
		}
464
	}
465
466
	/**
467
	 * @param int[] $checkIds
468
	 * @return array[]
469
	 */
470
	public function getChecks(array $checkIds) {
471
		$checkIds = array_map('intval', $checkIds);
472
473
		$checks = [];
474
		foreach ($checkIds as $i => $checkId) {
475
			if (isset($this->checks[$checkId])) {
476
				$checks[$checkId] = $this->checks[$checkId];
477
				unset($checkIds[$i]);
478
			}
479
		}
480
481
		if (empty($checkIds)) {
482
			return $checks;
483
		}
484
485
		$query = $this->connection->getQueryBuilder();
486
		$query->select('*')
487
			->from('flow_checks')
488
			->where($query->expr()->in('id', $query->createNamedParameter($checkIds, IQueryBuilder::PARAM_INT_ARRAY)));
489
		$result = $query->execute();
490
491
		while ($row = $result->fetch()) {
492
			$this->checks[(int) $row['id']] = $row;
493
			$checks[(int) $row['id']] = $row;
494
		}
495
		$result->closeCursor();
496
497
		$checkIds = array_diff($checkIds, array_keys($checks));
498
499
		if (!empty($checkIds)) {
500
			$missingCheck = array_pop($checkIds);
501
			throw new \UnexpectedValueException($this->l->t('Check #%s does not exist', $missingCheck));
502
		}
503
504
		return $checks;
505
	}
506
507
	/**
508
	 * @param string $class
509
	 * @param string $operator
510
	 * @param string $value
511
	 * @return int Check unique ID
512
	 */
513
	protected function addCheck($class, $operator, $value) {
514
		$hash = md5($class . '::' . $operator . '::' . $value);
515
516
		$query = $this->connection->getQueryBuilder();
517
		$query->select('id')
518
			->from('flow_checks')
519
			->where($query->expr()->eq('hash', $query->createNamedParameter($hash)));
520
		$result = $query->execute();
521
522
		if ($row = $result->fetch()) {
523
			$result->closeCursor();
524
			return (int) $row['id'];
525
		}
526
527
		$query = $this->connection->getQueryBuilder();
528
		$query->insert('flow_checks')
529
			->values([
530
				'class' => $query->createNamedParameter($class),
531
				'operator' => $query->createNamedParameter($operator),
532
				'value' => $query->createNamedParameter($value),
533
				'hash' => $query->createNamedParameter($hash),
534
			]);
535
		$query->execute();
536
537
		return $query->getLastInsertId();
538
	}
539
540
	protected function addScope(int $operationId, ScopeContext $scope): void {
541
		$query = $this->connection->getQueryBuilder();
542
543
		$insertQuery = $query->insert('flow_operations_scope');
544
		$insertQuery->values([
545
			'operation_id' => $query->createNamedParameter($operationId),
546
			'type' => $query->createNamedParameter($scope->getScope()),
547
			'value' => $query->createNamedParameter($scope->getScopeId()),
548
		]);
549
		$insertQuery->execute();
550
	}
551
552
	public function formatOperation(array $operation): array {
553
		$checkIds = json_decode($operation['checks'], true);
554
		$checks = $this->getChecks($checkIds);
555
556
		$operation['checks'] = [];
557
		foreach ($checks as $check) {
558
			// Remove internal values
559
			unset($check['id']);
560
			unset($check['hash']);
561
562
			$operation['checks'][] = $check;
563
		}
564
565
		return $operation;
566
	}
567
568
	/**
569
	 * @return IEntity[]
570
	 */
571
	public function getEntitiesList(): array {
572
		$this->eventDispatcher->dispatch(IManager::EVENT_NAME_REG_ENTITY, new GenericEvent($this));
0 ignored issues
show
Unused Code introduced by
The call to Symfony\Contracts\EventD...erInterface::dispatch() has too many arguments starting with new Symfony\Component\Ev...her\GenericEvent($this). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

572
		$this->eventDispatcher->/** @scrutinizer ignore-call */ 
573
                          dispatch(IManager::EVENT_NAME_REG_ENTITY, new GenericEvent($this));

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
Bug introduced by
OCP\WorkflowEngine\IManager::EVENT_NAME_REG_ENTITY of type string is incompatible with the type object expected by parameter $event of Symfony\Contracts\EventD...erInterface::dispatch(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

572
		$this->eventDispatcher->dispatch(/** @scrutinizer ignore-type */ IManager::EVENT_NAME_REG_ENTITY, new GenericEvent($this));
Loading history...
573
574
		return array_merge($this->getBuildInEntities(), $this->registeredEntities);
575
	}
576
577
	/**
578
	 * @return IOperation[]
579
	 */
580
	public function getOperatorList(): array {
581
		$this->eventDispatcher->dispatch(IManager::EVENT_NAME_REG_OPERATION, new GenericEvent($this));
0 ignored issues
show
Unused Code introduced by
The call to Symfony\Contracts\EventD...erInterface::dispatch() has too many arguments starting with new Symfony\Component\Ev...her\GenericEvent($this). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

581
		$this->eventDispatcher->/** @scrutinizer ignore-call */ 
582
                          dispatch(IManager::EVENT_NAME_REG_OPERATION, new GenericEvent($this));

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
Bug introduced by
OCP\WorkflowEngine\IMana...VENT_NAME_REG_OPERATION of type string is incompatible with the type object expected by parameter $event of Symfony\Contracts\EventD...erInterface::dispatch(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

581
		$this->eventDispatcher->dispatch(/** @scrutinizer ignore-type */ IManager::EVENT_NAME_REG_OPERATION, new GenericEvent($this));
Loading history...
582
583
		return array_merge($this->getBuildInOperators(), $this->registeredOperators);
584
	}
585
586
	/**
587
	 * @return ICheck[]
588
	 */
589
	public function getCheckList(): array {
590
		$this->eventDispatcher->dispatch(IManager::EVENT_NAME_REG_CHECK, new GenericEvent($this));
0 ignored issues
show
Unused Code introduced by
The call to Symfony\Contracts\EventD...erInterface::dispatch() has too many arguments starting with new Symfony\Component\Ev...her\GenericEvent($this). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

590
		$this->eventDispatcher->/** @scrutinizer ignore-call */ 
591
                          dispatch(IManager::EVENT_NAME_REG_CHECK, new GenericEvent($this));

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
Bug introduced by
OCP\WorkflowEngine\IManager::EVENT_NAME_REG_CHECK of type string is incompatible with the type object expected by parameter $event of Symfony\Contracts\EventD...erInterface::dispatch(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

590
		$this->eventDispatcher->dispatch(/** @scrutinizer ignore-type */ IManager::EVENT_NAME_REG_CHECK, new GenericEvent($this));
Loading history...
591
592
		return array_merge($this->getBuildInChecks(), $this->registeredChecks);
593
	}
594
595
	public function registerEntity(IEntity $entity): void {
596
		$this->registeredEntities[get_class($entity)] = $entity;
597
	}
598
599
	public function registerOperation(IOperation $operator): void {
600
		$this->registeredOperators[get_class($operator)] = $operator;
601
	}
602
603
	public function registerCheck(ICheck $check): void {
604
		$this->registeredChecks[get_class($check)] = $check;
605
	}
606
607
	/**
608
	 * @return IEntity[]
609
	 */
610
	protected function getBuildInEntities(): array {
611
		try {
612
			return [
613
				$this->container->query(File::class),
614
			];
615
		} catch (QueryException $e) {
616
			$this->logger->logException($e);
617
			return [];
618
		}
619
	}
620
621
	/**
622
	 * @return IOperation[]
623
	 */
624
	protected function getBuildInOperators(): array {
625
		try {
626
			return [
627
				// None yet
628
			];
629
		} catch (QueryException $e) {
0 ignored issues
show
Unused Code introduced by
catch (\OCP\AppFramework\QueryException $e) is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
630
			$this->logger->logException($e);
631
			return [];
632
		}
633
	}
634
635
	/**
636
	 * @return IEntity[]
637
	 */
638
	protected function getBuildInChecks(): array {
639
		try {
640
			return [
641
				$this->container->query(FileMimeType::class),
642
				$this->container->query(FileName::class),
643
				$this->container->query(FileSize::class),
644
				$this->container->query(FileSystemTags::class),
645
				$this->container->query(RequestRemoteAddress::class),
646
				$this->container->query(RequestTime::class),
647
				$this->container->query(RequestURL::class),
648
				$this->container->query(RequestUserAgent::class),
649
				$this->container->query(UserGroupMembership::class),
650
			];
651
		} catch (QueryException $e) {
652
			$this->logger->logException($e);
653
			return [];
654
		}
655
	}
656
}
657