Completed
Push — master ( fdd22c...c37488 )
by
unknown
21:37 queued 11s
created
lib/public/ITags.php 1 patch
Indentation   +188 added lines, -188 removed lines patch added patch discarded remove patch
@@ -25,192 +25,192 @@
 block discarded – undo
25 25
  */
26 26
 
27 27
 interface ITags {
28
-	/**
29
-	 * @since 19.0.0
30
-	 */
31
-	public const TAG_FAVORITE = '_$!<Favorite>!$_';
32
-
33
-	/**
34
-	 * Check if any tags are saved for this type and user.
35
-	 * @since 6.0.0
36
-	 */
37
-	public function isEmpty(): bool;
38
-
39
-	/**
40
-	 * Returns an array mapping a given tag's properties to its values:
41
-	 * ['id' => 0, 'name' = 'Tag', 'owner' = 'User', 'type' => 'tagtype']
42
-	 *
43
-	 * @param string $id The ID of the tag that is going to be mapped
44
-	 * @return array|false
45
-	 * @since 8.0.0
46
-	 */
47
-	public function getTag(string $id);
48
-
49
-	/**
50
-	 * Get the tags for a specific user.
51
-	 *
52
-	 * This returns an array with id/name maps:
53
-	 *
54
-	 * ```php
55
-	 * [
56
-	 * 	['id' => 0, 'name' = 'First tag'],
57
-	 * 	['id' => 1, 'name' = 'Second tag'],
58
-	 * ]
59
-	 * ```
60
-	 *
61
-	 * @return array<array-key, array{id: int, name: string}>
62
-	 * @since 6.0.0
63
-	 */
64
-	public function getTags(): array;
65
-
66
-	/**
67
-	 * Get a list of tags for the given item ids.
68
-	 *
69
-	 * This returns an array with object id / tag names:
70
-	 *
71
-	 * ```php
72
-	 * [
73
-	 *   1 => array('First tag', 'Second tag'),
74
-	 *   2 => array('Second tag'),
75
-	 *   3 => array('Second tag', 'Third tag'),
76
-	 * ]
77
-	 * ```
78
-	 *
79
-	 * @param list<int> $objIds item ids
80
-	 * @return array<int, list<string>>|false with object id as key and an array
81
-	 *                                        of tag names as value or false if an error occurred
82
-	 * @since 8.0.0
83
-	 */
84
-	public function getTagsForObjects(array $objIds);
85
-
86
-	/**
87
-	 * Get a list of items tagged with $tag.
88
-	 *
89
-	 * Throws an exception if the tag could not be found.
90
-	 *
91
-	 * @param string|integer $tag Tag id or name.
92
-	 * @return array|false An array of object ids or false on error.
93
-	 * @since 6.0.0
94
-	 */
95
-	public function getIdsForTag($tag);
96
-
97
-	/**
98
-	 * Checks whether a tag is already saved.
99
-	 *
100
-	 * @param string $name The name to check for.
101
-	 * @since 6.0.0
102
-	 */
103
-	public function hasTag(string $name): bool;
104
-
105
-	/**
106
-	 * Checks whether a tag is saved for the given user,
107
-	 * disregarding the ones shared with them.
108
-	 *
109
-	 * @param string $name The tag name to check for.
110
-	 * @param string $user The user whose tags are to be checked.
111
-	 * @return bool
112
-	 * @since 8.0.0
113
-	 */
114
-	public function userHasTag(string $name, string $user): bool;
115
-
116
-	/**
117
-	 * Add a new tag.
118
-	 *
119
-	 * @param string $name A string with a name of the tag
120
-	 * @return int|false the id of the added tag or false if it already exists.
121
-	 * @since 6.0.0
122
-	 */
123
-	public function add(string $name);
124
-
125
-	/**
126
-	 * Rename tag.
127
-	 *
128
-	 * @param string|integer $from The name or ID of the existing tag
129
-	 * @param string $to The new name of the tag.
130
-	 * @return bool
131
-	 * @since 6.0.0
132
-	 */
133
-	public function rename($from, string $to): bool;
134
-
135
-	/**
136
-	 * Add a list of new tags.
137
-	 *
138
-	 * @param string|string[] $names A string with a name or an array of strings containing
139
-	 *                               the name(s) of the to add.
140
-	 * @param bool $sync When true, save the tags
141
-	 * @param int|null $id int Optional object id to add to this|these tag(s)
142
-	 * @return bool Returns false on error.
143
-	 * @since 6.0.0
144
-	 */
145
-	public function addMultiple($names, bool $sync = false, ?int $id = null): bool;
146
-
147
-	/**
148
-	 * Delete tag/object relations from the db
149
-	 *
150
-	 * @param array $ids The ids of the objects
151
-	 * @return boolean Returns false on error.
152
-	 * @since 6.0.0
153
-	 */
154
-	public function purgeObjects(array $ids);
155
-
156
-	/**
157
-	 * Get favorites for an object type
158
-	 *
159
-	 * @return array|false An array of object ids.
160
-	 * @since 6.0.0
161
-	 */
162
-	public function getFavorites();
163
-
164
-	/**
165
-	 * Add an object to favorites
166
-	 *
167
-	 * @param int $objid The id of the object
168
-	 * @return boolean
169
-	 * @since 6.0.0
170
-	 */
171
-	public function addToFavorites($objid);
172
-
173
-	/**
174
-	 * Remove an object from favorites
175
-	 *
176
-	 * @param int $objid The id of the object
177
-	 * @return boolean
178
-	 * @since 6.0.0
179
-	 */
180
-	public function removeFromFavorites($objid);
181
-
182
-	/**
183
-	 * Creates a tag/object relation.
184
-	 *
185
-	 * @param int $objid The id of the object
186
-	 * @param string $tag The id or name of the tag
187
-	 * @param string $path The optional path of the node. Used when dispatching the favorite change event.
188
-	 * @return boolean Returns false on database error.
189
-	 * @since 6.0.0
190
-	 * @since 31.0.0 Added the $path argument.
191
-	 * @since 33.0.0 Change $path default value from '' to null.
192
-	 */
193
-	public function tagAs($objid, $tag, ?string $path = null);
194
-
195
-	/**
196
-	 * Delete single tag/object relation from the db
197
-	 *
198
-	 * @param int $objid The id of the object
199
-	 * @param string $tag The id or name of the tag
200
-	 * @param string $path The optional path of the node. Used when dispatching the favorite change event.
201
-	 * @return boolean
202
-	 * @since 6.0.0
203
-	 * @since 31.0.0 Added the $path argument.
204
-	 * @since 33.0.0 Change $path default value from '' to null.
205
-	 */
206
-	public function unTag($objid, $tag, ?string $path = null);
207
-
208
-	/**
209
-	 * Delete tags from the database
210
-	 *
211
-	 * @param string[]|integer[] $names An array of tags (names or IDs) to delete
212
-	 * @return bool Returns false on error
213
-	 * @since 6.0.0
214
-	 */
215
-	public function delete($names);
28
+    /**
29
+     * @since 19.0.0
30
+     */
31
+    public const TAG_FAVORITE = '_$!<Favorite>!$_';
32
+
33
+    /**
34
+     * Check if any tags are saved for this type and user.
35
+     * @since 6.0.0
36
+     */
37
+    public function isEmpty(): bool;
38
+
39
+    /**
40
+     * Returns an array mapping a given tag's properties to its values:
41
+     * ['id' => 0, 'name' = 'Tag', 'owner' = 'User', 'type' => 'tagtype']
42
+     *
43
+     * @param string $id The ID of the tag that is going to be mapped
44
+     * @return array|false
45
+     * @since 8.0.0
46
+     */
47
+    public function getTag(string $id);
48
+
49
+    /**
50
+     * Get the tags for a specific user.
51
+     *
52
+     * This returns an array with id/name maps:
53
+     *
54
+     * ```php
55
+     * [
56
+     * 	['id' => 0, 'name' = 'First tag'],
57
+     * 	['id' => 1, 'name' = 'Second tag'],
58
+     * ]
59
+     * ```
60
+     *
61
+     * @return array<array-key, array{id: int, name: string}>
62
+     * @since 6.0.0
63
+     */
64
+    public function getTags(): array;
65
+
66
+    /**
67
+     * Get a list of tags for the given item ids.
68
+     *
69
+     * This returns an array with object id / tag names:
70
+     *
71
+     * ```php
72
+     * [
73
+     *   1 => array('First tag', 'Second tag'),
74
+     *   2 => array('Second tag'),
75
+     *   3 => array('Second tag', 'Third tag'),
76
+     * ]
77
+     * ```
78
+     *
79
+     * @param list<int> $objIds item ids
80
+     * @return array<int, list<string>>|false with object id as key and an array
81
+     *                                        of tag names as value or false if an error occurred
82
+     * @since 8.0.0
83
+     */
84
+    public function getTagsForObjects(array $objIds);
85
+
86
+    /**
87
+     * Get a list of items tagged with $tag.
88
+     *
89
+     * Throws an exception if the tag could not be found.
90
+     *
91
+     * @param string|integer $tag Tag id or name.
92
+     * @return array|false An array of object ids or false on error.
93
+     * @since 6.0.0
94
+     */
95
+    public function getIdsForTag($tag);
96
+
97
+    /**
98
+     * Checks whether a tag is already saved.
99
+     *
100
+     * @param string $name The name to check for.
101
+     * @since 6.0.0
102
+     */
103
+    public function hasTag(string $name): bool;
104
+
105
+    /**
106
+     * Checks whether a tag is saved for the given user,
107
+     * disregarding the ones shared with them.
108
+     *
109
+     * @param string $name The tag name to check for.
110
+     * @param string $user The user whose tags are to be checked.
111
+     * @return bool
112
+     * @since 8.0.0
113
+     */
114
+    public function userHasTag(string $name, string $user): bool;
115
+
116
+    /**
117
+     * Add a new tag.
118
+     *
119
+     * @param string $name A string with a name of the tag
120
+     * @return int|false the id of the added tag or false if it already exists.
121
+     * @since 6.0.0
122
+     */
123
+    public function add(string $name);
124
+
125
+    /**
126
+     * Rename tag.
127
+     *
128
+     * @param string|integer $from The name or ID of the existing tag
129
+     * @param string $to The new name of the tag.
130
+     * @return bool
131
+     * @since 6.0.0
132
+     */
133
+    public function rename($from, string $to): bool;
134
+
135
+    /**
136
+     * Add a list of new tags.
137
+     *
138
+     * @param string|string[] $names A string with a name or an array of strings containing
139
+     *                               the name(s) of the to add.
140
+     * @param bool $sync When true, save the tags
141
+     * @param int|null $id int Optional object id to add to this|these tag(s)
142
+     * @return bool Returns false on error.
143
+     * @since 6.0.0
144
+     */
145
+    public function addMultiple($names, bool $sync = false, ?int $id = null): bool;
146
+
147
+    /**
148
+     * Delete tag/object relations from the db
149
+     *
150
+     * @param array $ids The ids of the objects
151
+     * @return boolean Returns false on error.
152
+     * @since 6.0.0
153
+     */
154
+    public function purgeObjects(array $ids);
155
+
156
+    /**
157
+     * Get favorites for an object type
158
+     *
159
+     * @return array|false An array of object ids.
160
+     * @since 6.0.0
161
+     */
162
+    public function getFavorites();
163
+
164
+    /**
165
+     * Add an object to favorites
166
+     *
167
+     * @param int $objid The id of the object
168
+     * @return boolean
169
+     * @since 6.0.0
170
+     */
171
+    public function addToFavorites($objid);
172
+
173
+    /**
174
+     * Remove an object from favorites
175
+     *
176
+     * @param int $objid The id of the object
177
+     * @return boolean
178
+     * @since 6.0.0
179
+     */
180
+    public function removeFromFavorites($objid);
181
+
182
+    /**
183
+     * Creates a tag/object relation.
184
+     *
185
+     * @param int $objid The id of the object
186
+     * @param string $tag The id or name of the tag
187
+     * @param string $path The optional path of the node. Used when dispatching the favorite change event.
188
+     * @return boolean Returns false on database error.
189
+     * @since 6.0.0
190
+     * @since 31.0.0 Added the $path argument.
191
+     * @since 33.0.0 Change $path default value from '' to null.
192
+     */
193
+    public function tagAs($objid, $tag, ?string $path = null);
194
+
195
+    /**
196
+     * Delete single tag/object relation from the db
197
+     *
198
+     * @param int $objid The id of the object
199
+     * @param string $tag The id or name of the tag
200
+     * @param string $path The optional path of the node. Used when dispatching the favorite change event.
201
+     * @return boolean
202
+     * @since 6.0.0
203
+     * @since 31.0.0 Added the $path argument.
204
+     * @since 33.0.0 Change $path default value from '' to null.
205
+     */
206
+    public function unTag($objid, $tag, ?string $path = null);
207
+
208
+    /**
209
+     * Delete tags from the database
210
+     *
211
+     * @param string[]|integer[] $names An array of tags (names or IDs) to delete
212
+     * @return bool Returns false on error
213
+     * @since 6.0.0
214
+     */
215
+    public function delete($names);
216 216
 }
Please login to merge, or discard this patch.
lib/private/TagManager.php 1 patch
Indentation   +100 added lines, -100 removed lines patch added patch discarded remove patch
@@ -26,115 +26,115 @@
 block discarded – undo
26 26
  */
27 27
 class TagManager implements ITagManager, IEventListener {
28 28
 
29
-	public function __construct(
30
-		private TagMapper $mapper,
31
-		private IUserSession $userSession,
32
-		private IDBConnection $connection,
33
-		private LoggerInterface $logger,
34
-		private IEventDispatcher $dispatcher,
35
-		private IRootFolder $rootFolder,
36
-	) {
37
-	}
29
+    public function __construct(
30
+        private TagMapper $mapper,
31
+        private IUserSession $userSession,
32
+        private IDBConnection $connection,
33
+        private LoggerInterface $logger,
34
+        private IEventDispatcher $dispatcher,
35
+        private IRootFolder $rootFolder,
36
+    ) {
37
+    }
38 38
 
39
-	/**
40
-	 * Create a new \OCP\ITags instance and load tags from db.
41
-	 *
42
-	 * @see \OCP\ITags
43
-	 * @param string $type The type identifier e.g. 'contact' or 'event'.
44
-	 * @param array $defaultTags An array of default tags to be used if none are stored.
45
-	 * @param boolean $includeShared Whether to include tags for items shared with this user by others.
46
-	 * @param string $userId user for which to retrieve the tags, defaults to the currently
47
-	 *                       logged in user
48
-	 * @return \OCP\ITags
49
-	 *
50
-	 * since 20.0.0 $includeShared isn't used anymore
51
-	 */
52
-	public function load($type, $defaultTags = [], $includeShared = false, $userId = null) {
53
-		if (is_null($userId)) {
54
-			$user = $this->userSession->getUser();
55
-			if ($user === null) {
56
-				// nothing we can do without a user
57
-				return null;
58
-			}
59
-			$userId = $this->userSession->getUser()->getUId();
60
-		}
61
-		$userFolder = $this->rootFolder->getUserFolder($userId);
62
-		return new Tags($this->mapper, $userId, $type, $this->logger, $this->connection, $this->dispatcher, $this->userSession, $userFolder, $defaultTags);
63
-	}
39
+    /**
40
+     * Create a new \OCP\ITags instance and load tags from db.
41
+     *
42
+     * @see \OCP\ITags
43
+     * @param string $type The type identifier e.g. 'contact' or 'event'.
44
+     * @param array $defaultTags An array of default tags to be used if none are stored.
45
+     * @param boolean $includeShared Whether to include tags for items shared with this user by others.
46
+     * @param string $userId user for which to retrieve the tags, defaults to the currently
47
+     *                       logged in user
48
+     * @return \OCP\ITags
49
+     *
50
+     * since 20.0.0 $includeShared isn't used anymore
51
+     */
52
+    public function load($type, $defaultTags = [], $includeShared = false, $userId = null) {
53
+        if (is_null($userId)) {
54
+            $user = $this->userSession->getUser();
55
+            if ($user === null) {
56
+                // nothing we can do without a user
57
+                return null;
58
+            }
59
+            $userId = $this->userSession->getUser()->getUId();
60
+        }
61
+        $userFolder = $this->rootFolder->getUserFolder($userId);
62
+        return new Tags($this->mapper, $userId, $type, $this->logger, $this->connection, $this->dispatcher, $this->userSession, $userFolder, $defaultTags);
63
+    }
64 64
 
65
-	/**
66
-	 * Get all users who favorited an object
67
-	 *
68
-	 * @param string $objectType
69
-	 * @param int $objectId
70
-	 * @return array
71
-	 */
72
-	public function getUsersFavoritingObject(string $objectType, int $objectId): array {
73
-		$query = $this->connection->getQueryBuilder();
74
-		$query->select('uid')
75
-			->from('vcategory_to_object', 'o')
76
-			->innerJoin('o', 'vcategory', 'c', $query->expr()->eq('o.categoryid', 'c.id'))
77
-			->where($query->expr()->eq('objid', $query->createNamedParameter($objectId, IQueryBuilder::PARAM_INT)))
78
-			->andWhere($query->expr()->eq('c.type', $query->createNamedParameter($objectType)))
79
-			->andWhere($query->expr()->eq('c.category', $query->createNamedParameter(ITags::TAG_FAVORITE)));
65
+    /**
66
+     * Get all users who favorited an object
67
+     *
68
+     * @param string $objectType
69
+     * @param int $objectId
70
+     * @return array
71
+     */
72
+    public function getUsersFavoritingObject(string $objectType, int $objectId): array {
73
+        $query = $this->connection->getQueryBuilder();
74
+        $query->select('uid')
75
+            ->from('vcategory_to_object', 'o')
76
+            ->innerJoin('o', 'vcategory', 'c', $query->expr()->eq('o.categoryid', 'c.id'))
77
+            ->where($query->expr()->eq('objid', $query->createNamedParameter($objectId, IQueryBuilder::PARAM_INT)))
78
+            ->andWhere($query->expr()->eq('c.type', $query->createNamedParameter($objectType)))
79
+            ->andWhere($query->expr()->eq('c.category', $query->createNamedParameter(ITags::TAG_FAVORITE)));
80 80
 
81
-		$result = $query->executeQuery();
82
-		$users = $result->fetchAll(\PDO::FETCH_COLUMN);
83
-		$result->closeCursor();
81
+        $result = $query->executeQuery();
82
+        $users = $result->fetchAll(\PDO::FETCH_COLUMN);
83
+        $result->closeCursor();
84 84
 
85
-		return $users;
86
-	}
85
+        return $users;
86
+    }
87 87
 
88
-	public function handle(Event $event): void {
89
-		if (!($event instanceof UserDeletedEvent)) {
90
-			return;
91
-		}
88
+    public function handle(Event $event): void {
89
+        if (!($event instanceof UserDeletedEvent)) {
90
+            return;
91
+        }
92 92
 
93
-		// Find all objectid/tagId pairs.
94
-		$user = $event->getUser();
95
-		$qb = $this->connection->getQueryBuilder();
96
-		$qb->select('id')
97
-			->from('vcategory')
98
-			->where($qb->expr()->eq('uid', $qb->createNamedParameter($user->getUID())));
99
-		try {
100
-			$result = $qb->executeQuery();
101
-		} catch (DBException $e) {
102
-			$this->logger->error($e->getMessage(), [
103
-				'app' => 'core',
104
-				'exception' => $e,
105
-			]);
106
-			return;
107
-		}
93
+        // Find all objectid/tagId pairs.
94
+        $user = $event->getUser();
95
+        $qb = $this->connection->getQueryBuilder();
96
+        $qb->select('id')
97
+            ->from('vcategory')
98
+            ->where($qb->expr()->eq('uid', $qb->createNamedParameter($user->getUID())));
99
+        try {
100
+            $result = $qb->executeQuery();
101
+        } catch (DBException $e) {
102
+            $this->logger->error($e->getMessage(), [
103
+                'app' => 'core',
104
+                'exception' => $e,
105
+            ]);
106
+            return;
107
+        }
108 108
 
109
-		$tagsIds = array_map(fn (array $row) => (int)$row['id'], $result->fetchAll());
110
-		$result->closeCursor();
109
+        $tagsIds = array_map(fn (array $row) => (int)$row['id'], $result->fetchAll());
110
+        $result->closeCursor();
111 111
 
112
-		if (count($tagsIds) === 0) {
113
-			return;
114
-		}
112
+        if (count($tagsIds) === 0) {
113
+            return;
114
+        }
115 115
 
116
-		// Clean vcategory_to_object table
117
-		$qb = $this->connection->getQueryBuilder();
118
-		$qb = $qb->delete('vcategory_to_object')
119
-			->where($qb->expr()->in('categoryid', $qb->createParameter('chunk')));
116
+        // Clean vcategory_to_object table
117
+        $qb = $this->connection->getQueryBuilder();
118
+        $qb = $qb->delete('vcategory_to_object')
119
+            ->where($qb->expr()->in('categoryid', $qb->createParameter('chunk')));
120 120
 
121
-		// Clean vcategory
122
-		$qb1 = $this->connection->getQueryBuilder();
123
-		$qb1 = $qb1->delete('vcategory')
124
-			->where($qb1->expr()->in('uid', $qb1->createParameter('chunk')));
121
+        // Clean vcategory
122
+        $qb1 = $this->connection->getQueryBuilder();
123
+        $qb1 = $qb1->delete('vcategory')
124
+            ->where($qb1->expr()->in('uid', $qb1->createParameter('chunk')));
125 125
 
126
-		foreach (array_chunk($tagsIds, 1000) as $tagChunk) {
127
-			$qb->setParameter('chunk', $tagChunk, IQueryBuilder::PARAM_INT_ARRAY);
128
-			$qb1->setParameter('chunk', $tagChunk, IQueryBuilder::PARAM_INT_ARRAY);
129
-			try {
130
-				$qb->executeStatement();
131
-				$qb1->executeStatement();
132
-			} catch (DBException $e) {
133
-				$this->logger->error($e->getMessage(), [
134
-					'app' => 'core',
135
-					'exception' => $e,
136
-				]);
137
-			}
138
-		}
139
-	}
126
+        foreach (array_chunk($tagsIds, 1000) as $tagChunk) {
127
+            $qb->setParameter('chunk', $tagChunk, IQueryBuilder::PARAM_INT_ARRAY);
128
+            $qb1->setParameter('chunk', $tagChunk, IQueryBuilder::PARAM_INT_ARRAY);
129
+            try {
130
+                $qb->executeStatement();
131
+                $qb1->executeStatement();
132
+            } catch (DBException $e) {
133
+                $this->logger->error($e->getMessage(), [
134
+                    'app' => 'core',
135
+                    'exception' => $e,
136
+                ]);
137
+            }
138
+        }
139
+    }
140 140
 }
Please login to merge, or discard this patch.
lib/private/Tags.php 2 patches
Indentation   +678 added lines, -678 removed lines patch added patch discarded remove patch
@@ -22,682 +22,682 @@
 block discarded – undo
22 22
 use Psr\Log\LoggerInterface;
23 23
 
24 24
 class Tags implements ITags {
25
-	/**
26
-	 * Used for storing objectid/categoryname pairs while rescanning.
27
-	 */
28
-	private static array $relations = [];
29
-	private array $tags = [];
30
-
31
-	/**
32
-	 * Are we including tags for shared items?
33
-	 */
34
-	private bool $includeShared = false;
35
-
36
-	/**
37
-	 * The current user, plus any owners of the items shared with the current
38
-	 * user, if $this->includeShared === true.
39
-	 */
40
-	private array $owners = [];
41
-
42
-	/**
43
-	 * The sharing backend for objects of $this->type. Required if
44
-	 * $this->includeShared === true to determine ownership of items.
45
-	 */
46
-	private ?Share_Backend $backend = null;
47
-
48
-	public const TAG_TABLE = 'vcategory';
49
-	public const RELATION_TABLE = 'vcategory_to_object';
50
-
51
-	/**
52
-	 * Constructor.
53
-	 *
54
-	 * @param TagMapper $mapper Instance of the TagMapper abstraction layer.
55
-	 * @param string $user The user whose data the object will operate on.
56
-	 * @param string $type The type of items for which tags will be loaded.
57
-	 * @param array $defaultTags Tags that should be created at construction.
58
-	 *
59
-	 * since 20.0.0 $includeShared isn't used anymore
60
-	 */
61
-	public function __construct(
62
-		private TagMapper $mapper,
63
-		private string $user,
64
-		private string $type,
65
-		private LoggerInterface $logger,
66
-		private IDBConnection $db,
67
-		private IEventDispatcher $dispatcher,
68
-		private IUserSession $userSession,
69
-		private Folder $userFolder,
70
-		array $defaultTags = [],
71
-	) {
72
-		$this->owners = [$this->user];
73
-		$this->tags = $this->mapper->loadTags($this->owners, $this->type);
74
-
75
-		if (count($defaultTags) > 0 && count($this->tags) === 0) {
76
-			$this->addMultiple($defaultTags, true);
77
-		}
78
-	}
79
-
80
-	/**
81
-	 * Check if any tags are saved for this type and user.
82
-	 *
83
-	 * @return boolean
84
-	 */
85
-	public function isEmpty(): bool {
86
-		return count($this->tags) === 0;
87
-	}
88
-
89
-	/**
90
-	 * Returns an array mapping a given tag's properties to its values:
91
-	 * ['id' => 0, 'name' = 'Tag', 'owner' = 'User', 'type' => 'tagtype']
92
-	 *
93
-	 * @param string $id The ID of the tag that is going to be mapped
94
-	 * @return array|false
95
-	 */
96
-	public function getTag(string $id) {
97
-		$key = $this->getTagById($id);
98
-		if ($key !== false) {
99
-			return $this->tagMap($this->tags[$key]);
100
-		}
101
-		return false;
102
-	}
103
-
104
-	/**
105
-	 * Get the tags for a specific user.
106
-	 *
107
-	 * This returns an array with maps containing each tag's properties:
108
-	 * [
109
-	 * 	['id' => 0, 'name' = 'First tag', 'owner' = 'User', 'type' => 'tagtype'],
110
-	 * 	['id' => 1, 'name' = 'Shared tag', 'owner' = 'Other user', 'type' => 'tagtype'],
111
-	 * ]
112
-	 *
113
-	 * @return array<array-key, array{id: int, name: string}>
114
-	 */
115
-	public function getTags(): array {
116
-		if (!count($this->tags)) {
117
-			return [];
118
-		}
119
-
120
-		usort($this->tags, function ($a, $b) {
121
-			return strnatcasecmp($a->getName(), $b->getName());
122
-		});
123
-		$tagMap = [];
124
-
125
-		foreach ($this->tags as $tag) {
126
-			if ($tag->getName() !== ITags::TAG_FAVORITE) {
127
-				$tagMap[] = $this->tagMap($tag);
128
-			}
129
-		}
130
-		return $tagMap;
131
-	}
132
-
133
-	/**
134
-	 * Return only the tags owned by the given user, omitting any tags shared
135
-	 * by other users.
136
-	 *
137
-	 * @param string $user The user whose tags are to be checked.
138
-	 * @return array An array of Tag objects.
139
-	 */
140
-	public function getTagsForUser(string $user): array {
141
-		return array_filter($this->tags,
142
-			function ($tag) use ($user) {
143
-				return $tag->getOwner() === $user;
144
-			}
145
-		);
146
-	}
147
-
148
-	/**
149
-	 * Get the list of tags for the given ids.
150
-	 *
151
-	 * @param list<int> $objIds array of object ids
152
-	 * @return array<int, list<string>>|false of tags id as key to array of tag names
153
-	 *                                        or false if an error occurred
154
-	 */
155
-	public function getTagsForObjects(array $objIds) {
156
-		$entries = [];
157
-
158
-		try {
159
-			$chunks = array_chunk($objIds, 900, false);
160
-			$qb = $this->db->getQueryBuilder();
161
-			$qb->select('category', 'categoryid', 'objid')
162
-				->from(self::RELATION_TABLE, 'r')
163
-				->join('r', self::TAG_TABLE, 't', $qb->expr()->eq('r.categoryid', 't.id'))
164
-				->where($qb->expr()->eq('uid', $qb->createParameter('uid')))
165
-				->andWhere($qb->expr()->eq('r.type', $qb->createParameter('type')))
166
-				->andWhere($qb->expr()->in('objid', $qb->createParameter('chunk')));
167
-			foreach ($chunks as $chunk) {
168
-				$qb->setParameter('uid', $this->user, IQueryBuilder::PARAM_STR);
169
-				$qb->setParameter('type', $this->type, IQueryBuilder::PARAM_STR);
170
-				$qb->setParameter('chunk', $chunk, IQueryBuilder::PARAM_INT_ARRAY);
171
-				$result = $qb->executeQuery();
172
-				while ($row = $result->fetch()) {
173
-					$objId = (int)$row['objid'];
174
-					if (!isset($entries[$objId])) {
175
-						$entries[$objId] = [];
176
-					}
177
-					$entries[$objId][] = $row['category'];
178
-				}
179
-				$result->closeCursor();
180
-			}
181
-		} catch (\Exception $e) {
182
-			$this->logger->error($e->getMessage(), [
183
-				'exception' => $e,
184
-				'app' => 'core',
185
-			]);
186
-			return false;
187
-		}
188
-
189
-		return $entries;
190
-	}
191
-
192
-	/**
193
-	 * Get the a list if items tagged with $tag.
194
-	 *
195
-	 * Throws an exception if the tag could not be found.
196
-	 *
197
-	 * @param string $tag Tag id or name.
198
-	 * @return int[]|false An array of object ids or false on error.
199
-	 * @throws \Exception
200
-	 */
201
-	public function getIdsForTag($tag) {
202
-		$tagId = false;
203
-		if (is_numeric($tag)) {
204
-			$tagId = $tag;
205
-		} elseif (is_string($tag)) {
206
-			$tag = trim($tag);
207
-			if ($tag === '') {
208
-				$this->logger->debug(__METHOD__ . ' Cannot use empty tag names', ['app' => 'core']);
209
-				return false;
210
-			}
211
-			$tagId = $this->getTagId($tag);
212
-		}
213
-
214
-		if ($tagId === false) {
215
-			$l10n = \OCP\Util::getL10N('core');
216
-			throw new \Exception(
217
-				$l10n->t('Could not find category "%s"', [$tag])
218
-			);
219
-		}
220
-
221
-		$ids = [];
222
-		try {
223
-			$qb = $this->db->getQueryBuilder();
224
-			$qb->select('objid')
225
-				->from(self::RELATION_TABLE)
226
-				->where($qb->expr()->eq('categoryid', $qb->createNamedParameter($tagId, IQueryBuilder::PARAM_STR)));
227
-			$result = $qb->executeQuery();
228
-		} catch (Exception $e) {
229
-			$this->logger->error($e->getMessage(), [
230
-				'app' => 'core',
231
-				'exception' => $e,
232
-			]);
233
-			return false;
234
-		}
235
-
236
-		while ($row = $result->fetch()) {
237
-			$ids[] = (int)$row['objid'];
238
-		}
239
-		$result->closeCursor();
240
-
241
-		return $ids;
242
-	}
243
-
244
-	/**
245
-	 * Checks whether a tag is saved for the given user,
246
-	 * disregarding the ones shared with them.
247
-	 *
248
-	 * @param string $name The tag name to check for.
249
-	 * @param string $user The user whose tags are to be checked.
250
-	 */
251
-	public function userHasTag(string $name, string $user): bool {
252
-		return $this->array_searchi($name, $this->getTagsForUser($user)) !== false;
253
-	}
254
-
255
-	/**
256
-	 * Checks whether a tag is saved for or shared with the current user.
257
-	 *
258
-	 * @param string $name The tag name to check for.
259
-	 */
260
-	public function hasTag(string $name): bool {
261
-		return $this->getTagId($name) !== false;
262
-	}
263
-
264
-	/**
265
-	 * Add a new tag.
266
-	 *
267
-	 * @param string $name A string with a name of the tag
268
-	 * @return false|int the id of the added tag or false on error.
269
-	 */
270
-	public function add(string $name) {
271
-		$name = trim($name);
272
-
273
-		if ($name === '') {
274
-			$this->logger->debug(__METHOD__ . ' Cannot add an empty tag', ['app' => 'core']);
275
-			return false;
276
-		}
277
-		if ($this->userHasTag($name, $this->user)) {
278
-			$this->logger->debug(__METHOD__ . ' Tag with name already exists', ['app' => 'core']);
279
-			return false;
280
-		}
281
-		try {
282
-			$tag = new Tag($this->user, $this->type, $name);
283
-			$tag = $this->mapper->insert($tag);
284
-			$this->tags[] = $tag;
285
-		} catch (\Exception $e) {
286
-			$this->logger->error($e->getMessage(), [
287
-				'exception' => $e,
288
-				'app' => 'core',
289
-			]);
290
-			return false;
291
-		}
292
-		$this->logger->debug(__METHOD__ . ' Added an tag with ' . $tag->getId(), ['app' => 'core']);
293
-		return $tag->getId();
294
-	}
295
-
296
-	/**
297
-	 * Rename tag.
298
-	 *
299
-	 * @param string|integer $from The name or ID of the existing tag
300
-	 * @param string $to The new name of the tag.
301
-	 * @return bool
302
-	 */
303
-	public function rename($from, string $to): bool {
304
-		$from = trim($from);
305
-		$to = trim($to);
306
-
307
-		if ($to === '' || $from === '') {
308
-			$this->logger->debug(__METHOD__ . 'Cannot use an empty tag names', ['app' => 'core']);
309
-			return false;
310
-		}
311
-
312
-		if (is_numeric($from)) {
313
-			$key = $this->getTagById($from);
314
-		} else {
315
-			$key = $this->getTagByName($from);
316
-		}
317
-		if ($key === false) {
318
-			$this->logger->debug(__METHOD__ . 'Tag ' . $from . 'does not exist', ['app' => 'core']);
319
-			return false;
320
-		}
321
-		$tag = $this->tags[$key];
322
-
323
-		if ($this->userHasTag($to, $tag->getOwner())) {
324
-			$this->logger->debug(__METHOD__ . 'A tag named' . $to . 'already exists for user' . $tag->getOwner(), ['app' => 'core']);
325
-			return false;
326
-		}
327
-
328
-		try {
329
-			$tag->setName($to);
330
-			$this->tags[$key] = $this->mapper->update($tag);
331
-		} catch (\Exception $e) {
332
-			$this->logger->error($e->getMessage(), [
333
-				'exception' => $e,
334
-				'app' => 'core',
335
-			]);
336
-			return false;
337
-		}
338
-		return true;
339
-	}
340
-
341
-	/**
342
-	 * Add a list of new tags.
343
-	 *
344
-	 * @param string|string[] $names A string with a name or an array of strings containing
345
-	 *                               the name(s) of the tag(s) to add.
346
-	 * @param bool $sync When true, save the tags
347
-	 * @param int|null $id int Optional object id to add to this|these tag(s)
348
-	 * @return bool Returns false on error.
349
-	 */
350
-	public function addMultiple($names, bool $sync = false, ?int $id = null): bool {
351
-		if (!is_array($names)) {
352
-			$names = [$names];
353
-		}
354
-		$names = array_map('trim', $names);
355
-		array_filter($names);
356
-
357
-		$newones = [];
358
-		foreach ($names as $name) {
359
-			if (!$this->hasTag($name) && $name !== '') {
360
-				$newones[] = new Tag($this->user, $this->type, $name);
361
-			}
362
-			if (!is_null($id)) {
363
-				// Insert $objectid, $categoryid  pairs if not exist.
364
-				self::$relations[] = ['objid' => $id, 'tag' => $name];
365
-			}
366
-		}
367
-		$this->tags = array_merge($this->tags, $newones);
368
-		if ($sync === true) {
369
-			$this->save();
370
-		}
371
-
372
-		return true;
373
-	}
374
-
375
-	/**
376
-	 * Save the list of tags and their object relations
377
-	 */
378
-	protected function save(): void {
379
-		foreach ($this->tags as $tag) {
380
-			try {
381
-				if (!$this->mapper->tagExists($tag)) {
382
-					$this->mapper->insert($tag);
383
-				}
384
-			} catch (\Exception $e) {
385
-				$this->logger->error($e->getMessage(), [
386
-					'exception' => $e,
387
-					'app' => 'core',
388
-				]);
389
-			}
390
-		}
391
-
392
-		// reload tags to get the proper ids.
393
-		$this->tags = $this->mapper->loadTags($this->owners, $this->type);
394
-		$this->logger->debug(__METHOD__ . 'tags' . print_r($this->tags, true), ['app' => 'core']);
395
-		// Loop through temporarily cached objectid/tagname pairs
396
-		// and save relations.
397
-		$tags = $this->tags;
398
-		// For some reason this is needed or array_search(i) will return 0..?
399
-		ksort($tags);
400
-		foreach (self::$relations as $relation) {
401
-			$tagId = $this->getTagId($relation['tag']);
402
-			$this->logger->debug(__METHOD__ . 'catid ' . $relation['tag'] . ' ' . $tagId, ['app' => 'core']);
403
-			if ($tagId) {
404
-				$qb = $this->db->getQueryBuilder();
405
-				$qb->insert(self::RELATION_TABLE)
406
-					->values([
407
-						'objid' => $qb->createNamedParameter($relation['objid'], IQueryBuilder::PARAM_INT),
408
-						'categoryid' => $qb->createNamedParameter($tagId, IQueryBuilder::PARAM_INT),
409
-						'type' => $qb->createNamedParameter($this->type),
410
-					]);
411
-				try {
412
-					$qb->executeStatement();
413
-				} catch (Exception $e) {
414
-					$this->logger->error($e->getMessage(), [
415
-						'exception' => $e,
416
-						'app' => 'core',
417
-					]);
418
-				}
419
-			}
420
-		}
421
-		self::$relations = []; // reset
422
-	}
423
-
424
-	/**
425
-	 * Delete tag/object relations from the db
426
-	 *
427
-	 * @param array $ids The ids of the objects
428
-	 * @return boolean Returns false on error.
429
-	 */
430
-	public function purgeObjects(array $ids): bool {
431
-		if (count($ids) === 0) {
432
-			// job done ;)
433
-			return true;
434
-		}
435
-		$updates = $ids;
436
-		$qb = $this->db->getQueryBuilder();
437
-		$qb->delete(self::RELATION_TABLE)
438
-			->where($qb->expr()->in('objid', $qb->createNamedParameter($ids)));
439
-		try {
440
-			$qb->executeStatement();
441
-		} catch (Exception $e) {
442
-			$this->logger->error($e->getMessage(), [
443
-				'app' => 'core',
444
-				'exception' => $e,
445
-			]);
446
-			return false;
447
-		}
448
-		return true;
449
-	}
450
-
451
-	/**
452
-	 * Get favorites for an object type
453
-	 *
454
-	 * @return array|false An array of object ids.
455
-	 */
456
-	public function getFavorites() {
457
-		if (!$this->userHasTag(ITags::TAG_FAVORITE, $this->user)) {
458
-			return [];
459
-		}
460
-
461
-		try {
462
-			return $this->getIdsForTag(ITags::TAG_FAVORITE);
463
-		} catch (\Exception $e) {
464
-			\OCP\Server::get(LoggerInterface::class)->error(
465
-				$e->getMessage(),
466
-				[
467
-					'app' => 'core',
468
-					'exception' => $e,
469
-				]
470
-			);
471
-			return [];
472
-		}
473
-	}
474
-
475
-	/**
476
-	 * Add an object to favorites
477
-	 *
478
-	 * @param int $objid The id of the object
479
-	 * @return boolean
480
-	 */
481
-	public function addToFavorites($objid) {
482
-		if (!$this->userHasTag(ITags::TAG_FAVORITE, $this->user)) {
483
-			$this->add(ITags::TAG_FAVORITE);
484
-		}
485
-		return $this->tagAs($objid, ITags::TAG_FAVORITE);
486
-	}
487
-
488
-	/**
489
-	 * Remove an object from favorites
490
-	 *
491
-	 * @param int $objid The id of the object
492
-	 * @return boolean
493
-	 */
494
-	public function removeFromFavorites($objid) {
495
-		return $this->unTag($objid, ITags::TAG_FAVORITE);
496
-	}
497
-
498
-	/**
499
-	 * Creates a tag/object relation.
500
-	 */
501
-	public function tagAs($objid, $tag, ?string $path = null) {
502
-		if (is_string($tag) && !is_numeric($tag)) {
503
-			$tag = trim($tag);
504
-			if ($tag === '') {
505
-				$this->logger->debug(__METHOD__ . ', Cannot add an empty tag');
506
-				return false;
507
-			}
508
-			if (!$this->hasTag($tag)) {
509
-				$this->add($tag);
510
-			}
511
-			$tagId = $this->getTagId($tag);
512
-		} else {
513
-			$tagId = $tag;
514
-		}
515
-		$qb = $this->db->getQueryBuilder();
516
-		$qb->insert(self::RELATION_TABLE)
517
-			->values([
518
-				'objid' => $qb->createNamedParameter($objid, IQueryBuilder::PARAM_INT),
519
-				'categoryid' => $qb->createNamedParameter($tagId, IQueryBuilder::PARAM_INT),
520
-				'type' => $qb->createNamedParameter($this->type, IQueryBuilder::PARAM_STR),
521
-			]);
522
-		try {
523
-			$qb->executeStatement();
524
-		} catch (\Exception $e) {
525
-			\OCP\Server::get(LoggerInterface::class)->error($e->getMessage(), [
526
-				'app' => 'core',
527
-				'exception' => $e,
528
-			]);
529
-			return false;
530
-		}
531
-		if ($tag === ITags::TAG_FAVORITE) {
532
-			if ($path === null) {
533
-				$node = $this->userFolder->getFirstNodeById($objid);
534
-				if ($node !== null) {
535
-					$path = $node->getPath();
536
-				} else {
537
-					throw new Exception('Failed to favorite: node with id ' . $objid . ' not found');
538
-				}
539
-			}
540
-
541
-			$this->dispatcher->dispatchTyped(new NodeAddedToFavorite($this->userSession->getUser(), $objid, $path));
542
-		}
543
-		return true;
544
-	}
545
-
546
-	/**
547
-	 * Delete single tag/object relation from the db
548
-	 */
549
-	public function unTag($objid, $tag, ?string $path = null) {
550
-		if (is_string($tag) && !is_numeric($tag)) {
551
-			$tag = trim($tag);
552
-			if ($tag === '') {
553
-				$this->logger->debug(__METHOD__ . ', Tag name is empty');
554
-				return false;
555
-			}
556
-			$tagId = $this->getTagId($tag);
557
-		} else {
558
-			$tagId = $tag;
559
-		}
560
-
561
-		try {
562
-			$qb = $this->db->getQueryBuilder();
563
-			$qb->delete(self::RELATION_TABLE)
564
-				->where($qb->expr()->andX(
565
-					$qb->expr()->eq('objid', $qb->createNamedParameter($objid)),
566
-					$qb->expr()->eq('categoryid', $qb->createNamedParameter($tagId)),
567
-					$qb->expr()->eq('type', $qb->createNamedParameter($this->type)),
568
-				))->executeStatement();
569
-		} catch (\Exception $e) {
570
-			$this->logger->error($e->getMessage(), [
571
-				'app' => 'core',
572
-				'exception' => $e,
573
-			]);
574
-			return false;
575
-		}
576
-		if ($tag === ITags::TAG_FAVORITE) {
577
-			if ($path === null) {
578
-				$node = $this->userFolder->getFirstNodeById($objid);
579
-				if ($node !== null) {
580
-					$path = $node->getPath();
581
-				} else {
582
-					throw new Exception('Failed to unfavorite: node with id ' . $objid . ' not found');
583
-				}
584
-			}
585
-
586
-			$this->dispatcher->dispatchTyped(new NodeRemovedFromFavorite($this->userSession->getUser(), $objid, $path));
587
-		}
588
-		return true;
589
-	}
590
-
591
-	/**
592
-	 * Delete tags from the database.
593
-	 *
594
-	 * @param string[]|integer[] $names An array of tags (names or IDs) to delete
595
-	 * @return bool Returns false on error
596
-	 */
597
-	public function delete($names) {
598
-		if (!is_array($names)) {
599
-			$names = [$names];
600
-		}
601
-
602
-		$names = array_map('trim', $names);
603
-		array_filter($names);
604
-
605
-		$this->logger->debug(__METHOD__ . ', before: ' . print_r($this->tags, true));
606
-		foreach ($names as $name) {
607
-			$id = null;
608
-
609
-			if (is_numeric($name)) {
610
-				$key = $this->getTagById($name);
611
-			} else {
612
-				$key = $this->getTagByName($name);
613
-			}
614
-			if ($key !== false) {
615
-				$tag = $this->tags[$key];
616
-				$id = $tag->getId();
617
-				unset($this->tags[$key]);
618
-				$this->mapper->delete($tag);
619
-			} else {
620
-				$this->logger->error(__METHOD__ . 'Cannot delete tag ' . $name . ': not found.');
621
-			}
622
-			if (!is_null($id) && $id !== false) {
623
-				try {
624
-					$qb = $this->db->getQueryBuilder();
625
-					$qb->delete(self::RELATION_TABLE)
626
-						->where($qb->expr()->eq('categoryid', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)))
627
-						->executeStatement();
628
-				} catch (\Exception $e) {
629
-					$this->logger->error($e->getMessage(), [
630
-						'app' => 'core',
631
-						'exception' => $e,
632
-					]);
633
-					return false;
634
-				}
635
-			}
636
-		}
637
-		return true;
638
-	}
639
-
640
-	// case-insensitive array_search
641
-	protected function array_searchi($needle, $haystack, $mem = 'getName') {
642
-		if (!is_array($haystack)) {
643
-			return false;
644
-		}
645
-		return array_search(strtolower($needle), array_map(
646
-			function ($tag) use ($mem) {
647
-				return strtolower(call_user_func([$tag, $mem]));
648
-			}, $haystack)
649
-		);
650
-	}
651
-
652
-	/**
653
-	 * Get a tag's ID.
654
-	 *
655
-	 * @param string $name The tag name to look for.
656
-	 * @return string|bool The tag's id or false if no matching tag is found.
657
-	 */
658
-	private function getTagId($name) {
659
-		$key = $this->array_searchi($name, $this->tags);
660
-		if ($key !== false) {
661
-			return $this->tags[$key]->getId();
662
-		}
663
-		return false;
664
-	}
665
-
666
-	/**
667
-	 * Get a tag by its name.
668
-	 *
669
-	 * @param string $name The tag name.
670
-	 * @return integer|bool The tag object's offset within the $this->tags
671
-	 *                      array or false if it doesn't exist.
672
-	 */
673
-	private function getTagByName($name) {
674
-		return $this->array_searchi($name, $this->tags, 'getName');
675
-	}
676
-
677
-	/**
678
-	 * Get a tag by its ID.
679
-	 *
680
-	 * @param string $id The tag ID to look for.
681
-	 * @return integer|bool The tag object's offset within the $this->tags
682
-	 *                      array or false if it doesn't exist.
683
-	 */
684
-	private function getTagById($id) {
685
-		return $this->array_searchi($id, $this->tags, 'getId');
686
-	}
687
-
688
-	/**
689
-	 * Returns an array mapping a given tag's properties to its values:
690
-	 * ['id' => 0, 'name' = 'Tag', 'owner' = 'User', 'type' => 'tagtype']
691
-	 *
692
-	 * @param Tag $tag The tag that is going to be mapped
693
-	 * @return array
694
-	 */
695
-	private function tagMap(Tag $tag) {
696
-		return [
697
-			'id' => $tag->getId(),
698
-			'name' => $tag->getName(),
699
-			'owner' => $tag->getOwner(),
700
-			'type' => $tag->getType()
701
-		];
702
-	}
25
+    /**
26
+     * Used for storing objectid/categoryname pairs while rescanning.
27
+     */
28
+    private static array $relations = [];
29
+    private array $tags = [];
30
+
31
+    /**
32
+     * Are we including tags for shared items?
33
+     */
34
+    private bool $includeShared = false;
35
+
36
+    /**
37
+     * The current user, plus any owners of the items shared with the current
38
+     * user, if $this->includeShared === true.
39
+     */
40
+    private array $owners = [];
41
+
42
+    /**
43
+     * The sharing backend for objects of $this->type. Required if
44
+     * $this->includeShared === true to determine ownership of items.
45
+     */
46
+    private ?Share_Backend $backend = null;
47
+
48
+    public const TAG_TABLE = 'vcategory';
49
+    public const RELATION_TABLE = 'vcategory_to_object';
50
+
51
+    /**
52
+     * Constructor.
53
+     *
54
+     * @param TagMapper $mapper Instance of the TagMapper abstraction layer.
55
+     * @param string $user The user whose data the object will operate on.
56
+     * @param string $type The type of items for which tags will be loaded.
57
+     * @param array $defaultTags Tags that should be created at construction.
58
+     *
59
+     * since 20.0.0 $includeShared isn't used anymore
60
+     */
61
+    public function __construct(
62
+        private TagMapper $mapper,
63
+        private string $user,
64
+        private string $type,
65
+        private LoggerInterface $logger,
66
+        private IDBConnection $db,
67
+        private IEventDispatcher $dispatcher,
68
+        private IUserSession $userSession,
69
+        private Folder $userFolder,
70
+        array $defaultTags = [],
71
+    ) {
72
+        $this->owners = [$this->user];
73
+        $this->tags = $this->mapper->loadTags($this->owners, $this->type);
74
+
75
+        if (count($defaultTags) > 0 && count($this->tags) === 0) {
76
+            $this->addMultiple($defaultTags, true);
77
+        }
78
+    }
79
+
80
+    /**
81
+     * Check if any tags are saved for this type and user.
82
+     *
83
+     * @return boolean
84
+     */
85
+    public function isEmpty(): bool {
86
+        return count($this->tags) === 0;
87
+    }
88
+
89
+    /**
90
+     * Returns an array mapping a given tag's properties to its values:
91
+     * ['id' => 0, 'name' = 'Tag', 'owner' = 'User', 'type' => 'tagtype']
92
+     *
93
+     * @param string $id The ID of the tag that is going to be mapped
94
+     * @return array|false
95
+     */
96
+    public function getTag(string $id) {
97
+        $key = $this->getTagById($id);
98
+        if ($key !== false) {
99
+            return $this->tagMap($this->tags[$key]);
100
+        }
101
+        return false;
102
+    }
103
+
104
+    /**
105
+     * Get the tags for a specific user.
106
+     *
107
+     * This returns an array with maps containing each tag's properties:
108
+     * [
109
+     * 	['id' => 0, 'name' = 'First tag', 'owner' = 'User', 'type' => 'tagtype'],
110
+     * 	['id' => 1, 'name' = 'Shared tag', 'owner' = 'Other user', 'type' => 'tagtype'],
111
+     * ]
112
+     *
113
+     * @return array<array-key, array{id: int, name: string}>
114
+     */
115
+    public function getTags(): array {
116
+        if (!count($this->tags)) {
117
+            return [];
118
+        }
119
+
120
+        usort($this->tags, function ($a, $b) {
121
+            return strnatcasecmp($a->getName(), $b->getName());
122
+        });
123
+        $tagMap = [];
124
+
125
+        foreach ($this->tags as $tag) {
126
+            if ($tag->getName() !== ITags::TAG_FAVORITE) {
127
+                $tagMap[] = $this->tagMap($tag);
128
+            }
129
+        }
130
+        return $tagMap;
131
+    }
132
+
133
+    /**
134
+     * Return only the tags owned by the given user, omitting any tags shared
135
+     * by other users.
136
+     *
137
+     * @param string $user The user whose tags are to be checked.
138
+     * @return array An array of Tag objects.
139
+     */
140
+    public function getTagsForUser(string $user): array {
141
+        return array_filter($this->tags,
142
+            function ($tag) use ($user) {
143
+                return $tag->getOwner() === $user;
144
+            }
145
+        );
146
+    }
147
+
148
+    /**
149
+     * Get the list of tags for the given ids.
150
+     *
151
+     * @param list<int> $objIds array of object ids
152
+     * @return array<int, list<string>>|false of tags id as key to array of tag names
153
+     *                                        or false if an error occurred
154
+     */
155
+    public function getTagsForObjects(array $objIds) {
156
+        $entries = [];
157
+
158
+        try {
159
+            $chunks = array_chunk($objIds, 900, false);
160
+            $qb = $this->db->getQueryBuilder();
161
+            $qb->select('category', 'categoryid', 'objid')
162
+                ->from(self::RELATION_TABLE, 'r')
163
+                ->join('r', self::TAG_TABLE, 't', $qb->expr()->eq('r.categoryid', 't.id'))
164
+                ->where($qb->expr()->eq('uid', $qb->createParameter('uid')))
165
+                ->andWhere($qb->expr()->eq('r.type', $qb->createParameter('type')))
166
+                ->andWhere($qb->expr()->in('objid', $qb->createParameter('chunk')));
167
+            foreach ($chunks as $chunk) {
168
+                $qb->setParameter('uid', $this->user, IQueryBuilder::PARAM_STR);
169
+                $qb->setParameter('type', $this->type, IQueryBuilder::PARAM_STR);
170
+                $qb->setParameter('chunk', $chunk, IQueryBuilder::PARAM_INT_ARRAY);
171
+                $result = $qb->executeQuery();
172
+                while ($row = $result->fetch()) {
173
+                    $objId = (int)$row['objid'];
174
+                    if (!isset($entries[$objId])) {
175
+                        $entries[$objId] = [];
176
+                    }
177
+                    $entries[$objId][] = $row['category'];
178
+                }
179
+                $result->closeCursor();
180
+            }
181
+        } catch (\Exception $e) {
182
+            $this->logger->error($e->getMessage(), [
183
+                'exception' => $e,
184
+                'app' => 'core',
185
+            ]);
186
+            return false;
187
+        }
188
+
189
+        return $entries;
190
+    }
191
+
192
+    /**
193
+     * Get the a list if items tagged with $tag.
194
+     *
195
+     * Throws an exception if the tag could not be found.
196
+     *
197
+     * @param string $tag Tag id or name.
198
+     * @return int[]|false An array of object ids or false on error.
199
+     * @throws \Exception
200
+     */
201
+    public function getIdsForTag($tag) {
202
+        $tagId = false;
203
+        if (is_numeric($tag)) {
204
+            $tagId = $tag;
205
+        } elseif (is_string($tag)) {
206
+            $tag = trim($tag);
207
+            if ($tag === '') {
208
+                $this->logger->debug(__METHOD__ . ' Cannot use empty tag names', ['app' => 'core']);
209
+                return false;
210
+            }
211
+            $tagId = $this->getTagId($tag);
212
+        }
213
+
214
+        if ($tagId === false) {
215
+            $l10n = \OCP\Util::getL10N('core');
216
+            throw new \Exception(
217
+                $l10n->t('Could not find category "%s"', [$tag])
218
+            );
219
+        }
220
+
221
+        $ids = [];
222
+        try {
223
+            $qb = $this->db->getQueryBuilder();
224
+            $qb->select('objid')
225
+                ->from(self::RELATION_TABLE)
226
+                ->where($qb->expr()->eq('categoryid', $qb->createNamedParameter($tagId, IQueryBuilder::PARAM_STR)));
227
+            $result = $qb->executeQuery();
228
+        } catch (Exception $e) {
229
+            $this->logger->error($e->getMessage(), [
230
+                'app' => 'core',
231
+                'exception' => $e,
232
+            ]);
233
+            return false;
234
+        }
235
+
236
+        while ($row = $result->fetch()) {
237
+            $ids[] = (int)$row['objid'];
238
+        }
239
+        $result->closeCursor();
240
+
241
+        return $ids;
242
+    }
243
+
244
+    /**
245
+     * Checks whether a tag is saved for the given user,
246
+     * disregarding the ones shared with them.
247
+     *
248
+     * @param string $name The tag name to check for.
249
+     * @param string $user The user whose tags are to be checked.
250
+     */
251
+    public function userHasTag(string $name, string $user): bool {
252
+        return $this->array_searchi($name, $this->getTagsForUser($user)) !== false;
253
+    }
254
+
255
+    /**
256
+     * Checks whether a tag is saved for or shared with the current user.
257
+     *
258
+     * @param string $name The tag name to check for.
259
+     */
260
+    public function hasTag(string $name): bool {
261
+        return $this->getTagId($name) !== false;
262
+    }
263
+
264
+    /**
265
+     * Add a new tag.
266
+     *
267
+     * @param string $name A string with a name of the tag
268
+     * @return false|int the id of the added tag or false on error.
269
+     */
270
+    public function add(string $name) {
271
+        $name = trim($name);
272
+
273
+        if ($name === '') {
274
+            $this->logger->debug(__METHOD__ . ' Cannot add an empty tag', ['app' => 'core']);
275
+            return false;
276
+        }
277
+        if ($this->userHasTag($name, $this->user)) {
278
+            $this->logger->debug(__METHOD__ . ' Tag with name already exists', ['app' => 'core']);
279
+            return false;
280
+        }
281
+        try {
282
+            $tag = new Tag($this->user, $this->type, $name);
283
+            $tag = $this->mapper->insert($tag);
284
+            $this->tags[] = $tag;
285
+        } catch (\Exception $e) {
286
+            $this->logger->error($e->getMessage(), [
287
+                'exception' => $e,
288
+                'app' => 'core',
289
+            ]);
290
+            return false;
291
+        }
292
+        $this->logger->debug(__METHOD__ . ' Added an tag with ' . $tag->getId(), ['app' => 'core']);
293
+        return $tag->getId();
294
+    }
295
+
296
+    /**
297
+     * Rename tag.
298
+     *
299
+     * @param string|integer $from The name or ID of the existing tag
300
+     * @param string $to The new name of the tag.
301
+     * @return bool
302
+     */
303
+    public function rename($from, string $to): bool {
304
+        $from = trim($from);
305
+        $to = trim($to);
306
+
307
+        if ($to === '' || $from === '') {
308
+            $this->logger->debug(__METHOD__ . 'Cannot use an empty tag names', ['app' => 'core']);
309
+            return false;
310
+        }
311
+
312
+        if (is_numeric($from)) {
313
+            $key = $this->getTagById($from);
314
+        } else {
315
+            $key = $this->getTagByName($from);
316
+        }
317
+        if ($key === false) {
318
+            $this->logger->debug(__METHOD__ . 'Tag ' . $from . 'does not exist', ['app' => 'core']);
319
+            return false;
320
+        }
321
+        $tag = $this->tags[$key];
322
+
323
+        if ($this->userHasTag($to, $tag->getOwner())) {
324
+            $this->logger->debug(__METHOD__ . 'A tag named' . $to . 'already exists for user' . $tag->getOwner(), ['app' => 'core']);
325
+            return false;
326
+        }
327
+
328
+        try {
329
+            $tag->setName($to);
330
+            $this->tags[$key] = $this->mapper->update($tag);
331
+        } catch (\Exception $e) {
332
+            $this->logger->error($e->getMessage(), [
333
+                'exception' => $e,
334
+                'app' => 'core',
335
+            ]);
336
+            return false;
337
+        }
338
+        return true;
339
+    }
340
+
341
+    /**
342
+     * Add a list of new tags.
343
+     *
344
+     * @param string|string[] $names A string with a name or an array of strings containing
345
+     *                               the name(s) of the tag(s) to add.
346
+     * @param bool $sync When true, save the tags
347
+     * @param int|null $id int Optional object id to add to this|these tag(s)
348
+     * @return bool Returns false on error.
349
+     */
350
+    public function addMultiple($names, bool $sync = false, ?int $id = null): bool {
351
+        if (!is_array($names)) {
352
+            $names = [$names];
353
+        }
354
+        $names = array_map('trim', $names);
355
+        array_filter($names);
356
+
357
+        $newones = [];
358
+        foreach ($names as $name) {
359
+            if (!$this->hasTag($name) && $name !== '') {
360
+                $newones[] = new Tag($this->user, $this->type, $name);
361
+            }
362
+            if (!is_null($id)) {
363
+                // Insert $objectid, $categoryid  pairs if not exist.
364
+                self::$relations[] = ['objid' => $id, 'tag' => $name];
365
+            }
366
+        }
367
+        $this->tags = array_merge($this->tags, $newones);
368
+        if ($sync === true) {
369
+            $this->save();
370
+        }
371
+
372
+        return true;
373
+    }
374
+
375
+    /**
376
+     * Save the list of tags and their object relations
377
+     */
378
+    protected function save(): void {
379
+        foreach ($this->tags as $tag) {
380
+            try {
381
+                if (!$this->mapper->tagExists($tag)) {
382
+                    $this->mapper->insert($tag);
383
+                }
384
+            } catch (\Exception $e) {
385
+                $this->logger->error($e->getMessage(), [
386
+                    'exception' => $e,
387
+                    'app' => 'core',
388
+                ]);
389
+            }
390
+        }
391
+
392
+        // reload tags to get the proper ids.
393
+        $this->tags = $this->mapper->loadTags($this->owners, $this->type);
394
+        $this->logger->debug(__METHOD__ . 'tags' . print_r($this->tags, true), ['app' => 'core']);
395
+        // Loop through temporarily cached objectid/tagname pairs
396
+        // and save relations.
397
+        $tags = $this->tags;
398
+        // For some reason this is needed or array_search(i) will return 0..?
399
+        ksort($tags);
400
+        foreach (self::$relations as $relation) {
401
+            $tagId = $this->getTagId($relation['tag']);
402
+            $this->logger->debug(__METHOD__ . 'catid ' . $relation['tag'] . ' ' . $tagId, ['app' => 'core']);
403
+            if ($tagId) {
404
+                $qb = $this->db->getQueryBuilder();
405
+                $qb->insert(self::RELATION_TABLE)
406
+                    ->values([
407
+                        'objid' => $qb->createNamedParameter($relation['objid'], IQueryBuilder::PARAM_INT),
408
+                        'categoryid' => $qb->createNamedParameter($tagId, IQueryBuilder::PARAM_INT),
409
+                        'type' => $qb->createNamedParameter($this->type),
410
+                    ]);
411
+                try {
412
+                    $qb->executeStatement();
413
+                } catch (Exception $e) {
414
+                    $this->logger->error($e->getMessage(), [
415
+                        'exception' => $e,
416
+                        'app' => 'core',
417
+                    ]);
418
+                }
419
+            }
420
+        }
421
+        self::$relations = []; // reset
422
+    }
423
+
424
+    /**
425
+     * Delete tag/object relations from the db
426
+     *
427
+     * @param array $ids The ids of the objects
428
+     * @return boolean Returns false on error.
429
+     */
430
+    public function purgeObjects(array $ids): bool {
431
+        if (count($ids) === 0) {
432
+            // job done ;)
433
+            return true;
434
+        }
435
+        $updates = $ids;
436
+        $qb = $this->db->getQueryBuilder();
437
+        $qb->delete(self::RELATION_TABLE)
438
+            ->where($qb->expr()->in('objid', $qb->createNamedParameter($ids)));
439
+        try {
440
+            $qb->executeStatement();
441
+        } catch (Exception $e) {
442
+            $this->logger->error($e->getMessage(), [
443
+                'app' => 'core',
444
+                'exception' => $e,
445
+            ]);
446
+            return false;
447
+        }
448
+        return true;
449
+    }
450
+
451
+    /**
452
+     * Get favorites for an object type
453
+     *
454
+     * @return array|false An array of object ids.
455
+     */
456
+    public function getFavorites() {
457
+        if (!$this->userHasTag(ITags::TAG_FAVORITE, $this->user)) {
458
+            return [];
459
+        }
460
+
461
+        try {
462
+            return $this->getIdsForTag(ITags::TAG_FAVORITE);
463
+        } catch (\Exception $e) {
464
+            \OCP\Server::get(LoggerInterface::class)->error(
465
+                $e->getMessage(),
466
+                [
467
+                    'app' => 'core',
468
+                    'exception' => $e,
469
+                ]
470
+            );
471
+            return [];
472
+        }
473
+    }
474
+
475
+    /**
476
+     * Add an object to favorites
477
+     *
478
+     * @param int $objid The id of the object
479
+     * @return boolean
480
+     */
481
+    public function addToFavorites($objid) {
482
+        if (!$this->userHasTag(ITags::TAG_FAVORITE, $this->user)) {
483
+            $this->add(ITags::TAG_FAVORITE);
484
+        }
485
+        return $this->tagAs($objid, ITags::TAG_FAVORITE);
486
+    }
487
+
488
+    /**
489
+     * Remove an object from favorites
490
+     *
491
+     * @param int $objid The id of the object
492
+     * @return boolean
493
+     */
494
+    public function removeFromFavorites($objid) {
495
+        return $this->unTag($objid, ITags::TAG_FAVORITE);
496
+    }
497
+
498
+    /**
499
+     * Creates a tag/object relation.
500
+     */
501
+    public function tagAs($objid, $tag, ?string $path = null) {
502
+        if (is_string($tag) && !is_numeric($tag)) {
503
+            $tag = trim($tag);
504
+            if ($tag === '') {
505
+                $this->logger->debug(__METHOD__ . ', Cannot add an empty tag');
506
+                return false;
507
+            }
508
+            if (!$this->hasTag($tag)) {
509
+                $this->add($tag);
510
+            }
511
+            $tagId = $this->getTagId($tag);
512
+        } else {
513
+            $tagId = $tag;
514
+        }
515
+        $qb = $this->db->getQueryBuilder();
516
+        $qb->insert(self::RELATION_TABLE)
517
+            ->values([
518
+                'objid' => $qb->createNamedParameter($objid, IQueryBuilder::PARAM_INT),
519
+                'categoryid' => $qb->createNamedParameter($tagId, IQueryBuilder::PARAM_INT),
520
+                'type' => $qb->createNamedParameter($this->type, IQueryBuilder::PARAM_STR),
521
+            ]);
522
+        try {
523
+            $qb->executeStatement();
524
+        } catch (\Exception $e) {
525
+            \OCP\Server::get(LoggerInterface::class)->error($e->getMessage(), [
526
+                'app' => 'core',
527
+                'exception' => $e,
528
+            ]);
529
+            return false;
530
+        }
531
+        if ($tag === ITags::TAG_FAVORITE) {
532
+            if ($path === null) {
533
+                $node = $this->userFolder->getFirstNodeById($objid);
534
+                if ($node !== null) {
535
+                    $path = $node->getPath();
536
+                } else {
537
+                    throw new Exception('Failed to favorite: node with id ' . $objid . ' not found');
538
+                }
539
+            }
540
+
541
+            $this->dispatcher->dispatchTyped(new NodeAddedToFavorite($this->userSession->getUser(), $objid, $path));
542
+        }
543
+        return true;
544
+    }
545
+
546
+    /**
547
+     * Delete single tag/object relation from the db
548
+     */
549
+    public function unTag($objid, $tag, ?string $path = null) {
550
+        if (is_string($tag) && !is_numeric($tag)) {
551
+            $tag = trim($tag);
552
+            if ($tag === '') {
553
+                $this->logger->debug(__METHOD__ . ', Tag name is empty');
554
+                return false;
555
+            }
556
+            $tagId = $this->getTagId($tag);
557
+        } else {
558
+            $tagId = $tag;
559
+        }
560
+
561
+        try {
562
+            $qb = $this->db->getQueryBuilder();
563
+            $qb->delete(self::RELATION_TABLE)
564
+                ->where($qb->expr()->andX(
565
+                    $qb->expr()->eq('objid', $qb->createNamedParameter($objid)),
566
+                    $qb->expr()->eq('categoryid', $qb->createNamedParameter($tagId)),
567
+                    $qb->expr()->eq('type', $qb->createNamedParameter($this->type)),
568
+                ))->executeStatement();
569
+        } catch (\Exception $e) {
570
+            $this->logger->error($e->getMessage(), [
571
+                'app' => 'core',
572
+                'exception' => $e,
573
+            ]);
574
+            return false;
575
+        }
576
+        if ($tag === ITags::TAG_FAVORITE) {
577
+            if ($path === null) {
578
+                $node = $this->userFolder->getFirstNodeById($objid);
579
+                if ($node !== null) {
580
+                    $path = $node->getPath();
581
+                } else {
582
+                    throw new Exception('Failed to unfavorite: node with id ' . $objid . ' not found');
583
+                }
584
+            }
585
+
586
+            $this->dispatcher->dispatchTyped(new NodeRemovedFromFavorite($this->userSession->getUser(), $objid, $path));
587
+        }
588
+        return true;
589
+    }
590
+
591
+    /**
592
+     * Delete tags from the database.
593
+     *
594
+     * @param string[]|integer[] $names An array of tags (names or IDs) to delete
595
+     * @return bool Returns false on error
596
+     */
597
+    public function delete($names) {
598
+        if (!is_array($names)) {
599
+            $names = [$names];
600
+        }
601
+
602
+        $names = array_map('trim', $names);
603
+        array_filter($names);
604
+
605
+        $this->logger->debug(__METHOD__ . ', before: ' . print_r($this->tags, true));
606
+        foreach ($names as $name) {
607
+            $id = null;
608
+
609
+            if (is_numeric($name)) {
610
+                $key = $this->getTagById($name);
611
+            } else {
612
+                $key = $this->getTagByName($name);
613
+            }
614
+            if ($key !== false) {
615
+                $tag = $this->tags[$key];
616
+                $id = $tag->getId();
617
+                unset($this->tags[$key]);
618
+                $this->mapper->delete($tag);
619
+            } else {
620
+                $this->logger->error(__METHOD__ . 'Cannot delete tag ' . $name . ': not found.');
621
+            }
622
+            if (!is_null($id) && $id !== false) {
623
+                try {
624
+                    $qb = $this->db->getQueryBuilder();
625
+                    $qb->delete(self::RELATION_TABLE)
626
+                        ->where($qb->expr()->eq('categoryid', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)))
627
+                        ->executeStatement();
628
+                } catch (\Exception $e) {
629
+                    $this->logger->error($e->getMessage(), [
630
+                        'app' => 'core',
631
+                        'exception' => $e,
632
+                    ]);
633
+                    return false;
634
+                }
635
+            }
636
+        }
637
+        return true;
638
+    }
639
+
640
+    // case-insensitive array_search
641
+    protected function array_searchi($needle, $haystack, $mem = 'getName') {
642
+        if (!is_array($haystack)) {
643
+            return false;
644
+        }
645
+        return array_search(strtolower($needle), array_map(
646
+            function ($tag) use ($mem) {
647
+                return strtolower(call_user_func([$tag, $mem]));
648
+            }, $haystack)
649
+        );
650
+    }
651
+
652
+    /**
653
+     * Get a tag's ID.
654
+     *
655
+     * @param string $name The tag name to look for.
656
+     * @return string|bool The tag's id or false if no matching tag is found.
657
+     */
658
+    private function getTagId($name) {
659
+        $key = $this->array_searchi($name, $this->tags);
660
+        if ($key !== false) {
661
+            return $this->tags[$key]->getId();
662
+        }
663
+        return false;
664
+    }
665
+
666
+    /**
667
+     * Get a tag by its name.
668
+     *
669
+     * @param string $name The tag name.
670
+     * @return integer|bool The tag object's offset within the $this->tags
671
+     *                      array or false if it doesn't exist.
672
+     */
673
+    private function getTagByName($name) {
674
+        return $this->array_searchi($name, $this->tags, 'getName');
675
+    }
676
+
677
+    /**
678
+     * Get a tag by its ID.
679
+     *
680
+     * @param string $id The tag ID to look for.
681
+     * @return integer|bool The tag object's offset within the $this->tags
682
+     *                      array or false if it doesn't exist.
683
+     */
684
+    private function getTagById($id) {
685
+        return $this->array_searchi($id, $this->tags, 'getId');
686
+    }
687
+
688
+    /**
689
+     * Returns an array mapping a given tag's properties to its values:
690
+     * ['id' => 0, 'name' = 'Tag', 'owner' = 'User', 'type' => 'tagtype']
691
+     *
692
+     * @param Tag $tag The tag that is going to be mapped
693
+     * @return array
694
+     */
695
+    private function tagMap(Tag $tag) {
696
+        return [
697
+            'id' => $tag->getId(),
698
+            'name' => $tag->getName(),
699
+            'owner' => $tag->getOwner(),
700
+            'type' => $tag->getType()
701
+        ];
702
+    }
703 703
 }
Please login to merge, or discard this patch.
Spacing   +20 added lines, -20 removed lines patch added patch discarded remove patch
@@ -117,7 +117,7 @@  discard block
 block discarded – undo
117 117
 			return [];
118 118
 		}
119 119
 
120
-		usort($this->tags, function ($a, $b) {
120
+		usort($this->tags, function($a, $b) {
121 121
 			return strnatcasecmp($a->getName(), $b->getName());
122 122
 		});
123 123
 		$tagMap = [];
@@ -139,7 +139,7 @@  discard block
 block discarded – undo
139 139
 	 */
140 140
 	public function getTagsForUser(string $user): array {
141 141
 		return array_filter($this->tags,
142
-			function ($tag) use ($user) {
142
+			function($tag) use ($user) {
143 143
 				return $tag->getOwner() === $user;
144 144
 			}
145 145
 		);
@@ -170,7 +170,7 @@  discard block
 block discarded – undo
170 170
 				$qb->setParameter('chunk', $chunk, IQueryBuilder::PARAM_INT_ARRAY);
171 171
 				$result = $qb->executeQuery();
172 172
 				while ($row = $result->fetch()) {
173
-					$objId = (int)$row['objid'];
173
+					$objId = (int) $row['objid'];
174 174
 					if (!isset($entries[$objId])) {
175 175
 						$entries[$objId] = [];
176 176
 					}
@@ -205,7 +205,7 @@  discard block
 block discarded – undo
205 205
 		} elseif (is_string($tag)) {
206 206
 			$tag = trim($tag);
207 207
 			if ($tag === '') {
208
-				$this->logger->debug(__METHOD__ . ' Cannot use empty tag names', ['app' => 'core']);
208
+				$this->logger->debug(__METHOD__.' Cannot use empty tag names', ['app' => 'core']);
209 209
 				return false;
210 210
 			}
211 211
 			$tagId = $this->getTagId($tag);
@@ -234,7 +234,7 @@  discard block
 block discarded – undo
234 234
 		}
235 235
 
236 236
 		while ($row = $result->fetch()) {
237
-			$ids[] = (int)$row['objid'];
237
+			$ids[] = (int) $row['objid'];
238 238
 		}
239 239
 		$result->closeCursor();
240 240
 
@@ -271,11 +271,11 @@  discard block
 block discarded – undo
271 271
 		$name = trim($name);
272 272
 
273 273
 		if ($name === '') {
274
-			$this->logger->debug(__METHOD__ . ' Cannot add an empty tag', ['app' => 'core']);
274
+			$this->logger->debug(__METHOD__.' Cannot add an empty tag', ['app' => 'core']);
275 275
 			return false;
276 276
 		}
277 277
 		if ($this->userHasTag($name, $this->user)) {
278
-			$this->logger->debug(__METHOD__ . ' Tag with name already exists', ['app' => 'core']);
278
+			$this->logger->debug(__METHOD__.' Tag with name already exists', ['app' => 'core']);
279 279
 			return false;
280 280
 		}
281 281
 		try {
@@ -289,7 +289,7 @@  discard block
 block discarded – undo
289 289
 			]);
290 290
 			return false;
291 291
 		}
292
-		$this->logger->debug(__METHOD__ . ' Added an tag with ' . $tag->getId(), ['app' => 'core']);
292
+		$this->logger->debug(__METHOD__.' Added an tag with '.$tag->getId(), ['app' => 'core']);
293 293
 		return $tag->getId();
294 294
 	}
295 295
 
@@ -305,7 +305,7 @@  discard block
 block discarded – undo
305 305
 		$to = trim($to);
306 306
 
307 307
 		if ($to === '' || $from === '') {
308
-			$this->logger->debug(__METHOD__ . 'Cannot use an empty tag names', ['app' => 'core']);
308
+			$this->logger->debug(__METHOD__.'Cannot use an empty tag names', ['app' => 'core']);
309 309
 			return false;
310 310
 		}
311 311
 
@@ -315,13 +315,13 @@  discard block
 block discarded – undo
315 315
 			$key = $this->getTagByName($from);
316 316
 		}
317 317
 		if ($key === false) {
318
-			$this->logger->debug(__METHOD__ . 'Tag ' . $from . 'does not exist', ['app' => 'core']);
318
+			$this->logger->debug(__METHOD__.'Tag '.$from.'does not exist', ['app' => 'core']);
319 319
 			return false;
320 320
 		}
321 321
 		$tag = $this->tags[$key];
322 322
 
323 323
 		if ($this->userHasTag($to, $tag->getOwner())) {
324
-			$this->logger->debug(__METHOD__ . 'A tag named' . $to . 'already exists for user' . $tag->getOwner(), ['app' => 'core']);
324
+			$this->logger->debug(__METHOD__.'A tag named'.$to.'already exists for user'.$tag->getOwner(), ['app' => 'core']);
325 325
 			return false;
326 326
 		}
327 327
 
@@ -391,7 +391,7 @@  discard block
 block discarded – undo
391 391
 
392 392
 		// reload tags to get the proper ids.
393 393
 		$this->tags = $this->mapper->loadTags($this->owners, $this->type);
394
-		$this->logger->debug(__METHOD__ . 'tags' . print_r($this->tags, true), ['app' => 'core']);
394
+		$this->logger->debug(__METHOD__.'tags'.print_r($this->tags, true), ['app' => 'core']);
395 395
 		// Loop through temporarily cached objectid/tagname pairs
396 396
 		// and save relations.
397 397
 		$tags = $this->tags;
@@ -399,7 +399,7 @@  discard block
 block discarded – undo
399 399
 		ksort($tags);
400 400
 		foreach (self::$relations as $relation) {
401 401
 			$tagId = $this->getTagId($relation['tag']);
402
-			$this->logger->debug(__METHOD__ . 'catid ' . $relation['tag'] . ' ' . $tagId, ['app' => 'core']);
402
+			$this->logger->debug(__METHOD__.'catid '.$relation['tag'].' '.$tagId, ['app' => 'core']);
403 403
 			if ($tagId) {
404 404
 				$qb = $this->db->getQueryBuilder();
405 405
 				$qb->insert(self::RELATION_TABLE)
@@ -502,7 +502,7 @@  discard block
 block discarded – undo
502 502
 		if (is_string($tag) && !is_numeric($tag)) {
503 503
 			$tag = trim($tag);
504 504
 			if ($tag === '') {
505
-				$this->logger->debug(__METHOD__ . ', Cannot add an empty tag');
505
+				$this->logger->debug(__METHOD__.', Cannot add an empty tag');
506 506
 				return false;
507 507
 			}
508 508
 			if (!$this->hasTag($tag)) {
@@ -534,7 +534,7 @@  discard block
 block discarded – undo
534 534
 				if ($node !== null) {
535 535
 					$path = $node->getPath();
536 536
 				} else {
537
-					throw new Exception('Failed to favorite: node with id ' . $objid . ' not found');
537
+					throw new Exception('Failed to favorite: node with id '.$objid.' not found');
538 538
 				}
539 539
 			}
540 540
 
@@ -550,7 +550,7 @@  discard block
 block discarded – undo
550 550
 		if (is_string($tag) && !is_numeric($tag)) {
551 551
 			$tag = trim($tag);
552 552
 			if ($tag === '') {
553
-				$this->logger->debug(__METHOD__ . ', Tag name is empty');
553
+				$this->logger->debug(__METHOD__.', Tag name is empty');
554 554
 				return false;
555 555
 			}
556 556
 			$tagId = $this->getTagId($tag);
@@ -579,7 +579,7 @@  discard block
 block discarded – undo
579 579
 				if ($node !== null) {
580 580
 					$path = $node->getPath();
581 581
 				} else {
582
-					throw new Exception('Failed to unfavorite: node with id ' . $objid . ' not found');
582
+					throw new Exception('Failed to unfavorite: node with id '.$objid.' not found');
583 583
 				}
584 584
 			}
585 585
 
@@ -602,7 +602,7 @@  discard block
 block discarded – undo
602 602
 		$names = array_map('trim', $names);
603 603
 		array_filter($names);
604 604
 
605
-		$this->logger->debug(__METHOD__ . ', before: ' . print_r($this->tags, true));
605
+		$this->logger->debug(__METHOD__.', before: '.print_r($this->tags, true));
606 606
 		foreach ($names as $name) {
607 607
 			$id = null;
608 608
 
@@ -617,7 +617,7 @@  discard block
 block discarded – undo
617 617
 				unset($this->tags[$key]);
618 618
 				$this->mapper->delete($tag);
619 619
 			} else {
620
-				$this->logger->error(__METHOD__ . 'Cannot delete tag ' . $name . ': not found.');
620
+				$this->logger->error(__METHOD__.'Cannot delete tag '.$name.': not found.');
621 621
 			}
622 622
 			if (!is_null($id) && $id !== false) {
623 623
 				try {
@@ -643,7 +643,7 @@  discard block
 block discarded – undo
643 643
 			return false;
644 644
 		}
645 645
 		return array_search(strtolower($needle), array_map(
646
-			function ($tag) use ($mem) {
646
+			function($tag) use ($mem) {
647 647
 				return strtolower(call_user_func([$tag, $mem]));
648 648
 			}, $haystack)
649 649
 		);
Please login to merge, or discard this patch.
apps/files/lib/Service/TagService.php 1 patch
Indentation   +41 added lines, -41 removed lines patch added patch discarded remove patch
@@ -18,51 +18,51 @@
 block discarded – undo
18 18
  */
19 19
 class TagService {
20 20
 
21
-	public function __construct(
22
-		private IUserSession $userSession,
23
-		private IManager $activityManager,
24
-		private ?ITags $tagger,
25
-		private ?Folder $homeFolder,
26
-	) {
27
-	}
21
+    public function __construct(
22
+        private IUserSession $userSession,
23
+        private IManager $activityManager,
24
+        private ?ITags $tagger,
25
+        private ?Folder $homeFolder,
26
+    ) {
27
+    }
28 28
 
29
-	/**
30
-	 * Updates the tags of the specified file path.
31
-	 * The passed tags are absolute, which means they will
32
-	 * replace the actual tag selection.
33
-	 *
34
-	 * @param string $path path
35
-	 * @param array $tags array of tags
36
-	 * @return array list of tags
37
-	 * @throws NotFoundException if the file does not exist
38
-	 */
39
-	public function updateFileTags($path, $tags) {
40
-		if ($this->tagger === null) {
41
-			throw new \RuntimeException('No tagger set');
42
-		}
43
-		if ($this->homeFolder === null) {
44
-			throw new \RuntimeException('No homeFolder set');
45
-		}
29
+    /**
30
+     * Updates the tags of the specified file path.
31
+     * The passed tags are absolute, which means they will
32
+     * replace the actual tag selection.
33
+     *
34
+     * @param string $path path
35
+     * @param array $tags array of tags
36
+     * @return array list of tags
37
+     * @throws NotFoundException if the file does not exist
38
+     */
39
+    public function updateFileTags($path, $tags) {
40
+        if ($this->tagger === null) {
41
+            throw new \RuntimeException('No tagger set');
42
+        }
43
+        if ($this->homeFolder === null) {
44
+            throw new \RuntimeException('No homeFolder set');
45
+        }
46 46
 
47
-		$fileId = $this->homeFolder->get($path)->getId();
47
+        $fileId = $this->homeFolder->get($path)->getId();
48 48
 
49
-		$currentTags = $this->tagger->getTagsForObjects([$fileId]);
49
+        $currentTags = $this->tagger->getTagsForObjects([$fileId]);
50 50
 
51
-		if (!empty($currentTags)) {
52
-			$currentTags = current($currentTags);
53
-		}
51
+        if (!empty($currentTags)) {
52
+            $currentTags = current($currentTags);
53
+        }
54 54
 
55
-		$newTags = array_diff($tags, $currentTags);
56
-		foreach ($newTags as $tag) {
57
-			$this->tagger->tagAs($fileId, $tag, $path);
58
-		}
59
-		$deletedTags = array_diff($currentTags, $tags);
60
-		foreach ($deletedTags as $tag) {
61
-			$this->tagger->unTag($fileId, $tag, $path);
62
-		}
55
+        $newTags = array_diff($tags, $currentTags);
56
+        foreach ($newTags as $tag) {
57
+            $this->tagger->tagAs($fileId, $tag, $path);
58
+        }
59
+        $deletedTags = array_diff($currentTags, $tags);
60
+        foreach ($deletedTags as $tag) {
61
+            $this->tagger->unTag($fileId, $tag, $path);
62
+        }
63 63
 
64
-		// TODO: re-read from tagger to make sure the
65
-		// list is up to date, in case of concurrent changes ?
66
-		return $tags;
67
-	}
64
+        // TODO: re-read from tagger to make sure the
65
+        // list is up to date, in case of concurrent changes ?
66
+        return $tags;
67
+    }
68 68
 }
Please login to merge, or discard this patch.
apps/dav/lib/Connector/Sabre/TagsPlugin.php 2 patches
Indentation   +238 added lines, -238 removed lines patch added patch discarded remove patch
@@ -37,267 +37,267 @@
 block discarded – undo
37 37
 
38 38
 class TagsPlugin extends \Sabre\DAV\ServerPlugin {
39 39
 
40
-	// namespace
41
-	public const NS_OWNCLOUD = 'http://owncloud.org/ns';
42
-	public const TAGS_PROPERTYNAME = '{http://owncloud.org/ns}tags';
43
-	public const FAVORITE_PROPERTYNAME = '{http://owncloud.org/ns}favorite';
44
-	public const TAG_FAVORITE = '_$!<Favorite>!$_';
40
+    // namespace
41
+    public const NS_OWNCLOUD = 'http://owncloud.org/ns';
42
+    public const TAGS_PROPERTYNAME = '{http://owncloud.org/ns}tags';
43
+    public const FAVORITE_PROPERTYNAME = '{http://owncloud.org/ns}favorite';
44
+    public const TAG_FAVORITE = '_$!<Favorite>!$_';
45 45
 
46
-	/**
47
-	 * Reference to main server object
48
-	 *
49
-	 * @var \Sabre\DAV\Server
50
-	 */
51
-	private $server;
46
+    /**
47
+     * Reference to main server object
48
+     *
49
+     * @var \Sabre\DAV\Server
50
+     */
51
+    private $server;
52 52
 
53
-	/**
54
-	 * @var ITags
55
-	 */
56
-	private $tagger;
53
+    /**
54
+     * @var ITags
55
+     */
56
+    private $tagger;
57 57
 
58
-	/**
59
-	 * Array of file id to tags array
60
-	 * The null value means the cache wasn't initialized.
61
-	 *
62
-	 * @var array
63
-	 */
64
-	private $cachedTags;
65
-	private array $cachedDirectories;
58
+    /**
59
+     * Array of file id to tags array
60
+     * The null value means the cache wasn't initialized.
61
+     *
62
+     * @var array
63
+     */
64
+    private $cachedTags;
65
+    private array $cachedDirectories;
66 66
 
67
-	/**
68
-	 * @param \Sabre\DAV\Tree $tree tree
69
-	 * @param ITagManager $tagManager tag manager
70
-	 */
71
-	public function __construct(
72
-		private \Sabre\DAV\Tree $tree,
73
-		private ITagManager $tagManager,
74
-		private IEventDispatcher $eventDispatcher,
75
-		private IUserSession $userSession,
76
-	) {
77
-		$this->tagger = null;
78
-		$this->cachedTags = [];
79
-	}
67
+    /**
68
+     * @param \Sabre\DAV\Tree $tree tree
69
+     * @param ITagManager $tagManager tag manager
70
+     */
71
+    public function __construct(
72
+        private \Sabre\DAV\Tree $tree,
73
+        private ITagManager $tagManager,
74
+        private IEventDispatcher $eventDispatcher,
75
+        private IUserSession $userSession,
76
+    ) {
77
+        $this->tagger = null;
78
+        $this->cachedTags = [];
79
+    }
80 80
 
81
-	/**
82
-	 * This initializes the plugin.
83
-	 *
84
-	 * This function is called by \Sabre\DAV\Server, after
85
-	 * addPlugin is called.
86
-	 *
87
-	 * This method should set up the required event subscriptions.
88
-	 *
89
-	 * @param \Sabre\DAV\Server $server
90
-	 * @return void
91
-	 */
92
-	public function initialize(\Sabre\DAV\Server $server) {
93
-		$server->xml->namespaceMap[self::NS_OWNCLOUD] = 'oc';
94
-		$server->xml->elementMap[self::TAGS_PROPERTYNAME] = TagList::class;
81
+    /**
82
+     * This initializes the plugin.
83
+     *
84
+     * This function is called by \Sabre\DAV\Server, after
85
+     * addPlugin is called.
86
+     *
87
+     * This method should set up the required event subscriptions.
88
+     *
89
+     * @param \Sabre\DAV\Server $server
90
+     * @return void
91
+     */
92
+    public function initialize(\Sabre\DAV\Server $server) {
93
+        $server->xml->namespaceMap[self::NS_OWNCLOUD] = 'oc';
94
+        $server->xml->elementMap[self::TAGS_PROPERTYNAME] = TagList::class;
95 95
 
96
-		$this->server = $server;
97
-		$this->server->on('preloadCollection', $this->preloadCollection(...));
98
-		$this->server->on('propFind', [$this, 'handleGetProperties']);
99
-		$this->server->on('propPatch', [$this, 'handleUpdateProperties']);
100
-		$this->server->on('preloadProperties', [$this, 'handlePreloadProperties']);
101
-	}
96
+        $this->server = $server;
97
+        $this->server->on('preloadCollection', $this->preloadCollection(...));
98
+        $this->server->on('propFind', [$this, 'handleGetProperties']);
99
+        $this->server->on('propPatch', [$this, 'handleUpdateProperties']);
100
+        $this->server->on('preloadProperties', [$this, 'handlePreloadProperties']);
101
+    }
102 102
 
103
-	/**
104
-	 * Returns the tagger
105
-	 *
106
-	 * @return ITags tagger
107
-	 */
108
-	private function getTagger() {
109
-		if (!$this->tagger) {
110
-			$this->tagger = $this->tagManager->load('files');
111
-		}
112
-		return $this->tagger;
113
-	}
103
+    /**
104
+     * Returns the tagger
105
+     *
106
+     * @return ITags tagger
107
+     */
108
+    private function getTagger() {
109
+        if (!$this->tagger) {
110
+            $this->tagger = $this->tagManager->load('files');
111
+        }
112
+        return $this->tagger;
113
+    }
114 114
 
115
-	/**
116
-	 * Returns tags and favorites.
117
-	 *
118
-	 * @param integer $fileId file id
119
-	 * @return array list($tags, $favorite) with $tags as tag array
120
-	 *               and $favorite is a boolean whether the file was favorited
121
-	 */
122
-	private function getTagsAndFav($fileId) {
123
-		$isFav = false;
124
-		$tags = $this->getTags($fileId);
125
-		if ($tags) {
126
-			$favPos = array_search(self::TAG_FAVORITE, $tags);
127
-			if ($favPos !== false) {
128
-				$isFav = true;
129
-				unset($tags[$favPos]);
130
-			}
131
-		}
132
-		return [$tags, $isFav];
133
-	}
115
+    /**
116
+     * Returns tags and favorites.
117
+     *
118
+     * @param integer $fileId file id
119
+     * @return array list($tags, $favorite) with $tags as tag array
120
+     *               and $favorite is a boolean whether the file was favorited
121
+     */
122
+    private function getTagsAndFav($fileId) {
123
+        $isFav = false;
124
+        $tags = $this->getTags($fileId);
125
+        if ($tags) {
126
+            $favPos = array_search(self::TAG_FAVORITE, $tags);
127
+            if ($favPos !== false) {
128
+                $isFav = true;
129
+                unset($tags[$favPos]);
130
+            }
131
+        }
132
+        return [$tags, $isFav];
133
+    }
134 134
 
135
-	/**
136
-	 * Returns tags for the given file id
137
-	 *
138
-	 * @param integer $fileId file id
139
-	 * @return array list of tags for that file
140
-	 */
141
-	private function getTags($fileId) {
142
-		if (isset($this->cachedTags[$fileId])) {
143
-			return $this->cachedTags[$fileId];
144
-		} else {
145
-			$tags = $this->getTagger()->getTagsForObjects([$fileId]);
146
-			if ($tags !== false) {
147
-				if (empty($tags)) {
148
-					return [];
149
-				}
150
-				return current($tags);
151
-			}
152
-		}
153
-		return null;
154
-	}
135
+    /**
136
+     * Returns tags for the given file id
137
+     *
138
+     * @param integer $fileId file id
139
+     * @return array list of tags for that file
140
+     */
141
+    private function getTags($fileId) {
142
+        if (isset($this->cachedTags[$fileId])) {
143
+            return $this->cachedTags[$fileId];
144
+        } else {
145
+            $tags = $this->getTagger()->getTagsForObjects([$fileId]);
146
+            if ($tags !== false) {
147
+                if (empty($tags)) {
148
+                    return [];
149
+                }
150
+                return current($tags);
151
+            }
152
+        }
153
+        return null;
154
+    }
155 155
 
156
-	/**
157
-	 * Prefetches tags for a list of file IDs and caches the results
158
-	 *
159
-	 * @param array $fileIds List of file IDs to prefetch tags for
160
-	 * @return void
161
-	 */
162
-	private function prefetchTagsForFileIds(array $fileIds) {
163
-		$tags = $this->getTagger()->getTagsForObjects($fileIds);
164
-		if ($tags === false) {
165
-			// the tags API returns false on error...
166
-			$tags = [];
167
-		}
156
+    /**
157
+     * Prefetches tags for a list of file IDs and caches the results
158
+     *
159
+     * @param array $fileIds List of file IDs to prefetch tags for
160
+     * @return void
161
+     */
162
+    private function prefetchTagsForFileIds(array $fileIds) {
163
+        $tags = $this->getTagger()->getTagsForObjects($fileIds);
164
+        if ($tags === false) {
165
+            // the tags API returns false on error...
166
+            $tags = [];
167
+        }
168 168
 
169
-		foreach ($fileIds as $fileId) {
170
-			$this->cachedTags[$fileId] = $tags[$fileId] ?? [];
171
-		}
172
-	}
169
+        foreach ($fileIds as $fileId) {
170
+            $this->cachedTags[$fileId] = $tags[$fileId] ?? [];
171
+        }
172
+    }
173 173
 
174
-	/**
175
-	 * Updates the tags of the given file id
176
-	 *
177
-	 * @param int $fileId
178
-	 * @param array $tags array of tag strings
179
-	 * @param string $path path of the file
180
-	 */
181
-	private function updateTags($fileId, $tags, string $path) {
182
-		$tagger = $this->getTagger();
183
-		$currentTags = $this->getTags($fileId);
174
+    /**
175
+     * Updates the tags of the given file id
176
+     *
177
+     * @param int $fileId
178
+     * @param array $tags array of tag strings
179
+     * @param string $path path of the file
180
+     */
181
+    private function updateTags($fileId, $tags, string $path) {
182
+        $tagger = $this->getTagger();
183
+        $currentTags = $this->getTags($fileId);
184 184
 
185
-		$newTags = array_diff($tags, $currentTags);
186
-		foreach ($newTags as $tag) {
187
-			if ($tag === self::TAG_FAVORITE) {
188
-				continue;
189
-			}
190
-			$tagger->tagAs($fileId, $tag, $path);
191
-		}
192
-		$deletedTags = array_diff($currentTags, $tags);
193
-		foreach ($deletedTags as $tag) {
194
-			if ($tag === self::TAG_FAVORITE) {
195
-				continue;
196
-			}
197
-			$tagger->unTag($fileId, $tag, $path);
198
-		}
199
-	}
185
+        $newTags = array_diff($tags, $currentTags);
186
+        foreach ($newTags as $tag) {
187
+            if ($tag === self::TAG_FAVORITE) {
188
+                continue;
189
+            }
190
+            $tagger->tagAs($fileId, $tag, $path);
191
+        }
192
+        $deletedTags = array_diff($currentTags, $tags);
193
+        foreach ($deletedTags as $tag) {
194
+            if ($tag === self::TAG_FAVORITE) {
195
+                continue;
196
+            }
197
+            $tagger->unTag($fileId, $tag, $path);
198
+        }
199
+    }
200 200
 
201
-	private function preloadCollection(PropFind $propFind, ICollection $collection):
202
-	void {
203
-		if (!($collection instanceof Node)) {
204
-			return;
205
-		}
201
+    private function preloadCollection(PropFind $propFind, ICollection $collection):
202
+    void {
203
+        if (!($collection instanceof Node)) {
204
+            return;
205
+        }
206 206
 
207
-		// need prefetch ?
208
-		if ($collection instanceof Directory
209
-			&& !isset($this->cachedDirectories[$collection->getPath()])
210
-			&& (!is_null($propFind->getStatus(self::TAGS_PROPERTYNAME))
211
-				|| !is_null($propFind->getStatus(self::FAVORITE_PROPERTYNAME))
212
-			)) {
213
-			// note: pre-fetching only supported for depth <= 1
214
-			$folderContent = $collection->getChildren();
215
-			$fileIds = [(int)$collection->getId()];
216
-			foreach ($folderContent as $info) {
217
-				$fileIds[] = (int)$info->getId();
218
-			}
219
-			$this->prefetchTagsForFileIds($fileIds);
220
-			$this->cachedDirectories[$collection->getPath()] = true;
221
-		}
222
-	}
207
+        // need prefetch ?
208
+        if ($collection instanceof Directory
209
+            && !isset($this->cachedDirectories[$collection->getPath()])
210
+            && (!is_null($propFind->getStatus(self::TAGS_PROPERTYNAME))
211
+                || !is_null($propFind->getStatus(self::FAVORITE_PROPERTYNAME))
212
+            )) {
213
+            // note: pre-fetching only supported for depth <= 1
214
+            $folderContent = $collection->getChildren();
215
+            $fileIds = [(int)$collection->getId()];
216
+            foreach ($folderContent as $info) {
217
+                $fileIds[] = (int)$info->getId();
218
+            }
219
+            $this->prefetchTagsForFileIds($fileIds);
220
+            $this->cachedDirectories[$collection->getPath()] = true;
221
+        }
222
+    }
223 223
 
224
-	/**
225
-	 * Adds tags and favorites properties to the response,
226
-	 * if requested.
227
-	 *
228
-	 * @param PropFind $propFind
229
-	 * @param \Sabre\DAV\INode $node
230
-	 * @return void
231
-	 */
232
-	public function handleGetProperties(
233
-		PropFind $propFind,
234
-		\Sabre\DAV\INode $node,
235
-	) {
236
-		if (!($node instanceof Node)) {
237
-			return;
238
-		}
224
+    /**
225
+     * Adds tags and favorites properties to the response,
226
+     * if requested.
227
+     *
228
+     * @param PropFind $propFind
229
+     * @param \Sabre\DAV\INode $node
230
+     * @return void
231
+     */
232
+    public function handleGetProperties(
233
+        PropFind $propFind,
234
+        \Sabre\DAV\INode $node,
235
+    ) {
236
+        if (!($node instanceof Node)) {
237
+            return;
238
+        }
239 239
 
240
-		$isFav = null;
240
+        $isFav = null;
241 241
 
242
-		$propFind->handle(self::TAGS_PROPERTYNAME, function () use (&$isFav, $node) {
243
-			[$tags, $isFav] = $this->getTagsAndFav($node->getId());
244
-			return new TagList($tags);
245
-		});
242
+        $propFind->handle(self::TAGS_PROPERTYNAME, function () use (&$isFav, $node) {
243
+            [$tags, $isFav] = $this->getTagsAndFav($node->getId());
244
+            return new TagList($tags);
245
+        });
246 246
 
247
-		$propFind->handle(self::FAVORITE_PROPERTYNAME, function () use ($isFav, $node) {
248
-			if (is_null($isFav)) {
249
-				[, $isFav] = $this->getTagsAndFav($node->getId());
250
-			}
251
-			if ($isFav) {
252
-				return 1;
253
-			} else {
254
-				return 0;
255
-			}
256
-		});
257
-	}
247
+        $propFind->handle(self::FAVORITE_PROPERTYNAME, function () use ($isFav, $node) {
248
+            if (is_null($isFav)) {
249
+                [, $isFav] = $this->getTagsAndFav($node->getId());
250
+            }
251
+            if ($isFav) {
252
+                return 1;
253
+            } else {
254
+                return 0;
255
+            }
256
+        });
257
+    }
258 258
 
259
-	/**
260
-	 * Updates tags and favorites properties, if applicable.
261
-	 *
262
-	 * @param string $path
263
-	 * @param PropPatch $propPatch
264
-	 *
265
-	 * @return void
266
-	 */
267
-	public function handleUpdateProperties($path, PropPatch $propPatch) {
268
-		$node = $this->tree->getNodeForPath($path);
269
-		if (!($node instanceof Node)) {
270
-			return;
271
-		}
259
+    /**
260
+     * Updates tags and favorites properties, if applicable.
261
+     *
262
+     * @param string $path
263
+     * @param PropPatch $propPatch
264
+     *
265
+     * @return void
266
+     */
267
+    public function handleUpdateProperties($path, PropPatch $propPatch) {
268
+        $node = $this->tree->getNodeForPath($path);
269
+        if (!($node instanceof Node)) {
270
+            return;
271
+        }
272 272
 
273
-		$propPatch->handle(self::TAGS_PROPERTYNAME, function ($tagList) use ($node, $path) {
274
-			$this->updateTags($node->getId(), $tagList->getTags(), $path);
275
-			return true;
276
-		});
273
+        $propPatch->handle(self::TAGS_PROPERTYNAME, function ($tagList) use ($node, $path) {
274
+            $this->updateTags($node->getId(), $tagList->getTags(), $path);
275
+            return true;
276
+        });
277 277
 
278
-		$propPatch->handle(self::FAVORITE_PROPERTYNAME, function ($favState) use ($node, $path) {
279
-			if ((int)$favState === 1 || $favState === 'true') {
280
-				$this->getTagger()->tagAs($node->getId(), self::TAG_FAVORITE, $path);
281
-			} else {
282
-				$this->getTagger()->unTag($node->getId(), self::TAG_FAVORITE, $path);
283
-			}
278
+        $propPatch->handle(self::FAVORITE_PROPERTYNAME, function ($favState) use ($node, $path) {
279
+            if ((int)$favState === 1 || $favState === 'true') {
280
+                $this->getTagger()->tagAs($node->getId(), self::TAG_FAVORITE, $path);
281
+            } else {
282
+                $this->getTagger()->unTag($node->getId(), self::TAG_FAVORITE, $path);
283
+            }
284 284
 
285
-			if (is_null($favState)) {
286
-				// confirm deletion
287
-				return 204;
288
-			}
285
+            if (is_null($favState)) {
286
+                // confirm deletion
287
+                return 204;
288
+            }
289 289
 
290
-			return 200;
291
-		});
292
-	}
290
+            return 200;
291
+        });
292
+    }
293 293
 
294
-	public function handlePreloadProperties(array $nodes, array $requestProperties): void {
295
-		if (
296
-			!in_array(self::FAVORITE_PROPERTYNAME, $requestProperties, true)
297
-			&& !in_array(self::TAGS_PROPERTYNAME, $requestProperties, true)
298
-		) {
299
-			return;
300
-		}
301
-		$this->prefetchTagsForFileIds(array_map(fn ($node) => $node->getId(), $nodes));
302
-	}
294
+    public function handlePreloadProperties(array $nodes, array $requestProperties): void {
295
+        if (
296
+            !in_array(self::FAVORITE_PROPERTYNAME, $requestProperties, true)
297
+            && !in_array(self::TAGS_PROPERTYNAME, $requestProperties, true)
298
+        ) {
299
+            return;
300
+        }
301
+        $this->prefetchTagsForFileIds(array_map(fn ($node) => $node->getId(), $nodes));
302
+    }
303 303
 }
Please login to merge, or discard this patch.
Spacing   +7 added lines, -7 removed lines patch added patch discarded remove patch
@@ -212,9 +212,9 @@  discard block
 block discarded – undo
212 212
 			)) {
213 213
 			// note: pre-fetching only supported for depth <= 1
214 214
 			$folderContent = $collection->getChildren();
215
-			$fileIds = [(int)$collection->getId()];
215
+			$fileIds = [(int) $collection->getId()];
216 216
 			foreach ($folderContent as $info) {
217
-				$fileIds[] = (int)$info->getId();
217
+				$fileIds[] = (int) $info->getId();
218 218
 			}
219 219
 			$this->prefetchTagsForFileIds($fileIds);
220 220
 			$this->cachedDirectories[$collection->getPath()] = true;
@@ -239,12 +239,12 @@  discard block
 block discarded – undo
239 239
 
240 240
 		$isFav = null;
241 241
 
242
-		$propFind->handle(self::TAGS_PROPERTYNAME, function () use (&$isFav, $node) {
242
+		$propFind->handle(self::TAGS_PROPERTYNAME, function() use (&$isFav, $node) {
243 243
 			[$tags, $isFav] = $this->getTagsAndFav($node->getId());
244 244
 			return new TagList($tags);
245 245
 		});
246 246
 
247
-		$propFind->handle(self::FAVORITE_PROPERTYNAME, function () use ($isFav, $node) {
247
+		$propFind->handle(self::FAVORITE_PROPERTYNAME, function() use ($isFav, $node) {
248 248
 			if (is_null($isFav)) {
249 249
 				[, $isFav] = $this->getTagsAndFav($node->getId());
250 250
 			}
@@ -270,13 +270,13 @@  discard block
 block discarded – undo
270 270
 			return;
271 271
 		}
272 272
 
273
-		$propPatch->handle(self::TAGS_PROPERTYNAME, function ($tagList) use ($node, $path) {
273
+		$propPatch->handle(self::TAGS_PROPERTYNAME, function($tagList) use ($node, $path) {
274 274
 			$this->updateTags($node->getId(), $tagList->getTags(), $path);
275 275
 			return true;
276 276
 		});
277 277
 
278
-		$propPatch->handle(self::FAVORITE_PROPERTYNAME, function ($favState) use ($node, $path) {
279
-			if ((int)$favState === 1 || $favState === 'true') {
278
+		$propPatch->handle(self::FAVORITE_PROPERTYNAME, function($favState) use ($node, $path) {
279
+			if ((int) $favState === 1 || $favState === 'true') {
280 280
 				$this->getTagger()->tagAs($node->getId(), self::TAG_FAVORITE, $path);
281 281
 			} else {
282 282
 				$this->getTagger()->unTag($node->getId(), self::TAG_FAVORITE, $path);
Please login to merge, or discard this patch.
apps/dav/tests/unit/Connector/Sabre/TagsPluginTest.php 1 patch
Indentation   +384 added lines, -384 removed lines patch added patch discarded remove patch
@@ -23,388 +23,388 @@
 block discarded – undo
23 23
 use Sabre\DAV\Tree;
24 24
 
25 25
 class TagsPluginTest extends \Test\TestCase {
26
-	public const TAGS_PROPERTYNAME = TagsPlugin::TAGS_PROPERTYNAME;
27
-	public const FAVORITE_PROPERTYNAME = TagsPlugin::FAVORITE_PROPERTYNAME;
28
-	public const TAG_FAVORITE = TagsPlugin::TAG_FAVORITE;
29
-
30
-	private \Sabre\DAV\Server $server;
31
-	private Tree&MockObject $tree;
32
-	private ITagManager&MockObject $tagManager;
33
-	private ITags&MockObject $tagger;
34
-	private IEventDispatcher&MockObject $eventDispatcher;
35
-	private IUserSession&MockObject $userSession;
36
-	private TagsPlugin $plugin;
37
-
38
-	protected function setUp(): void {
39
-		parent::setUp();
40
-
41
-		$this->server = new \Sabre\DAV\Server();
42
-		$this->tree = $this->createMock(Tree::class);
43
-		$this->tagger = $this->createMock(ITags::class);
44
-		$this->tagManager = $this->createMock(ITagManager::class);
45
-		$this->eventDispatcher = $this->createMock(IEventDispatcher::class);
46
-		$user = $this->createMock(IUser::class);
47
-
48
-		$this->userSession = $this->createMock(IUserSession::class);
49
-		$this->userSession->expects($this->any())
50
-			->method('getUser')
51
-			->withAnyParameters()
52
-			->willReturn($user);
53
-		$this->tagManager->expects($this->any())
54
-			->method('load')
55
-			->with('files')
56
-			->willReturn($this->tagger);
57
-		$this->plugin = new TagsPlugin($this->tree, $this->tagManager, $this->eventDispatcher, $this->userSession);
58
-		$this->plugin->initialize($this->server);
59
-	}
60
-
61
-	#[\PHPUnit\Framework\Attributes\DataProvider('tagsGetPropertiesDataProvider')]
62
-	public function testGetProperties(array $tags, array $requestedProperties, array $expectedProperties): void {
63
-		$node = $this->createMock(Node::class);
64
-		$node->expects($this->any())
65
-			->method('getId')
66
-			->willReturn(123);
67
-
68
-		$expectedCallCount = 0;
69
-		if (count($requestedProperties) > 0) {
70
-			$expectedCallCount = 1;
71
-		}
72
-
73
-		$this->tagger->expects($this->exactly($expectedCallCount))
74
-			->method('getTagsForObjects')
75
-			->with($this->equalTo([123]))
76
-			->willReturn([123 => $tags]);
77
-
78
-		$propFind = new \Sabre\DAV\PropFind(
79
-			'/dummyPath',
80
-			$requestedProperties,
81
-			0
82
-		);
83
-
84
-		$this->plugin->handleGetProperties(
85
-			$propFind,
86
-			$node
87
-		);
88
-
89
-		$result = $propFind->getResultForMultiStatus();
90
-
91
-		$this->assertEmpty($result[404]);
92
-		unset($result[404]);
93
-		$this->assertEquals($expectedProperties, $result);
94
-	}
95
-
96
-	#[\PHPUnit\Framework\Attributes\DataProvider('tagsGetPropertiesDataProvider')]
97
-	public function testPreloadThenGetProperties(array $tags, array $requestedProperties, array $expectedProperties): void {
98
-		$node1 = $this->createMock(File::class);
99
-		$node1->expects($this->any())
100
-			->method('getId')
101
-			->willReturn(111);
102
-		$node2 = $this->createMock(File::class);
103
-		$node2->expects($this->any())
104
-			->method('getId')
105
-			->willReturn(222);
106
-
107
-		$expectedCallCount = 0;
108
-		if (count($requestedProperties) > 0) {
109
-			// this guarantees that getTagsForObjects
110
-			// is only called once and then the tags
111
-			// are cached
112
-			$expectedCallCount = 1;
113
-		}
114
-
115
-		$node = $this->createMock(Directory::class);
116
-		$node->expects($this->any())
117
-			->method('getId')
118
-			->willReturn(123);
119
-		$node->expects($this->exactly($expectedCallCount))
120
-			->method('getChildren')
121
-			->willReturn([$node1, $node2]);
122
-
123
-		$this->tagger->expects($this->exactly($expectedCallCount))
124
-			->method('getTagsForObjects')
125
-			->with($this->equalTo([123, 111, 222]))
126
-			->willReturn(
127
-				[
128
-					111 => $tags,
129
-					123 => $tags
130
-				]
131
-			);
132
-
133
-		// simulate sabre recursive PROPFIND traversal
134
-		$propFindRoot = new \Sabre\DAV\PropFind(
135
-			'/subdir',
136
-			$requestedProperties,
137
-			1
138
-		);
139
-		$propFind1 = new \Sabre\DAV\PropFind(
140
-			'/subdir/test.txt',
141
-			$requestedProperties,
142
-			0
143
-		);
144
-		$propFind2 = new \Sabre\DAV\PropFind(
145
-			'/subdir/test2.txt',
146
-			$requestedProperties,
147
-			0
148
-		);
149
-
150
-		$this->server->emit('preloadCollection', [$propFindRoot, $node]);
151
-
152
-		$this->plugin->handleGetProperties(
153
-			$propFindRoot,
154
-			$node
155
-		);
156
-		$this->plugin->handleGetProperties(
157
-			$propFind1,
158
-			$node1
159
-		);
160
-		$this->plugin->handleGetProperties(
161
-			$propFind2,
162
-			$node2
163
-		);
164
-
165
-		$result = $propFind1->getResultForMultiStatus();
166
-
167
-		$this->assertEmpty($result[404]);
168
-		unset($result[404]);
169
-		$this->assertEquals($expectedProperties, $result);
170
-	}
171
-
172
-	public static function tagsGetPropertiesDataProvider(): array {
173
-		return [
174
-			// request both, receive both
175
-			[
176
-				['tag1', 'tag2', self::TAG_FAVORITE],
177
-				[self::TAGS_PROPERTYNAME, self::FAVORITE_PROPERTYNAME],
178
-				[
179
-					200 => [
180
-						self::TAGS_PROPERTYNAME => new TagList(['tag1', 'tag2']),
181
-						self::FAVORITE_PROPERTYNAME => true,
182
-					]
183
-				]
184
-			],
185
-			// request tags alone
186
-			[
187
-				['tag1', 'tag2', self::TAG_FAVORITE],
188
-				[self::TAGS_PROPERTYNAME],
189
-				[
190
-					200 => [
191
-						self::TAGS_PROPERTYNAME => new TagList(['tag1', 'tag2']),
192
-					]
193
-				]
194
-			],
195
-			// request fav alone
196
-			[
197
-				['tag1', 'tag2', self::TAG_FAVORITE],
198
-				[self::FAVORITE_PROPERTYNAME],
199
-				[
200
-					200 => [
201
-						self::FAVORITE_PROPERTYNAME => true,
202
-					]
203
-				]
204
-			],
205
-			// request none
206
-			[
207
-				['tag1', 'tag2', self::TAG_FAVORITE],
208
-				[],
209
-				[
210
-					200 => []
211
-				],
212
-			],
213
-			// request both with none set, receive both
214
-			[
215
-				[],
216
-				[self::TAGS_PROPERTYNAME, self::FAVORITE_PROPERTYNAME],
217
-				[
218
-					200 => [
219
-						self::TAGS_PROPERTYNAME => new TagList([]),
220
-						self::FAVORITE_PROPERTYNAME => false,
221
-					]
222
-				]
223
-			],
224
-		];
225
-	}
226
-
227
-	public function testGetPropertiesSkipChunks(): void {
228
-		$sabreNode = $this->createMock(UploadFile::class);
229
-
230
-		$propFind = new \Sabre\DAV\PropFind(
231
-			'/dummyPath',
232
-			[self::TAGS_PROPERTYNAME, self::TAG_FAVORITE],
233
-			0
234
-		);
235
-
236
-		$this->plugin->handleGetProperties(
237
-			$propFind,
238
-			$sabreNode
239
-		);
240
-
241
-		$result = $propFind->getResultForMultiStatus();
242
-		$this->assertCount(2, $result[404]);
243
-	}
244
-
245
-	public function testUpdateTags(): void {
246
-		// this test will replace the existing tags "tagremove" with "tag1" and "tag2"
247
-		// and keep "tagkeep"
248
-		$node = $this->createMock(Node::class);
249
-		$node->expects($this->any())
250
-			->method('getId')
251
-			->willReturn(123);
252
-
253
-		$this->tree->expects($this->any())
254
-			->method('getNodeForPath')
255
-			->with('/dummypath')
256
-			->willReturn($node);
257
-
258
-		$this->tagger->expects($this->once())
259
-			->method('getTagsForObjects')
260
-			->with($this->equalTo([123]))
261
-			->willReturn([123 => ['tagkeep', 'tagremove', self::TAG_FAVORITE]]);
262
-
263
-		// then tag as tag1 and tag2
264
-		$calls = [
265
-			[123, 'tag1', '/dummypath'],
266
-			[123, 'tag2', '/dummypath'],
267
-		];
268
-		$this->tagger->expects($this->exactly(count($calls)))
269
-			->method('tagAs')
270
-			->willReturnCallback(function () use (&$calls): void {
271
-				$expected = array_shift($calls);
272
-				$this->assertEquals($expected, func_get_args());
273
-			});
274
-
275
-		// it will untag tag3
276
-		$this->tagger->expects($this->once())
277
-			->method('unTag')
278
-			->with(123, 'tagremove');
279
-
280
-		// properties to set
281
-		$propPatch = new \Sabre\DAV\PropPatch([
282
-			self::TAGS_PROPERTYNAME => new TagList(['tag1', 'tag2', 'tagkeep'])
283
-		]);
284
-
285
-		$this->plugin->handleUpdateProperties(
286
-			'/dummypath',
287
-			$propPatch
288
-		);
289
-
290
-		$propPatch->commit();
291
-
292
-		// all requested properties removed, as they were processed already
293
-		$this->assertEmpty($propPatch->getRemainingMutations());
294
-
295
-		$result = $propPatch->getResult();
296
-		$this->assertEquals(200, $result[self::TAGS_PROPERTYNAME]);
297
-		$this->assertArrayNotHasKey(self::FAVORITE_PROPERTYNAME, $result);
298
-	}
299
-
300
-	public function testUpdateTagsFromScratch(): void {
301
-		$node = $this->createMock(Node::class);
302
-		$node->expects($this->any())
303
-			->method('getId')
304
-			->willReturn(123);
305
-
306
-		$this->tree->expects($this->any())
307
-			->method('getNodeForPath')
308
-			->with('/dummypath')
309
-			->willReturn($node);
310
-
311
-		$this->tagger->expects($this->once())
312
-			->method('getTagsForObjects')
313
-			->with($this->equalTo([123]))
314
-			->willReturn([]);
315
-
316
-		// then tag as tag1 and tag2
317
-		$calls = [
318
-			[123, 'tag1', '/dummypath'],
319
-			[123, 'tag2', '/dummypath'],
320
-		];
321
-		$this->tagger->expects($this->exactly(count($calls)))
322
-			->method('tagAs')
323
-			->willReturnCallback(function () use (&$calls): void {
324
-				$expected = array_shift($calls);
325
-				$this->assertEquals($expected, func_get_args());
326
-			});
327
-
328
-		// properties to set
329
-		$propPatch = new \Sabre\DAV\PropPatch([
330
-			self::TAGS_PROPERTYNAME => new TagList(['tag1', 'tag2'])
331
-		]);
332
-
333
-		$this->plugin->handleUpdateProperties(
334
-			'/dummypath',
335
-			$propPatch
336
-		);
337
-
338
-		$propPatch->commit();
339
-
340
-		// all requested properties removed, as they were processed already
341
-		$this->assertEmpty($propPatch->getRemainingMutations());
342
-
343
-		$result = $propPatch->getResult();
344
-		$this->assertEquals(200, $result[self::TAGS_PROPERTYNAME]);
345
-		$this->assertArrayNotHasKey(self::FAVORITE_PROPERTYNAME, $result);
346
-	}
347
-
348
-	public function testUpdateFav(): void {
349
-		// this test will replace the existing tags "tagremove" with "tag1" and "tag2"
350
-		// and keep "tagkeep"
351
-		$node = $this->createMock(Node::class);
352
-		$node->expects($this->any())
353
-			->method('getId')
354
-			->willReturn(123);
355
-
356
-		$this->tree->expects($this->any())
357
-			->method('getNodeForPath')
358
-			->with('/dummypath')
359
-			->willReturn($node);
360
-
361
-		// set favorite tag
362
-		$this->tagger->expects($this->once())
363
-			->method('tagAs')
364
-			->with(123, self::TAG_FAVORITE);
365
-
366
-		// properties to set
367
-		$propPatch = new \Sabre\DAV\PropPatch([
368
-			self::FAVORITE_PROPERTYNAME => true
369
-		]);
370
-
371
-		$this->plugin->handleUpdateProperties(
372
-			'/dummypath',
373
-			$propPatch
374
-		);
375
-
376
-		$propPatch->commit();
377
-
378
-		// all requested properties removed, as they were processed already
379
-		$this->assertEmpty($propPatch->getRemainingMutations());
380
-
381
-		$result = $propPatch->getResult();
382
-		$this->assertArrayNotHasKey(self::TAGS_PROPERTYNAME, $result);
383
-		$this->assertEquals(200, $result[self::FAVORITE_PROPERTYNAME]);
384
-
385
-		// unfavorite now
386
-		// set favorite tag
387
-		$this->tagger->expects($this->once())
388
-			->method('unTag')
389
-			->with(123, self::TAG_FAVORITE);
390
-
391
-		// properties to set
392
-		$propPatch = new \Sabre\DAV\PropPatch([
393
-			self::FAVORITE_PROPERTYNAME => false
394
-		]);
395
-
396
-		$this->plugin->handleUpdateProperties(
397
-			'/dummypath',
398
-			$propPatch
399
-		);
400
-
401
-		$propPatch->commit();
402
-
403
-		// all requested properties removed, as they were processed already
404
-		$this->assertEmpty($propPatch->getRemainingMutations());
405
-
406
-		$result = $propPatch->getResult();
407
-		$this->assertArrayNotHasKey(self::TAGS_PROPERTYNAME, $result);
408
-		$this->assertEquals(200, $result[self::FAVORITE_PROPERTYNAME]);
409
-	}
26
+    public const TAGS_PROPERTYNAME = TagsPlugin::TAGS_PROPERTYNAME;
27
+    public const FAVORITE_PROPERTYNAME = TagsPlugin::FAVORITE_PROPERTYNAME;
28
+    public const TAG_FAVORITE = TagsPlugin::TAG_FAVORITE;
29
+
30
+    private \Sabre\DAV\Server $server;
31
+    private Tree&MockObject $tree;
32
+    private ITagManager&MockObject $tagManager;
33
+    private ITags&MockObject $tagger;
34
+    private IEventDispatcher&MockObject $eventDispatcher;
35
+    private IUserSession&MockObject $userSession;
36
+    private TagsPlugin $plugin;
37
+
38
+    protected function setUp(): void {
39
+        parent::setUp();
40
+
41
+        $this->server = new \Sabre\DAV\Server();
42
+        $this->tree = $this->createMock(Tree::class);
43
+        $this->tagger = $this->createMock(ITags::class);
44
+        $this->tagManager = $this->createMock(ITagManager::class);
45
+        $this->eventDispatcher = $this->createMock(IEventDispatcher::class);
46
+        $user = $this->createMock(IUser::class);
47
+
48
+        $this->userSession = $this->createMock(IUserSession::class);
49
+        $this->userSession->expects($this->any())
50
+            ->method('getUser')
51
+            ->withAnyParameters()
52
+            ->willReturn($user);
53
+        $this->tagManager->expects($this->any())
54
+            ->method('load')
55
+            ->with('files')
56
+            ->willReturn($this->tagger);
57
+        $this->plugin = new TagsPlugin($this->tree, $this->tagManager, $this->eventDispatcher, $this->userSession);
58
+        $this->plugin->initialize($this->server);
59
+    }
60
+
61
+    #[\PHPUnit\Framework\Attributes\DataProvider('tagsGetPropertiesDataProvider')]
62
+    public function testGetProperties(array $tags, array $requestedProperties, array $expectedProperties): void {
63
+        $node = $this->createMock(Node::class);
64
+        $node->expects($this->any())
65
+            ->method('getId')
66
+            ->willReturn(123);
67
+
68
+        $expectedCallCount = 0;
69
+        if (count($requestedProperties) > 0) {
70
+            $expectedCallCount = 1;
71
+        }
72
+
73
+        $this->tagger->expects($this->exactly($expectedCallCount))
74
+            ->method('getTagsForObjects')
75
+            ->with($this->equalTo([123]))
76
+            ->willReturn([123 => $tags]);
77
+
78
+        $propFind = new \Sabre\DAV\PropFind(
79
+            '/dummyPath',
80
+            $requestedProperties,
81
+            0
82
+        );
83
+
84
+        $this->plugin->handleGetProperties(
85
+            $propFind,
86
+            $node
87
+        );
88
+
89
+        $result = $propFind->getResultForMultiStatus();
90
+
91
+        $this->assertEmpty($result[404]);
92
+        unset($result[404]);
93
+        $this->assertEquals($expectedProperties, $result);
94
+    }
95
+
96
+    #[\PHPUnit\Framework\Attributes\DataProvider('tagsGetPropertiesDataProvider')]
97
+    public function testPreloadThenGetProperties(array $tags, array $requestedProperties, array $expectedProperties): void {
98
+        $node1 = $this->createMock(File::class);
99
+        $node1->expects($this->any())
100
+            ->method('getId')
101
+            ->willReturn(111);
102
+        $node2 = $this->createMock(File::class);
103
+        $node2->expects($this->any())
104
+            ->method('getId')
105
+            ->willReturn(222);
106
+
107
+        $expectedCallCount = 0;
108
+        if (count($requestedProperties) > 0) {
109
+            // this guarantees that getTagsForObjects
110
+            // is only called once and then the tags
111
+            // are cached
112
+            $expectedCallCount = 1;
113
+        }
114
+
115
+        $node = $this->createMock(Directory::class);
116
+        $node->expects($this->any())
117
+            ->method('getId')
118
+            ->willReturn(123);
119
+        $node->expects($this->exactly($expectedCallCount))
120
+            ->method('getChildren')
121
+            ->willReturn([$node1, $node2]);
122
+
123
+        $this->tagger->expects($this->exactly($expectedCallCount))
124
+            ->method('getTagsForObjects')
125
+            ->with($this->equalTo([123, 111, 222]))
126
+            ->willReturn(
127
+                [
128
+                    111 => $tags,
129
+                    123 => $tags
130
+                ]
131
+            );
132
+
133
+        // simulate sabre recursive PROPFIND traversal
134
+        $propFindRoot = new \Sabre\DAV\PropFind(
135
+            '/subdir',
136
+            $requestedProperties,
137
+            1
138
+        );
139
+        $propFind1 = new \Sabre\DAV\PropFind(
140
+            '/subdir/test.txt',
141
+            $requestedProperties,
142
+            0
143
+        );
144
+        $propFind2 = new \Sabre\DAV\PropFind(
145
+            '/subdir/test2.txt',
146
+            $requestedProperties,
147
+            0
148
+        );
149
+
150
+        $this->server->emit('preloadCollection', [$propFindRoot, $node]);
151
+
152
+        $this->plugin->handleGetProperties(
153
+            $propFindRoot,
154
+            $node
155
+        );
156
+        $this->plugin->handleGetProperties(
157
+            $propFind1,
158
+            $node1
159
+        );
160
+        $this->plugin->handleGetProperties(
161
+            $propFind2,
162
+            $node2
163
+        );
164
+
165
+        $result = $propFind1->getResultForMultiStatus();
166
+
167
+        $this->assertEmpty($result[404]);
168
+        unset($result[404]);
169
+        $this->assertEquals($expectedProperties, $result);
170
+    }
171
+
172
+    public static function tagsGetPropertiesDataProvider(): array {
173
+        return [
174
+            // request both, receive both
175
+            [
176
+                ['tag1', 'tag2', self::TAG_FAVORITE],
177
+                [self::TAGS_PROPERTYNAME, self::FAVORITE_PROPERTYNAME],
178
+                [
179
+                    200 => [
180
+                        self::TAGS_PROPERTYNAME => new TagList(['tag1', 'tag2']),
181
+                        self::FAVORITE_PROPERTYNAME => true,
182
+                    ]
183
+                ]
184
+            ],
185
+            // request tags alone
186
+            [
187
+                ['tag1', 'tag2', self::TAG_FAVORITE],
188
+                [self::TAGS_PROPERTYNAME],
189
+                [
190
+                    200 => [
191
+                        self::TAGS_PROPERTYNAME => new TagList(['tag1', 'tag2']),
192
+                    ]
193
+                ]
194
+            ],
195
+            // request fav alone
196
+            [
197
+                ['tag1', 'tag2', self::TAG_FAVORITE],
198
+                [self::FAVORITE_PROPERTYNAME],
199
+                [
200
+                    200 => [
201
+                        self::FAVORITE_PROPERTYNAME => true,
202
+                    ]
203
+                ]
204
+            ],
205
+            // request none
206
+            [
207
+                ['tag1', 'tag2', self::TAG_FAVORITE],
208
+                [],
209
+                [
210
+                    200 => []
211
+                ],
212
+            ],
213
+            // request both with none set, receive both
214
+            [
215
+                [],
216
+                [self::TAGS_PROPERTYNAME, self::FAVORITE_PROPERTYNAME],
217
+                [
218
+                    200 => [
219
+                        self::TAGS_PROPERTYNAME => new TagList([]),
220
+                        self::FAVORITE_PROPERTYNAME => false,
221
+                    ]
222
+                ]
223
+            ],
224
+        ];
225
+    }
226
+
227
+    public function testGetPropertiesSkipChunks(): void {
228
+        $sabreNode = $this->createMock(UploadFile::class);
229
+
230
+        $propFind = new \Sabre\DAV\PropFind(
231
+            '/dummyPath',
232
+            [self::TAGS_PROPERTYNAME, self::TAG_FAVORITE],
233
+            0
234
+        );
235
+
236
+        $this->plugin->handleGetProperties(
237
+            $propFind,
238
+            $sabreNode
239
+        );
240
+
241
+        $result = $propFind->getResultForMultiStatus();
242
+        $this->assertCount(2, $result[404]);
243
+    }
244
+
245
+    public function testUpdateTags(): void {
246
+        // this test will replace the existing tags "tagremove" with "tag1" and "tag2"
247
+        // and keep "tagkeep"
248
+        $node = $this->createMock(Node::class);
249
+        $node->expects($this->any())
250
+            ->method('getId')
251
+            ->willReturn(123);
252
+
253
+        $this->tree->expects($this->any())
254
+            ->method('getNodeForPath')
255
+            ->with('/dummypath')
256
+            ->willReturn($node);
257
+
258
+        $this->tagger->expects($this->once())
259
+            ->method('getTagsForObjects')
260
+            ->with($this->equalTo([123]))
261
+            ->willReturn([123 => ['tagkeep', 'tagremove', self::TAG_FAVORITE]]);
262
+
263
+        // then tag as tag1 and tag2
264
+        $calls = [
265
+            [123, 'tag1', '/dummypath'],
266
+            [123, 'tag2', '/dummypath'],
267
+        ];
268
+        $this->tagger->expects($this->exactly(count($calls)))
269
+            ->method('tagAs')
270
+            ->willReturnCallback(function () use (&$calls): void {
271
+                $expected = array_shift($calls);
272
+                $this->assertEquals($expected, func_get_args());
273
+            });
274
+
275
+        // it will untag tag3
276
+        $this->tagger->expects($this->once())
277
+            ->method('unTag')
278
+            ->with(123, 'tagremove');
279
+
280
+        // properties to set
281
+        $propPatch = new \Sabre\DAV\PropPatch([
282
+            self::TAGS_PROPERTYNAME => new TagList(['tag1', 'tag2', 'tagkeep'])
283
+        ]);
284
+
285
+        $this->plugin->handleUpdateProperties(
286
+            '/dummypath',
287
+            $propPatch
288
+        );
289
+
290
+        $propPatch->commit();
291
+
292
+        // all requested properties removed, as they were processed already
293
+        $this->assertEmpty($propPatch->getRemainingMutations());
294
+
295
+        $result = $propPatch->getResult();
296
+        $this->assertEquals(200, $result[self::TAGS_PROPERTYNAME]);
297
+        $this->assertArrayNotHasKey(self::FAVORITE_PROPERTYNAME, $result);
298
+    }
299
+
300
+    public function testUpdateTagsFromScratch(): void {
301
+        $node = $this->createMock(Node::class);
302
+        $node->expects($this->any())
303
+            ->method('getId')
304
+            ->willReturn(123);
305
+
306
+        $this->tree->expects($this->any())
307
+            ->method('getNodeForPath')
308
+            ->with('/dummypath')
309
+            ->willReturn($node);
310
+
311
+        $this->tagger->expects($this->once())
312
+            ->method('getTagsForObjects')
313
+            ->with($this->equalTo([123]))
314
+            ->willReturn([]);
315
+
316
+        // then tag as tag1 and tag2
317
+        $calls = [
318
+            [123, 'tag1', '/dummypath'],
319
+            [123, 'tag2', '/dummypath'],
320
+        ];
321
+        $this->tagger->expects($this->exactly(count($calls)))
322
+            ->method('tagAs')
323
+            ->willReturnCallback(function () use (&$calls): void {
324
+                $expected = array_shift($calls);
325
+                $this->assertEquals($expected, func_get_args());
326
+            });
327
+
328
+        // properties to set
329
+        $propPatch = new \Sabre\DAV\PropPatch([
330
+            self::TAGS_PROPERTYNAME => new TagList(['tag1', 'tag2'])
331
+        ]);
332
+
333
+        $this->plugin->handleUpdateProperties(
334
+            '/dummypath',
335
+            $propPatch
336
+        );
337
+
338
+        $propPatch->commit();
339
+
340
+        // all requested properties removed, as they were processed already
341
+        $this->assertEmpty($propPatch->getRemainingMutations());
342
+
343
+        $result = $propPatch->getResult();
344
+        $this->assertEquals(200, $result[self::TAGS_PROPERTYNAME]);
345
+        $this->assertArrayNotHasKey(self::FAVORITE_PROPERTYNAME, $result);
346
+    }
347
+
348
+    public function testUpdateFav(): void {
349
+        // this test will replace the existing tags "tagremove" with "tag1" and "tag2"
350
+        // and keep "tagkeep"
351
+        $node = $this->createMock(Node::class);
352
+        $node->expects($this->any())
353
+            ->method('getId')
354
+            ->willReturn(123);
355
+
356
+        $this->tree->expects($this->any())
357
+            ->method('getNodeForPath')
358
+            ->with('/dummypath')
359
+            ->willReturn($node);
360
+
361
+        // set favorite tag
362
+        $this->tagger->expects($this->once())
363
+            ->method('tagAs')
364
+            ->with(123, self::TAG_FAVORITE);
365
+
366
+        // properties to set
367
+        $propPatch = new \Sabre\DAV\PropPatch([
368
+            self::FAVORITE_PROPERTYNAME => true
369
+        ]);
370
+
371
+        $this->plugin->handleUpdateProperties(
372
+            '/dummypath',
373
+            $propPatch
374
+        );
375
+
376
+        $propPatch->commit();
377
+
378
+        // all requested properties removed, as they were processed already
379
+        $this->assertEmpty($propPatch->getRemainingMutations());
380
+
381
+        $result = $propPatch->getResult();
382
+        $this->assertArrayNotHasKey(self::TAGS_PROPERTYNAME, $result);
383
+        $this->assertEquals(200, $result[self::FAVORITE_PROPERTYNAME]);
384
+
385
+        // unfavorite now
386
+        // set favorite tag
387
+        $this->tagger->expects($this->once())
388
+            ->method('unTag')
389
+            ->with(123, self::TAG_FAVORITE);
390
+
391
+        // properties to set
392
+        $propPatch = new \Sabre\DAV\PropPatch([
393
+            self::FAVORITE_PROPERTYNAME => false
394
+        ]);
395
+
396
+        $this->plugin->handleUpdateProperties(
397
+            '/dummypath',
398
+            $propPatch
399
+        );
400
+
401
+        $propPatch->commit();
402
+
403
+        // all requested properties removed, as they were processed already
404
+        $this->assertEmpty($propPatch->getRemainingMutations());
405
+
406
+        $result = $propPatch->getResult();
407
+        $this->assertArrayNotHasKey(self::TAGS_PROPERTYNAME, $result);
408
+        $this->assertEquals(200, $result[self::FAVORITE_PROPERTYNAME]);
409
+    }
410 410
 }
Please login to merge, or discard this patch.
tests/lib/TagsTest.php 1 patch
Indentation   +267 added lines, -267 removed lines patch added patch discarded remove patch
@@ -28,271 +28,271 @@
 block discarded – undo
28 28
  * @group DB
29 29
  */
30 30
 class TagsTest extends \Test\TestCase {
31
-	protected $objectType;
32
-	/** @var IUser */
33
-	protected $user;
34
-	/** @var IUserSession */
35
-	protected $userSession;
36
-	protected $backupGlobals = false;
37
-	/** @var \OC\Tagging\TagMapper */
38
-	protected $tagMapper;
39
-	/** @var ITagManager */
40
-	protected $tagMgr;
41
-
42
-	protected function setUp(): void {
43
-		parent::setUp();
44
-
45
-		Server::get(IUserManager::class)->clearBackends();
46
-		Server::get(IUserManager::class)->registerBackend(new \Test\Util\User\Dummy());
47
-		$userId = $this->getUniqueID('user_');
48
-		Server::get(IUserManager::class)->createUser($userId, 'pass');
49
-		\OC_User::setUserId($userId);
50
-		$this->user = $this->createMock(IUser::class);
51
-		$this->user->method('getUID')
52
-			->willReturn($userId);
53
-		$this->userSession = $this->createMock(IUserSession::class);
54
-		$this->userSession
55
-			->expects($this->any())
56
-			->method('getUser')
57
-			->willReturn($this->user);
58
-		$userFolder = $this->createMock(Folder::class);
59
-		$node = $this->createMock(Node::class);
60
-		$this->rootFolder = $this->createMock(IRootFolder::class);
61
-		$this->rootFolder
62
-			->method('getUserFolder')
63
-			->willReturnCallback(fn () => $userFolder);
64
-		$userFolder
65
-			->method('getFirstNodeById')
66
-			->willReturnCallback(fn () => $node);
67
-		$node
68
-			->method('getPath')
69
-			->willReturnCallback(fn () => 'file.txt');
70
-
71
-		$this->objectType = $this->getUniqueID('type_');
72
-		$this->tagMapper = new TagMapper(Server::get(IDBConnection::class));
73
-		$this->tagMgr = new TagManager($this->tagMapper, $this->userSession, Server::get(IDBConnection::class), Server::get(LoggerInterface::class), Server::get(IEventDispatcher::class), $this->rootFolder);
74
-	}
75
-
76
-	protected function tearDown(): void {
77
-		$conn = Server::get(IDBConnection::class);
78
-		$conn->executeQuery('DELETE FROM `*PREFIX*vcategory_to_object`');
79
-		$conn->executeQuery('DELETE FROM `*PREFIX*vcategory`');
80
-
81
-		parent::tearDown();
82
-	}
83
-
84
-	public function testTagManagerWithoutUserReturnsNull(): void {
85
-		$this->userSession = $this->createMock(IUserSession::class);
86
-		$this->userSession
87
-			->expects($this->any())
88
-			->method('getUser')
89
-			->willReturn(null);
90
-		$this->tagMgr = new TagManager($this->tagMapper, $this->userSession, Server::get(IDBConnection::class), Server::get(LoggerInterface::class), Server::get(IEventDispatcher::class), $this->rootFolder);
91
-		$this->assertNull($this->tagMgr->load($this->objectType));
92
-	}
93
-
94
-	public function testInstantiateWithDefaults(): void {
95
-		$defaultTags = ['Friends', 'Family', 'Work', 'Other'];
96
-
97
-		$tagger = $this->tagMgr->load($this->objectType, $defaultTags);
98
-
99
-		$this->assertEquals(4, count($tagger->getTags()));
100
-	}
101
-
102
-	public function testAddTags(): void {
103
-		$tags = ['Friends', 'Family', 'Work', 'Other'];
104
-
105
-		$tagger = $this->tagMgr->load($this->objectType);
106
-
107
-		foreach ($tags as $tag) {
108
-			$result = $tagger->add($tag);
109
-			$this->assertGreaterThan(0, $result, 'add() returned an ID <= 0');
110
-			$this->assertTrue((bool)$result);
111
-		}
112
-
113
-		$this->assertFalse($tagger->add('Family'));
114
-		$this->assertFalse($tagger->add('fAMILY'));
115
-
116
-		$this->assertCount(4, $tagger->getTags(), 'Wrong number of added tags');
117
-	}
118
-
119
-	public function testAddMultiple(): void {
120
-		$tags = ['Friends', 'Family', 'Work', 'Other'];
121
-
122
-		$tagger = $this->tagMgr->load($this->objectType);
123
-
124
-		foreach ($tags as $tag) {
125
-			$this->assertFalse($tagger->hasTag($tag));
126
-		}
127
-
128
-		$result = $tagger->addMultiple($tags);
129
-		$this->assertTrue((bool)$result);
130
-
131
-		foreach ($tags as $tag) {
132
-			$this->assertTrue($tagger->hasTag($tag));
133
-		}
134
-
135
-		$tagMaps = $tagger->getTags();
136
-		$this->assertCount(4, $tagMaps, 'Not all tags added');
137
-		foreach ($tagMaps as $tagMap) {
138
-			$this->assertEquals(null, $tagMap['id']);
139
-		}
140
-
141
-		// As addMultiple has been called without $sync=true, the tags aren't
142
-		// saved to the database, so they're gone when we reload $tagger:
143
-
144
-		$tagger = $this->tagMgr->load($this->objectType);
145
-		$this->assertEquals(0, count($tagger->getTags()));
146
-
147
-		// Now, we call addMultiple() with $sync=true so the tags will be
148
-		// be saved to the database.
149
-		$result = $tagger->addMultiple($tags, true);
150
-		$this->assertTrue((bool)$result);
151
-
152
-		$tagMaps = $tagger->getTags();
153
-		foreach ($tagMaps as $tagMap) {
154
-			$this->assertNotEquals(null, $tagMap['id']);
155
-		}
156
-
157
-		// Reload the tagger.
158
-		$tagger = $this->tagMgr->load($this->objectType);
159
-
160
-		foreach ($tags as $tag) {
161
-			$this->assertTrue($tagger->hasTag($tag));
162
-		}
163
-
164
-		$this->assertCount(4, $tagger->getTags(), 'Not all previously saved tags found');
165
-	}
166
-
167
-	public function testIsEmpty(): void {
168
-		$tagger = $this->tagMgr->load($this->objectType);
169
-
170
-		$this->assertEquals(0, count($tagger->getTags()));
171
-		$this->assertTrue($tagger->isEmpty());
172
-
173
-		$result = $tagger->add('Tag');
174
-		$this->assertGreaterThan(0, $result, 'add() returned an ID <= 0');
175
-		$this->assertNotEquals(false, $result, 'add() returned false');
176
-		$this->assertFalse($tagger->isEmpty());
177
-	}
178
-
179
-	public function testGetTagsForObjects(): void {
180
-		$defaultTags = ['Friends', 'Family', 'Work', 'Other'];
181
-		$tagger = $this->tagMgr->load($this->objectType, $defaultTags);
182
-
183
-		$tagger->tagAs(1, 'Friends');
184
-		$tagger->tagAs(1, 'Other');
185
-		$tagger->tagAs(2, 'Family');
186
-
187
-		$tags = $tagger->getTagsForObjects([1]);
188
-		$this->assertEquals(1, count($tags));
189
-		$tags = current($tags);
190
-		sort($tags);
191
-		$this->assertSame(['Friends', 'Other'], $tags);
192
-
193
-		$tags = $tagger->getTagsForObjects([1, 2]);
194
-		$this->assertEquals(2, count($tags));
195
-		$tags1 = $tags[1];
196
-		sort($tags1);
197
-		$this->assertSame(['Friends', 'Other'], $tags1);
198
-		$this->assertSame(['Family'], $tags[2]);
199
-		$this->assertEquals(
200
-			[],
201
-			$tagger->getTagsForObjects([4])
202
-		);
203
-		$this->assertEquals(
204
-			[],
205
-			$tagger->getTagsForObjects([4, 5])
206
-		);
207
-	}
208
-
209
-	public function testGetTagsForObjectsMassiveResults(): void {
210
-		$defaultTags = ['tag1'];
211
-		$tagger = $this->tagMgr->load($this->objectType, $defaultTags);
212
-		$tagData = $tagger->getTags();
213
-		$tagId = $tagData[0]['id'];
214
-		$tagType = $tagData[0]['type'];
215
-
216
-		$conn = Server::get(IDBConnection::class);
217
-		$statement = $conn->prepare(
218
-			'INSERT INTO `*PREFIX*vcategory_to_object` '
219
-			. '(`objid`, `categoryid`, `type`) VALUES '
220
-			. '(?, ?, ?)'
221
-		);
222
-
223
-		// insert lots of entries
224
-		$idsArray = [];
225
-		for ($i = 1; $i <= 1500; $i++) {
226
-			$statement->execute([$i, $tagId, $tagType]);
227
-			$idsArray[] = $i;
228
-		}
229
-
230
-		$tags = $tagger->getTagsForObjects($idsArray);
231
-		$this->assertEquals(1500, count($tags));
232
-	}
233
-
234
-	public function testDeleteTags(): void {
235
-		$defaultTags = ['Friends', 'Family', 'Work', 'Other'];
236
-		$tagger = $this->tagMgr->load($this->objectType, $defaultTags);
237
-
238
-		$this->assertEquals(4, count($tagger->getTags()));
239
-
240
-		$tagger->delete('family');
241
-		$this->assertEquals(3, count($tagger->getTags()));
242
-
243
-		$tagger->delete(['Friends', 'Work', 'Other']);
244
-		$this->assertEquals(0, count($tagger->getTags()));
245
-	}
246
-
247
-	public function testRenameTag(): void {
248
-		$defaultTags = ['Friends', 'Family', 'Wrok', 'Other'];
249
-		$tagger = $this->tagMgr->load($this->objectType, $defaultTags);
250
-
251
-		$this->assertTrue($tagger->rename('Wrok', 'Work'));
252
-		$this->assertTrue($tagger->hasTag('Work'));
253
-		$this->assertFalse($tagger->hasTag('Wrok'));
254
-		$this->assertFalse($tagger->rename('Wrok', 'Work')); // Rename non-existant tag.
255
-		$this->assertFalse($tagger->rename('Work', 'Family')); // Collide with existing tag.
256
-	}
257
-
258
-	public function testTagAs(): void {
259
-		$objids = [1, 2, 3, 4, 5, 6, 7, 8, 9];
260
-
261
-		$tagger = $this->tagMgr->load($this->objectType);
262
-
263
-		foreach ($objids as $id) {
264
-			$this->assertTrue($tagger->tagAs($id, 'Family'));
265
-		}
266
-
267
-		$this->assertEquals(1, count($tagger->getTags()));
268
-		$this->assertEquals(9, count($tagger->getIdsForTag('Family')));
269
-	}
270
-
271
-	/**
272
-	 * @depends testTagAs
273
-	 */
274
-	public function testUnTag(): void {
275
-		$objIds = [1, 2, 3, 4, 5, 6, 7, 8, 9];
276
-
277
-		// Is this "legal"?
278
-		$this->testTagAs();
279
-		$tagger = $this->tagMgr->load($this->objectType);
280
-
281
-		foreach ($objIds as $id) {
282
-			$this->assertTrue(in_array($id, $tagger->getIdsForTag('Family')));
283
-			$tagger->unTag($id, 'Family');
284
-			$this->assertFalse(in_array($id, $tagger->getIdsForTag('Family')));
285
-		}
286
-
287
-		$this->assertEquals(1, count($tagger->getTags()));
288
-		$this->assertEquals(0, count($tagger->getIdsForTag('Family')));
289
-	}
290
-
291
-	public function testFavorite(): void {
292
-		$tagger = $this->tagMgr->load($this->objectType);
293
-		$this->assertTrue($tagger->addToFavorites(1));
294
-		$this->assertEquals([1], $tagger->getFavorites());
295
-		$this->assertTrue($tagger->removeFromFavorites(1));
296
-		$this->assertEquals([], $tagger->getFavorites());
297
-	}
31
+    protected $objectType;
32
+    /** @var IUser */
33
+    protected $user;
34
+    /** @var IUserSession */
35
+    protected $userSession;
36
+    protected $backupGlobals = false;
37
+    /** @var \OC\Tagging\TagMapper */
38
+    protected $tagMapper;
39
+    /** @var ITagManager */
40
+    protected $tagMgr;
41
+
42
+    protected function setUp(): void {
43
+        parent::setUp();
44
+
45
+        Server::get(IUserManager::class)->clearBackends();
46
+        Server::get(IUserManager::class)->registerBackend(new \Test\Util\User\Dummy());
47
+        $userId = $this->getUniqueID('user_');
48
+        Server::get(IUserManager::class)->createUser($userId, 'pass');
49
+        \OC_User::setUserId($userId);
50
+        $this->user = $this->createMock(IUser::class);
51
+        $this->user->method('getUID')
52
+            ->willReturn($userId);
53
+        $this->userSession = $this->createMock(IUserSession::class);
54
+        $this->userSession
55
+            ->expects($this->any())
56
+            ->method('getUser')
57
+            ->willReturn($this->user);
58
+        $userFolder = $this->createMock(Folder::class);
59
+        $node = $this->createMock(Node::class);
60
+        $this->rootFolder = $this->createMock(IRootFolder::class);
61
+        $this->rootFolder
62
+            ->method('getUserFolder')
63
+            ->willReturnCallback(fn () => $userFolder);
64
+        $userFolder
65
+            ->method('getFirstNodeById')
66
+            ->willReturnCallback(fn () => $node);
67
+        $node
68
+            ->method('getPath')
69
+            ->willReturnCallback(fn () => 'file.txt');
70
+
71
+        $this->objectType = $this->getUniqueID('type_');
72
+        $this->tagMapper = new TagMapper(Server::get(IDBConnection::class));
73
+        $this->tagMgr = new TagManager($this->tagMapper, $this->userSession, Server::get(IDBConnection::class), Server::get(LoggerInterface::class), Server::get(IEventDispatcher::class), $this->rootFolder);
74
+    }
75
+
76
+    protected function tearDown(): void {
77
+        $conn = Server::get(IDBConnection::class);
78
+        $conn->executeQuery('DELETE FROM `*PREFIX*vcategory_to_object`');
79
+        $conn->executeQuery('DELETE FROM `*PREFIX*vcategory`');
80
+
81
+        parent::tearDown();
82
+    }
83
+
84
+    public function testTagManagerWithoutUserReturnsNull(): void {
85
+        $this->userSession = $this->createMock(IUserSession::class);
86
+        $this->userSession
87
+            ->expects($this->any())
88
+            ->method('getUser')
89
+            ->willReturn(null);
90
+        $this->tagMgr = new TagManager($this->tagMapper, $this->userSession, Server::get(IDBConnection::class), Server::get(LoggerInterface::class), Server::get(IEventDispatcher::class), $this->rootFolder);
91
+        $this->assertNull($this->tagMgr->load($this->objectType));
92
+    }
93
+
94
+    public function testInstantiateWithDefaults(): void {
95
+        $defaultTags = ['Friends', 'Family', 'Work', 'Other'];
96
+
97
+        $tagger = $this->tagMgr->load($this->objectType, $defaultTags);
98
+
99
+        $this->assertEquals(4, count($tagger->getTags()));
100
+    }
101
+
102
+    public function testAddTags(): void {
103
+        $tags = ['Friends', 'Family', 'Work', 'Other'];
104
+
105
+        $tagger = $this->tagMgr->load($this->objectType);
106
+
107
+        foreach ($tags as $tag) {
108
+            $result = $tagger->add($tag);
109
+            $this->assertGreaterThan(0, $result, 'add() returned an ID <= 0');
110
+            $this->assertTrue((bool)$result);
111
+        }
112
+
113
+        $this->assertFalse($tagger->add('Family'));
114
+        $this->assertFalse($tagger->add('fAMILY'));
115
+
116
+        $this->assertCount(4, $tagger->getTags(), 'Wrong number of added tags');
117
+    }
118
+
119
+    public function testAddMultiple(): void {
120
+        $tags = ['Friends', 'Family', 'Work', 'Other'];
121
+
122
+        $tagger = $this->tagMgr->load($this->objectType);
123
+
124
+        foreach ($tags as $tag) {
125
+            $this->assertFalse($tagger->hasTag($tag));
126
+        }
127
+
128
+        $result = $tagger->addMultiple($tags);
129
+        $this->assertTrue((bool)$result);
130
+
131
+        foreach ($tags as $tag) {
132
+            $this->assertTrue($tagger->hasTag($tag));
133
+        }
134
+
135
+        $tagMaps = $tagger->getTags();
136
+        $this->assertCount(4, $tagMaps, 'Not all tags added');
137
+        foreach ($tagMaps as $tagMap) {
138
+            $this->assertEquals(null, $tagMap['id']);
139
+        }
140
+
141
+        // As addMultiple has been called without $sync=true, the tags aren't
142
+        // saved to the database, so they're gone when we reload $tagger:
143
+
144
+        $tagger = $this->tagMgr->load($this->objectType);
145
+        $this->assertEquals(0, count($tagger->getTags()));
146
+
147
+        // Now, we call addMultiple() with $sync=true so the tags will be
148
+        // be saved to the database.
149
+        $result = $tagger->addMultiple($tags, true);
150
+        $this->assertTrue((bool)$result);
151
+
152
+        $tagMaps = $tagger->getTags();
153
+        foreach ($tagMaps as $tagMap) {
154
+            $this->assertNotEquals(null, $tagMap['id']);
155
+        }
156
+
157
+        // Reload the tagger.
158
+        $tagger = $this->tagMgr->load($this->objectType);
159
+
160
+        foreach ($tags as $tag) {
161
+            $this->assertTrue($tagger->hasTag($tag));
162
+        }
163
+
164
+        $this->assertCount(4, $tagger->getTags(), 'Not all previously saved tags found');
165
+    }
166
+
167
+    public function testIsEmpty(): void {
168
+        $tagger = $this->tagMgr->load($this->objectType);
169
+
170
+        $this->assertEquals(0, count($tagger->getTags()));
171
+        $this->assertTrue($tagger->isEmpty());
172
+
173
+        $result = $tagger->add('Tag');
174
+        $this->assertGreaterThan(0, $result, 'add() returned an ID <= 0');
175
+        $this->assertNotEquals(false, $result, 'add() returned false');
176
+        $this->assertFalse($tagger->isEmpty());
177
+    }
178
+
179
+    public function testGetTagsForObjects(): void {
180
+        $defaultTags = ['Friends', 'Family', 'Work', 'Other'];
181
+        $tagger = $this->tagMgr->load($this->objectType, $defaultTags);
182
+
183
+        $tagger->tagAs(1, 'Friends');
184
+        $tagger->tagAs(1, 'Other');
185
+        $tagger->tagAs(2, 'Family');
186
+
187
+        $tags = $tagger->getTagsForObjects([1]);
188
+        $this->assertEquals(1, count($tags));
189
+        $tags = current($tags);
190
+        sort($tags);
191
+        $this->assertSame(['Friends', 'Other'], $tags);
192
+
193
+        $tags = $tagger->getTagsForObjects([1, 2]);
194
+        $this->assertEquals(2, count($tags));
195
+        $tags1 = $tags[1];
196
+        sort($tags1);
197
+        $this->assertSame(['Friends', 'Other'], $tags1);
198
+        $this->assertSame(['Family'], $tags[2]);
199
+        $this->assertEquals(
200
+            [],
201
+            $tagger->getTagsForObjects([4])
202
+        );
203
+        $this->assertEquals(
204
+            [],
205
+            $tagger->getTagsForObjects([4, 5])
206
+        );
207
+    }
208
+
209
+    public function testGetTagsForObjectsMassiveResults(): void {
210
+        $defaultTags = ['tag1'];
211
+        $tagger = $this->tagMgr->load($this->objectType, $defaultTags);
212
+        $tagData = $tagger->getTags();
213
+        $tagId = $tagData[0]['id'];
214
+        $tagType = $tagData[0]['type'];
215
+
216
+        $conn = Server::get(IDBConnection::class);
217
+        $statement = $conn->prepare(
218
+            'INSERT INTO `*PREFIX*vcategory_to_object` '
219
+            . '(`objid`, `categoryid`, `type`) VALUES '
220
+            . '(?, ?, ?)'
221
+        );
222
+
223
+        // insert lots of entries
224
+        $idsArray = [];
225
+        for ($i = 1; $i <= 1500; $i++) {
226
+            $statement->execute([$i, $tagId, $tagType]);
227
+            $idsArray[] = $i;
228
+        }
229
+
230
+        $tags = $tagger->getTagsForObjects($idsArray);
231
+        $this->assertEquals(1500, count($tags));
232
+    }
233
+
234
+    public function testDeleteTags(): void {
235
+        $defaultTags = ['Friends', 'Family', 'Work', 'Other'];
236
+        $tagger = $this->tagMgr->load($this->objectType, $defaultTags);
237
+
238
+        $this->assertEquals(4, count($tagger->getTags()));
239
+
240
+        $tagger->delete('family');
241
+        $this->assertEquals(3, count($tagger->getTags()));
242
+
243
+        $tagger->delete(['Friends', 'Work', 'Other']);
244
+        $this->assertEquals(0, count($tagger->getTags()));
245
+    }
246
+
247
+    public function testRenameTag(): void {
248
+        $defaultTags = ['Friends', 'Family', 'Wrok', 'Other'];
249
+        $tagger = $this->tagMgr->load($this->objectType, $defaultTags);
250
+
251
+        $this->assertTrue($tagger->rename('Wrok', 'Work'));
252
+        $this->assertTrue($tagger->hasTag('Work'));
253
+        $this->assertFalse($tagger->hasTag('Wrok'));
254
+        $this->assertFalse($tagger->rename('Wrok', 'Work')); // Rename non-existant tag.
255
+        $this->assertFalse($tagger->rename('Work', 'Family')); // Collide with existing tag.
256
+    }
257
+
258
+    public function testTagAs(): void {
259
+        $objids = [1, 2, 3, 4, 5, 6, 7, 8, 9];
260
+
261
+        $tagger = $this->tagMgr->load($this->objectType);
262
+
263
+        foreach ($objids as $id) {
264
+            $this->assertTrue($tagger->tagAs($id, 'Family'));
265
+        }
266
+
267
+        $this->assertEquals(1, count($tagger->getTags()));
268
+        $this->assertEquals(9, count($tagger->getIdsForTag('Family')));
269
+    }
270
+
271
+    /**
272
+     * @depends testTagAs
273
+     */
274
+    public function testUnTag(): void {
275
+        $objIds = [1, 2, 3, 4, 5, 6, 7, 8, 9];
276
+
277
+        // Is this "legal"?
278
+        $this->testTagAs();
279
+        $tagger = $this->tagMgr->load($this->objectType);
280
+
281
+        foreach ($objIds as $id) {
282
+            $this->assertTrue(in_array($id, $tagger->getIdsForTag('Family')));
283
+            $tagger->unTag($id, 'Family');
284
+            $this->assertFalse(in_array($id, $tagger->getIdsForTag('Family')));
285
+        }
286
+
287
+        $this->assertEquals(1, count($tagger->getTags()));
288
+        $this->assertEquals(0, count($tagger->getIdsForTag('Family')));
289
+    }
290
+
291
+    public function testFavorite(): void {
292
+        $tagger = $this->tagMgr->load($this->objectType);
293
+        $this->assertTrue($tagger->addToFavorites(1));
294
+        $this->assertEquals([1], $tagger->getFavorites());
295
+        $this->assertTrue($tagger->removeFromFavorites(1));
296
+        $this->assertEquals([], $tagger->getFavorites());
297
+    }
298 298
 }
Please login to merge, or discard this patch.