Passed
Push — master ( 4e14c1...3b82c6 )
by Morris
13:21 queued 10s
created
lib/private/TagManager.php 1 patch
Indentation   +52 added lines, -52 removed lines patch added patch discarded remove patch
@@ -46,62 +46,62 @@
 block discarded – undo
46 46
 
47 47
 class TagManager implements ITagManager {
48 48
 
49
-	/** @var TagMapper */
50
-	private $mapper;
49
+    /** @var TagMapper */
50
+    private $mapper;
51 51
 
52
-	/** @var IUserSession */
53
-	private $userSession;
52
+    /** @var IUserSession */
53
+    private $userSession;
54 54
 
55
-	/** @var IDBConnection */
56
-	private $connection;
55
+    /** @var IDBConnection */
56
+    private $connection;
57 57
 
58
-	public function __construct(TagMapper $mapper, IUserSession $userSession, IDBConnection $connection) {
59
-		$this->mapper = $mapper;
60
-		$this->userSession = $userSession;
61
-		$this->connection = $connection;
62
-	}
58
+    public function __construct(TagMapper $mapper, IUserSession $userSession, IDBConnection $connection) {
59
+        $this->mapper = $mapper;
60
+        $this->userSession = $userSession;
61
+        $this->connection = $connection;
62
+    }
63 63
 
64
-	/**
65
-	 * Create a new \OCP\ITags instance and load tags from db.
66
-	 *
67
-	 * @see \OCP\ITags
68
-	 * @param string $type The type identifier e.g. 'contact' or 'event'.
69
-	 * @param array $defaultTags An array of default tags to be used if none are stored.
70
-	 * @param boolean $includeShared Whether to include tags for items shared with this user by others.
71
-	 * @param string $userId user for which to retrieve the tags, defaults to the currently
72
-	 * logged in user
73
-	 * @return \OCP\ITags
74
-	 *
75
-	 * since 20.0.0 $includeShared isn't used anymore
76
-	 */
77
-	public function load($type, $defaultTags = [], $includeShared = false, $userId = null) {
78
-		if (is_null($userId)) {
79
-			$user = $this->userSession->getUser();
80
-			if ($user === null) {
81
-				// nothing we can do without a user
82
-				return null;
83
-			}
84
-			$userId = $this->userSession->getUser()->getUId();
85
-		}
86
-		return new Tags($this->mapper, $userId, $type, $defaultTags);
87
-	}
64
+    /**
65
+     * Create a new \OCP\ITags instance and load tags from db.
66
+     *
67
+     * @see \OCP\ITags
68
+     * @param string $type The type identifier e.g. 'contact' or 'event'.
69
+     * @param array $defaultTags An array of default tags to be used if none are stored.
70
+     * @param boolean $includeShared Whether to include tags for items shared with this user by others.
71
+     * @param string $userId user for which to retrieve the tags, defaults to the currently
72
+     * logged in user
73
+     * @return \OCP\ITags
74
+     *
75
+     * since 20.0.0 $includeShared isn't used anymore
76
+     */
77
+    public function load($type, $defaultTags = [], $includeShared = false, $userId = null) {
78
+        if (is_null($userId)) {
79
+            $user = $this->userSession->getUser();
80
+            if ($user === null) {
81
+                // nothing we can do without a user
82
+                return null;
83
+            }
84
+            $userId = $this->userSession->getUser()->getUId();
85
+        }
86
+        return new Tags($this->mapper, $userId, $type, $defaultTags);
87
+    }
88 88
 
89
-	/**
90
-	 * Get all users who favorited an object
91
-	 *
92
-	 * @param string $objectType
93
-	 * @param int $objectId
94
-	 * @return array
95
-	 */
96
-	public function getUsersFavoritingObject(string $objectType, int $objectId): array {
97
-		$query = $this->connection->getQueryBuilder();
98
-		$query->select('uid')
99
-			->from('vcategory_to_object', 'o')
100
-			->innerJoin('o', 'vcategory', 'c', $query->expr()->eq('o.categoryid', 'c.id'))
101
-			->where($query->expr()->eq('objid', $query->createNamedParameter($objectId, IQueryBuilder::PARAM_INT)))
102
-			->andWhere($query->expr()->eq('c.type', $query->createNamedParameter($objectType)))
103
-			->andWhere($query->expr()->eq('c.category', $query->createNamedParameter(ITags::TAG_FAVORITE)));
89
+    /**
90
+     * Get all users who favorited an object
91
+     *
92
+     * @param string $objectType
93
+     * @param int $objectId
94
+     * @return array
95
+     */
96
+    public function getUsersFavoritingObject(string $objectType, int $objectId): array {
97
+        $query = $this->connection->getQueryBuilder();
98
+        $query->select('uid')
99
+            ->from('vcategory_to_object', 'o')
100
+            ->innerJoin('o', 'vcategory', 'c', $query->expr()->eq('o.categoryid', 'c.id'))
101
+            ->where($query->expr()->eq('objid', $query->createNamedParameter($objectId, IQueryBuilder::PARAM_INT)))
102
+            ->andWhere($query->expr()->eq('c.type', $query->createNamedParameter($objectType)))
103
+            ->andWhere($query->expr()->eq('c.category', $query->createNamedParameter(ITags::TAG_FAVORITE)));
104 104
 
105
-		return $query->execute()->fetchAll(\PDO::FETCH_COLUMN);
106
-	}
105
+        return $query->execute()->fetchAll(\PDO::FETCH_COLUMN);
106
+    }
107 107
 }
Please login to merge, or discard this patch.
lib/private/Tags.php 1 patch
Indentation   +785 added lines, -785 removed lines patch added patch discarded remove patch
@@ -51,789 +51,789 @@
 block discarded – undo
51 51
 
52 52
 class Tags implements ITags {
53 53
 
54
-	/**
55
-	 * Tags
56
-	 *
57
-	 * @var array
58
-	 */
59
-	private $tags = [];
60
-
61
-	/**
62
-	 * Used for storing objectid/categoryname pairs while rescanning.
63
-	 *
64
-	 * @var array
65
-	 */
66
-	private static $relations = [];
67
-
68
-	/**
69
-	 * Type
70
-	 *
71
-	 * @var string
72
-	 */
73
-	private $type;
74
-
75
-	/**
76
-	 * User
77
-	 *
78
-	 * @var string
79
-	 */
80
-	private $user;
81
-
82
-	/**
83
-	 * Are we including tags for shared items?
84
-	 *
85
-	 * @var bool
86
-	 */
87
-	private $includeShared = false;
88
-
89
-	/**
90
-	 * The current user, plus any owners of the items shared with the current
91
-	 * user, if $this->includeShared === true.
92
-	 *
93
-	 * @var array
94
-	 */
95
-	private $owners = [];
96
-
97
-	/**
98
-	 * The Mapper we're using to communicate our Tag objects to the database.
99
-	 *
100
-	 * @var TagMapper
101
-	 */
102
-	private $mapper;
103
-
104
-	/**
105
-	 * The sharing backend for objects of $this->type. Required if
106
-	 * $this->includeShared === true to determine ownership of items.
107
-	 *
108
-	 * @var \OCP\Share_Backend
109
-	 */
110
-	private $backend;
111
-
112
-	public const TAG_TABLE = '*PREFIX*vcategory';
113
-	public const RELATION_TABLE = '*PREFIX*vcategory_to_object';
114
-
115
-	/**
116
-	 * Constructor.
117
-	 *
118
-	 * @param TagMapper $mapper Instance of the TagMapper abstraction layer.
119
-	 * @param string $user The user whose data the object will operate on.
120
-	 * @param string $type The type of items for which tags will be loaded.
121
-	 * @param array $defaultTags Tags that should be created at construction.
122
-	 *
123
-	 * since 20.0.0 $includeShared isn't used anymore
124
-	 */
125
-	public function __construct(TagMapper $mapper, $user, $type, $defaultTags = []) {
126
-		$this->mapper = $mapper;
127
-		$this->user = $user;
128
-		$this->type = $type;
129
-		$this->owners = [$this->user];
130
-		$this->tags = $this->mapper->loadTags($this->owners, $this->type);
131
-
132
-		if (count($defaultTags) > 0 && count($this->tags) === 0) {
133
-			$this->addMultiple($defaultTags, true);
134
-		}
135
-	}
136
-
137
-	/**
138
-	 * Check if any tags are saved for this type and user.
139
-	 *
140
-	 * @return boolean
141
-	 */
142
-	public function isEmpty() {
143
-		return count($this->tags) === 0;
144
-	}
145
-
146
-	/**
147
-	 * Returns an array mapping a given tag's properties to its values:
148
-	 * ['id' => 0, 'name' = 'Tag', 'owner' = 'User', 'type' => 'tagtype']
149
-	 *
150
-	 * @param string $id The ID of the tag that is going to be mapped
151
-	 * @return array|false
152
-	 */
153
-	public function getTag($id) {
154
-		$key = $this->getTagById($id);
155
-		if ($key !== false) {
156
-			return $this->tagMap($this->tags[$key]);
157
-		}
158
-		return false;
159
-	}
160
-
161
-	/**
162
-	 * Get the tags for a specific user.
163
-	 *
164
-	 * This returns an array with maps containing each tag's properties:
165
-	 * [
166
-	 * 	['id' => 0, 'name' = 'First tag', 'owner' = 'User', 'type' => 'tagtype'],
167
-	 * 	['id' => 1, 'name' = 'Shared tag', 'owner' = 'Other user', 'type' => 'tagtype'],
168
-	 * ]
169
-	 *
170
-	 * @return array
171
-	 */
172
-	public function getTags() {
173
-		if (!count($this->tags)) {
174
-			return [];
175
-		}
176
-
177
-		usort($this->tags, function ($a, $b) {
178
-			return strnatcasecmp($a->getName(), $b->getName());
179
-		});
180
-		$tagMap = [];
181
-
182
-		foreach ($this->tags as $tag) {
183
-			if ($tag->getName() !== ITags::TAG_FAVORITE) {
184
-				$tagMap[] = $this->tagMap($tag);
185
-			}
186
-		}
187
-		return $tagMap;
188
-	}
189
-
190
-	/**
191
-	 * Return only the tags owned by the given user, omitting any tags shared
192
-	 * by other users.
193
-	 *
194
-	 * @param string $user The user whose tags are to be checked.
195
-	 * @return array An array of Tag objects.
196
-	 */
197
-	public function getTagsForUser($user) {
198
-		return array_filter($this->tags,
199
-			function ($tag) use ($user) {
200
-				return $tag->getOwner() === $user;
201
-			}
202
-		);
203
-	}
204
-
205
-	/**
206
-	 * Get the list of tags for the given ids.
207
-	 *
208
-	 * @param array $objIds array of object ids
209
-	 * @return array|boolean of tags id as key to array of tag names
210
-	 * or false if an error occurred
211
-	 */
212
-	public function getTagsForObjects(array $objIds) {
213
-		$entries = [];
214
-
215
-		try {
216
-			$conn = \OC::$server->getDatabaseConnection();
217
-			$chunks = array_chunk($objIds, 900, false);
218
-			foreach ($chunks as $chunk) {
219
-				$result = $conn->executeQuery(
220
-					'SELECT `category`, `categoryid`, `objid` ' .
221
-					'FROM `' . self::RELATION_TABLE . '` r, `' . self::TAG_TABLE . '` ' .
222
-					'WHERE `categoryid` = `id` AND `uid` = ? AND r.`type` = ? AND `objid` IN (?)',
223
-					[$this->user, $this->type, $chunk],
224
-					[null, null, IQueryBuilder::PARAM_INT_ARRAY]
225
-				);
226
-				while ($row = $result->fetch()) {
227
-					$objId = (int)$row['objid'];
228
-					if (!isset($entries[$objId])) {
229
-						$entries[$objId] = [];
230
-					}
231
-					$entries[$objId][] = $row['category'];
232
-				}
233
-				if ($result === null) {
234
-					\OCP\Util::writeLog('core', __METHOD__. 'DB error: ' . \OC::$server->getDatabaseConnection()->getError(), ILogger::ERROR);
235
-					return false;
236
-				}
237
-			}
238
-		} catch (\Exception $e) {
239
-			\OC::$server->getLogger()->logException($e, [
240
-				'message' => __METHOD__,
241
-				'level' => ILogger::ERROR,
242
-				'app' => 'core',
243
-			]);
244
-			return false;
245
-		}
246
-
247
-		return $entries;
248
-	}
249
-
250
-	/**
251
-	 * Get the a list if items tagged with $tag.
252
-	 *
253
-	 * Throws an exception if the tag could not be found.
254
-	 *
255
-	 * @param string $tag Tag id or name.
256
-	 * @return array|false An array of object ids or false on error.
257
-	 * @throws \Exception
258
-	 */
259
-	public function getIdsForTag($tag) {
260
-		$result = null;
261
-		$tagId = false;
262
-		if (is_numeric($tag)) {
263
-			$tagId = $tag;
264
-		} elseif (is_string($tag)) {
265
-			$tag = trim($tag);
266
-			if ($tag === '') {
267
-				\OCP\Util::writeLog('core', __METHOD__.', Cannot use empty tag names', ILogger::DEBUG);
268
-				return false;
269
-			}
270
-			$tagId = $this->getTagId($tag);
271
-		}
272
-
273
-		if ($tagId === false) {
274
-			$l10n = \OC::$server->getL10N('core');
275
-			throw new \Exception(
276
-				$l10n->t('Could not find category "%s"', [$tag])
277
-			);
278
-		}
279
-
280
-		$ids = [];
281
-		$sql = 'SELECT `objid` FROM `' . self::RELATION_TABLE
282
-			. '` WHERE `categoryid` = ?';
283
-
284
-		try {
285
-			$stmt = \OC_DB::prepare($sql);
286
-			$result = $stmt->execute([$tagId]);
287
-			if ($result === null) {
288
-				\OCP\Util::writeLog('core', __METHOD__. 'DB error: ' . \OC::$server->getDatabaseConnection()->getError(), ILogger::ERROR);
289
-				return false;
290
-			}
291
-		} catch (\Exception $e) {
292
-			\OC::$server->getLogger()->logException($e, [
293
-				'message' => __METHOD__,
294
-				'level' => ILogger::ERROR,
295
-				'app' => 'core',
296
-			]);
297
-			return false;
298
-		}
299
-
300
-		if (!is_null($result)) {
301
-			while ($row = $result->fetchRow()) {
302
-				$ids[] = (int)$row['objid'];
303
-			}
304
-		}
305
-
306
-		return $ids;
307
-	}
308
-
309
-	/**
310
-	 * Checks whether a tag is saved for the given user,
311
-	 * disregarding the ones shared with him or her.
312
-	 *
313
-	 * @param string $name The tag name to check for.
314
-	 * @param string $user The user whose tags are to be checked.
315
-	 * @return bool
316
-	 */
317
-	public function userHasTag($name, $user) {
318
-		$key = $this->array_searchi($name, $this->getTagsForUser($user));
319
-		return ($key !== false) ? $this->tags[$key]->getId() : false;
320
-	}
321
-
322
-	/**
323
-	 * Checks whether a tag is saved for or shared with the current user.
324
-	 *
325
-	 * @param string $name The tag name to check for.
326
-	 * @return bool
327
-	 */
328
-	public function hasTag($name) {
329
-		return $this->getTagId($name) !== false;
330
-	}
331
-
332
-	/**
333
-	 * Add a new tag.
334
-	 *
335
-	 * @param string $name A string with a name of the tag
336
-	 * @return false|int the id of the added tag or false on error.
337
-	 */
338
-	public function add($name) {
339
-		$name = trim($name);
340
-
341
-		if ($name === '') {
342
-			\OCP\Util::writeLog('core', __METHOD__.', Cannot add an empty tag', ILogger::DEBUG);
343
-			return false;
344
-		}
345
-		if ($this->userHasTag($name, $this->user)) {
346
-			\OCP\Util::writeLog('core', __METHOD__.', name: ' . $name. ' exists already', ILogger::DEBUG);
347
-			return false;
348
-		}
349
-		try {
350
-			$tag = new Tag($this->user, $this->type, $name);
351
-			$tag = $this->mapper->insert($tag);
352
-			$this->tags[] = $tag;
353
-		} catch (\Exception $e) {
354
-			\OC::$server->getLogger()->logException($e, [
355
-				'message' => __METHOD__,
356
-				'level' => ILogger::ERROR,
357
-				'app' => 'core',
358
-			]);
359
-			return false;
360
-		}
361
-		\OCP\Util::writeLog('core', __METHOD__.', id: ' . $tag->getId(), ILogger::DEBUG);
362
-		return $tag->getId();
363
-	}
364
-
365
-	/**
366
-	 * Rename tag.
367
-	 *
368
-	 * @param string|integer $from The name or ID of the existing tag
369
-	 * @param string $to The new name of the tag.
370
-	 * @return bool
371
-	 */
372
-	public function rename($from, $to) {
373
-		$from = trim($from);
374
-		$to = trim($to);
375
-
376
-		if ($to === '' || $from === '') {
377
-			\OCP\Util::writeLog('core', __METHOD__.', Cannot use empty tag names', ILogger::DEBUG);
378
-			return false;
379
-		}
380
-
381
-		if (is_numeric($from)) {
382
-			$key = $this->getTagById($from);
383
-		} else {
384
-			$key = $this->getTagByName($from);
385
-		}
386
-		if ($key === false) {
387
-			\OCP\Util::writeLog('core', __METHOD__.', tag: ' . $from. ' does not exist', ILogger::DEBUG);
388
-			return false;
389
-		}
390
-		$tag = $this->tags[$key];
391
-
392
-		if ($this->userHasTag($to, $tag->getOwner())) {
393
-			\OCP\Util::writeLog('core', __METHOD__.', A tag named ' . $to. ' already exists for user ' . $tag->getOwner() . '.', ILogger::DEBUG);
394
-			return false;
395
-		}
396
-
397
-		try {
398
-			$tag->setName($to);
399
-			$this->tags[$key] = $this->mapper->update($tag);
400
-		} catch (\Exception $e) {
401
-			\OC::$server->getLogger()->logException($e, [
402
-				'message' => __METHOD__,
403
-				'level' => ILogger::ERROR,
404
-				'app' => 'core',
405
-			]);
406
-			return false;
407
-		}
408
-		return true;
409
-	}
410
-
411
-	/**
412
-	 * Add a list of new tags.
413
-	 *
414
-	 * @param string[] $names A string with a name or an array of strings containing
415
-	 * the name(s) of the tag(s) to add.
416
-	 * @param bool $sync When true, save the tags
417
-	 * @param int|null $id int Optional object id to add to this|these tag(s)
418
-	 * @return bool Returns false on error.
419
-	 */
420
-	public function addMultiple($names, $sync = false, $id = null) {
421
-		if (!is_array($names)) {
422
-			$names = [$names];
423
-		}
424
-		$names = array_map('trim', $names);
425
-		array_filter($names);
426
-
427
-		$newones = [];
428
-		foreach ($names as $name) {
429
-			if (!$this->hasTag($name) && $name !== '') {
430
-				$newones[] = new Tag($this->user, $this->type, $name);
431
-			}
432
-			if (!is_null($id)) {
433
-				// Insert $objectid, $categoryid  pairs if not exist.
434
-				self::$relations[] = ['objid' => $id, 'tag' => $name];
435
-			}
436
-		}
437
-		$this->tags = array_merge($this->tags, $newones);
438
-		if ($sync === true) {
439
-			$this->save();
440
-		}
441
-
442
-		return true;
443
-	}
444
-
445
-	/**
446
-	 * Save the list of tags and their object relations
447
-	 */
448
-	protected function save() {
449
-		if (is_array($this->tags)) {
450
-			foreach ($this->tags as $tag) {
451
-				try {
452
-					if (!$this->mapper->tagExists($tag)) {
453
-						$this->mapper->insert($tag);
454
-					}
455
-				} catch (\Exception $e) {
456
-					\OC::$server->getLogger()->logException($e, [
457
-						'message' => __METHOD__,
458
-						'level' => ILogger::ERROR,
459
-						'app' => 'core',
460
-					]);
461
-				}
462
-			}
463
-
464
-			// reload tags to get the proper ids.
465
-			$this->tags = $this->mapper->loadTags($this->owners, $this->type);
466
-			\OCP\Util::writeLog('core', __METHOD__.', tags: ' . print_r($this->tags, true),
467
-				ILogger::DEBUG);
468
-			// Loop through temporarily cached objectid/tagname pairs
469
-			// and save relations.
470
-			$tags = $this->tags;
471
-			// For some reason this is needed or array_search(i) will return 0..?
472
-			ksort($tags);
473
-			$dbConnection = \OC::$server->getDatabaseConnection();
474
-			foreach (self::$relations as $relation) {
475
-				$tagId = $this->getTagId($relation['tag']);
476
-				\OCP\Util::writeLog('core', __METHOD__ . 'catid, ' . $relation['tag'] . ' ' . $tagId, ILogger::DEBUG);
477
-				if ($tagId) {
478
-					try {
479
-						$dbConnection->insertIfNotExist(self::RELATION_TABLE,
480
-							[
481
-								'objid' => $relation['objid'],
482
-								'categoryid' => $tagId,
483
-								'type' => $this->type,
484
-							]);
485
-					} catch (\Exception $e) {
486
-						\OC::$server->getLogger()->logException($e, [
487
-							'message' => __METHOD__,
488
-							'level' => ILogger::ERROR,
489
-							'app' => 'core',
490
-						]);
491
-					}
492
-				}
493
-			}
494
-			self::$relations = []; // reset
495
-		} else {
496
-			\OCP\Util::writeLog('core', __METHOD__.', $this->tags is not an array! '
497
-				. print_r($this->tags, true), ILogger::ERROR);
498
-		}
499
-	}
500
-
501
-	/**
502
-	 * Delete tags and tag/object relations for a user.
503
-	 *
504
-	 * For hooking up on post_deleteUser
505
-	 *
506
-	 * @param array $arguments
507
-	 */
508
-	public static function post_deleteUser($arguments) {
509
-		// Find all objectid/tagId pairs.
510
-		$result = null;
511
-		try {
512
-			$stmt = \OC_DB::prepare('SELECT `id` FROM `' . self::TAG_TABLE . '` '
513
-				. 'WHERE `uid` = ?');
514
-			$result = $stmt->execute([$arguments['uid']]);
515
-			if ($result === null) {
516
-				\OCP\Util::writeLog('core', __METHOD__. 'DB error: ' . \OC::$server->getDatabaseConnection()->getError(), ILogger::ERROR);
517
-			}
518
-		} catch (\Exception $e) {
519
-			\OC::$server->getLogger()->logException($e, [
520
-				'message' => __METHOD__,
521
-				'level' => ILogger::ERROR,
522
-				'app' => 'core',
523
-			]);
524
-		}
525
-
526
-		if (!is_null($result)) {
527
-			try {
528
-				$stmt = \OC_DB::prepare('DELETE FROM `' . self::RELATION_TABLE . '` '
529
-					. 'WHERE `categoryid` = ?');
530
-				while ($row = $result->fetchRow()) {
531
-					try {
532
-						$stmt->execute([$row['id']]);
533
-					} catch (\Exception $e) {
534
-						\OC::$server->getLogger()->logException($e, [
535
-							'message' => __METHOD__,
536
-							'level' => ILogger::ERROR,
537
-							'app' => 'core',
538
-						]);
539
-					}
540
-				}
541
-			} catch (\Exception $e) {
542
-				\OC::$server->getLogger()->logException($e, [
543
-					'message' => __METHOD__,
544
-					'level' => ILogger::ERROR,
545
-					'app' => 'core',
546
-				]);
547
-			}
548
-		}
549
-		try {
550
-			$stmt = \OC_DB::prepare('DELETE FROM `' . self::TAG_TABLE . '` '
551
-				. 'WHERE `uid` = ?');
552
-			$result = $stmt->execute([$arguments['uid']]);
553
-			if ($result === null) {
554
-				\OCP\Util::writeLog('core', __METHOD__. ', DB error: ' . \OC::$server->getDatabaseConnection()->getError(), ILogger::ERROR);
555
-			}
556
-		} catch (\Exception $e) {
557
-			\OC::$server->getLogger()->logException($e, [
558
-				'message' => __METHOD__,
559
-				'level' => ILogger::ERROR,
560
-				'app' => 'core',
561
-			]);
562
-		}
563
-	}
564
-
565
-	/**
566
-	 * Delete tag/object relations from the db
567
-	 *
568
-	 * @param array $ids The ids of the objects
569
-	 * @return boolean Returns false on error.
570
-	 */
571
-	public function purgeObjects(array $ids) {
572
-		if (count($ids) === 0) {
573
-			// job done ;)
574
-			return true;
575
-		}
576
-		$updates = $ids;
577
-		try {
578
-			$query = 'DELETE FROM `' . self::RELATION_TABLE . '` ';
579
-			$query .= 'WHERE `objid` IN (' . str_repeat('?,', count($ids) - 1) . '?) ';
580
-			$query .= 'AND `type`= ?';
581
-			$updates[] = $this->type;
582
-			$stmt = \OC_DB::prepare($query);
583
-			$result = $stmt->execute($updates);
584
-			if ($result === null) {
585
-				\OCP\Util::writeLog('core', __METHOD__. 'DB error: ' . \OC::$server->getDatabaseConnection()->getError(), ILogger::ERROR);
586
-				return false;
587
-			}
588
-		} catch (\Exception $e) {
589
-			\OC::$server->getLogger()->logException($e, [
590
-				'message' => __METHOD__,
591
-				'level' => ILogger::ERROR,
592
-				'app' => 'core',
593
-			]);
594
-			return false;
595
-		}
596
-		return true;
597
-	}
598
-
599
-	/**
600
-	 * Get favorites for an object type
601
-	 *
602
-	 * @return array|false An array of object ids.
603
-	 */
604
-	public function getFavorites() {
605
-		if (!$this->userHasTag(ITags::TAG_FAVORITE, $this->user)) {
606
-			return [];
607
-		}
608
-
609
-		try {
610
-			return $this->getIdsForTag(ITags::TAG_FAVORITE);
611
-		} catch (\Exception $e) {
612
-			\OC::$server->getLogger()->logException($e, [
613
-				'message' => __METHOD__,
614
-				'level' => ILogger::ERROR,
615
-				'app' => 'core',
616
-			]);
617
-			return [];
618
-		}
619
-	}
620
-
621
-	/**
622
-	 * Add an object to favorites
623
-	 *
624
-	 * @param int $objid The id of the object
625
-	 * @return boolean
626
-	 */
627
-	public function addToFavorites($objid) {
628
-		if (!$this->userHasTag(ITags::TAG_FAVORITE, $this->user)) {
629
-			$this->add(ITags::TAG_FAVORITE);
630
-		}
631
-		return $this->tagAs($objid, ITags::TAG_FAVORITE);
632
-	}
633
-
634
-	/**
635
-	 * Remove an object from favorites
636
-	 *
637
-	 * @param int $objid The id of the object
638
-	 * @return boolean
639
-	 */
640
-	public function removeFromFavorites($objid) {
641
-		return $this->unTag($objid, ITags::TAG_FAVORITE);
642
-	}
643
-
644
-	/**
645
-	 * Creates a tag/object relation.
646
-	 *
647
-	 * @param int $objid The id of the object
648
-	 * @param string $tag The id or name of the tag
649
-	 * @return boolean Returns false on error.
650
-	 */
651
-	public function tagAs($objid, $tag) {
652
-		if (is_string($tag) && !is_numeric($tag)) {
653
-			$tag = trim($tag);
654
-			if ($tag === '') {
655
-				\OCP\Util::writeLog('core', __METHOD__.', Cannot add an empty tag', ILogger::DEBUG);
656
-				return false;
657
-			}
658
-			if (!$this->hasTag($tag)) {
659
-				$this->add($tag);
660
-			}
661
-			$tagId = $this->getTagId($tag);
662
-		} else {
663
-			$tagId = $tag;
664
-		}
665
-		try {
666
-			\OC::$server->getDatabaseConnection()->insertIfNotExist(self::RELATION_TABLE,
667
-				[
668
-					'objid' => $objid,
669
-					'categoryid' => $tagId,
670
-					'type' => $this->type,
671
-				]);
672
-		} catch (\Exception $e) {
673
-			\OC::$server->getLogger()->logException($e, [
674
-				'message' => __METHOD__,
675
-				'level' => ILogger::ERROR,
676
-				'app' => 'core',
677
-			]);
678
-			return false;
679
-		}
680
-		return true;
681
-	}
682
-
683
-	/**
684
-	 * Delete single tag/object relation from the db
685
-	 *
686
-	 * @param int $objid The id of the object
687
-	 * @param string $tag The id or name of the tag
688
-	 * @return boolean
689
-	 */
690
-	public function unTag($objid, $tag) {
691
-		if (is_string($tag) && !is_numeric($tag)) {
692
-			$tag = trim($tag);
693
-			if ($tag === '') {
694
-				\OCP\Util::writeLog('core', __METHOD__.', Tag name is empty', ILogger::DEBUG);
695
-				return false;
696
-			}
697
-			$tagId = $this->getTagId($tag);
698
-		} else {
699
-			$tagId = $tag;
700
-		}
701
-
702
-		try {
703
-			$sql = 'DELETE FROM `' . self::RELATION_TABLE . '` '
704
-					. 'WHERE `objid` = ? AND `categoryid` = ? AND `type` = ?';
705
-			$stmt = \OC_DB::prepare($sql);
706
-			$stmt->execute([$objid, $tagId, $this->type]);
707
-		} catch (\Exception $e) {
708
-			\OC::$server->getLogger()->logException($e, [
709
-				'message' => __METHOD__,
710
-				'level' => ILogger::ERROR,
711
-				'app' => 'core',
712
-			]);
713
-			return false;
714
-		}
715
-		return true;
716
-	}
717
-
718
-	/**
719
-	 * Delete tags from the database.
720
-	 *
721
-	 * @param string[]|integer[] $names An array of tags (names or IDs) to delete
722
-	 * @return bool Returns false on error
723
-	 */
724
-	public function delete($names) {
725
-		if (!is_array($names)) {
726
-			$names = [$names];
727
-		}
728
-
729
-		$names = array_map('trim', $names);
730
-		array_filter($names);
731
-
732
-		\OCP\Util::writeLog('core', __METHOD__ . ', before: '
733
-			. print_r($this->tags, true), ILogger::DEBUG);
734
-		foreach ($names as $name) {
735
-			$id = null;
736
-
737
-			if (is_numeric($name)) {
738
-				$key = $this->getTagById($name);
739
-			} else {
740
-				$key = $this->getTagByName($name);
741
-			}
742
-			if ($key !== false) {
743
-				$tag = $this->tags[$key];
744
-				$id = $tag->getId();
745
-				unset($this->tags[$key]);
746
-				$this->mapper->delete($tag);
747
-			} else {
748
-				\OCP\Util::writeLog('core', __METHOD__ . 'Cannot delete tag ' . $name
749
-					. ': not found.', ILogger::ERROR);
750
-			}
751
-			if (!is_null($id) && $id !== false) {
752
-				try {
753
-					$sql = 'DELETE FROM `' . self::RELATION_TABLE . '` '
754
-							. 'WHERE `categoryid` = ?';
755
-					$stmt = \OC_DB::prepare($sql);
756
-					$result = $stmt->execute([$id]);
757
-					if ($result === null) {
758
-						\OCP\Util::writeLog('core',
759
-							__METHOD__. 'DB error: ' . \OC::$server->getDatabaseConnection()->getError(),
760
-							ILogger::ERROR);
761
-						return false;
762
-					}
763
-				} catch (\Exception $e) {
764
-					\OC::$server->getLogger()->logException($e, [
765
-						'message' => __METHOD__,
766
-						'level' => ILogger::ERROR,
767
-						'app' => 'core',
768
-					]);
769
-					return false;
770
-				}
771
-			}
772
-		}
773
-		return true;
774
-	}
775
-
776
-	// case-insensitive array_search
777
-	protected function array_searchi($needle, $haystack, $mem = 'getName') {
778
-		if (!is_array($haystack)) {
779
-			return false;
780
-		}
781
-		return array_search(strtolower($needle), array_map(
782
-			function ($tag) use ($mem) {
783
-				return strtolower(call_user_func([$tag, $mem]));
784
-			}, $haystack)
785
-		);
786
-	}
787
-
788
-	/**
789
-	 * Get a tag's ID.
790
-	 *
791
-	 * @param string $name The tag name to look for.
792
-	 * @return string|bool The tag's id or false if no matching tag is found.
793
-	 */
794
-	private function getTagId($name) {
795
-		$key = $this->array_searchi($name, $this->tags);
796
-		if ($key !== false) {
797
-			return $this->tags[$key]->getId();
798
-		}
799
-		return false;
800
-	}
801
-
802
-	/**
803
-	 * Get a tag by its name.
804
-	 *
805
-	 * @param string $name The tag name.
806
-	 * @return integer|bool The tag object's offset within the $this->tags
807
-	 *                      array or false if it doesn't exist.
808
-	 */
809
-	private function getTagByName($name) {
810
-		return $this->array_searchi($name, $this->tags, 'getName');
811
-	}
812
-
813
-	/**
814
-	 * Get a tag by its ID.
815
-	 *
816
-	 * @param string $id The tag ID to look for.
817
-	 * @return integer|bool The tag object's offset within the $this->tags
818
-	 *                      array or false if it doesn't exist.
819
-	 */
820
-	private function getTagById($id) {
821
-		return $this->array_searchi($id, $this->tags, 'getId');
822
-	}
823
-
824
-	/**
825
-	 * Returns an array mapping a given tag's properties to its values:
826
-	 * ['id' => 0, 'name' = 'Tag', 'owner' = 'User', 'type' => 'tagtype']
827
-	 *
828
-	 * @param Tag $tag The tag that is going to be mapped
829
-	 * @return array
830
-	 */
831
-	private function tagMap(Tag $tag) {
832
-		return [
833
-			'id' => $tag->getId(),
834
-			'name' => $tag->getName(),
835
-			'owner' => $tag->getOwner(),
836
-			'type' => $tag->getType()
837
-		];
838
-	}
54
+    /**
55
+     * Tags
56
+     *
57
+     * @var array
58
+     */
59
+    private $tags = [];
60
+
61
+    /**
62
+     * Used for storing objectid/categoryname pairs while rescanning.
63
+     *
64
+     * @var array
65
+     */
66
+    private static $relations = [];
67
+
68
+    /**
69
+     * Type
70
+     *
71
+     * @var string
72
+     */
73
+    private $type;
74
+
75
+    /**
76
+     * User
77
+     *
78
+     * @var string
79
+     */
80
+    private $user;
81
+
82
+    /**
83
+     * Are we including tags for shared items?
84
+     *
85
+     * @var bool
86
+     */
87
+    private $includeShared = false;
88
+
89
+    /**
90
+     * The current user, plus any owners of the items shared with the current
91
+     * user, if $this->includeShared === true.
92
+     *
93
+     * @var array
94
+     */
95
+    private $owners = [];
96
+
97
+    /**
98
+     * The Mapper we're using to communicate our Tag objects to the database.
99
+     *
100
+     * @var TagMapper
101
+     */
102
+    private $mapper;
103
+
104
+    /**
105
+     * The sharing backend for objects of $this->type. Required if
106
+     * $this->includeShared === true to determine ownership of items.
107
+     *
108
+     * @var \OCP\Share_Backend
109
+     */
110
+    private $backend;
111
+
112
+    public const TAG_TABLE = '*PREFIX*vcategory';
113
+    public const RELATION_TABLE = '*PREFIX*vcategory_to_object';
114
+
115
+    /**
116
+     * Constructor.
117
+     *
118
+     * @param TagMapper $mapper Instance of the TagMapper abstraction layer.
119
+     * @param string $user The user whose data the object will operate on.
120
+     * @param string $type The type of items for which tags will be loaded.
121
+     * @param array $defaultTags Tags that should be created at construction.
122
+     *
123
+     * since 20.0.0 $includeShared isn't used anymore
124
+     */
125
+    public function __construct(TagMapper $mapper, $user, $type, $defaultTags = []) {
126
+        $this->mapper = $mapper;
127
+        $this->user = $user;
128
+        $this->type = $type;
129
+        $this->owners = [$this->user];
130
+        $this->tags = $this->mapper->loadTags($this->owners, $this->type);
131
+
132
+        if (count($defaultTags) > 0 && count($this->tags) === 0) {
133
+            $this->addMultiple($defaultTags, true);
134
+        }
135
+    }
136
+
137
+    /**
138
+     * Check if any tags are saved for this type and user.
139
+     *
140
+     * @return boolean
141
+     */
142
+    public function isEmpty() {
143
+        return count($this->tags) === 0;
144
+    }
145
+
146
+    /**
147
+     * Returns an array mapping a given tag's properties to its values:
148
+     * ['id' => 0, 'name' = 'Tag', 'owner' = 'User', 'type' => 'tagtype']
149
+     *
150
+     * @param string $id The ID of the tag that is going to be mapped
151
+     * @return array|false
152
+     */
153
+    public function getTag($id) {
154
+        $key = $this->getTagById($id);
155
+        if ($key !== false) {
156
+            return $this->tagMap($this->tags[$key]);
157
+        }
158
+        return false;
159
+    }
160
+
161
+    /**
162
+     * Get the tags for a specific user.
163
+     *
164
+     * This returns an array with maps containing each tag's properties:
165
+     * [
166
+     * 	['id' => 0, 'name' = 'First tag', 'owner' = 'User', 'type' => 'tagtype'],
167
+     * 	['id' => 1, 'name' = 'Shared tag', 'owner' = 'Other user', 'type' => 'tagtype'],
168
+     * ]
169
+     *
170
+     * @return array
171
+     */
172
+    public function getTags() {
173
+        if (!count($this->tags)) {
174
+            return [];
175
+        }
176
+
177
+        usort($this->tags, function ($a, $b) {
178
+            return strnatcasecmp($a->getName(), $b->getName());
179
+        });
180
+        $tagMap = [];
181
+
182
+        foreach ($this->tags as $tag) {
183
+            if ($tag->getName() !== ITags::TAG_FAVORITE) {
184
+                $tagMap[] = $this->tagMap($tag);
185
+            }
186
+        }
187
+        return $tagMap;
188
+    }
189
+
190
+    /**
191
+     * Return only the tags owned by the given user, omitting any tags shared
192
+     * by other users.
193
+     *
194
+     * @param string $user The user whose tags are to be checked.
195
+     * @return array An array of Tag objects.
196
+     */
197
+    public function getTagsForUser($user) {
198
+        return array_filter($this->tags,
199
+            function ($tag) use ($user) {
200
+                return $tag->getOwner() === $user;
201
+            }
202
+        );
203
+    }
204
+
205
+    /**
206
+     * Get the list of tags for the given ids.
207
+     *
208
+     * @param array $objIds array of object ids
209
+     * @return array|boolean of tags id as key to array of tag names
210
+     * or false if an error occurred
211
+     */
212
+    public function getTagsForObjects(array $objIds) {
213
+        $entries = [];
214
+
215
+        try {
216
+            $conn = \OC::$server->getDatabaseConnection();
217
+            $chunks = array_chunk($objIds, 900, false);
218
+            foreach ($chunks as $chunk) {
219
+                $result = $conn->executeQuery(
220
+                    'SELECT `category`, `categoryid`, `objid` ' .
221
+                    'FROM `' . self::RELATION_TABLE . '` r, `' . self::TAG_TABLE . '` ' .
222
+                    'WHERE `categoryid` = `id` AND `uid` = ? AND r.`type` = ? AND `objid` IN (?)',
223
+                    [$this->user, $this->type, $chunk],
224
+                    [null, null, IQueryBuilder::PARAM_INT_ARRAY]
225
+                );
226
+                while ($row = $result->fetch()) {
227
+                    $objId = (int)$row['objid'];
228
+                    if (!isset($entries[$objId])) {
229
+                        $entries[$objId] = [];
230
+                    }
231
+                    $entries[$objId][] = $row['category'];
232
+                }
233
+                if ($result === null) {
234
+                    \OCP\Util::writeLog('core', __METHOD__. 'DB error: ' . \OC::$server->getDatabaseConnection()->getError(), ILogger::ERROR);
235
+                    return false;
236
+                }
237
+            }
238
+        } catch (\Exception $e) {
239
+            \OC::$server->getLogger()->logException($e, [
240
+                'message' => __METHOD__,
241
+                'level' => ILogger::ERROR,
242
+                'app' => 'core',
243
+            ]);
244
+            return false;
245
+        }
246
+
247
+        return $entries;
248
+    }
249
+
250
+    /**
251
+     * Get the a list if items tagged with $tag.
252
+     *
253
+     * Throws an exception if the tag could not be found.
254
+     *
255
+     * @param string $tag Tag id or name.
256
+     * @return array|false An array of object ids or false on error.
257
+     * @throws \Exception
258
+     */
259
+    public function getIdsForTag($tag) {
260
+        $result = null;
261
+        $tagId = false;
262
+        if (is_numeric($tag)) {
263
+            $tagId = $tag;
264
+        } elseif (is_string($tag)) {
265
+            $tag = trim($tag);
266
+            if ($tag === '') {
267
+                \OCP\Util::writeLog('core', __METHOD__.', Cannot use empty tag names', ILogger::DEBUG);
268
+                return false;
269
+            }
270
+            $tagId = $this->getTagId($tag);
271
+        }
272
+
273
+        if ($tagId === false) {
274
+            $l10n = \OC::$server->getL10N('core');
275
+            throw new \Exception(
276
+                $l10n->t('Could not find category "%s"', [$tag])
277
+            );
278
+        }
279
+
280
+        $ids = [];
281
+        $sql = 'SELECT `objid` FROM `' . self::RELATION_TABLE
282
+            . '` WHERE `categoryid` = ?';
283
+
284
+        try {
285
+            $stmt = \OC_DB::prepare($sql);
286
+            $result = $stmt->execute([$tagId]);
287
+            if ($result === null) {
288
+                \OCP\Util::writeLog('core', __METHOD__. 'DB error: ' . \OC::$server->getDatabaseConnection()->getError(), ILogger::ERROR);
289
+                return false;
290
+            }
291
+        } catch (\Exception $e) {
292
+            \OC::$server->getLogger()->logException($e, [
293
+                'message' => __METHOD__,
294
+                'level' => ILogger::ERROR,
295
+                'app' => 'core',
296
+            ]);
297
+            return false;
298
+        }
299
+
300
+        if (!is_null($result)) {
301
+            while ($row = $result->fetchRow()) {
302
+                $ids[] = (int)$row['objid'];
303
+            }
304
+        }
305
+
306
+        return $ids;
307
+    }
308
+
309
+    /**
310
+     * Checks whether a tag is saved for the given user,
311
+     * disregarding the ones shared with him or her.
312
+     *
313
+     * @param string $name The tag name to check for.
314
+     * @param string $user The user whose tags are to be checked.
315
+     * @return bool
316
+     */
317
+    public function userHasTag($name, $user) {
318
+        $key = $this->array_searchi($name, $this->getTagsForUser($user));
319
+        return ($key !== false) ? $this->tags[$key]->getId() : false;
320
+    }
321
+
322
+    /**
323
+     * Checks whether a tag is saved for or shared with the current user.
324
+     *
325
+     * @param string $name The tag name to check for.
326
+     * @return bool
327
+     */
328
+    public function hasTag($name) {
329
+        return $this->getTagId($name) !== false;
330
+    }
331
+
332
+    /**
333
+     * Add a new tag.
334
+     *
335
+     * @param string $name A string with a name of the tag
336
+     * @return false|int the id of the added tag or false on error.
337
+     */
338
+    public function add($name) {
339
+        $name = trim($name);
340
+
341
+        if ($name === '') {
342
+            \OCP\Util::writeLog('core', __METHOD__.', Cannot add an empty tag', ILogger::DEBUG);
343
+            return false;
344
+        }
345
+        if ($this->userHasTag($name, $this->user)) {
346
+            \OCP\Util::writeLog('core', __METHOD__.', name: ' . $name. ' exists already', ILogger::DEBUG);
347
+            return false;
348
+        }
349
+        try {
350
+            $tag = new Tag($this->user, $this->type, $name);
351
+            $tag = $this->mapper->insert($tag);
352
+            $this->tags[] = $tag;
353
+        } catch (\Exception $e) {
354
+            \OC::$server->getLogger()->logException($e, [
355
+                'message' => __METHOD__,
356
+                'level' => ILogger::ERROR,
357
+                'app' => 'core',
358
+            ]);
359
+            return false;
360
+        }
361
+        \OCP\Util::writeLog('core', __METHOD__.', id: ' . $tag->getId(), ILogger::DEBUG);
362
+        return $tag->getId();
363
+    }
364
+
365
+    /**
366
+     * Rename tag.
367
+     *
368
+     * @param string|integer $from The name or ID of the existing tag
369
+     * @param string $to The new name of the tag.
370
+     * @return bool
371
+     */
372
+    public function rename($from, $to) {
373
+        $from = trim($from);
374
+        $to = trim($to);
375
+
376
+        if ($to === '' || $from === '') {
377
+            \OCP\Util::writeLog('core', __METHOD__.', Cannot use empty tag names', ILogger::DEBUG);
378
+            return false;
379
+        }
380
+
381
+        if (is_numeric($from)) {
382
+            $key = $this->getTagById($from);
383
+        } else {
384
+            $key = $this->getTagByName($from);
385
+        }
386
+        if ($key === false) {
387
+            \OCP\Util::writeLog('core', __METHOD__.', tag: ' . $from. ' does not exist', ILogger::DEBUG);
388
+            return false;
389
+        }
390
+        $tag = $this->tags[$key];
391
+
392
+        if ($this->userHasTag($to, $tag->getOwner())) {
393
+            \OCP\Util::writeLog('core', __METHOD__.', A tag named ' . $to. ' already exists for user ' . $tag->getOwner() . '.', ILogger::DEBUG);
394
+            return false;
395
+        }
396
+
397
+        try {
398
+            $tag->setName($to);
399
+            $this->tags[$key] = $this->mapper->update($tag);
400
+        } catch (\Exception $e) {
401
+            \OC::$server->getLogger()->logException($e, [
402
+                'message' => __METHOD__,
403
+                'level' => ILogger::ERROR,
404
+                'app' => 'core',
405
+            ]);
406
+            return false;
407
+        }
408
+        return true;
409
+    }
410
+
411
+    /**
412
+     * Add a list of new tags.
413
+     *
414
+     * @param string[] $names A string with a name or an array of strings containing
415
+     * the name(s) of the tag(s) to add.
416
+     * @param bool $sync When true, save the tags
417
+     * @param int|null $id int Optional object id to add to this|these tag(s)
418
+     * @return bool Returns false on error.
419
+     */
420
+    public function addMultiple($names, $sync = false, $id = null) {
421
+        if (!is_array($names)) {
422
+            $names = [$names];
423
+        }
424
+        $names = array_map('trim', $names);
425
+        array_filter($names);
426
+
427
+        $newones = [];
428
+        foreach ($names as $name) {
429
+            if (!$this->hasTag($name) && $name !== '') {
430
+                $newones[] = new Tag($this->user, $this->type, $name);
431
+            }
432
+            if (!is_null($id)) {
433
+                // Insert $objectid, $categoryid  pairs if not exist.
434
+                self::$relations[] = ['objid' => $id, 'tag' => $name];
435
+            }
436
+        }
437
+        $this->tags = array_merge($this->tags, $newones);
438
+        if ($sync === true) {
439
+            $this->save();
440
+        }
441
+
442
+        return true;
443
+    }
444
+
445
+    /**
446
+     * Save the list of tags and their object relations
447
+     */
448
+    protected function save() {
449
+        if (is_array($this->tags)) {
450
+            foreach ($this->tags as $tag) {
451
+                try {
452
+                    if (!$this->mapper->tagExists($tag)) {
453
+                        $this->mapper->insert($tag);
454
+                    }
455
+                } catch (\Exception $e) {
456
+                    \OC::$server->getLogger()->logException($e, [
457
+                        'message' => __METHOD__,
458
+                        'level' => ILogger::ERROR,
459
+                        'app' => 'core',
460
+                    ]);
461
+                }
462
+            }
463
+
464
+            // reload tags to get the proper ids.
465
+            $this->tags = $this->mapper->loadTags($this->owners, $this->type);
466
+            \OCP\Util::writeLog('core', __METHOD__.', tags: ' . print_r($this->tags, true),
467
+                ILogger::DEBUG);
468
+            // Loop through temporarily cached objectid/tagname pairs
469
+            // and save relations.
470
+            $tags = $this->tags;
471
+            // For some reason this is needed or array_search(i) will return 0..?
472
+            ksort($tags);
473
+            $dbConnection = \OC::$server->getDatabaseConnection();
474
+            foreach (self::$relations as $relation) {
475
+                $tagId = $this->getTagId($relation['tag']);
476
+                \OCP\Util::writeLog('core', __METHOD__ . 'catid, ' . $relation['tag'] . ' ' . $tagId, ILogger::DEBUG);
477
+                if ($tagId) {
478
+                    try {
479
+                        $dbConnection->insertIfNotExist(self::RELATION_TABLE,
480
+                            [
481
+                                'objid' => $relation['objid'],
482
+                                'categoryid' => $tagId,
483
+                                'type' => $this->type,
484
+                            ]);
485
+                    } catch (\Exception $e) {
486
+                        \OC::$server->getLogger()->logException($e, [
487
+                            'message' => __METHOD__,
488
+                            'level' => ILogger::ERROR,
489
+                            'app' => 'core',
490
+                        ]);
491
+                    }
492
+                }
493
+            }
494
+            self::$relations = []; // reset
495
+        } else {
496
+            \OCP\Util::writeLog('core', __METHOD__.', $this->tags is not an array! '
497
+                . print_r($this->tags, true), ILogger::ERROR);
498
+        }
499
+    }
500
+
501
+    /**
502
+     * Delete tags and tag/object relations for a user.
503
+     *
504
+     * For hooking up on post_deleteUser
505
+     *
506
+     * @param array $arguments
507
+     */
508
+    public static function post_deleteUser($arguments) {
509
+        // Find all objectid/tagId pairs.
510
+        $result = null;
511
+        try {
512
+            $stmt = \OC_DB::prepare('SELECT `id` FROM `' . self::TAG_TABLE . '` '
513
+                . 'WHERE `uid` = ?');
514
+            $result = $stmt->execute([$arguments['uid']]);
515
+            if ($result === null) {
516
+                \OCP\Util::writeLog('core', __METHOD__. 'DB error: ' . \OC::$server->getDatabaseConnection()->getError(), ILogger::ERROR);
517
+            }
518
+        } catch (\Exception $e) {
519
+            \OC::$server->getLogger()->logException($e, [
520
+                'message' => __METHOD__,
521
+                'level' => ILogger::ERROR,
522
+                'app' => 'core',
523
+            ]);
524
+        }
525
+
526
+        if (!is_null($result)) {
527
+            try {
528
+                $stmt = \OC_DB::prepare('DELETE FROM `' . self::RELATION_TABLE . '` '
529
+                    . 'WHERE `categoryid` = ?');
530
+                while ($row = $result->fetchRow()) {
531
+                    try {
532
+                        $stmt->execute([$row['id']]);
533
+                    } catch (\Exception $e) {
534
+                        \OC::$server->getLogger()->logException($e, [
535
+                            'message' => __METHOD__,
536
+                            'level' => ILogger::ERROR,
537
+                            'app' => 'core',
538
+                        ]);
539
+                    }
540
+                }
541
+            } catch (\Exception $e) {
542
+                \OC::$server->getLogger()->logException($e, [
543
+                    'message' => __METHOD__,
544
+                    'level' => ILogger::ERROR,
545
+                    'app' => 'core',
546
+                ]);
547
+            }
548
+        }
549
+        try {
550
+            $stmt = \OC_DB::prepare('DELETE FROM `' . self::TAG_TABLE . '` '
551
+                . 'WHERE `uid` = ?');
552
+            $result = $stmt->execute([$arguments['uid']]);
553
+            if ($result === null) {
554
+                \OCP\Util::writeLog('core', __METHOD__. ', DB error: ' . \OC::$server->getDatabaseConnection()->getError(), ILogger::ERROR);
555
+            }
556
+        } catch (\Exception $e) {
557
+            \OC::$server->getLogger()->logException($e, [
558
+                'message' => __METHOD__,
559
+                'level' => ILogger::ERROR,
560
+                'app' => 'core',
561
+            ]);
562
+        }
563
+    }
564
+
565
+    /**
566
+     * Delete tag/object relations from the db
567
+     *
568
+     * @param array $ids The ids of the objects
569
+     * @return boolean Returns false on error.
570
+     */
571
+    public function purgeObjects(array $ids) {
572
+        if (count($ids) === 0) {
573
+            // job done ;)
574
+            return true;
575
+        }
576
+        $updates = $ids;
577
+        try {
578
+            $query = 'DELETE FROM `' . self::RELATION_TABLE . '` ';
579
+            $query .= 'WHERE `objid` IN (' . str_repeat('?,', count($ids) - 1) . '?) ';
580
+            $query .= 'AND `type`= ?';
581
+            $updates[] = $this->type;
582
+            $stmt = \OC_DB::prepare($query);
583
+            $result = $stmt->execute($updates);
584
+            if ($result === null) {
585
+                \OCP\Util::writeLog('core', __METHOD__. 'DB error: ' . \OC::$server->getDatabaseConnection()->getError(), ILogger::ERROR);
586
+                return false;
587
+            }
588
+        } catch (\Exception $e) {
589
+            \OC::$server->getLogger()->logException($e, [
590
+                'message' => __METHOD__,
591
+                'level' => ILogger::ERROR,
592
+                'app' => 'core',
593
+            ]);
594
+            return false;
595
+        }
596
+        return true;
597
+    }
598
+
599
+    /**
600
+     * Get favorites for an object type
601
+     *
602
+     * @return array|false An array of object ids.
603
+     */
604
+    public function getFavorites() {
605
+        if (!$this->userHasTag(ITags::TAG_FAVORITE, $this->user)) {
606
+            return [];
607
+        }
608
+
609
+        try {
610
+            return $this->getIdsForTag(ITags::TAG_FAVORITE);
611
+        } catch (\Exception $e) {
612
+            \OC::$server->getLogger()->logException($e, [
613
+                'message' => __METHOD__,
614
+                'level' => ILogger::ERROR,
615
+                'app' => 'core',
616
+            ]);
617
+            return [];
618
+        }
619
+    }
620
+
621
+    /**
622
+     * Add an object to favorites
623
+     *
624
+     * @param int $objid The id of the object
625
+     * @return boolean
626
+     */
627
+    public function addToFavorites($objid) {
628
+        if (!$this->userHasTag(ITags::TAG_FAVORITE, $this->user)) {
629
+            $this->add(ITags::TAG_FAVORITE);
630
+        }
631
+        return $this->tagAs($objid, ITags::TAG_FAVORITE);
632
+    }
633
+
634
+    /**
635
+     * Remove an object from favorites
636
+     *
637
+     * @param int $objid The id of the object
638
+     * @return boolean
639
+     */
640
+    public function removeFromFavorites($objid) {
641
+        return $this->unTag($objid, ITags::TAG_FAVORITE);
642
+    }
643
+
644
+    /**
645
+     * Creates a tag/object relation.
646
+     *
647
+     * @param int $objid The id of the object
648
+     * @param string $tag The id or name of the tag
649
+     * @return boolean Returns false on error.
650
+     */
651
+    public function tagAs($objid, $tag) {
652
+        if (is_string($tag) && !is_numeric($tag)) {
653
+            $tag = trim($tag);
654
+            if ($tag === '') {
655
+                \OCP\Util::writeLog('core', __METHOD__.', Cannot add an empty tag', ILogger::DEBUG);
656
+                return false;
657
+            }
658
+            if (!$this->hasTag($tag)) {
659
+                $this->add($tag);
660
+            }
661
+            $tagId = $this->getTagId($tag);
662
+        } else {
663
+            $tagId = $tag;
664
+        }
665
+        try {
666
+            \OC::$server->getDatabaseConnection()->insertIfNotExist(self::RELATION_TABLE,
667
+                [
668
+                    'objid' => $objid,
669
+                    'categoryid' => $tagId,
670
+                    'type' => $this->type,
671
+                ]);
672
+        } catch (\Exception $e) {
673
+            \OC::$server->getLogger()->logException($e, [
674
+                'message' => __METHOD__,
675
+                'level' => ILogger::ERROR,
676
+                'app' => 'core',
677
+            ]);
678
+            return false;
679
+        }
680
+        return true;
681
+    }
682
+
683
+    /**
684
+     * Delete single tag/object relation from the db
685
+     *
686
+     * @param int $objid The id of the object
687
+     * @param string $tag The id or name of the tag
688
+     * @return boolean
689
+     */
690
+    public function unTag($objid, $tag) {
691
+        if (is_string($tag) && !is_numeric($tag)) {
692
+            $tag = trim($tag);
693
+            if ($tag === '') {
694
+                \OCP\Util::writeLog('core', __METHOD__.', Tag name is empty', ILogger::DEBUG);
695
+                return false;
696
+            }
697
+            $tagId = $this->getTagId($tag);
698
+        } else {
699
+            $tagId = $tag;
700
+        }
701
+
702
+        try {
703
+            $sql = 'DELETE FROM `' . self::RELATION_TABLE . '` '
704
+                    . 'WHERE `objid` = ? AND `categoryid` = ? AND `type` = ?';
705
+            $stmt = \OC_DB::prepare($sql);
706
+            $stmt->execute([$objid, $tagId, $this->type]);
707
+        } catch (\Exception $e) {
708
+            \OC::$server->getLogger()->logException($e, [
709
+                'message' => __METHOD__,
710
+                'level' => ILogger::ERROR,
711
+                'app' => 'core',
712
+            ]);
713
+            return false;
714
+        }
715
+        return true;
716
+    }
717
+
718
+    /**
719
+     * Delete tags from the database.
720
+     *
721
+     * @param string[]|integer[] $names An array of tags (names or IDs) to delete
722
+     * @return bool Returns false on error
723
+     */
724
+    public function delete($names) {
725
+        if (!is_array($names)) {
726
+            $names = [$names];
727
+        }
728
+
729
+        $names = array_map('trim', $names);
730
+        array_filter($names);
731
+
732
+        \OCP\Util::writeLog('core', __METHOD__ . ', before: '
733
+            . print_r($this->tags, true), ILogger::DEBUG);
734
+        foreach ($names as $name) {
735
+            $id = null;
736
+
737
+            if (is_numeric($name)) {
738
+                $key = $this->getTagById($name);
739
+            } else {
740
+                $key = $this->getTagByName($name);
741
+            }
742
+            if ($key !== false) {
743
+                $tag = $this->tags[$key];
744
+                $id = $tag->getId();
745
+                unset($this->tags[$key]);
746
+                $this->mapper->delete($tag);
747
+            } else {
748
+                \OCP\Util::writeLog('core', __METHOD__ . 'Cannot delete tag ' . $name
749
+                    . ': not found.', ILogger::ERROR);
750
+            }
751
+            if (!is_null($id) && $id !== false) {
752
+                try {
753
+                    $sql = 'DELETE FROM `' . self::RELATION_TABLE . '` '
754
+                            . 'WHERE `categoryid` = ?';
755
+                    $stmt = \OC_DB::prepare($sql);
756
+                    $result = $stmt->execute([$id]);
757
+                    if ($result === null) {
758
+                        \OCP\Util::writeLog('core',
759
+                            __METHOD__. 'DB error: ' . \OC::$server->getDatabaseConnection()->getError(),
760
+                            ILogger::ERROR);
761
+                        return false;
762
+                    }
763
+                } catch (\Exception $e) {
764
+                    \OC::$server->getLogger()->logException($e, [
765
+                        'message' => __METHOD__,
766
+                        'level' => ILogger::ERROR,
767
+                        'app' => 'core',
768
+                    ]);
769
+                    return false;
770
+                }
771
+            }
772
+        }
773
+        return true;
774
+    }
775
+
776
+    // case-insensitive array_search
777
+    protected function array_searchi($needle, $haystack, $mem = 'getName') {
778
+        if (!is_array($haystack)) {
779
+            return false;
780
+        }
781
+        return array_search(strtolower($needle), array_map(
782
+            function ($tag) use ($mem) {
783
+                return strtolower(call_user_func([$tag, $mem]));
784
+            }, $haystack)
785
+        );
786
+    }
787
+
788
+    /**
789
+     * Get a tag's ID.
790
+     *
791
+     * @param string $name The tag name to look for.
792
+     * @return string|bool The tag's id or false if no matching tag is found.
793
+     */
794
+    private function getTagId($name) {
795
+        $key = $this->array_searchi($name, $this->tags);
796
+        if ($key !== false) {
797
+            return $this->tags[$key]->getId();
798
+        }
799
+        return false;
800
+    }
801
+
802
+    /**
803
+     * Get a tag by its name.
804
+     *
805
+     * @param string $name The tag name.
806
+     * @return integer|bool The tag object's offset within the $this->tags
807
+     *                      array or false if it doesn't exist.
808
+     */
809
+    private function getTagByName($name) {
810
+        return $this->array_searchi($name, $this->tags, 'getName');
811
+    }
812
+
813
+    /**
814
+     * Get a tag by its ID.
815
+     *
816
+     * @param string $id The tag ID to look for.
817
+     * @return integer|bool The tag object's offset within the $this->tags
818
+     *                      array or false if it doesn't exist.
819
+     */
820
+    private function getTagById($id) {
821
+        return $this->array_searchi($id, $this->tags, 'getId');
822
+    }
823
+
824
+    /**
825
+     * Returns an array mapping a given tag's properties to its values:
826
+     * ['id' => 0, 'name' = 'Tag', 'owner' = 'User', 'type' => 'tagtype']
827
+     *
828
+     * @param Tag $tag The tag that is going to be mapped
829
+     * @return array
830
+     */
831
+    private function tagMap(Tag $tag) {
832
+        return [
833
+            'id' => $tag->getId(),
834
+            'name' => $tag->getName(),
835
+            'owner' => $tag->getOwner(),
836
+            'type' => $tag->getType()
837
+        ];
838
+    }
839 839
 }
Please login to merge, or discard this patch.