Issues (125)

lib/Db/PersonMapper.php (23 issues)

1
<?php
2
/**
3
 * @copyright Copyright (c) 2018-2021, Matias De lellis <[email protected]>
4
 * @copyright Copyright (c) 2018-2019, Branko Kokanovic <[email protected]>
5
 *
6
 * @author Branko Kokanovic <[email protected]>
7
 *
8
 * @license GNU AGPL version 3 or any later version
9
 *
10
 * This program is free software: you can redistribute it and/or modify
11
 * it under the terms of the GNU Affero General Public License as
12
 * published by the Free Software Foundation, either version 3 of the
13
 * License, or (at your option) any later version.
14
 *
15
 * This program is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
 * GNU Affero General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU Affero General Public License
21
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
22
 *
23
 */
24
namespace OCA\FaceRecognition\Db;
25
26
use OC\DB\QueryBuilder\Literal;
0 ignored issues
show
The type OC\DB\QueryBuilder\Literal was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
27
28
use OCP\IDBConnection;
29
use OCP\IUser;
30
31
use OCP\AppFramework\Db\QBMapper;
32
use OCP\AppFramework\Db\DoesNotExistException;
33
use OCP\DB\QueryBuilder\IQueryBuilder;
34
35
class PersonMapper extends QBMapper {
36
37 1
	public function __construct(IDBConnection $db) {
38 1
		parent::__construct($db, 'facerecog_persons', '\OCA\FaceRecognition\Db\Person');
39
	}
40
41
	/**
42
	 * @param string $userId ID of the user
43
	 * @param int $personId ID of the person
44
	 *
45
	 * @return Person
46
	 */
47 8
	public function find(string $userId, int $personId): Person {
48 8
		$qb = $this->db->getQueryBuilder();
49 8
		$qb->select('id', 'name', 'is_visible')
50 8
			->from($this->getTableName(), 'p')
51 8
			->where($qb->expr()->eq('id', $qb->createNamedParameter($personId)))
52 8
			->andWhere($qb->expr()->eq('user', $qb->createNamedParameter($userId)));
53 8
		return $this->findEntity($qb);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->findEntity($qb) returns the type OCP\AppFramework\Db\Entity which includes types incompatible with the type-hinted return OCA\FaceRecognition\Db\Person.
Loading history...
54
	}
55
56
	/**
57
	 * @param string $userId ID of the user
58
	 * @param int $modelId ID of the model
59
	 * @param string $personName name of the person to find
60
	 * @return Person[]
61
	 */
62 2
	public function findByName(string $userId, int $modelId, string $personName): array {
63 2
		$sub = $this->db->getQueryBuilder();
64 2
		$sub->select(new Literal('1'))
65 2
			->from('facerecog_faces', 'f')
66 2
			->innerJoin('f', 'facerecog_images' ,'i', $sub->expr()->eq('f.image', 'i.id'))
67 2
			->where($sub->expr()->eq('p.id', 'f.person'))
68 2
			->andWhere($sub->expr()->eq('i.user', $sub->createParameter('user_id')))
69 2
			->andWhere($sub->expr()->eq('i.model', $sub->createParameter('model_id')))
70 2
			->andWhere($sub->expr()->eq('p.name', $sub->createParameter('person_name')));
71
72 2
		$qb = $this->db->getQueryBuilder();
73 2
		$qb->select('id', 'name', 'is_valid')
74 2
			->from($this->getTableName(), 'p')
75 2
			->where('EXISTS (' . $sub->getSQL() . ')')
76 2
			->setParameter('user_id', $userId)
77 2
			->setParameter('model_id', $modelId)
78 2
			->setParameter('person_name', $personName);
79
80 2
		return $this->findEntities($qb);
81
	}
82
83
	/**
84
	 * @param string $userId ID of the user
85
	 * @param int $modelId ID of the model
86
	 * @return Person[]
87
	 */
88
	public function findUnassigned(string $userId, int $modelId): array {
89
		$sub = $this->db->getQueryBuilder();
90
		$sub->select(new Literal('1'))
91
			->from('facerecog_faces', 'f')
92
			->innerJoin('f', 'facerecog_images' ,'i', $sub->expr()->eq('f.image', 'i.id'))
93
			->where($sub->expr()->eq('p.id', 'f.person'))
94
			->andWhere($sub->expr()->eq('i.user', $sub->createParameter('user_id')))
95
			->andWhere($sub->expr()->eq('i.model', $sub->createParameter('model_id')));
96
97
		$qb = $this->db->getQueryBuilder();
98
		$qb->select('id', 'is_valid')
99
			->from($this->getTableName(), 'p')
100
			->where('EXISTS (' . $sub->getSQL() . ')')
101
			->andWhere($qb->expr()->eq('is_valid', $qb->createParameter('is_valid')))
102
			->andWhere($qb->expr()->eq('is_visible', $qb->createParameter('is_visible')))
103
			->andWhere($qb->expr()->isNull('name'))
104
			->setParameter('user_id', $userId)
105
			->setParameter('model_id', $modelId)
106
			->setParameter('is_valid', true, IQueryBuilder::PARAM_BOOL)
107
			->setParameter('is_visible', true, IQueryBuilder::PARAM_BOOL);
108
109
		return $this->findEntities($qb);
110
	}
111
112
	/**
113
	 * @param string $userId ID of the user
114
	 * @param int $modelId ID of the model
115
	 * @return Person[]
116
	 */
117
	public function findIgnored(string $userId, int $modelId): array {
118
		$sub = $this->db->getQueryBuilder();
119
		$sub->select(new Literal('1'))
120
			->from('facerecog_faces', 'f')
121
			->innerJoin('f', 'facerecog_images' ,'i', $sub->expr()->eq('f.image', 'i.id'))
122
			->where($sub->expr()->eq('p.id', 'f.person'))
123
			->andWhere($sub->expr()->eq('i.user', $sub->createParameter('user_id')))
124
			->andWhere($sub->expr()->eq('i.model', $sub->createParameter('model_id')));
125
126
		$qb = $this->db->getQueryBuilder();
127
		$qb->select('id', 'is_valid')
128
			->from($this->getTableName(), 'p')
129
			->where('EXISTS (' . $sub->getSQL() . ')')
130
			->andWhere($qb->expr()->eq('is_valid', $qb->createParameter('is_valid')))
131
			->andWhere($qb->expr()->eq('is_visible', $qb->createParameter('is_visible')))
132
			->andWhere($qb->expr()->isNull('name'))
133
			->setParameter('user_id', $userId)
134
			->setParameter('model_id', $modelId)
135
			->setParameter('is_valid', true, IQueryBuilder::PARAM_BOOL)
136
			->setParameter('is_visible', false, IQueryBuilder::PARAM_BOOL);
137
138
		return $this->findEntities($qb);
139
	}
140
141
	/**
142
	 * @param string $userId ID of the user
143
	 * @param int $modelId ID of the model
144
	 * @return Person[]
145
	 */
146 13
	public function findAll(string $userId, int $modelId): array {
147 13
		$sub = $this->db->getQueryBuilder();
148 13
		$sub->select(new Literal('1'))
149 13
			->from('facerecog_faces', 'f')
150 13
			->innerJoin('f', 'facerecog_images' ,'i', $sub->expr()->eq('f.image', 'i.id'))
151 13
			->where($sub->expr()->eq('p.id', 'f.person'))
152 13
			->andWhere($sub->expr()->eq('i.user', $sub->createParameter('user_id')))
153 13
			->andWhere($sub->expr()->eq('i.model', $sub->createParameter('model_id')));
154
155 13
		$qb = $this->db->getQueryBuilder();
156 13
		$qb->select('id', 'name', 'is_valid')
157 13
			->from($this->getTableName(), 'p')
158 13
			->where('EXISTS (' . $sub->getSQL() . ')')
159 13
			->setParameter('user_id', $userId)
160 13
			->setParameter('model_id', $modelId);
161
162 13
		return $this->findEntities($qb);
163
	}
164
165
	/**
166
	 * @param string $userId ID of the user
167
	 *
168
	 * @return Person[]
169
	 */
170 7
	public function findDistinctNames(string $userId, int $modelId): array {
171 7
		$qb = $this->db->getQueryBuilder();
172 7
		$qb->selectDistinct('name')
173 7
			->from($this->getTableName(), 'p')
174 7
			->innerJoin('p', 'facerecog_faces' , 'f', $qb->expr()->eq('f.person', 'p.id'))
175 7
			->innerJoin('f', 'facerecog_images' ,'i', $qb->expr()->eq('f.image', 'i.id'))
176 7
			->where($qb->expr()->eq('i.user', $qb->createParameter('user_id')))
177 7
			->andWhere($qb->expr()->eq('i.model', $qb->createParameter('model_id')))
178 7
			->andwhere($qb->expr()->isNotNull('p.name'))
179 7
			->setParameter('user_id', $userId)
180 7
			->setParameter('model_id', $modelId);
181 7
		return $this->findEntities($qb);
182
	}
183
184
	/**
185
	 * @param string $userId ID of the user
186
	 *
187
	 * @return Person[]
188
	 */
189
	public function findDistinctNamesSelected(string $userId, int $modelId, $faceNames): array {
190
		$qb = $this->db->getQueryBuilder();
191
		$qb->selectDistinct('name')
192
			->from($this->getTableName(), 'p')
193
			->innerJoin('p', 'facerecog_faces' , 'f', $qb->expr()->eq('f.person', 'p.id'))
194
			->innerJoin('f', 'facerecog_images' ,'i', $qb->expr()->eq('f.image', 'i.id'))
195
			->where($qb->expr()->eq('i.user', $qb->createParameter('user_id')))
196
			->andWhere($qb->expr()->eq('i.model', $qb->createParameter('model_id')))
197
			->andwhere($qb->expr()->isNotNull('p.name'))
198
			->andWhere($qb->expr()->eq('p.name', $qb->createParameter('faceNames')))
199
			->setParameter('user_id', $userId)
200
			->setParameter('model_id', $modelId)
201
			->setParameter('faceNames', $faceNames);
202
		return $this->findEntities($qb);
203
	}
204
205
	/**
206
	 * Search Person by name
207
	 *
208
	 * @param int|null $offset
209
	 * @param int|null $limit
210
	 */
211
	public function findPersonsLike(string $userId, int $modelId, string $name, ?int $offset = null, ?int $limit = null): array {
212
		$qb = $this->db->getQueryBuilder();
213
		$qb->selectDistinct('p.name')
214
			->from($this->getTableName(), 'p')
215
			->innerJoin('p', 'facerecog_faces', 'f', $qb->expr()->eq('f.person', 'p.id'))
216
			->innerJoin('p', 'facerecog_images', 'i', $qb->expr()->eq('f.image', 'i.id'))
217
			->where($qb->expr()->eq('p.user', $qb->createNamedParameter($userId)))
218
			->andWhere($qb->expr()->eq('model', $qb->createNamedParameter($modelId)))
219
			->andWhere($qb->expr()->eq('is_processed', $qb->createNamedParameter(True)))
220
			->andWhere($qb->expr()->like($qb->func()->lower('p.name'), $qb->createParameter('query')));
221
222
		$query = '%' . $this->db->escapeLikeParameter(strtolower($name)) . '%';
223
		$qb->setParameter('query', $query);
224
225
		$qb->setFirstResult($offset);
226
		$qb->setMaxResults($limit);
227
228
		return $this->findEntities($qb);
229
	}
230
231
	/**
232
	 * Returns count of persons found for a given user.
233
	 *
234
	 * @param string $userId ID of the user
235
	 * @param int $modelId ID of the model
236
	 * @return int Count of persons
237
	 */
238 7
	public function countPersons(string $userId, int $modelId): int {
239 7
		return count($this->findDistinctNames($userId, $modelId));
240
	}
241
242
	/**
243
	 * Returns count of clusters found for a given user.
244
	 *
245
	 * @param string $userId ID of the user
246
	 * @param int $modelId ID of the model
247
	 * @param bool $onlyInvalid True if client wants count of invalid clusters only,
248
	 *  false if client want count of all clusters
249
	 * @return int Count of clusters
250
	 */
251 11
	public function countClusters(string $userId, int $modelId, bool $onlyInvalid=false): int {
252 11
		$sub = $this->db->getQueryBuilder();
253 11
		$sub->select(new Literal('1'))
254 11
			->from('facerecog_faces', 'f')
255 11
			->innerJoin('f', 'facerecog_images' ,'i', $sub->expr()->eq('f.image', 'i.id'))
256 11
			->where($sub->expr()->eq('p.id', 'f.person'))
257 11
			->andWhere($sub->expr()->eq('i.user', $sub->createParameter('user_id')))
258 11
			->andWhere($sub->expr()->eq('i.model', $sub->createParameter('model_id')));
259
260 11
		$qb = $this->db->getQueryBuilder();
261 11
		$qb->select($qb->createFunction('COUNT(' . $qb->getColumnName('id') . ')'))
262 11
			->from($this->getTableName(), 'p')
263 11
			->where('EXISTS (' . $sub->getSQL() . ')');
264
265 11
		if ($onlyInvalid) {
266
			$qb = $qb
267
				->andWhere($qb->expr()->eq('is_valid', $qb->createParameter('is_valid')))
268
				->setParameter('is_valid', false, IQueryBuilder::PARAM_BOOL);
269
		}
270
271 11
		$qb = $qb
272 11
			->setParameter('user_id', $userId)
273 11
			->setParameter('model_id', $modelId);
274
275 11
		$resultStatement = $qb->execute();
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\QueryBuilder\IQueryBuilder::execute() has been deprecated: 22.0.0 Use executeQuery or executeStatement ( Ignorable by Annotation )

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

275
		$resultStatement = /** @scrutinizer ignore-deprecated */ $qb->execute();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
276 11
		$data = $resultStatement->fetch(\PDO::FETCH_NUM);
277 11
		$resultStatement->closeCursor();
278
279 11
		return (int)$data[0];
280
	}
281
282
	/**
283
	 * Based on a given image, takes all faces that belong to that image
284
	 * and invalidates all person that those faces belongs to.
285
	 *
286
	 * @param int $imageId ID of image for which to invalidate persons for
287
	 *
288
	 * @return void
289
	 */
290 12
	public function invalidatePersons(int $imageId): void {
291 12
		$sub = $this->db->getQueryBuilder();
292 12
		$tableNameWithPrefixWithoutQuotes = trim($sub->getTableName($this->getTableName()), '`');
293 12
		$sub->select(new Literal('1'));
294 12
		$sub->from('facerecog_images', 'i')
295 12
			->innerJoin('i', 'facerecog_faces' ,'f', $sub->expr()->eq('i.id', 'f.image'))
296 12
			->where($sub->expr()->eq($tableNameWithPrefixWithoutQuotes . '.id', 'f.person'))
297 12
			->andWhere($sub->expr()->eq('i.id', $sub->createParameter('image_id')));
298
299 12
		$qb = $this->db->getQueryBuilder();
300 12
		$qb->update($this->getTableName())
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\QueryBuilder\IQueryBuilder::execute() has been deprecated: 22.0.0 Use executeQuery or executeStatement ( Ignorable by Annotation )

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

300
		/** @scrutinizer ignore-deprecated */ $qb->update($this->getTableName())

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
301 12
			->set("is_valid", $qb->createParameter('is_valid'))
302 12
			->where('EXISTS (' . $sub->getSQL() . ')')
303 12
			->setParameter('image_id', $imageId)
304 12
			->setParameter('is_valid', false, IQueryBuilder::PARAM_BOOL)
305 12
			->execute();
306
	}
307
308
	/**
309
	 * Based on current clusters and new clusters, do database reconciliation.
310
	 * It tries to do that in minimal number of SQL queries. Operation is atomic.
311
	 *
312
	 * Clusters are array, where keys are ID of persons, and values are indexed arrays
313
	 * with values that are ID of the faces for those persons.
314
	 *
315
	 * @param string $userId ID of the user that clusters belong to
316
	 * @param array $currentClusters Current clusters
317
	 * @param array $newClusters New clusters
318
	 *
319
	 * @return void
320
	 */
321 14
	public function mergeClusterToDatabase(string $userId, $currentClusters, $newClusters): void {
322 14
		$this->db->beginTransaction();
323 14
		$currentDateTime = new \DateTime();
324
325
		try {
326
			// Delete clusters that do not exist anymore
327 14
			foreach($currentClusters as $oldPerson => $oldFaces) {
328 11
				if (array_key_exists($oldPerson, $newClusters)) {
329 6
					continue;
330
				}
331
332
				// OK, we bumped into cluster that existed and now it does not exist.
333
				// We need to remove all references to it and to delete it.
334 7
				foreach ($oldFaces as $oldFace) {
335 7
					$this->updateFace($oldFace, null);
336
				}
337
338
				// todo: this is not very cool. What if user had associated linked user to this. And all lost?
339 7
				$qb = $this->db->getQueryBuilder();
340
				// todo: for extra safety, we should probably add here additional condition, where (user=$userId)
341 7
				$qb
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\QueryBuilder\IQueryBuilder::execute() has been deprecated: 22.0.0 Use executeQuery or executeStatement ( Ignorable by Annotation )

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

341
				/** @scrutinizer ignore-deprecated */ $qb

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
342 7
					->delete($this->getTableName())
343 7
					->where($qb->expr()->eq('id', $qb->createNamedParameter($oldPerson)))
344 7
					->execute();
345
			}
346
347
			// Modify existing clusters
348 14
			foreach($newClusters as $newPerson=>$newFaces) {
349 12
				if (!array_key_exists($newPerson, $currentClusters)) {
350
					// This cluster didn't exist, there is nothing to modify
351
					// It will be processed during cluster adding operation
352 9
					continue;
353
				}
354
355 6
				$oldFaces = $currentClusters[$newPerson];
356 6
				if ($newFaces === $oldFaces) {
357
					// Set cluster as valid now
358 2
					$qb = $this->db->getQueryBuilder();
359 2
					$qb
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\QueryBuilder\IQueryBuilder::execute() has been deprecated: 22.0.0 Use executeQuery or executeStatement ( Ignorable by Annotation )

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

359
					/** @scrutinizer ignore-deprecated */ $qb

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
360 2
						->update($this->getTableName())
361 2
						->set("is_valid", $qb->createParameter('is_valid'))
362 2
						->where($qb->expr()->eq('id', $qb->createNamedParameter($newPerson)))
363 2
						->setParameter('is_valid', true, IQueryBuilder::PARAM_BOOL)
364 2
						->execute();
365 2
					continue;
366
				}
367
368
				// OK, set of faces do differ. Now, we could potentially go into finer grain details
369
				// and add/remove each individual face, but this seems too detailed. Enough is to
370
				// reset all existing faces to null and to add new faces to new person. That should
371
				// take care of both faces that are removed from cluster, as well as for newly added
372
				// faces to this cluster.
373
374
				// First remove all old faces from any cluster (reset them to null)
375 5
				foreach ($oldFaces as $oldFace) {
376
					// Reset face to null only if it wasn't moved to other cluster!
377
					// (if face is just moved to other cluster, do not reset to null, as some other
378
					// pass for some other cluster will eventually update it to proper cluster)
379 5
					if ($this->isFaceInClusters($oldFace, $newClusters) === false) {
380 1
						$this->updateFace($oldFace, null);
381
					}
382
				}
383
384
				// Then set all new faces to belong to this cluster
385 5
				foreach ($newFaces as $newFace) {
386 5
					$this->updateFace($newFace, $newPerson);
387
				}
388
389
				// Set cluster as valid now
390 5
				$qb = $this->db->getQueryBuilder();
391 5
				$qb
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\QueryBuilder\IQueryBuilder::execute() has been deprecated: 22.0.0 Use executeQuery or executeStatement ( Ignorable by Annotation )

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

391
				/** @scrutinizer ignore-deprecated */ $qb

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
392 5
					->update($this->getTableName())
393 5
					->set("is_valid", $qb->createParameter('is_valid'))
394 5
					->where($qb->expr()->eq('id', $qb->createNamedParameter($newPerson)))
395 5
					->setParameter('is_valid', true, IQueryBuilder::PARAM_BOOL)
396 5
					->execute();
397
			}
398
399
			// Add new clusters
400 14
			foreach($newClusters as $newPerson=>$newFaces) {
401 12
				if (array_key_exists($newPerson, $currentClusters)) {
402
					// This cluster already existed, nothing to add
403
					// It was already processed during modify cluster operation
404 6
					continue;
405
				}
406
407
				// Create new cluster and add all faces to it
408 9
				$qb = $this->db->getQueryBuilder();
409 9
				$qb
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\QueryBuilder\IQueryBuilder::execute() has been deprecated: 22.0.0 Use executeQuery or executeStatement ( Ignorable by Annotation )

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

409
				/** @scrutinizer ignore-deprecated */ $qb

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
410 9
					->insert($this->getTableName())
411 9
					->values([
412 9
						'user' => $qb->createNamedParameter($userId),
413 9
						'is_valid' => $qb->createNamedParameter(true),
414 9
						'last_generation_time' => $qb->createNamedParameter($currentDateTime, IQueryBuilder::PARAM_DATE),
0 ignored issues
show
OCP\DB\QueryBuilder\IQueryBuilder::PARAM_DATE of type string is incompatible with the type OCP\DB\QueryBuilder\IQueryBuilder expected by parameter $type of OCP\DB\QueryBuilder\IQue...:createNamedParameter(). ( Ignorable by Annotation )

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

414
						'last_generation_time' => $qb->createNamedParameter($currentDateTime, /** @scrutinizer ignore-type */ IQueryBuilder::PARAM_DATE),
Loading history...
415 9
						'linked_user' => $qb->createNamedParameter(null)])
416 9
					->execute();
417 9
				$insertedPersonId = $qb->getLastInsertId();
418 9
				foreach ($newFaces as $newFace) {
419 9
					$this->updateFace($newFace, $insertedPersonId);
420
				}
421
			}
422
423 14
			$this->db->commit();
424
		} catch (\Exception $e) {
425
			$this->db->rollBack();
426
			throw $e;
427
		}
428
	}
429
430
	/**
431
	 * Deletes all persons from that user.
432
	 *
433
	 * @param string $userId User to drop persons from a table.
434
	 *
435
	 * @return void
436
	 */
437 28
	public function deleteUserPersons(string $userId): void {
438 28
		$qb = $this->db->getQueryBuilder();
439 28
		$qb->delete($this->getTableName())
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\QueryBuilder\IQueryBuilder::execute() has been deprecated: 22.0.0 Use executeQuery or executeStatement ( Ignorable by Annotation )

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

439
		/** @scrutinizer ignore-deprecated */ $qb->delete($this->getTableName())

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
440 28
			->where($qb->expr()->eq('user', $qb->createNamedParameter($userId)))
441 28
			->execute();
442
	}
443
444
	/**
445
	 * Deletes all persons from that user and model
446
	 *
447
	 * @param string $userId ID of user for drop from table
448
	 * @param int $modelId
449
	 *
450
	 * @return void
451
	 */
452
	public function deleteUserModel(string $userId, int $modelId): void {
453
		//TODO: Make it atomic
454
		$qb = $this->db->getQueryBuilder();
455
		$qb->delete($this->getTableName())
456
			->where($qb->expr()->eq('id', $qb->createParameter('person')));
457
458
		$persons = $this->findAll($userId, $modelId);
459
		foreach ($persons as $person) {
460
			$qb->setParameter('person', $person->getId())->execute();
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\QueryBuilder\IQueryBuilder::execute() has been deprecated: 22.0.0 Use executeQuery or executeStatement ( Ignorable by Annotation )

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

460
			/** @scrutinizer ignore-deprecated */ $qb->setParameter('person', $person->getId())->execute();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
461
		}
462
	}
463
464
	/**
465
	 * Deletes person if it is empty (have no faces associated to it)
466
	 *
467
	 * @param int $personId Person to check if it should be deleted
468
	 *
469
	 * @return void
470
	 */
471
	public function removeIfEmpty(int $personId): void {
472
		$sub = $this->db->getQueryBuilder();
473
		$sub->select(new Literal('1'));
474
		$sub->from('facerecog_faces', 'f')
475
			->where($sub->expr()->eq('f.person', $sub->createParameter('person')));
476
477
		$qb = $this->db->getQueryBuilder();
478
		$qb->delete($this->getTableName())
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\QueryBuilder\IQueryBuilder::execute() has been deprecated: 22.0.0 Use executeQuery or executeStatement ( Ignorable by Annotation )

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

478
		/** @scrutinizer ignore-deprecated */ $qb->delete($this->getTableName())

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
479
			->where($qb->expr()->eq('id', $qb->createParameter('person')))
480
			->andWhere('NOT EXISTS (' . $sub->getSQL() . ')')
481
			->setParameter('person', $personId)
482
			->execute();
483
	}
484
485
	/**
486
	 * Deletes all persons that have no faces associated to them
487
	 *
488
	 * @param string $userId ID of user for which we are deleting orphaned persons
489
	 */
490 1
	public function deleteOrphaned(string $userId): int {
491 1
		$sub = $this->db->getQueryBuilder();
492 1
		$sub->select(new Literal('1'));
493 1
		$sub->from('facerecog_faces', 'f')
494 1
			->where($sub->expr()->eq('f.person', 'p.id'));
495
496 1
		$qb = $this->db->getQueryBuilder();
497 1
		$qb->select('p.id')
498 1
			->from($this->getTableName(), 'p')
499 1
			->where($qb->expr()->eq('p.user', $qb->createParameter('user')))
500 1
			->andWhere('NOT EXISTS (' . $sub->getSQL() . ')')
501 1
			->setParameter('user', $userId);
502 1
		$orphanedPersons = $this->findEntities($qb);
503
504 1
		$orphaned = 0;
505 1
		foreach ($orphanedPersons as $person) {
506
			$qb = $this->db->getQueryBuilder();
507
			$orphaned += $qb->delete($this->getTableName())
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\QueryBuilder\IQueryBuilder::execute() has been deprecated: 22.0.0 Use executeQuery or executeStatement ( Ignorable by Annotation )

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

507
			$orphaned += /** @scrutinizer ignore-deprecated */ $qb->delete($this->getTableName())

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
508
				->where($qb->expr()->eq('id', $qb->createNamedParameter($person->id)))
509
				->execute();
510
		}
511 1
		return $orphaned;
512
	}
513
514
	/*
515
	 * Mark the cluster as hidden or visible to user.
516
	 *
517
	 * @param int $personId ID of the person
518
	 * @param bool $visible visibility of the person
519
	 *
520
	 * @return void
521
	 */
522
	public function setVisibility (int $personId, bool $visible): void {
523
		$qb = $this->db->getQueryBuilder();
524
		if ($visible) {
525
			$qb->update($this->getTableName())
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\QueryBuilder\IQueryBuilder::execute() has been deprecated: 22.0.0 Use executeQuery or executeStatement ( Ignorable by Annotation )

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

525
			/** @scrutinizer ignore-deprecated */ $qb->update($this->getTableName())

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
526
				->set('is_visible', $qb->createNamedParameter(1))
527
				->where($qb->expr()->eq('id', $qb->createNamedParameter($personId)))
528
				->execute();
529
		} else {
530
			$qb->update($this->getTableName())
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\QueryBuilder\IQueryBuilder::execute() has been deprecated: 22.0.0 Use executeQuery or executeStatement ( Ignorable by Annotation )

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

530
			/** @scrutinizer ignore-deprecated */ $qb->update($this->getTableName())

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
531
				->set('is_visible', $qb->createNamedParameter(0))
532
				->set('name', $qb->createNamedParameter(null))
533
				->where($qb->expr()->eq('id', $qb->createNamedParameter($personId)))
534
				->execute();
535
		}
536
	}
537
538
	/*
539
	 * Mark the cluster as hidden or visible to user.
540
	 *
541
	 * @param int $personId ID of the person
542
	 * @param int $faceId visibility of the person
543
	 * @param string|null $name optional name to rename them.
544
	 *
545
	 * @return Person
546
	 */
547
	public function detachFace(int $personId, int $faceId, $name = null): Person {
548
		// Mark the face as non groupable.
549
		$qb = $this->db->getQueryBuilder();
550
		$qb->update('facerecog_faces')
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\QueryBuilder\IQueryBuilder::execute() has been deprecated: 22.0.0 Use executeQuery or executeStatement ( Ignorable by Annotation )

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

550
		/** @scrutinizer ignore-deprecated */ $qb->update('facerecog_faces')

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
551
			->set('is_groupable', $qb->createParameter('is_groupable'))
552
			->where($qb->expr()->eq('id', $qb->createNamedParameter($faceId)))
553
			->setParameter('is_groupable', false, IQueryBuilder::PARAM_BOOL)
554
			->execute();
555
556
		if ($this->countClusterFaces($personId) === 1) {
557
			// If cluster is an single face just rename it.
558
			$qb = $this->db->getQueryBuilder();
559
			$qb->update($this->getTableName())
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\QueryBuilder\IQueryBuilder::execute() has been deprecated: 22.0.0 Use executeQuery or executeStatement ( Ignorable by Annotation )

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

559
			/** @scrutinizer ignore-deprecated */ $qb->update($this->getTableName())

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
560
				->set('name', $qb->createNamedParameter($name))
561
				->set('is_visible', $qb->createNamedParameter(true))
562
				->where($qb->expr()->eq('id', $qb->createNamedParameter($personId)))
563
				->execute();
564
		} else {
565
			// If there are other faces, must create a new person for that face.
566
			$qb = $this->db->getQueryBuilder();
567
			$qb->select('user')
568
				->from($this->getTableName())
569
				->where($qb->expr()->eq('id', $qb->createNamedParameter($personId)));
570
			$oldPerson = $this->findEntity($qb);
571
572
			$qb = $this->db->getQueryBuilder();
573
			$qb->insert($this->getTableName())->values([
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\QueryBuilder\IQueryBuilder::execute() has been deprecated: 22.0.0 Use executeQuery or executeStatement ( Ignorable by Annotation )

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

573
			/** @scrutinizer ignore-deprecated */ $qb->insert($this->getTableName())->values([

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
574
				'user' => $qb->createNamedParameter($oldPerson->getUser()),
575
				'name' => $qb->createNamedParameter($name),
576
				'is_valid' => $qb->createNamedParameter(true),
577
				'last_generation_time' => $qb->createNamedParameter(new \DateTime(), IQueryBuilder::PARAM_DATE),
0 ignored issues
show
OCP\DB\QueryBuilder\IQueryBuilder::PARAM_DATE of type string is incompatible with the type OCP\DB\QueryBuilder\IQueryBuilder expected by parameter $type of OCP\DB\QueryBuilder\IQue...:createNamedParameter(). ( Ignorable by Annotation )

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

577
				'last_generation_time' => $qb->createNamedParameter(new \DateTime(), /** @scrutinizer ignore-type */ IQueryBuilder::PARAM_DATE),
Loading history...
578
				'linked_user' => $qb->createNamedParameter(null),
579
				'is_visible' => $qb->createNamedParameter(true)
580
			])->execute();
581
582
			$personId = $qb->getLastInsertId();
583
584
			$qb = $this->db->getQueryBuilder();
585
			$qb->update('facerecog_faces')
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\QueryBuilder\IQueryBuilder::execute() has been deprecated: 22.0.0 Use executeQuery or executeStatement ( Ignorable by Annotation )

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

585
			/** @scrutinizer ignore-deprecated */ $qb->update('facerecog_faces')

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
586
				->set('person', $qb->createParameter('person'))
587
				->where($qb->expr()->eq('id', $qb->createNamedParameter($faceId)))
588
				->setParameter('person', $personId)
589
				->execute();
590
		}
591
592
		$qb = $this->db->getQueryBuilder();
593
		$qb->select('id', 'name', 'is_valid', 'is_visible')
594
		   ->from($this->getTableName())
595
		   ->where($qb->expr()->eq('id', $qb->createNamedParameter($personId)));
596
		return $this->findEntity($qb);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->findEntity($qb) returns the type OCP\AppFramework\Db\Entity which includes types incompatible with the type-hinted return OCA\FaceRecognition\Db\Person.
Loading history...
597
	}
598
599
	public function countClusterFaces(int $personId): int {
600
		$qb = $this->db->getQueryBuilder();
601
		$query = $qb
602
			->select($qb->createFunction('COUNT(' . $qb->getColumnName('id') . ')'))
603
			->from('facerecog_faces')
604
			->where($qb->expr()->eq('person', $qb->createParameter('person')))
605
			->setParameter('person', $personId);
606
		$resultStatement = $query->execute();
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\QueryBuilder\IQueryBuilder::execute() has been deprecated: 22.0.0 Use executeQuery or executeStatement ( Ignorable by Annotation )

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

606
		$resultStatement = /** @scrutinizer ignore-deprecated */ $query->execute();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
607
		$data = $resultStatement->fetch(\PDO::FETCH_NUM);
608
		$resultStatement->closeCursor();
609
610
		return (int)$data[0];
611
	}
612
613
	/**
614
	 * Updates one face with $faceId to database to person ID $personId.
615
	 *
616
	 * @param int $faceId ID of the face
617
	 * @param int|null $personId ID of the person
618
	 *
619
	 * @return void
620
	 */
621 12
	private function updateFace(int $faceId, $personId): void {
622 12
		$qb = $this->db->getQueryBuilder();
623 12
		$qb->update('facerecog_faces')
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\QueryBuilder\IQueryBuilder::execute() has been deprecated: 22.0.0 Use executeQuery or executeStatement ( Ignorable by Annotation )

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

623
		/** @scrutinizer ignore-deprecated */ $qb->update('facerecog_faces')

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
624 12
			->set("person", $qb->createNamedParameter($personId))
625 12
			->where($qb->expr()->eq('id', $qb->createNamedParameter($faceId)))
626 12
			->execute();
627
	}
628
629
	/**
630
	 * Checks if face with a given ID is in any cluster.
631
	 *
632
	 * @param int $faceId ID of the face to check
633
	 * @param array $cluster All clusters to check into
634
	 *
635
	 * @return bool True if face is found in any cluster, false otherwise.
636
	 */
637 5
	private function isFaceInClusters(int $faceId, array $clusters): bool {
638 5
		foreach ($clusters as $_=>$faces) {
639 5
			if (in_array($faceId, $faces)) {
640 5
				return true;
641
			}
642
		}
643 1
		return false;
644
	}
645
}
646