Completed
Push — master ( 72ae75...318d68 )
by Joas
36:13 queued 23:35
created

Manager::getOperation()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 15
Code Lines 11

Duplication

Lines 15
Ratio 100 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 2
eloc 11
c 1
b 0
f 1
nc 2
nop 1
dl 15
loc 15
rs 9.4285
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
25
use OCP\AppFramework\QueryException;
26
use OCP\DB\QueryBuilder\IQueryBuilder;
27
use OCP\Files\Storage\IStorage;
28
use OCP\IDBConnection;
29
use OCP\IServerContainer;
30
use OCP\WorkflowEngine\ICheck;
31
use OCP\WorkflowEngine\IManager;
32
33
class Manager implements IManager {
34
35
	/** @var IStorage */
36
	protected $storage;
37
38
	/** @var string */
39
	protected $path;
40
41
	/** @var array[] */
42
	protected $operations = [];
43
44
	/** @var array[] */
45
	protected $checks = [];
46
47
	/** @var IDBConnection */
48
	protected $connection;
49
50
	/** @var IServerContainer|\OC\Server */
51
	protected $container;
52
53
	/**
54
	 * @param IDBConnection $connection
55
	 * @param IServerContainer $container
56
	 */
57
	public function __construct(IDBConnection $connection, IServerContainer $container) {
58
		$this->connection = $connection;
59
		$this->container = $container;
60
	}
61
62
	/**
63
	 * @inheritdoc
64
	 */
65
	public function setFileInfo(IStorage $storage, $path) {
66
		$this->storage = $storage;
67
		$this->path = $path;
68
	}
69
70
	/**
71
	 * @inheritdoc
72
	 */
73
	public function getMatchingOperations($class, $returnFirstMatchingOperationOnly = true) {
74
		$operations = $this->getOperations($class);
75
76
		$matches = [];
77
		foreach ($operations as $operation) {
78
			$checkIds = json_decode($operation['checks'], true);
79
			$checks = $this->getChecks($checkIds);
80
81
			foreach ($checks as $check) {
82
				if (!$this->check($check)) {
83
					// Check did not match, continue with the next operation
84
					continue 2;
85
				}
86
			}
87
88
			if ($returnFirstMatchingOperationOnly) {
89
				return $operation;
90
			}
91
			$matches[] = $operation;
92
		}
93
94
		return $matches;
95
	}
96
97
	/**
98
	 * @param array $check
99
	 * @return bool
100
	 */
101
	protected function check(array $check) {
102
		try {
103
			$checkInstance = $this->container->query($check['class']);
0 ignored issues
show
Bug introduced by
The method query does only exist in OC\Server, but not in OCP\IServerContainer.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
104
		} catch (QueryException $e) {
105
			// Check does not exist, assume it matches.
106
			return true;
107
		}
108
109
		if ($checkInstance instanceof ICheck) {
110
			$checkInstance->setFileInfo($this->storage, $this->path);
111
			return $checkInstance->executeCheck($check['operator'], $check['value']);
112
		} else {
113
			// Check is invalid, assume it matches.
114
			return true;
115
		}
116
	}
117
118
	/**
119
	 * @param string $class
120
	 * @return array[]
121
	 */
122
	public function getOperations($class) {
123
		if (isset($this->operations[$class])) {
124
			return $this->operations[$class];
125
		}
126
127
		$query = $this->connection->getQueryBuilder();
128
129
		$query->select('*')
130
			->from('flow_operations')
131
			->where($query->expr()->eq('class', $query->createNamedParameter($class)));
132
		$result = $query->execute();
133
134
		$this->operations[$class] = [];
135
		while ($row = $result->fetch()) {
136
			$this->operations[$class][] = $row;
137
		}
138
		$result->closeCursor();
139
140
		return $this->operations[$class];
141
	}
142
143
	/**
144
	 * @param int $id
145
	 * @return array
146
	 * @throws \UnexpectedValueException
147
	 */
148 View Code Duplication
	protected function getOperation($id) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
149
		$query = $this->connection->getQueryBuilder();
150
		$query->select('*')
151
			->from('flow_operations')
152
			->where($query->expr()->eq('id', $query->createNamedParameter($id)));
153
		$result = $query->execute();
154
		$row = $result->fetch();
155
		$result->closeCursor();
156
157
		if ($row) {
158
			return $row;
159
		}
160
161
		throw new \UnexpectedValueException('Operation does not exist');
162
	}
163
164
	/**
165
	 * @param string $class
166
	 * @param string $name
167
	 * @param array[] $checks
168
	 * @param string $operation
169
	 * @return array The added operation
170
	 * @throws \UnexpectedValueException
171
	 */
172
	public function addOperation($class, $name, array $checks, $operation) {
173
		$checkIds = [];
174 View Code Duplication
		foreach ($checks as $check) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
175
			$checkIds[] = $this->addCheck($check['class'], $check['operator'], $check['value']);
176
		}
177
178
		$query = $this->connection->getQueryBuilder();
179
		$query->insert('flow_operations')
180
			->values([
181
				'class' => $query->createNamedParameter($class),
182
				'name' => $query->createNamedParameter($name),
183
				'checks' => $query->createNamedParameter(json_encode(array_unique($checkIds))),
184
				'operation' => $query->createNamedParameter($operation),
185
			]);
186
		$query->execute();
187
188
		$id = $query->getLastInsertId();
189
		return $this->getOperation($id);
190
	}
191
192
	/**
193
	 * @param int $id
194
	 * @param string $name
195
	 * @param array[] $checks
196
	 * @param string $operation
197
	 * @return array The updated operation
198
	 * @throws \UnexpectedValueException
199
	 */
200
	public function updateOperation($id, $name, array $checks, $operation) {
201
		$checkIds = [];
202 View Code Duplication
		foreach ($checks as $check) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
203
			$checkIds[] = $this->addCheck($check['class'], $check['operator'], $check['value']);
204
		}
205
206
		$query = $this->connection->getQueryBuilder();
207
		$query->update('flow_operations')
208
			->set('name', $query->createNamedParameter($name))
209
			->set('checks', $query->createNamedParameter(json_encode(array_unique($checkIds))))
210
			->set('operation', $query->createNamedParameter($operation))
211
			->where($query->expr()->eq('id', $query->createNamedParameter($id)));
212
		$query->execute();
213
214
		return $this->getOperation($id);
215
	}
216
217
	/**
218
	 * @param int $id
219
	 * @return bool
220
	 * @throws \UnexpectedValueException
221
	 */
222
	public function deleteOperation($id) {
223
		$query = $this->connection->getQueryBuilder();
224
		$query->delete('flow_operations')
225
			->where($query->expr()->eq('id', $query->createNamedParameter($id)));
226
		return (bool) $query->execute();
227
	}
228
229
	/**
230
	 * @param int[] $checkIds
231
	 * @return array[]
232
	 */
233
	public function getChecks(array $checkIds) {
234
		$checkIds = array_map('intval', $checkIds);
235
236
		$checks = [];
237
		foreach ($checkIds as $i => $checkId) {
238
			if (isset($this->checks[$checkId])) {
239
				$checks[$checkId] = $this->checks[$checkId];
240
				unset($checkIds[$i]);
241
			}
242
		}
243
244
		if (empty($checkIds)) {
245
			return $checks;
246
		}
247
248
		$query = $this->connection->getQueryBuilder();
249
		$query->select('*')
250
			->from('flow_checks')
251
			->where($query->expr()->in('id', $query->createNamedParameter($checkIds, IQueryBuilder::PARAM_INT_ARRAY)));
252
		$result = $query->execute();
253
254
		$checks = [];
255
		while ($row = $result->fetch()) {
256
			$this->checks[(int) $row['id']] = $row;
257
			$checks[(int) $row['id']] = $row;
258
		}
259
		$result->closeCursor();
260
261
		// TODO What if a check is missing? Should we throw?
262
		// As long as we only allow AND-concatenation of checks, a missing check
263
		// is like a matching check, so it evaluates to true and therefor blocks
264
		// access. So better save than sorry.
265
266
		return $checks;
267
	}
268
269
	/**
270
	 * @param string $class
271
	 * @param string $operator
272
	 * @param string $value
273
	 * @return int Check unique ID
274
	 * @throws \UnexpectedValueException
275
	 */
276
	protected function addCheck($class, $operator, $value) {
277
		/** @var ICheck $check */
278
		$check = $this->container->query($class);
0 ignored issues
show
Bug introduced by
The method query does only exist in OC\Server, but not in OCP\IServerContainer.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
279
		$check->validateCheck($operator, $value);
280
281
		$hash = md5($class . '::' . $operator . '::' . $value);
282
283
		$query = $this->connection->getQueryBuilder();
284
		$query->select('id')
285
			->from('flow_checks')
286
			->where($query->expr()->eq('hash', $query->createNamedParameter($hash)));
287
		$result = $query->execute();
288
289
		if ($row = $result->fetch()) {
290
			$result->closeCursor();
291
			return (int) $row['id'];
292
		}
293
294
		$query = $this->connection->getQueryBuilder();
295
		$query->insert('flow_checks')
296
			->values([
297
				'class' => $query->createNamedParameter($class),
298
				'operator' => $query->createNamedParameter($operator),
299
				'value' => $query->createNamedParameter($value),
300
				'hash' => $query->createNamedParameter($hash),
301
			]);
302
		$query->execute();
303
304
		return $query->getLastInsertId();
305
	}
306
}
307