Completed
Push — master ( e67f8e...cddd66 )
by
unknown
29:25
created
lib/private/Group/Database.php 1 patch
Indentation   +574 added lines, -574 removed lines patch added patch discarded remove patch
@@ -30,578 +30,578 @@
 block discarded – undo
30 30
  * Class for group management in a SQL Database (e.g. MySQL, SQLite)
31 31
  */
32 32
 class Database extends ABackend implements
33
-	IAddToGroupBackend,
34
-	ICountDisabledInGroup,
35
-	ICountUsersBackend,
36
-	ICreateNamedGroupBackend,
37
-	IDeleteGroupBackend,
38
-	IGetDisplayNameBackend,
39
-	IGroupDetailsBackend,
40
-	IRemoveFromGroupBackend,
41
-	ISetDisplayNameBackend,
42
-	ISearchableGroupBackend,
43
-	IBatchMethodsBackend,
44
-	INamedBackend {
45
-	/** @var array<string, array{gid: string, displayname: string}> */
46
-	private $groupCache = [];
47
-
48
-	/**
49
-	 * \OC\Group\Database constructor.
50
-	 *
51
-	 * @param IDBConnection|null $dbConn
52
-	 */
53
-	public function __construct(
54
-		private ?IDBConnection $dbConn = null,
55
-	) {
56
-	}
57
-
58
-	/**
59
-	 * FIXME: This function should not be required!
60
-	 */
61
-	private function fixDI() {
62
-		if ($this->dbConn === null) {
63
-			$this->dbConn = \OC::$server->getDatabaseConnection();
64
-		}
65
-	}
66
-
67
-	public function createGroup(string $name): ?string {
68
-		$this->fixDI();
69
-
70
-		$name = $this->sanitizeGroupName($name);
71
-		$gid = $this->computeGid($name);
72
-		try {
73
-			// Add group
74
-			$builder = $this->dbConn->getQueryBuilder();
75
-			$builder->insert('groups')
76
-				->setValue('gid', $builder->createNamedParameter($gid))
77
-				->setValue('displayname', $builder->createNamedParameter($name))
78
-				->executeStatement();
79
-		} catch (Exception $e) {
80
-			if ($e->getReason() === Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
81
-				return null;
82
-			} else {
83
-				throw $e;
84
-			}
85
-		}
86
-
87
-		// Add to cache
88
-		$this->groupCache[$gid] = [
89
-			'gid' => $gid,
90
-			'displayname' => $name
91
-		];
92
-
93
-		return $gid;
94
-	}
95
-
96
-	/**
97
-	 * delete a group
98
-	 * @param string $gid gid of the group to delete
99
-	 * @return bool
100
-	 *
101
-	 * Deletes a group and removes it from the group_user-table
102
-	 */
103
-	public function deleteGroup(string $gid): bool {
104
-		$this->fixDI();
105
-
106
-		// Delete the group
107
-		$qb = $this->dbConn->getQueryBuilder();
108
-		$qb->delete('groups')
109
-			->where($qb->expr()->eq('gid', $qb->createNamedParameter($gid)))
110
-			->executeStatement();
111
-
112
-		// Delete the group-user relation
113
-		$qb = $this->dbConn->getQueryBuilder();
114
-		$qb->delete('group_user')
115
-			->where($qb->expr()->eq('gid', $qb->createNamedParameter($gid)))
116
-			->executeStatement();
117
-
118
-		// Delete the group-groupadmin relation
119
-		$qb = $this->dbConn->getQueryBuilder();
120
-		$qb->delete('group_admin')
121
-			->where($qb->expr()->eq('gid', $qb->createNamedParameter($gid)))
122
-			->executeStatement();
123
-
124
-		// Delete from cache
125
-		unset($this->groupCache[$gid]);
126
-
127
-		return true;
128
-	}
129
-
130
-	/**
131
-	 * is user in group?
132
-	 * @param string $uid uid of the user
133
-	 * @param string $gid gid of the group
134
-	 * @return bool
135
-	 *
136
-	 * Checks whether the user is member of a group or not.
137
-	 */
138
-	public function inGroup($uid, $gid) {
139
-		$this->fixDI();
140
-
141
-		// check
142
-		$qb = $this->dbConn->getQueryBuilder();
143
-		$cursor = $qb->select('uid')
144
-			->from('group_user')
145
-			->where($qb->expr()->eq('gid', $qb->createNamedParameter($gid)))
146
-			->andWhere($qb->expr()->eq('uid', $qb->createNamedParameter($uid)))
147
-			->executeQuery();
148
-
149
-		$result = $cursor->fetch();
150
-		$cursor->closeCursor();
151
-
152
-		return $result ? true : false;
153
-	}
154
-
155
-	/**
156
-	 * Add a user to a group
157
-	 * @param string $uid Name of the user to add to group
158
-	 * @param string $gid Name of the group in which add the user
159
-	 * @return bool
160
-	 *
161
-	 * Adds a user to a group.
162
-	 */
163
-	public function addToGroup(string $uid, string $gid): bool {
164
-		$this->fixDI();
165
-
166
-		// No duplicate entries!
167
-		if (!$this->inGroup($uid, $gid)) {
168
-			$qb = $this->dbConn->getQueryBuilder();
169
-			$qb->insert('group_user')
170
-				->setValue('uid', $qb->createNamedParameter($uid))
171
-				->setValue('gid', $qb->createNamedParameter($gid))
172
-				->executeStatement();
173
-			return true;
174
-		} else {
175
-			return false;
176
-		}
177
-	}
178
-
179
-	/**
180
-	 * Removes a user from a group
181
-	 * @param string $uid Name of the user to remove from group
182
-	 * @param string $gid Name of the group from which remove the user
183
-	 * @return bool
184
-	 *
185
-	 * removes the user from a group.
186
-	 */
187
-	public function removeFromGroup(string $uid, string $gid): bool {
188
-		$this->fixDI();
189
-
190
-		$qb = $this->dbConn->getQueryBuilder();
191
-		$qb->delete('group_user')
192
-			->where($qb->expr()->eq('uid', $qb->createNamedParameter($uid)))
193
-			->andWhere($qb->expr()->eq('gid', $qb->createNamedParameter($gid)))
194
-			->executeStatement();
195
-
196
-		return true;
197
-	}
198
-
199
-	/**
200
-	 * Get all groups a user belongs to
201
-	 * @param string $uid Name of the user
202
-	 * @return list<string> an array of group names
203
-	 *
204
-	 * This function fetches all groups a user belongs to. It does not check
205
-	 * if the user exists at all.
206
-	 */
207
-	public function getUserGroups($uid) {
208
-		//guests has empty or null $uid
209
-		if ($uid === null || $uid === '') {
210
-			return [];
211
-		}
212
-
213
-		$this->fixDI();
214
-
215
-		// No magic!
216
-		$qb = $this->dbConn->getQueryBuilder();
217
-		$cursor = $qb->select('gu.gid', 'g.displayname')
218
-			->from('group_user', 'gu')
219
-			->leftJoin('gu', 'groups', 'g', $qb->expr()->eq('gu.gid', 'g.gid'))
220
-			->where($qb->expr()->eq('uid', $qb->createNamedParameter($uid)))
221
-			->executeQuery();
222
-
223
-		$groups = [];
224
-		while ($row = $cursor->fetch()) {
225
-			$groups[] = $row['gid'];
226
-			$this->groupCache[$row['gid']] = [
227
-				'gid' => $row['gid'],
228
-				'displayname' => $row['displayname'],
229
-			];
230
-		}
231
-		$cursor->closeCursor();
232
-
233
-		return $groups;
234
-	}
235
-
236
-	/**
237
-	 * get a list of all groups
238
-	 * @param string $search
239
-	 * @param int $limit
240
-	 * @param int $offset
241
-	 * @return array an array of group names
242
-	 *
243
-	 * Returns a list with all groups
244
-	 */
245
-	public function getGroups(string $search = '', int $limit = -1, int $offset = 0) {
246
-		$this->fixDI();
247
-
248
-		$query = $this->dbConn->getQueryBuilder();
249
-		$query->select('gid', 'displayname')
250
-			->from('groups')
251
-			->orderBy('gid', 'ASC');
252
-
253
-		if ($search !== '') {
254
-			$query->where($query->expr()->iLike('gid', $query->createNamedParameter(
255
-				'%' . $this->dbConn->escapeLikeParameter($search) . '%'
256
-			)));
257
-			$query->orWhere($query->expr()->iLike('displayname', $query->createNamedParameter(
258
-				'%' . $this->dbConn->escapeLikeParameter($search) . '%'
259
-			)));
260
-		}
261
-
262
-		if ($limit > 0) {
263
-			$query->setMaxResults($limit);
264
-		}
265
-		if ($offset > 0) {
266
-			$query->setFirstResult($offset);
267
-		}
268
-		$result = $query->executeQuery();
269
-
270
-		$groups = [];
271
-		while ($row = $result->fetch()) {
272
-			$this->groupCache[$row['gid']] = [
273
-				'displayname' => $row['displayname'],
274
-				'gid' => $row['gid'],
275
-			];
276
-			$groups[] = $row['gid'];
277
-		}
278
-		$result->closeCursor();
279
-
280
-		return $groups;
281
-	}
282
-
283
-	/**
284
-	 * check if a group exists
285
-	 * @param string $gid
286
-	 * @return bool
287
-	 */
288
-	public function groupExists($gid) {
289
-		$this->fixDI();
290
-
291
-		// Check cache first
292
-		if (isset($this->groupCache[$gid])) {
293
-			return true;
294
-		}
295
-
296
-		$qb = $this->dbConn->getQueryBuilder();
297
-		$cursor = $qb->select('gid', 'displayname')
298
-			->from('groups')
299
-			->where($qb->expr()->eq('gid', $qb->createNamedParameter($gid)))
300
-			->executeQuery();
301
-		$result = $cursor->fetch();
302
-		$cursor->closeCursor();
303
-
304
-		if ($result !== false) {
305
-			$this->groupCache[$gid] = [
306
-				'gid' => $gid,
307
-				'displayname' => $result['displayname'],
308
-			];
309
-			return true;
310
-		}
311
-		return false;
312
-	}
313
-
314
-	/**
315
-	 * {@inheritdoc}
316
-	 */
317
-	public function groupsExists(array $gids): array {
318
-		$notFoundGids = [];
319
-		$existingGroups = [];
320
-
321
-		// In case the data is already locally accessible, not need to do SQL query
322
-		// or do a SQL query but with a smaller in clause
323
-		foreach ($gids as $gid) {
324
-			if (isset($this->groupCache[$gid])) {
325
-				$existingGroups[] = $gid;
326
-			} else {
327
-				$notFoundGids[] = $gid;
328
-			}
329
-		}
330
-
331
-		$qb = $this->dbConn->getQueryBuilder();
332
-		$qb->select('gid', 'displayname')
333
-			->from('groups')
334
-			->where($qb->expr()->in('gid', $qb->createParameter('ids')));
335
-		foreach (array_chunk($notFoundGids, 1000) as $chunk) {
336
-			$qb->setParameter('ids', $chunk, IQueryBuilder::PARAM_STR_ARRAY);
337
-			$result = $qb->executeQuery();
338
-			while ($row = $result->fetch()) {
339
-				$this->groupCache[(string)$row['gid']] = [
340
-					'displayname' => (string)$row['displayname'],
341
-					'gid' => (string)$row['gid'],
342
-				];
343
-				$existingGroups[] = (string)$row['gid'];
344
-			}
345
-			$result->closeCursor();
346
-		}
347
-
348
-		return $existingGroups;
349
-	}
350
-
351
-	/**
352
-	 * Get a list of all users in a group
353
-	 * @param string $gid
354
-	 * @param string $search
355
-	 * @param int $limit
356
-	 * @param int $offset
357
-	 * @return array<int,string> an array of user ids
358
-	 */
359
-	public function usersInGroup($gid, $search = '', $limit = -1, $offset = 0): array {
360
-		return array_values(array_map(fn ($user) => $user->getUid(), $this->searchInGroup($gid, $search, $limit, $offset)));
361
-	}
362
-
363
-	public function searchInGroup(string $gid, string $search = '', int $limit = -1, int $offset = 0): array {
364
-		$this->fixDI();
365
-
366
-		$query = $this->dbConn->getQueryBuilder();
367
-		$query->select('g.uid', 'dn.value AS displayname');
368
-
369
-		$query->from('group_user', 'g')
370
-			->where($query->expr()->eq('gid', $query->createNamedParameter($gid)))
371
-			->orderBy('g.uid', 'ASC');
372
-
373
-		// Join displayname and email from oc_accounts_data
374
-		$query->leftJoin('g', 'accounts_data', 'dn',
375
-			$query->expr()->andX(
376
-				$query->expr()->eq('dn.uid', 'g.uid'),
377
-				$query->expr()->eq('dn.name', $query->expr()->literal('displayname'))
378
-			)
379
-		);
380
-
381
-		$query->leftJoin('g', 'accounts_data', 'em',
382
-			$query->expr()->andX(
383
-				$query->expr()->eq('em.uid', 'g.uid'),
384
-				$query->expr()->eq('em.name', $query->expr()->literal('email'))
385
-			)
386
-		);
387
-
388
-		if ($search !== '') {
389
-			// sqlite doesn't like re-using a single named parameter here
390
-			$searchParam1 = $query->createNamedParameter('%' . $this->dbConn->escapeLikeParameter($search) . '%');
391
-			$searchParam2 = $query->createNamedParameter('%' . $this->dbConn->escapeLikeParameter($search) . '%');
392
-			$searchParam3 = $query->createNamedParameter('%' . $this->dbConn->escapeLikeParameter($search) . '%');
393
-
394
-			$query->andWhere(
395
-				$query->expr()->orX(
396
-					$query->expr()->ilike('g.uid', $searchParam1),
397
-					$query->expr()->ilike('dn.value', $searchParam2),
398
-					$query->expr()->ilike('em.value', $searchParam3)
399
-				)
400
-			)
401
-				->orderBy('g.uid', 'ASC');
402
-		}
403
-
404
-
405
-		if ($limit !== -1) {
406
-			$query->setMaxResults($limit);
407
-		}
408
-		if ($offset !== 0) {
409
-			$query->setFirstResult($offset);
410
-		}
411
-
412
-		$result = $query->executeQuery();
413
-
414
-		$users = [];
415
-		$userManager = \OCP\Server::get(IUserManager::class);
416
-		while ($row = $result->fetch()) {
417
-			$users[$row['uid']] = new LazyUser($row['uid'], $userManager, $row['displayname'] ?? null);
418
-		}
419
-		$result->closeCursor();
420
-
421
-		return $users;
422
-	}
423
-
424
-	/**
425
-	 * get the number of all users matching the search string in a group
426
-	 * @param string $gid
427
-	 * @param string $search
428
-	 * @return int
429
-	 */
430
-	public function countUsersInGroup(string $gid, string $search = ''): int {
431
-		$this->fixDI();
432
-
433
-		$query = $this->dbConn->getQueryBuilder();
434
-		$query->select($query->func()->count('*', 'num_users'))
435
-			->from('group_user')
436
-			->where($query->expr()->eq('gid', $query->createNamedParameter($gid)));
437
-
438
-		if ($search !== '') {
439
-			$query->andWhere($query->expr()->like('uid', $query->createNamedParameter(
440
-				'%' . $this->dbConn->escapeLikeParameter($search) . '%'
441
-			)));
442
-		}
443
-
444
-		$result = $query->executeQuery();
445
-		$count = $result->fetchOne();
446
-		$result->closeCursor();
447
-
448
-		if ($count !== false) {
449
-			$count = (int)$count;
450
-		} else {
451
-			$count = 0;
452
-		}
453
-
454
-		return $count;
455
-	}
456
-
457
-	/**
458
-	 * get the number of disabled users in a group
459
-	 *
460
-	 * @param string $search
461
-	 *
462
-	 * @return int
463
-	 */
464
-	public function countDisabledInGroup(string $gid): int {
465
-		$this->fixDI();
466
-
467
-		$query = $this->dbConn->getQueryBuilder();
468
-		$query->select($query->createFunction('COUNT(DISTINCT ' . $query->getColumnName('uid') . ')'))
469
-			->from('preferences', 'p')
470
-			->innerJoin('p', 'group_user', 'g', $query->expr()->eq('p.userid', 'g.uid'))
471
-			->where($query->expr()->eq('appid', $query->createNamedParameter('core')))
472
-			->andWhere($query->expr()->eq('configkey', $query->createNamedParameter('enabled')))
473
-			->andWhere($query->expr()->eq('configvalue', $query->createNamedParameter('false'), IQueryBuilder::PARAM_STR))
474
-			->andWhere($query->expr()->eq('gid', $query->createNamedParameter($gid), IQueryBuilder::PARAM_STR));
475
-
476
-		$result = $query->executeQuery();
477
-		$count = $result->fetchOne();
478
-		$result->closeCursor();
479
-
480
-		if ($count !== false) {
481
-			$count = (int)$count;
482
-		} else {
483
-			$count = 0;
484
-		}
485
-
486
-		return $count;
487
-	}
488
-
489
-	public function getDisplayName(string $gid): string {
490
-		if (isset($this->groupCache[$gid])) {
491
-			$displayName = $this->groupCache[$gid]['displayname'];
492
-
493
-			if (isset($displayName) && trim($displayName) !== '') {
494
-				return $displayName;
495
-			}
496
-		}
497
-
498
-		$this->fixDI();
499
-
500
-		$query = $this->dbConn->getQueryBuilder();
501
-		$query->select('displayname')
502
-			->from('groups')
503
-			->where($query->expr()->eq('gid', $query->createNamedParameter($gid)));
504
-
505
-		$result = $query->executeQuery();
506
-		$displayName = $result->fetchOne();
507
-		$result->closeCursor();
508
-
509
-		return (string)$displayName;
510
-	}
511
-
512
-	public function getGroupDetails(string $gid): array {
513
-		$displayName = $this->getDisplayName($gid);
514
-		if ($displayName !== '') {
515
-			return ['displayName' => $displayName];
516
-		}
517
-
518
-		return [];
519
-	}
520
-
521
-	/**
522
-	 * {@inheritdoc}
523
-	 */
524
-	public function getGroupsDetails(array $gids): array {
525
-		$notFoundGids = [];
526
-		$details = [];
527
-
528
-		$this->fixDI();
529
-
530
-		// In case the data is already locally accessible, not need to do SQL query
531
-		// or do a SQL query but with a smaller in clause
532
-		foreach ($gids as $gid) {
533
-			if (isset($this->groupCache[$gid])) {
534
-				$details[$gid] = ['displayName' => $this->groupCache[$gid]['displayname']];
535
-			} else {
536
-				$notFoundGids[] = $gid;
537
-			}
538
-		}
539
-
540
-		foreach (array_chunk($notFoundGids, 1000) as $chunk) {
541
-			$query = $this->dbConn->getQueryBuilder();
542
-			$query->select('gid', 'displayname')
543
-				->from('groups')
544
-				->where($query->expr()->in('gid', $query->createNamedParameter($chunk, IQueryBuilder::PARAM_STR_ARRAY)));
545
-
546
-			$result = $query->executeQuery();
547
-			while ($row = $result->fetch()) {
548
-				$details[(string)$row['gid']] = ['displayName' => (string)$row['displayname']];
549
-				$this->groupCache[(string)$row['gid']] = [
550
-					'displayname' => (string)$row['displayname'],
551
-					'gid' => (string)$row['gid'],
552
-				];
553
-			}
554
-			$result->closeCursor();
555
-		}
556
-
557
-		return $details;
558
-	}
559
-
560
-	public function setDisplayName(string $gid, string $displayName): bool {
561
-		if (!$this->groupExists($gid)) {
562
-			return false;
563
-		}
564
-
565
-		$this->fixDI();
566
-
567
-		$displayName = trim($displayName);
568
-		if ($displayName === '') {
569
-			$displayName = $gid;
570
-		}
571
-
572
-		$query = $this->dbConn->getQueryBuilder();
573
-		$query->update('groups')
574
-			->set('displayname', $query->createNamedParameter($displayName))
575
-			->where($query->expr()->eq('gid', $query->createNamedParameter($gid)));
576
-		$query->executeStatement();
577
-
578
-		return true;
579
-	}
580
-
581
-	/**
582
-	 * Backend name to be shown in group management
583
-	 * @return string the name of the backend to be shown
584
-	 * @since 21.0.0
585
-	 */
586
-	public function getBackendName(): string {
587
-		return 'Database';
588
-	}
589
-
590
-	/**
591
-	 * Merge any white spaces to a single space in group name, then trim it.
592
-	 */
593
-	private function sanitizeGroupName(string $displayName): string {
594
-		$cleanedDisplayName = preg_replace('/\s+/', ' ', $displayName);
595
-		return trim($cleanedDisplayName);
596
-	}
597
-
598
-	/**
599
-	 * Compute group ID from display name (GIDs are limited to 64 characters in database)
600
-	 */
601
-	private function computeGid(string $displayName): string {
602
-		$displayNameWithoutWhitespace = preg_replace('/\s+/', '_', $displayName);
603
-		return mb_strlen($displayNameWithoutWhitespace) > 64
604
-			? hash('sha256', $displayNameWithoutWhitespace)
605
-			: $displayNameWithoutWhitespace;
606
-	}
33
+    IAddToGroupBackend,
34
+    ICountDisabledInGroup,
35
+    ICountUsersBackend,
36
+    ICreateNamedGroupBackend,
37
+    IDeleteGroupBackend,
38
+    IGetDisplayNameBackend,
39
+    IGroupDetailsBackend,
40
+    IRemoveFromGroupBackend,
41
+    ISetDisplayNameBackend,
42
+    ISearchableGroupBackend,
43
+    IBatchMethodsBackend,
44
+    INamedBackend {
45
+    /** @var array<string, array{gid: string, displayname: string}> */
46
+    private $groupCache = [];
47
+
48
+    /**
49
+     * \OC\Group\Database constructor.
50
+     *
51
+     * @param IDBConnection|null $dbConn
52
+     */
53
+    public function __construct(
54
+        private ?IDBConnection $dbConn = null,
55
+    ) {
56
+    }
57
+
58
+    /**
59
+     * FIXME: This function should not be required!
60
+     */
61
+    private function fixDI() {
62
+        if ($this->dbConn === null) {
63
+            $this->dbConn = \OC::$server->getDatabaseConnection();
64
+        }
65
+    }
66
+
67
+    public function createGroup(string $name): ?string {
68
+        $this->fixDI();
69
+
70
+        $name = $this->sanitizeGroupName($name);
71
+        $gid = $this->computeGid($name);
72
+        try {
73
+            // Add group
74
+            $builder = $this->dbConn->getQueryBuilder();
75
+            $builder->insert('groups')
76
+                ->setValue('gid', $builder->createNamedParameter($gid))
77
+                ->setValue('displayname', $builder->createNamedParameter($name))
78
+                ->executeStatement();
79
+        } catch (Exception $e) {
80
+            if ($e->getReason() === Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
81
+                return null;
82
+            } else {
83
+                throw $e;
84
+            }
85
+        }
86
+
87
+        // Add to cache
88
+        $this->groupCache[$gid] = [
89
+            'gid' => $gid,
90
+            'displayname' => $name
91
+        ];
92
+
93
+        return $gid;
94
+    }
95
+
96
+    /**
97
+     * delete a group
98
+     * @param string $gid gid of the group to delete
99
+     * @return bool
100
+     *
101
+     * Deletes a group and removes it from the group_user-table
102
+     */
103
+    public function deleteGroup(string $gid): bool {
104
+        $this->fixDI();
105
+
106
+        // Delete the group
107
+        $qb = $this->dbConn->getQueryBuilder();
108
+        $qb->delete('groups')
109
+            ->where($qb->expr()->eq('gid', $qb->createNamedParameter($gid)))
110
+            ->executeStatement();
111
+
112
+        // Delete the group-user relation
113
+        $qb = $this->dbConn->getQueryBuilder();
114
+        $qb->delete('group_user')
115
+            ->where($qb->expr()->eq('gid', $qb->createNamedParameter($gid)))
116
+            ->executeStatement();
117
+
118
+        // Delete the group-groupadmin relation
119
+        $qb = $this->dbConn->getQueryBuilder();
120
+        $qb->delete('group_admin')
121
+            ->where($qb->expr()->eq('gid', $qb->createNamedParameter($gid)))
122
+            ->executeStatement();
123
+
124
+        // Delete from cache
125
+        unset($this->groupCache[$gid]);
126
+
127
+        return true;
128
+    }
129
+
130
+    /**
131
+     * is user in group?
132
+     * @param string $uid uid of the user
133
+     * @param string $gid gid of the group
134
+     * @return bool
135
+     *
136
+     * Checks whether the user is member of a group or not.
137
+     */
138
+    public function inGroup($uid, $gid) {
139
+        $this->fixDI();
140
+
141
+        // check
142
+        $qb = $this->dbConn->getQueryBuilder();
143
+        $cursor = $qb->select('uid')
144
+            ->from('group_user')
145
+            ->where($qb->expr()->eq('gid', $qb->createNamedParameter($gid)))
146
+            ->andWhere($qb->expr()->eq('uid', $qb->createNamedParameter($uid)))
147
+            ->executeQuery();
148
+
149
+        $result = $cursor->fetch();
150
+        $cursor->closeCursor();
151
+
152
+        return $result ? true : false;
153
+    }
154
+
155
+    /**
156
+     * Add a user to a group
157
+     * @param string $uid Name of the user to add to group
158
+     * @param string $gid Name of the group in which add the user
159
+     * @return bool
160
+     *
161
+     * Adds a user to a group.
162
+     */
163
+    public function addToGroup(string $uid, string $gid): bool {
164
+        $this->fixDI();
165
+
166
+        // No duplicate entries!
167
+        if (!$this->inGroup($uid, $gid)) {
168
+            $qb = $this->dbConn->getQueryBuilder();
169
+            $qb->insert('group_user')
170
+                ->setValue('uid', $qb->createNamedParameter($uid))
171
+                ->setValue('gid', $qb->createNamedParameter($gid))
172
+                ->executeStatement();
173
+            return true;
174
+        } else {
175
+            return false;
176
+        }
177
+    }
178
+
179
+    /**
180
+     * Removes a user from a group
181
+     * @param string $uid Name of the user to remove from group
182
+     * @param string $gid Name of the group from which remove the user
183
+     * @return bool
184
+     *
185
+     * removes the user from a group.
186
+     */
187
+    public function removeFromGroup(string $uid, string $gid): bool {
188
+        $this->fixDI();
189
+
190
+        $qb = $this->dbConn->getQueryBuilder();
191
+        $qb->delete('group_user')
192
+            ->where($qb->expr()->eq('uid', $qb->createNamedParameter($uid)))
193
+            ->andWhere($qb->expr()->eq('gid', $qb->createNamedParameter($gid)))
194
+            ->executeStatement();
195
+
196
+        return true;
197
+    }
198
+
199
+    /**
200
+     * Get all groups a user belongs to
201
+     * @param string $uid Name of the user
202
+     * @return list<string> an array of group names
203
+     *
204
+     * This function fetches all groups a user belongs to. It does not check
205
+     * if the user exists at all.
206
+     */
207
+    public function getUserGroups($uid) {
208
+        //guests has empty or null $uid
209
+        if ($uid === null || $uid === '') {
210
+            return [];
211
+        }
212
+
213
+        $this->fixDI();
214
+
215
+        // No magic!
216
+        $qb = $this->dbConn->getQueryBuilder();
217
+        $cursor = $qb->select('gu.gid', 'g.displayname')
218
+            ->from('group_user', 'gu')
219
+            ->leftJoin('gu', 'groups', 'g', $qb->expr()->eq('gu.gid', 'g.gid'))
220
+            ->where($qb->expr()->eq('uid', $qb->createNamedParameter($uid)))
221
+            ->executeQuery();
222
+
223
+        $groups = [];
224
+        while ($row = $cursor->fetch()) {
225
+            $groups[] = $row['gid'];
226
+            $this->groupCache[$row['gid']] = [
227
+                'gid' => $row['gid'],
228
+                'displayname' => $row['displayname'],
229
+            ];
230
+        }
231
+        $cursor->closeCursor();
232
+
233
+        return $groups;
234
+    }
235
+
236
+    /**
237
+     * get a list of all groups
238
+     * @param string $search
239
+     * @param int $limit
240
+     * @param int $offset
241
+     * @return array an array of group names
242
+     *
243
+     * Returns a list with all groups
244
+     */
245
+    public function getGroups(string $search = '', int $limit = -1, int $offset = 0) {
246
+        $this->fixDI();
247
+
248
+        $query = $this->dbConn->getQueryBuilder();
249
+        $query->select('gid', 'displayname')
250
+            ->from('groups')
251
+            ->orderBy('gid', 'ASC');
252
+
253
+        if ($search !== '') {
254
+            $query->where($query->expr()->iLike('gid', $query->createNamedParameter(
255
+                '%' . $this->dbConn->escapeLikeParameter($search) . '%'
256
+            )));
257
+            $query->orWhere($query->expr()->iLike('displayname', $query->createNamedParameter(
258
+                '%' . $this->dbConn->escapeLikeParameter($search) . '%'
259
+            )));
260
+        }
261
+
262
+        if ($limit > 0) {
263
+            $query->setMaxResults($limit);
264
+        }
265
+        if ($offset > 0) {
266
+            $query->setFirstResult($offset);
267
+        }
268
+        $result = $query->executeQuery();
269
+
270
+        $groups = [];
271
+        while ($row = $result->fetch()) {
272
+            $this->groupCache[$row['gid']] = [
273
+                'displayname' => $row['displayname'],
274
+                'gid' => $row['gid'],
275
+            ];
276
+            $groups[] = $row['gid'];
277
+        }
278
+        $result->closeCursor();
279
+
280
+        return $groups;
281
+    }
282
+
283
+    /**
284
+     * check if a group exists
285
+     * @param string $gid
286
+     * @return bool
287
+     */
288
+    public function groupExists($gid) {
289
+        $this->fixDI();
290
+
291
+        // Check cache first
292
+        if (isset($this->groupCache[$gid])) {
293
+            return true;
294
+        }
295
+
296
+        $qb = $this->dbConn->getQueryBuilder();
297
+        $cursor = $qb->select('gid', 'displayname')
298
+            ->from('groups')
299
+            ->where($qb->expr()->eq('gid', $qb->createNamedParameter($gid)))
300
+            ->executeQuery();
301
+        $result = $cursor->fetch();
302
+        $cursor->closeCursor();
303
+
304
+        if ($result !== false) {
305
+            $this->groupCache[$gid] = [
306
+                'gid' => $gid,
307
+                'displayname' => $result['displayname'],
308
+            ];
309
+            return true;
310
+        }
311
+        return false;
312
+    }
313
+
314
+    /**
315
+     * {@inheritdoc}
316
+     */
317
+    public function groupsExists(array $gids): array {
318
+        $notFoundGids = [];
319
+        $existingGroups = [];
320
+
321
+        // In case the data is already locally accessible, not need to do SQL query
322
+        // or do a SQL query but with a smaller in clause
323
+        foreach ($gids as $gid) {
324
+            if (isset($this->groupCache[$gid])) {
325
+                $existingGroups[] = $gid;
326
+            } else {
327
+                $notFoundGids[] = $gid;
328
+            }
329
+        }
330
+
331
+        $qb = $this->dbConn->getQueryBuilder();
332
+        $qb->select('gid', 'displayname')
333
+            ->from('groups')
334
+            ->where($qb->expr()->in('gid', $qb->createParameter('ids')));
335
+        foreach (array_chunk($notFoundGids, 1000) as $chunk) {
336
+            $qb->setParameter('ids', $chunk, IQueryBuilder::PARAM_STR_ARRAY);
337
+            $result = $qb->executeQuery();
338
+            while ($row = $result->fetch()) {
339
+                $this->groupCache[(string)$row['gid']] = [
340
+                    'displayname' => (string)$row['displayname'],
341
+                    'gid' => (string)$row['gid'],
342
+                ];
343
+                $existingGroups[] = (string)$row['gid'];
344
+            }
345
+            $result->closeCursor();
346
+        }
347
+
348
+        return $existingGroups;
349
+    }
350
+
351
+    /**
352
+     * Get a list of all users in a group
353
+     * @param string $gid
354
+     * @param string $search
355
+     * @param int $limit
356
+     * @param int $offset
357
+     * @return array<int,string> an array of user ids
358
+     */
359
+    public function usersInGroup($gid, $search = '', $limit = -1, $offset = 0): array {
360
+        return array_values(array_map(fn ($user) => $user->getUid(), $this->searchInGroup($gid, $search, $limit, $offset)));
361
+    }
362
+
363
+    public function searchInGroup(string $gid, string $search = '', int $limit = -1, int $offset = 0): array {
364
+        $this->fixDI();
365
+
366
+        $query = $this->dbConn->getQueryBuilder();
367
+        $query->select('g.uid', 'dn.value AS displayname');
368
+
369
+        $query->from('group_user', 'g')
370
+            ->where($query->expr()->eq('gid', $query->createNamedParameter($gid)))
371
+            ->orderBy('g.uid', 'ASC');
372
+
373
+        // Join displayname and email from oc_accounts_data
374
+        $query->leftJoin('g', 'accounts_data', 'dn',
375
+            $query->expr()->andX(
376
+                $query->expr()->eq('dn.uid', 'g.uid'),
377
+                $query->expr()->eq('dn.name', $query->expr()->literal('displayname'))
378
+            )
379
+        );
380
+
381
+        $query->leftJoin('g', 'accounts_data', 'em',
382
+            $query->expr()->andX(
383
+                $query->expr()->eq('em.uid', 'g.uid'),
384
+                $query->expr()->eq('em.name', $query->expr()->literal('email'))
385
+            )
386
+        );
387
+
388
+        if ($search !== '') {
389
+            // sqlite doesn't like re-using a single named parameter here
390
+            $searchParam1 = $query->createNamedParameter('%' . $this->dbConn->escapeLikeParameter($search) . '%');
391
+            $searchParam2 = $query->createNamedParameter('%' . $this->dbConn->escapeLikeParameter($search) . '%');
392
+            $searchParam3 = $query->createNamedParameter('%' . $this->dbConn->escapeLikeParameter($search) . '%');
393
+
394
+            $query->andWhere(
395
+                $query->expr()->orX(
396
+                    $query->expr()->ilike('g.uid', $searchParam1),
397
+                    $query->expr()->ilike('dn.value', $searchParam2),
398
+                    $query->expr()->ilike('em.value', $searchParam3)
399
+                )
400
+            )
401
+                ->orderBy('g.uid', 'ASC');
402
+        }
403
+
404
+
405
+        if ($limit !== -1) {
406
+            $query->setMaxResults($limit);
407
+        }
408
+        if ($offset !== 0) {
409
+            $query->setFirstResult($offset);
410
+        }
411
+
412
+        $result = $query->executeQuery();
413
+
414
+        $users = [];
415
+        $userManager = \OCP\Server::get(IUserManager::class);
416
+        while ($row = $result->fetch()) {
417
+            $users[$row['uid']] = new LazyUser($row['uid'], $userManager, $row['displayname'] ?? null);
418
+        }
419
+        $result->closeCursor();
420
+
421
+        return $users;
422
+    }
423
+
424
+    /**
425
+     * get the number of all users matching the search string in a group
426
+     * @param string $gid
427
+     * @param string $search
428
+     * @return int
429
+     */
430
+    public function countUsersInGroup(string $gid, string $search = ''): int {
431
+        $this->fixDI();
432
+
433
+        $query = $this->dbConn->getQueryBuilder();
434
+        $query->select($query->func()->count('*', 'num_users'))
435
+            ->from('group_user')
436
+            ->where($query->expr()->eq('gid', $query->createNamedParameter($gid)));
437
+
438
+        if ($search !== '') {
439
+            $query->andWhere($query->expr()->like('uid', $query->createNamedParameter(
440
+                '%' . $this->dbConn->escapeLikeParameter($search) . '%'
441
+            )));
442
+        }
443
+
444
+        $result = $query->executeQuery();
445
+        $count = $result->fetchOne();
446
+        $result->closeCursor();
447
+
448
+        if ($count !== false) {
449
+            $count = (int)$count;
450
+        } else {
451
+            $count = 0;
452
+        }
453
+
454
+        return $count;
455
+    }
456
+
457
+    /**
458
+     * get the number of disabled users in a group
459
+     *
460
+     * @param string $search
461
+     *
462
+     * @return int
463
+     */
464
+    public function countDisabledInGroup(string $gid): int {
465
+        $this->fixDI();
466
+
467
+        $query = $this->dbConn->getQueryBuilder();
468
+        $query->select($query->createFunction('COUNT(DISTINCT ' . $query->getColumnName('uid') . ')'))
469
+            ->from('preferences', 'p')
470
+            ->innerJoin('p', 'group_user', 'g', $query->expr()->eq('p.userid', 'g.uid'))
471
+            ->where($query->expr()->eq('appid', $query->createNamedParameter('core')))
472
+            ->andWhere($query->expr()->eq('configkey', $query->createNamedParameter('enabled')))
473
+            ->andWhere($query->expr()->eq('configvalue', $query->createNamedParameter('false'), IQueryBuilder::PARAM_STR))
474
+            ->andWhere($query->expr()->eq('gid', $query->createNamedParameter($gid), IQueryBuilder::PARAM_STR));
475
+
476
+        $result = $query->executeQuery();
477
+        $count = $result->fetchOne();
478
+        $result->closeCursor();
479
+
480
+        if ($count !== false) {
481
+            $count = (int)$count;
482
+        } else {
483
+            $count = 0;
484
+        }
485
+
486
+        return $count;
487
+    }
488
+
489
+    public function getDisplayName(string $gid): string {
490
+        if (isset($this->groupCache[$gid])) {
491
+            $displayName = $this->groupCache[$gid]['displayname'];
492
+
493
+            if (isset($displayName) && trim($displayName) !== '') {
494
+                return $displayName;
495
+            }
496
+        }
497
+
498
+        $this->fixDI();
499
+
500
+        $query = $this->dbConn->getQueryBuilder();
501
+        $query->select('displayname')
502
+            ->from('groups')
503
+            ->where($query->expr()->eq('gid', $query->createNamedParameter($gid)));
504
+
505
+        $result = $query->executeQuery();
506
+        $displayName = $result->fetchOne();
507
+        $result->closeCursor();
508
+
509
+        return (string)$displayName;
510
+    }
511
+
512
+    public function getGroupDetails(string $gid): array {
513
+        $displayName = $this->getDisplayName($gid);
514
+        if ($displayName !== '') {
515
+            return ['displayName' => $displayName];
516
+        }
517
+
518
+        return [];
519
+    }
520
+
521
+    /**
522
+     * {@inheritdoc}
523
+     */
524
+    public function getGroupsDetails(array $gids): array {
525
+        $notFoundGids = [];
526
+        $details = [];
527
+
528
+        $this->fixDI();
529
+
530
+        // In case the data is already locally accessible, not need to do SQL query
531
+        // or do a SQL query but with a smaller in clause
532
+        foreach ($gids as $gid) {
533
+            if (isset($this->groupCache[$gid])) {
534
+                $details[$gid] = ['displayName' => $this->groupCache[$gid]['displayname']];
535
+            } else {
536
+                $notFoundGids[] = $gid;
537
+            }
538
+        }
539
+
540
+        foreach (array_chunk($notFoundGids, 1000) as $chunk) {
541
+            $query = $this->dbConn->getQueryBuilder();
542
+            $query->select('gid', 'displayname')
543
+                ->from('groups')
544
+                ->where($query->expr()->in('gid', $query->createNamedParameter($chunk, IQueryBuilder::PARAM_STR_ARRAY)));
545
+
546
+            $result = $query->executeQuery();
547
+            while ($row = $result->fetch()) {
548
+                $details[(string)$row['gid']] = ['displayName' => (string)$row['displayname']];
549
+                $this->groupCache[(string)$row['gid']] = [
550
+                    'displayname' => (string)$row['displayname'],
551
+                    'gid' => (string)$row['gid'],
552
+                ];
553
+            }
554
+            $result->closeCursor();
555
+        }
556
+
557
+        return $details;
558
+    }
559
+
560
+    public function setDisplayName(string $gid, string $displayName): bool {
561
+        if (!$this->groupExists($gid)) {
562
+            return false;
563
+        }
564
+
565
+        $this->fixDI();
566
+
567
+        $displayName = trim($displayName);
568
+        if ($displayName === '') {
569
+            $displayName = $gid;
570
+        }
571
+
572
+        $query = $this->dbConn->getQueryBuilder();
573
+        $query->update('groups')
574
+            ->set('displayname', $query->createNamedParameter($displayName))
575
+            ->where($query->expr()->eq('gid', $query->createNamedParameter($gid)));
576
+        $query->executeStatement();
577
+
578
+        return true;
579
+    }
580
+
581
+    /**
582
+     * Backend name to be shown in group management
583
+     * @return string the name of the backend to be shown
584
+     * @since 21.0.0
585
+     */
586
+    public function getBackendName(): string {
587
+        return 'Database';
588
+    }
589
+
590
+    /**
591
+     * Merge any white spaces to a single space in group name, then trim it.
592
+     */
593
+    private function sanitizeGroupName(string $displayName): string {
594
+        $cleanedDisplayName = preg_replace('/\s+/', ' ', $displayName);
595
+        return trim($cleanedDisplayName);
596
+    }
597
+
598
+    /**
599
+     * Compute group ID from display name (GIDs are limited to 64 characters in database)
600
+     */
601
+    private function computeGid(string $displayName): string {
602
+        $displayNameWithoutWhitespace = preg_replace('/\s+/', '_', $displayName);
603
+        return mb_strlen($displayNameWithoutWhitespace) > 64
604
+            ? hash('sha256', $displayNameWithoutWhitespace)
605
+            : $displayNameWithoutWhitespace;
606
+    }
607 607
 }
Please login to merge, or discard this patch.
tests/lib/Group/DatabaseTest.php 2 patches
Indentation   +55 added lines, -55 removed lines patch added patch discarded remove patch
@@ -15,59 +15,59 @@
 block discarded – undo
15 15
  */
16 16
 #[\PHPUnit\Framework\Attributes\Group('DB')]
17 17
 class DatabaseTest extends Backend {
18
-	private $groups = [];
19
-
20
-	/**
21
-	 * get a new unique group name
22
-	 * test cases can override this in order to clean up created groups
23
-	 */
24
-	public function getGroupName($name = null): string {
25
-		$name = parent::getGroupName($name);
26
-		$this->groups[] = $name;
27
-		return $name;
28
-	}
29
-
30
-	protected function setUp(): void {
31
-		parent::setUp();
32
-		$this->backend = new Database();
33
-	}
34
-
35
-	protected function tearDown(): void {
36
-		foreach ($this->groups as $group) {
37
-			$this->backend->deleteGroup($group);
38
-		}
39
-		parent::tearDown();
40
-	}
41
-
42
-	public function testAddDoubleNoCache(): void {
43
-		$group = $this->getGroupName();
44
-
45
-		$this->backend->createGroup($group);
46
-
47
-		$backend = new Database();
48
-		$this->assertNull($backend->createGroup($group));
49
-	}
50
-
51
-	public function testAddLongGroupName(): void {
52
-		$groupName = $this->getUniqueID('test_', 100);
53
-
54
-		$gidCreated = $this->backend->createGroup($groupName);
55
-		$this->assertEquals(64, strlen($gidCreated));
56
-
57
-		$group = $this->backend->getGroupDetails($gidCreated);
58
-		$this->assertEquals(['displayName' => $groupName], $group);
59
-	}
60
-
61
-	public function testWhiteSpaceInGroupName(): void {
62
-		$randomId = $this->getUniqueID('test_', 10);
63
-		$groupName = " 	group  name	with  	weird spaces \n" . $randomId;
64
-		$expectedGroupName = 'group name with weird spaces ' . $randomId;
65
-		$expectedGroupId = 'group_name_with_weird_spaces_' . $randomId;
66
-
67
-		$gidCreated = $this->backend->createGroup($groupName);
68
-		$this->assertEquals($expectedGroupId, $gidCreated);
69
-
70
-		$group = $this->backend->getGroupDetails($gidCreated);
71
-		$this->assertEquals(['displayName' => $expectedGroupName], $group);
72
-	}
18
+    private $groups = [];
19
+
20
+    /**
21
+     * get a new unique group name
22
+     * test cases can override this in order to clean up created groups
23
+     */
24
+    public function getGroupName($name = null): string {
25
+        $name = parent::getGroupName($name);
26
+        $this->groups[] = $name;
27
+        return $name;
28
+    }
29
+
30
+    protected function setUp(): void {
31
+        parent::setUp();
32
+        $this->backend = new Database();
33
+    }
34
+
35
+    protected function tearDown(): void {
36
+        foreach ($this->groups as $group) {
37
+            $this->backend->deleteGroup($group);
38
+        }
39
+        parent::tearDown();
40
+    }
41
+
42
+    public function testAddDoubleNoCache(): void {
43
+        $group = $this->getGroupName();
44
+
45
+        $this->backend->createGroup($group);
46
+
47
+        $backend = new Database();
48
+        $this->assertNull($backend->createGroup($group));
49
+    }
50
+
51
+    public function testAddLongGroupName(): void {
52
+        $groupName = $this->getUniqueID('test_', 100);
53
+
54
+        $gidCreated = $this->backend->createGroup($groupName);
55
+        $this->assertEquals(64, strlen($gidCreated));
56
+
57
+        $group = $this->backend->getGroupDetails($gidCreated);
58
+        $this->assertEquals(['displayName' => $groupName], $group);
59
+    }
60
+
61
+    public function testWhiteSpaceInGroupName(): void {
62
+        $randomId = $this->getUniqueID('test_', 10);
63
+        $groupName = " 	group  name	with  	weird spaces \n" . $randomId;
64
+        $expectedGroupName = 'group name with weird spaces ' . $randomId;
65
+        $expectedGroupId = 'group_name_with_weird_spaces_' . $randomId;
66
+
67
+        $gidCreated = $this->backend->createGroup($groupName);
68
+        $this->assertEquals($expectedGroupId, $gidCreated);
69
+
70
+        $group = $this->backend->getGroupDetails($gidCreated);
71
+        $this->assertEquals(['displayName' => $expectedGroupName], $group);
72
+    }
73 73
 }
Please login to merge, or discard this patch.
Spacing   +3 added lines, -3 removed lines patch added patch discarded remove patch
@@ -60,9 +60,9 @@
 block discarded – undo
60 60
 
61 61
 	public function testWhiteSpaceInGroupName(): void {
62 62
 		$randomId = $this->getUniqueID('test_', 10);
63
-		$groupName = " 	group  name	with  	weird spaces \n" . $randomId;
64
-		$expectedGroupName = 'group name with weird spaces ' . $randomId;
65
-		$expectedGroupId = 'group_name_with_weird_spaces_' . $randomId;
63
+		$groupName = " 	group  name	with  	weird spaces \n".$randomId;
64
+		$expectedGroupName = 'group name with weird spaces '.$randomId;
65
+		$expectedGroupId = 'group_name_with_weird_spaces_'.$randomId;
66 66
 
67 67
 		$gidCreated = $this->backend->createGroup($groupName);
68 68
 		$this->assertEquals($expectedGroupId, $gidCreated);
Please login to merge, or discard this patch.