Completed
Pull Request — master (#551)
by Maxence
02:04
created

CoreQueryBuilder::leftJoinMemberFromInstance()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 18
rs 9.6666
c 0
b 0
f 0
cc 2
nc 2
nop 3
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * Circles - Bring cloud-users closer together.
7
 *
8
 * This file is licensed under the Affero General Public License version 3 or
9
 * later. See the COPYING file.
10
 *
11
 * @author Maxence Lange <[email protected]>
12
 * @copyright 2017
13
 * @license GNU AGPL version 3 or any later version
14
 *
15
 * This program is free software: you can redistribute it and/or modify
16
 * it under the terms of the GNU Affero General Public License as
17
 * published by the Free Software Foundation, either version 3 of the
18
 * License, or (at your option) any later version.
19
 *
20
 * This program is distributed in the hope that it will be useful,
21
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23
 * GNU Affero General Public License for more details.
24
 *
25
 * You should have received a copy of the GNU Affero General Public License
26
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
27
 *
28
 */
29
30
31
namespace OCA\Circles\Db;
32
33
34
use daita\MySmallPhpTools\Db\Nextcloud\nc21\NC21ExtendedQueryBuilder;
35
use Doctrine\DBAL\Query\QueryBuilder;
36
use OC;
37
use OCA\Circles\IFederatedUser;
38
use OCA\Circles\Model\Circle;
39
use OCA\Circles\Model\Federated\RemoteInstance;
40
use OCA\Circles\Model\Member;
41
use OCA\Circles\Service\ConfigService;
42
43
/**
44
 * Class CoreQueryBuilder
45
 *
46
 * @package OCA\Circles\Db
47
 */
48
class CoreQueryBuilder extends NC21ExtendedQueryBuilder {
49
50
51
	const PREFIX_MEMBER = 'member_';
52
	const PREFIX_OWNER = 'owner_';
53
	const PREFIX_INITIATOR = 'initiator_';
54
	const PREFIX_CIRCLE = 'circle_';
55
56
57
	/** @var ConfigService */
58
	private $configService;
59
60
61
	/**
62
	 * CoreQueryBuilder constructor.
63
	 */
64
	public function __construct() {
65
		parent::__construct();
66
67
		$this->configService = OC::$server->get(ConfigService::class);
68
	}
69
70
71
	/**
72
	 * @param IFederatedUser $member
73
	 *
74
	 * @return string
75
	 */
76
	public function getInstance(IFederatedUser $member): string {
77
		$instance = $member->getInstance();
78
79
		return ($this->configService->isLocalInstance($instance)) ? '' : $instance;
80
	}
81
82
83
	/**
84
	 * @param string $id
85
	 */
86
	public function limitToCircleId(string $id): void {
87
		$this->limitToDBField('circle_id', $id, true);
88
	}
89
90
	/**
91
	 * @param int $config
92
	 */
93
	public function limitToConfig(int $config): void {
94
		$this->limitToDBFieldInt('config', $config);
95
	}
96
97
98
	/**
99
	 * @param string $singleId
100
	 */
101
	public function limitToSingleId(string $singleId): void {
102
		$this->limitToDBField('single_id', $singleId, true);
103
	}
104
105
	/**
106
	 * @param string $host
107
	 */
108
	public function limitToInstance(string $host): void {
109
		$this->limitToDBField('instance', $host, false);
110
	}
111
112
113
	/**
114
	 * @param int $userType
115
	 */
116
	public function limitToUserType(int $userType): void {
117
		$this->limitToDBFieldInt('user_type', $userType);
118
	}
119
120
121
	/**
122
	 * @param IFederatedUser $initiator
123
	 * @param string $alias
124
	 */
125
	public function limitToInitiator(IFederatedUser $initiator, string $alias = ''): void {
126
		$this->leftJoinInitiator($initiator, 'init', $alias);
127
		$this->limitVisibility('init', $alias);
128
	}
129
130
131
	/**
132
	 * @param string $instance
133
	 * @param string $aliasCircle
134
	 * @param string $aliasOwner
135
	 * @param bool $sensitive
136
	 */
137
	public function limitToRemoteInstance(
138
		string $instance,
139
		bool $sensitive = true,
140
		string $aliasCircle = 'c',
141
		string $aliasOwner = 'o'
142
	): void {
143
		$this->leftJoinRemoteInstance($instance, 'ri');
144
		$this->leftJoinMemberFromInstance($instance, 'mi', $aliasCircle);
145
		$this->limitRemoteVisibility($sensitive, 'ri', $aliasCircle, $aliasOwner, 'mi');
146
	}
147
148
149
	/**
150
	 * @param IFederatedUser $member
151
	 * @param int $level
152
	 */
153
	public function limitToMembership(IFederatedUser $member, int $level = Member::LEVEL_MEMBER): void {
154
		if ($this->getType() !== QueryBuilder::SELECT) {
155
			return;
156
		}
157
158
		$expr = $this->expr();
159
160
		$alias = 'm';
161
		$this->generateMemberSelectAlias($alias, self::PREFIX_MEMBER)
162
			 ->leftJoin(
163
				 $this->getDefaultSelectAlias(), CoreRequestBuilder::TABLE_MEMBER, $alias,
164
				 $expr->eq($alias . '.circle_id', $this->getDefaultSelectAlias() . '.unique_id')
165
			 );
166
167
		// TODO: Check in big table if it is better to put condition in andWhere() or in LeftJoin()
168
		$this->andWhere(
169
			$expr->andX(
170
				$expr->eq($alias . '.user_id', $this->createNamedParameter($member->getUserId())),
171
				$expr->eq($alias . '.user_type', $this->createNamedParameter($member->getUserType())),
172
				$expr->eq($alias . '.instance', $this->createNamedParameter($this->getInstance($member))),
173
				$expr->gte($alias . '.level', $this->createNamedParameter($level))
174
			)
175
		);
176
	}
177
178
179
	/**
180
	 * @param IFederatedUser|null $initiator
181
	 */
182
	public function leftJoinCircle(?IFederatedUser $initiator = null) {
183
		if ($this->getType() !== QueryBuilder::SELECT) {
184
			return;
185
		}
186
187
		$expr = $this->expr();
188
189
		$alias = 'c';
190
		$this->generateCircleSelectAlias($alias, self::PREFIX_CIRCLE)
191
			 ->leftJoin(
192
				 $this->getDefaultSelectAlias(), CoreRequestBuilder::TABLE_CIRCLE, $alias,
193
				 $expr->andX(
194
					 $expr->eq($alias . '.unique_id', $this->getDefaultSelectAlias() . '.circle_id')
195
				 )
196
			 );
197
198
		$this->leftJoinOwner($alias);
199
		if (!is_null($initiator)) {
200
			$this->limitToInitiator($initiator, $alias);
201
		}
202
203
	}
204
205
206
	/**
207
	 * @param string $circleTableAlias
208
	 */
209
	public function leftJoinOwner(string $circleTableAlias = '') {
210
		if ($this->getType() !== QueryBuilder::SELECT) {
211
			return;
212
		}
213
214
		if ($circleTableAlias === '') {
215
			$circleTableAlias = $this->getDefaultSelectAlias();
216
		}
217
		$expr = $this->expr();
218
219
		$alias = 'o';
220
		$this->generateMemberSelectAlias($alias, self::PREFIX_OWNER)
221
			 ->leftJoin(
222
				 $circleTableAlias, CoreRequestBuilder::TABLE_MEMBER, $alias,
223
				 $expr->andX(
224
					 $expr->eq($alias . '.circle_id', $circleTableAlias . '.unique_id'),
225
					 $expr->eq($alias . '.level', $this->createNamedParameter(Member::LEVEL_OWNER))
226
				 )
227
			 );
228
	}
229
230
231
	/**
232
	 * Left join members to filter userId as initiator.
233
	 *
234
	 * @param IFederatedUser $initiator
235
	 * @param string $alias
236
	 * @param string $aliasCircle
237
	 */
238
	public function leftJoinInitiator(
239
		IFederatedUser $initiator, string $alias = 'init', string $aliasCircle = ''
240
	): void {
241
		if ($this->getType() !== QueryBuilder::SELECT) {
242
			return;
243
		}
244
245
		$expr = $this->expr();
246
		$aliasCircle = ($aliasCircle === '') ? $this->getDefaultSelectAlias() : $aliasCircle;
247
		$this->generateMemberSelectAlias($alias, self::PREFIX_INITIATOR)
248
			 ->leftJoin(
249
				 $aliasCircle, CoreRequestBuilder::TABLE_MEMBER, $alias,
250
				 $expr->andX(
251
					 $expr->eq($alias . '.circle_id', $aliasCircle . '.unique_id'),
252
					 $expr->eq($alias . '.user_id', $this->createNamedParameter($initiator->getUserId())),
253
					 $expr->eq($alias . '.user_type', $this->createNamedParameter($initiator->getUserType())),
254
					 $expr->eq(
255
						 $alias . '.instance', $this->createNamedParameter($this->getInstance($initiator))
256
					 )
257
				 )
258
			 );
259
	}
260
261
262
	/**
263
	 * left join members to check memberships of someone from instance
264
	 *
265
	 * @param string $instance
266
	 * @param string $alias
267
	 * @param string $aliasCircle
268
	 */
269
	public function leftJoinMemberFromInstance(
270
		string $instance, string $alias = 'mi', string $aliasCircle = 'c'
271
	) {
272
		if ($this->getType() !== QueryBuilder::SELECT) {
273
			return;
274
		}
275
276
		$expr = $this->expr();
277
278
		$this->leftJoin(
279
			$this->getDefaultSelectAlias(), CoreRequestBuilder::TABLE_MEMBER, $alias,
280
			$expr->andX(
281
				$expr->eq($alias . '.circle_id', $aliasCircle . '.unique_id'),
282
				$expr->eq($alias . '.instance', $this->createNamedParameter($instance)),
283
				$expr->gte($alias . '.level', $this->createNamedParameter(Member::LEVEL_MEMBER))
284
			)
285
		);
286
	}
287
288
289
	/**
290
	 * Left join remotes to filter visibility based on RemoteInstance.
291
	 *
292
	 * @param string $instance
293
	 * @param string $alias
294
	 */
295
	public function leftJoinRemoteInstance(string $instance, string $alias = 'ri'): void {
296
		if ($this->getType() !== QueryBuilder::SELECT) {
297
			return;
298
		}
299
300
		$expr = $this->expr();
301
		$this->leftJoin(
302
			$this->getDefaultSelectAlias(), CoreRequestBuilder::TABLE_REMOTE, $alias,
303
			$expr->eq($alias . '.instance', $this->createNamedParameter($instance))
304
		);
305
	}
306
307
308
	/**
309
	 * @param string $alias
310
	 * @param string $aliasCircle
311
	 */
312
	protected function limitVisibility(string $alias = 'init', string $aliasCircle = '') {
313
		$expr = $this->expr();
314
		$aliasCircle = ($aliasCircle === '') ? $this->getDefaultSelectAlias() : $aliasCircle;
315
316
		// Visibility to non-member is
317
		// - 0 (default), if initiator is member
318
		// - 2 (Personal), if initiator is owner)
319
		// - 4 (Visible to everyone)
320
		$orX = $expr->orX();
321
		$orX->add(
322
			$expr->andX($expr->gte($alias . '.level', $this->createNamedParameter(Member::LEVEL_MEMBER)))
323
		);
324
		$orX->add(
325
			$expr->andX(
326
				$expr->bitwiseAnd($aliasCircle . '.config', Circle::CFG_PERSONAL),
327
				$expr->eq($alias . '.level', $this->createNamedParameter(Member::LEVEL_OWNER))
328
			)
329
		);
330
		$orX->add($expr->bitwiseAnd($aliasCircle . '.config', Circle::CFG_VISIBLE));
331
		$this->andWhere($orX);
332
333
334
//		$orTypes = $this->generateLimit($qb, $circleUniqueId, $userId, $type, $name, $forceAll);
335
//		if (sizeof($orTypes) === 0) {
336
//			throw new ConfigNoCircleAvailableException(
337
//				$this->l10n->t(
338
//					'You cannot use the Circles Application until your administrator has allowed at least one type of circles'
339
//				)
340
//			);
341
//		}
342
343
//		$orXTypes = $this->expr()
344
//						 ->orX();
345
//		foreach ($orTypes as $orType) {
346
//			$orXTypes->add($orType);
347
//		}
348
//
349
//		$qb->andWhere($orXTypes);
350
	}
351
352
353
	/**
354
	 * CFG_SINGLE, CFG_HIDDEN and CFG_BACKEND means hidden from listing.
355
	 *
356
	 * TODO: add a filter for allowing those circles in some request
357
	 *
358
	 * @param string $alias
359
	 */
360
	public function filterSystemCircles(string $alias = ''): void {
361
		$alias = ($alias === '') ? $this->getDefaultSelectAlias() : $alias;
362
		$expr = $this->expr();
363
364
		$orHidden = $expr->orX();
365
		$orHidden->add($expr->bitwiseAnd($alias . '.config', Circle::CFG_SINGLE));
366
		$orHidden->add($expr->bitwiseAnd($alias . '.config', Circle::CFG_HIDDEN));
367
		$orHidden->add($expr->bitwiseAnd($alias . '.config', Circle::CFG_BACKEND));
368
		$this->andWhere($this->createFunction('NOT') . $orHidden);
369
	}
370
371
372
	/**
373
	 * - global_scale: visibility on all Circles
374
	 * - trusted: visibility on all FEDERATED Circle if owner is local
375
	 * - external: visibility on all FEDERATED Circle if owner is local and with at least one member from
376
	 * this instance
377
	 * - passive: no visibility on any Circle, user should be able to share to the circle without having
378
	 * access to Circles' details not members
379
	 *
380
	 * @param bool $sensitive
381
	 * @param string $alias
382
	 * @param string $aliasCircle
383
	 * @param string $aliasOwner
384
	 * @param string $aliasMembers
385
	 */
386
	protected function limitRemoteVisibility(
387
		bool $sensitive = true,
388
		string $alias = 'ri',
389
		string $aliasCircle = 'c',
390
		string $aliasOwner = 'o',
391
		string $aliasMembers = 'mi'
392
	) {
393
		$expr = $this->expr();
394
395
		$orX = $expr->orX();
396
		$orX->add(
397
			$expr->eq($alias . '.type', $this->createNamedParameter(RemoteInstance::TYPE_GLOBAL_SCALE))
398
		);
399
400
		$orExtOrPassive = $expr->orX();
401
		$orExtOrPassive->add(
402
			$expr->eq($alias . '.type', $this->createNamedParameter(RemoteInstance::TYPE_EXTERNAL))
403
		);
404
		if (!$sensitive) {
405
			$orExtOrPassive->add(
406
				$expr->eq($alias . '.type', $this->createNamedParameter(RemoteInstance::TYPE_PASSIVE))
407
			);
408
		} else {
409
			if ($this->getDefaultSelectAlias() === 'm') {
410
				$andPassive = $expr->andX();
411
				$andPassive->add(
412
					$expr->eq($alias . '.type', $this->createNamedParameter(RemoteInstance::TYPE_PASSIVE))
413
				);
414
				$orMemberOrLevel = $expr->orX();
415
				$orMemberOrLevel->add(
416
					$expr->eq($this->getDefaultSelectAlias() . '.instance', $alias . '.instance')
417
				);
418
				$orMemberOrLevel->add(
419
					$expr->eq(
420
						$this->getDefaultSelectAlias() . '.level',
421
						$this->createNamedParameter(Member::LEVEL_OWNER)
422
					)
423
				);
424
				$andPassive->add($orMemberOrLevel);
425
				$orExtOrPassive->add($andPassive);
426
			}
427
		}
428
429
		$andExternal = $expr->andX();
430
		$andExternal->add($orExtOrPassive);
431
		$andExternal->add($expr->isNotNull($aliasMembers . '.instance'));
432
433
		$orExtOrTrusted = $expr->orX();
434
		$orExtOrTrusted->add($andExternal);
435
		$orExtOrTrusted->add(
436
			$expr->eq($alias . '.type', $this->createNamedParameter(RemoteInstance::TYPE_TRUSTED))
437
		);
438
439
		$andTrusted = $expr->andX();
440
		$andTrusted->add($orExtOrTrusted);
441
		$andTrusted->add($expr->bitwiseAnd($aliasCircle . '.config', Circle::CFG_FEDERATED));
442
		$andTrusted->add($expr->emptyString($aliasOwner . '.instance'));
443
		$orX->add($andTrusted);
444
445
		$this->andWhere($orX);
446
	}
447
448
449
	/**
450
	 * @param int $flag
451
	 */
452
	public function filterConfig(int $flag): void {
453
		$this->andWhere($this->expr()->bitwiseAnd($this->getDefaultSelectAlias() . '.config', $flag));
454
	}
455
456
457
	/**
458
	 * @param string $alias
459
	 * @param string $prefix
460
	 *
461
	 * @return $this
462
	 */
463
	private function generateCircleSelectAlias(string $alias, string $prefix): self {
464
		$this->selectAlias($alias . '.unique_id', $prefix . 'unique_id')
465
			 ->selectAlias($alias . '.name', $prefix . 'name')
466
			 ->selectAlias($alias . '.alt_name', $prefix . 'alt_name')
467
			 ->selectAlias($alias . '.description', $prefix . 'description')
468
			 ->selectAlias($alias . '.settings', $prefix . 'settings')
469
			 ->selectAlias($alias . '.config', $prefix . 'config')
470
			 ->selectAlias($alias . '.contact_addressbook', $prefix . 'contact_addressbook')
471
			 ->selectAlias($alias . '.contact_groupname', $prefix . 'contact_groupname')
472
			 ->selectAlias($alias . '.creation', $prefix . 'creation');
473
474
		return $this;
475
	}
476
477
	/**
478
	 * @param string $alias
479
	 * @param string $prefix
480
	 *
481
	 * @return $this
482
	 */
483
	private function generateMemberSelectAlias(string $alias, string $prefix): self {
484
		$this->selectAlias($alias . '.circle_id', $prefix . 'circle_id')
485
			 ->selectAlias($alias . '.single_id', $prefix . 'single_id')
486
			 ->selectAlias($alias . '.user_id', $prefix . 'user_id')
487
			 ->selectAlias($alias . '.user_type', $prefix . 'user_type')
488
			 ->selectAlias($alias . '.member_id', $prefix . 'member_id')
489
			 ->selectAlias($alias . '.instance', $prefix . 'instance')
490
			 ->selectAlias($alias . '.cached_name', $prefix . 'cached_name')
491
			 ->selectAlias($alias . '.cached_update', $prefix . 'cached_update')
492
			 ->selectAlias($alias . '.status', $prefix . 'status')
493
			 ->selectAlias($alias . '.level', $prefix . 'level')
494
			 ->selectAlias($alias . '.note', $prefix . 'note')
495
			 ->selectAlias($alias . '.contact_id', $prefix . 'contact_id')
496
			 ->selectAlias($alias . '.contact_meta', $prefix . 'contact_meta')
497
			 ->selectAlias($alias . '.joined', $prefix . 'joined');
498
499
		return $this;
500
	}
501
502
}
503
504