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

CoreQueryBuilder::leftJoinFileCache()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 22
rs 9.568
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
6
/**
7
 * Circles - Bring cloud-users closer together.
8
 *
9
 * This file is licensed under the Affero General Public License version 3 or
10
 * later. See the COPYING file.
11
 *
12
 * @author Maxence Lange <[email protected]>
13
 * @copyright 2021
14
 * @license GNU AGPL version 3 or any later version
15
 *
16
 * This program is free software: you can redistribute it and/or modify
17
 * it under the terms of the GNU Affero General Public License as
18
 * published by the Free Software Foundation, either version 3 of the
19
 * License, or (at your option) any later version.
20
 *
21
 * This program is distributed in the hope that it will be useful,
22
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24
 * GNU Affero General Public License for more details.
25
 *
26
 * You should have received a copy of the GNU Affero General Public License
27
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
28
 *
29
 */
30
31
32
namespace OCA\Circles\Db;
33
34
35
use daita\MySmallPhpTools\Db\Nextcloud\nc22\NC22ExtendedQueryBuilder;
36
use daita\MySmallPhpTools\Traits\TArrayTools;
37
use Doctrine\DBAL\Query\QueryBuilder;
38
use OC;
39
use OCA\Circles\Exceptions\RequestBuilderException;
40
use OCA\Circles\IFederatedModel;
41
use OCA\Circles\IFederatedUser;
42
use OCA\Circles\Model\Circle;
43
use OCA\Circles\Model\Federated\RemoteInstance;
44
use OCA\Circles\Model\FederatedUser;
45
use OCA\Circles\Model\Member;
46
use OCA\Circles\Service\ConfigService;
47
use OCP\DB\QueryBuilder\ICompositeExpression;
48
49
50
/**
51
 * Class CoreRequestBuilder
52
 *
53
 * @package OCA\Circles\Db
54
 */
55
class CoreQueryBuilder extends NC22ExtendedQueryBuilder {
56
57
58
	use TArrayTools;
59
60
61
	const SINGLE = 'single';
62
	const CIRCLE = 'circle';
63
	const MEMBER = 'member';
64
	const OWNER = 'owner';
65
	const REMOTE = 'remote';
66
	const BASED_ON = 'basedOn';
67
	const INITIATOR = 'initiator';
68
	const MEMBERSHIPS = 'memberships';
69
	const UPSTREAM_MEMBERSHIPS = 'upstreamMemberships';
70
	const INHERITANCE_FROM = 'inheritanceFrom';
71
	const INHERITED_BY = 'inheritedBy';
72
	const MOUNT = 'mount';
73
	const MOUNTPOINT = 'mountpoint';
74
	const SHARE = 'share';
75
	const FILE_CACHE = 'fileCache';
76
	const STORAGES = 'storages';
77
	const OPTIONS = 'options';
78
79
80
	public static $SQL_PATH = [
81
		self::SINGLE => [
82
			self::MEMBER
83
		],
84
		self::CIRCLE => [
85
			self::OPTIONS   => [
86
				'getPersonalCircle' => true
87
			],
88
			self::MEMBER,
89
			self::OWNER     => [
90
				self::BASED_ON
91
			],
92
			self::MEMBERSHIPS,
93
			self::INITIATOR => [
94
				self::BASED_ON,
95
				self::INHERITED_BY => [
96
					self::MEMBERSHIPS
97
				]
98
			],
99
			self::REMOTE    => [
100
				self::MEMBER,
101
				self::CIRCLE => [
102
					self::OWNER
103
				]
104
			]
105
		],
106
		self::MEMBER => [
107
			self::MEMBERSHIPS,
108
			self::INHERITANCE_FROM,
109
			self::CIRCLE   => [
110
				self::OPTIONS   => [
111
					'getData' => true
112
				],
113
				self::OWNER,
114
				self::MEMBERSHIPS,
115
				self::INITIATOR => [
116
					self::OPTIONS      => [
117
						'mustBeMember' => true,
118
						'canBeVisitor' => false
119
					],
120
					self::BASED_ON,
121
					self::INHERITED_BY => [
122
						self::MEMBERSHIPS
123
					]
124
				]
125
			],
126
			self::BASED_ON => [
127
				self::OWNER,
128
				self::MEMBERSHIPS,
129
				self::INITIATOR => [
130
					self::BASED_ON,
131
					self::INHERITED_BY => [
132
						self::MEMBERSHIPS
133
					]
134
				]
135
			],
136
			self::REMOTE   => [
137
				self::MEMBER,
138
				self::CIRCLE => [
139
					self::OWNER
140
				]
141
			]
142
		],
143
		self::SHARE  => [
144
			self::SHARE,
145
			self::FILE_CACHE           => [
146
				self::STORAGES
147
			],
148
			self::UPSTREAM_MEMBERSHIPS => [
149
				self::MEMBERSHIPS,
150
				self::INHERITED_BY => [
151
					self::BASED_ON
152
				],
153
				self::SHARE,
154
			],
155
			self::MEMBERSHIPS,
156
			self::INHERITANCE_FROM,
157
			self::INHERITED_BY         => [
158
				self::BASED_ON
159
			],
160
			self::CIRCLE               => [
161
				self::OWNER
162
			],
163
			self::INITIATOR            => [
164
				self::BASED_ON,
165
				self::INHERITED_BY => [
166
					self::MEMBERSHIPS
167
				]
168
			]
169
		],
170
		self::REMOTE => [
171
			self::MEMBER
172
		],
173
		self::MOUNT  => [
174
			self::MEMBER    => [
175
				self::REMOTE
176
			],
177
			self::INITIATOR => [
178
				self::INHERITED_BY => [
179
					self::MEMBERSHIPS
180
				]
181
			],
182
			self::MOUNTPOINT,
183
			self::MEMBERSHIPS
184
		]
185
	];
186
187
188
	/** @var ConfigService */
189
	private $configService;
190
191
192
	/** @var array */
193
	private $options = [];
194
195
	/**
196
	 * CoreRequestBuilder constructor.
197
	 */
198
	public function __construct() {
199
		parent::__construct();
200
201
		$this->configService = OC::$server->get(ConfigService::class);
202
	}
203
204
205
	/**
206
	 * @param IFederatedModel $federatedModel
207
	 *
208
	 * @return string
209
	 */
210
	public function getInstance(IFederatedModel $federatedModel): string {
211
		$instance = $federatedModel->getInstance();
212
213
		return ($this->configService->isLocalInstance($instance)) ? '' : $instance;
214
	}
215
216
217
	/**
218
	 * @param string $id
219
	 */
220
	public function limitToCircleId(string $id): void {
221
		$this->limitToDBField('circle_id', $id, true);
222
	}
223
224
	/**
225
	 * @param string $name
226
	 */
227
	public function limitToName(string $name): void {
228
		$this->limitToDBField('name', $name);
229
	}
230
231
	/**
232
	 * @param int $config
233
	 */
234
	public function limitToConfig(int $config): void {
235
		$this->limitToDBFieldInt('config', $config);
236
	}
237
238
	/**
239
	 * @param int $source
240
	 */
241
	public function limitToSource(int $source): void {
242
		$this->limitToDBFieldInt('source', $source);
243
	}
244
245
	/**
246
	 * @param int $config
247
	 */
248
	public function limitToConfigFlag(int $config): void {
249
		$this->andWhere($this->expr()->bitwiseAnd($this->getDefaultSelectAlias() . '.config', $config));
250
	}
251
252
253
	/**
254
	 * @param string $singleId
255
	 */
256
	public function limitToSingleId(string $singleId): void {
257
		$this->limitToDBField('single_id', $singleId, true);
258
	}
259
260
261
	/**
262
	 * @param string $itemId
263
	 */
264
	public function limitToItemId(string $itemId): void {
265
		$this->limitToDBField('item_id', $itemId, true);
266
	}
267
268
269
	/**
270
	 * @param string $host
271
	 */
272
	public function limitToInstance(string $host): void {
273
		$this->limitToDBField('instance', $host, false);
274
	}
275
276
277
	/**
278
	 * @param int $userType
279
	 */
280
	public function limitToUserType(int $userType): void {
281
		$this->limitToDBFieldInt('user_type', $userType);
282
	}
283
284
285
	/**
286
	 * @param int $shareType
287
	 */
288
	public function limitToShareType(int $shareType): void {
289
		$this->limitToDBFieldInt('share_type', $shareType);
290
	}
291
292
293
	/**
294
	 * @param string $shareWith
295
	 */
296
	public function limitToShareWith(string $shareWith): void {
297
		$this->limitToDBField('share_with', $shareWith);
298
	}
299
300
301
	/**
302
	 * @param int $nodeId
303
	 */
304
	public function limitToFileSource(int $nodeId): void {
305
		$this->limitToDBFieldInt('file_source', $nodeId);
306
	}
307
308
	/**
309
	 * @param array $files
310
	 */
311
	public function limitToFileSourceArray(array $files): void {
312
		$this->limitToDBFieldInArray('file_source', $files);
313
	}
314
315
316
	/**
317
	 * @param int $shareId
318
	 */
319
	public function limitToShareParent(int $shareId): void {
320
		$this->limitToDBFieldInt('parent', $shareId);
321
	}
322
323
324
	/**
325
	 * @param Circle $circle
326
	 */
327
	public function filterCircle(Circle $circle): void {
328
		if ($this->getType() !== QueryBuilder::SELECT) {
329
			return;
330
		}
331
332
		if ($circle->getDisplayName() !== '') {
333
			$this->searchInDBField('display_name', '%' . $circle->getDisplayName() . '%');
334
		}
335
	}
336
337
338
	/**
339
	 * left join RemoteInstance based on a Member
340
	 */
341
	public function leftJoinRemoteInstance(string $alias): void {
342
		$expr = $this->expr();
343
344
		try {
345
			$aliasRemoteInstance = $this->generateAlias($alias, self::REMOTE);
346
			$this->generateRemoteInstanceSelectAlias($aliasRemoteInstance)
347
				 ->leftJoin(
348
					 $alias, CoreRequestBuilder::TABLE_REMOTE, $aliasRemoteInstance,
349
					 $expr->eq($alias . '.instance', $aliasRemoteInstance . '.instance')
350
				 );
351
		} catch (RequestBuilderException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
352
		}
353
	}
354
355
356
	/**
357
	 * @param string $alias
358
	 * @param RemoteInstance $remoteInstance
359
	 * @param bool $filterSensitiveData
360
	 * @param string $aliasCircle
361
	 *
362
	 * @throws RequestBuilderException
363
	 */
364
	public function limitToRemoteInstance(
365
		string $alias,
366
		RemoteInstance $remoteInstance,
367
		bool $filterSensitiveData = true,
368
		string $aliasCircle = ''
369
	): void {
370
371
		if ($aliasCircle === '') {
372
			$aliasCircle = $alias;
373
		}
374
375
		$this->leftJoinRemoteInstanceIncomingRequest($alias, $remoteInstance);
376
		$this->leftJoinMemberFromInstance($alias, $remoteInstance, $aliasCircle);
377
		$this->leftJoinMemberFromRemoteCircle($alias, $remoteInstance, $aliasCircle);
378
		$this->limitRemoteVisibility($alias, $filterSensitiveData, $aliasCircle);
379
	}
380
381
382
	/**
383
	 * Left join RemoteInstance based on an incoming request
384
	 *
385
	 * @param string $alias
386
	 * @param RemoteInstance $remoteInstance
387
	 *
388
	 * @throws RequestBuilderException
389
	 */
390
	public function leftJoinRemoteInstanceIncomingRequest(
391
		string $alias,
392
		RemoteInstance $remoteInstance
393
	): void {
394
		if ($this->getType() !== QueryBuilder::SELECT) {
395
			return;
396
		}
397
398
		$aliasRemote = $this->generateAlias($alias, self::REMOTE);
399
		$expr = $this->expr();
400
		$this->leftJoin(
401
			$this->getDefaultSelectAlias(), CoreRequestBuilder::TABLE_REMOTE, $aliasRemote,
402
			$expr->eq($aliasRemote . '.instance', $this->createNamedParameter($remoteInstance->getInstance()))
403
		);
404
	}
405
406
407
	/**
408
	 * left join members to check memberships of someone from instance
409
	 *
410
	 * @param string $alias
411
	 * @param RemoteInstance $remoteInstance
412
	 * @param string $aliasCircle
413
	 *
414
	 * @throws RequestBuilderException
415
	 */
416
	private function leftJoinMemberFromInstance(
417
		string $alias, RemoteInstance $remoteInstance, string $aliasCircle
418
	) {
419
		if ($this->getType() !== QueryBuilder::SELECT) {
420
			return;
421
		}
422
423
		$aliasRemote = $this->generateAlias($alias, self::REMOTE);
424
		$aliasRemoteMember = $this->generateAlias($aliasRemote, self::MEMBER);
425
426
		$expr = $this->expr();
427
		$this->leftJoin(
428
			$this->getDefaultSelectAlias(), CoreRequestBuilder::TABLE_MEMBER, $aliasRemoteMember,
429
			$expr->andX(
430
				$expr->eq($aliasRemoteMember . '.circle_id', $aliasCircle . '.unique_id'),
431
				$expr->eq(
432
					$aliasRemoteMember . '.instance',
433
					$this->createNamedParameter($remoteInstance->getInstance())
434
				),
435
				$expr->gte($aliasRemoteMember . '.level', $this->createNamedParameter(Member::LEVEL_MEMBER))
436
			)
437
		);
438
	}
439
440
441
	/**
442
	 * left join circle is member of a circle from remote instance
443
	 *
444
	 * @param string $alias
445
	 * @param RemoteInstance $remoteInstance
446
	 * @param string $aliasCircle
447
	 *
448
	 * @throws RequestBuilderException
449
	 */
450
	private function leftJoinMemberFromRemoteCircle(
451
		string $alias,
452
		RemoteInstance $remoteInstance,
453
		string $aliasCircle
454
	) {
455
		if ($this->getType() !== QueryBuilder::SELECT) {
456
			return;
457
		}
458
459
		$aliasRemote = $this->generateAlias($alias, self::REMOTE);
460
		$aliasRemoteCircle = $this->generateAlias($aliasRemote, self::CIRCLE);
461
		$aliasRemoteCircleOwner = $this->generateAlias($aliasRemoteCircle, self::OWNER);
462
463
		$expr = $this->expr();
464
		$this->leftJoin(
465
			$this->getDefaultSelectAlias(), CoreRequestBuilder::TABLE_MEMBER, $aliasRemoteCircle,
466
			$expr->andX(
467
				$expr->eq($aliasRemoteCircle . '.single_id', $aliasCircle . '.unique_id'),
468
				$expr->emptyString($aliasRemoteCircle . '.instance'),
469
				$expr->gte($aliasRemoteCircle . '.level', $this->createNamedParameter(Member::LEVEL_MEMBER))
470
			)
471
		);
472
		$this->leftJoin(
473
			$this->getDefaultSelectAlias(), CoreRequestBuilder::TABLE_MEMBER, $aliasRemoteCircleOwner,
474
			$expr->andX(
475
				$expr->eq($aliasRemoteCircle . '.circle_id', $aliasRemoteCircleOwner . '.circle_id'),
476
				$expr->eq(
477
					$aliasRemoteCircleOwner . '.instance',
478
					$this->createNamedParameter($remoteInstance->getInstance())
479
				),
480
				$expr->eq(
481
					$aliasRemoteCircleOwner . '.level', $this->createNamedParameter(Member::LEVEL_OWNER)
482
				)
483
			)
484
		);
485
	}
486
487
488
	/**
489
	 * - global_scale: visibility on all Circles
490
	 * - trusted: visibility on all FEDERATED Circle if owner is local
491
	 * - external: visibility on all FEDERATED Circle if owner is local and:
492
	 *    - with if Circle contains at least one member from the remote instance
493
	 *    - one circle from the remote instance contains the local circle as member, and confirmed (using
494
	 *      sync locally)
495
	 * - passive: like external, but the members list will only contains member from the local instance and
496
	 * from the remote instance.
497
	 *
498
	 * @param string $alias
499
	 * @param bool $sensitive
500
	 * @param string $aliasCircle
501
	 *
502
	 * @throws RequestBuilderException
503
	 */
504
	protected function limitRemoteVisibility(string $alias, bool $sensitive, string $aliasCircle) {
505
		$aliasRemote = $this->generateAlias($alias, self::REMOTE);
506
		$aliasOwner = $this->generateAlias($aliasCircle, self::OWNER);
507
		$aliasRemoteMember = $this->generateAlias($aliasRemote, self::MEMBER);
508
		$aliasRemoteCircle = $this->generateAlias($aliasRemote, self::CIRCLE);
509
		$aliasRemoteCircleOwner = $this->generateAlias($aliasRemoteCircle, self::OWNER);
510
511
		$expr = $this->expr();
512
		$orX = $expr->orX();
513
		$orX->add(
514
			$expr->eq($aliasRemote . '.type', $this->createNamedParameter(RemoteInstance::TYPE_GLOBAL_SCALE))
515
		);
516
517
		$orExtOrPassive = $expr->orX();
518
		$orExtOrPassive->add(
519
			$expr->eq($aliasRemote . '.type', $this->createNamedParameter(RemoteInstance::TYPE_EXTERNAL))
520
		);
521
		if (!$sensitive) {
522
			$orExtOrPassive->add(
523
				$expr->eq($aliasRemote . '.type', $this->createNamedParameter(RemoteInstance::TYPE_PASSIVE))
524
			);
525
		} else {
526
			if ($this->getDefaultSelectAlias() === CoreQueryBuilder::MEMBER) {
527
				$orExtOrPassive->add($this->limitRemoteVisibility_Sensitive_Members($aliasRemote));
528
			}
529
		}
530
531
		$orInstance = $expr->orX();
532
		$orInstance->add($expr->isNotNull($aliasRemoteMember . '.instance'));
533
		$orInstance->add($expr->isNotNull($aliasRemoteCircleOwner . '.instance'));
534
535
		$andExternal = $expr->andX();
536
		$andExternal->add($orExtOrPassive);
537
		$andExternal->add($orInstance);
538
539
		$orExtOrTrusted = $expr->orX();
540
		$orExtOrTrusted->add($andExternal);
541
		$orExtOrTrusted->add(
542
			$expr->eq($aliasRemote . '.type', $this->createNamedParameter(RemoteInstance::TYPE_TRUSTED))
543
		);
544
545
		$andTrusted = $expr->andX();
546
		$andTrusted->add($orExtOrTrusted);
547
		$andTrusted->add($expr->bitwiseAnd($aliasCircle . '.config', Circle::CFG_FEDERATED));
548
		$andTrusted->add($expr->emptyString($aliasOwner . '.instance'));
549
		$orX->add($andTrusted);
550
551
		$this->andWhere($orX);
552
	}
553
554
555
	/**
556
	 * @param string $alias
557
	 * @param Member $member
558
	 *
559
	 * @throws RequestBuilderException
560
	 */
561
	public function limitToDirectMembership(string $alias, Member $member): void {
562
		if ($this->getType() !== QueryBuilder::SELECT) {
563
			return;
564
		}
565
566
		$aliasMember = $this->generateAlias($alias, self::MEMBER, $options);
567
		$getData = $this->getBool('getData', $options, false);
568
569
		$expr = $this->expr();
570
		if ($getData) {
571
			$this->generateMemberSelectAlias($aliasMember);
572
		}
573
		$this->leftJoin(
574
			$this->getDefaultSelectAlias(), CoreRequestBuilder::TABLE_MEMBER, $aliasMember,
575
			$expr->eq($aliasMember . '.circle_id', $alias . '.unique_id')
576
		);
577
578
		$this->filterDirectMembership($aliasMember, $member);
579
	}
580
581
582
	/**
583
	 * @param string $aliasMember
584
	 * @param Member $member
585
	 */
586
	public function filterDirectMembership(string $aliasMember, Member $member): void {
587
		if ($this->getType() !== QueryBuilder::SELECT) {
588
			return;
589
		}
590
591
		$expr = $this->expr();
592
		$andX = $expr->andX();
593
594
		if ($member->getUserId() !== '') {
595
			$andX->add(
596
				$expr->eq($aliasMember . '.user_id', $this->createNamedParameter($member->getUserId()))
597
			);
598
		}
599
600
		if ($member->getSingleId() !== '') {
601
			$andX->add(
602
				$expr->eq($aliasMember . '.single_id', $this->createNamedParameter($member->getSingleId()))
603
			);
604
		}
605
606
		if ($member->getUserType() > 0) {
607
			$andX->add(
608
				$expr->eq($aliasMember . '.user_type', $this->createNamedParameter($member->getUserType()))
609
			);
610
		}
611
612
		$andX->add(
613
			$expr->eq($aliasMember . '.instance', $this->createNamedParameter($this->getInstance($member)))
614
		);
615
616
		if ($member->getLevel() > 0) {
617
			$andX->add($expr->gte($aliasMember . '.level', $this->createNamedParameter($member->getLevel())));
618
		}
619
620
		$this->andWhere($andX);
621
	}
622
623
624
	/**
625
	 * @param string $alias
626
	 * @param IFederatedUser|null $initiator
627
	 * @param string $field
628
	 *
629
	 * @throws RequestBuilderException
630
	 */
631
	public function leftJoinCircle(
632
		string $alias,
633
		?IFederatedUser $initiator = null,
634
		string $field = 'circle_id'
635
	): void {
636
		if ($this->getType() !== QueryBuilder::SELECT) {
637
			return;
638
		}
639
640
		$aliasCircle = $this->generateAlias($alias, self::CIRCLE, $options);
641
		$getData = $this->getBool('getData', $options, false);
642
		$expr = $this->expr();
643
644
		if ($getData) {
645
			$this->generateCircleSelectAlias($aliasCircle);
646
		}
647
648
		$this->leftJoin(
649
			$alias, CoreRequestBuilder::TABLE_CIRCLE, $aliasCircle,
650
			$expr->eq($aliasCircle . '.unique_id', $alias . '.' . $field)
651
		);
652
653
		if (!is_null($initiator)) {
654
//			$this->setOptions(
655
//				explode('_', $aliasCircle), [
656
//											  'mustBeMember' => true,
657
//											  'canBeVisitor' => false
658
//										  ]
659
//			);
660
661
			$this->limitToInitiator($aliasCircle, $initiator);
662
		}
663
664
		$this->leftJoinOwner($aliasCircle);
665
	}
666
667
668
	/**
669
	 * @param string $aliasMember
670
	 * @param IFederatedUser|null $initiator
671
	 *
672
	 * @throws RequestBuilderException
673
	 */
674
	public function leftJoinBasedOn(
675
		string $aliasMember,
676
		?IFederatedUser $initiator = null
677
	): void {
678
		if ($this->getType() !== QueryBuilder::SELECT) {
679
			return;
680
		}
681
682
		try {
683
			$aliasBasedOn = $this->generateAlias($aliasMember, self::BASED_ON, $options);
684
		} catch (RequestBuilderException $e) {
685
			return;
686
		}
687
688
		$expr = $this->expr();
689
		$this->generateCircleSelectAlias($aliasBasedOn)
690
			 ->leftJoin(
691
				 $aliasMember, CoreRequestBuilder::TABLE_CIRCLE, $aliasBasedOn,
692
				 $expr->eq($aliasBasedOn . '.unique_id', $aliasMember . '.single_id')
693
			 );
694
695
		if (!is_null($initiator)) {
696
			$this->leftJoinInitiator($aliasBasedOn, $initiator);
697
			$this->leftJoinOwner($aliasBasedOn);
698
		}
699
	}
700
701
702
	/**
703
	 * @param string $alias
704
	 * @param string $field
705
	 *
706
	 * @throws RequestBuilderException
707
	 */
708
	public function leftJoinOwner(string $alias, string $field = 'unique_id'): void {
709
		if ($this->getType() !== QueryBuilder::SELECT) {
710
			return;
711
		}
712
713
		try {
714
			$aliasMember = $this->generateAlias($alias, self::OWNER, $options);
715
			$getData = $this->getBool('getData', $options, false);
0 ignored issues
show
Unused Code introduced by
$getData is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
716
		} catch (RequestBuilderException $e) {
717
			return;
718
		}
719
720
		$expr = $this->expr();
721
		$this->generateMemberSelectAlias($aliasMember)
722
			 ->leftJoin(
723
				 $alias, CoreRequestBuilder::TABLE_MEMBER, $aliasMember,
724
				 $expr->andX(
725
					 $expr->eq($aliasMember . '.circle_id', $alias . '.' . $field),
726
					 $expr->eq(
727
						 $aliasMember . '.level',
728
						 $this->createNamedParameter(Member::LEVEL_OWNER, self::PARAM_INT)
729
					 )
730
				 )
731
			 );
732
733
		$this->leftJoinBasedOn($aliasMember);
734
	}
735
736
737
	/**
738
	 * @param string $alias
739
	 * @param string $fieldCircleId
740
	 * @param string $fieldSingleId
741
	 *
742
	 * @throws RequestBuilderException
743
	 */
744
	public function leftJoinMember(
745
		string $alias,
746
		string $fieldCircleId = 'circle_id',
747
		string $fieldSingleId = 'single_id'
748
	): void {
749
		if ($this->getType() !== QueryBuilder::SELECT) {
750
			return;
751
		}
752
753
		try {
754
			$aliasMember = $this->generateAlias($alias, self::MEMBER, $options);
755
			$getData = $this->getBool('getData', $options, false);
0 ignored issues
show
Unused Code introduced by
$getData is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
756
		} catch (RequestBuilderException $e) {
757
			return;
758
		}
759
760
		$expr = $this->expr();
761
		$this->generateMemberSelectAlias($aliasMember)
762
			 ->leftJoin(
763
				 $alias, CoreRequestBuilder::TABLE_MEMBER, $aliasMember,
764
				 $expr->andX(
765
					 $expr->eq($aliasMember . '.circle_id', $alias . '.' . $fieldCircleId),
766
					 $expr->eq($aliasMember . '.single_id', $alias . '.' . $fieldSingleId),
767
					 $expr->gte(
768
						 $aliasMember . '.level',
769
						 $this->createNamedParameter(Member::LEVEL_MEMBER, self::PARAM_INT)
770
					 )
771
				 )
772
			 );
773
774
		$this->leftJoinRemoteInstance($aliasMember);
775
		$this->leftJoinBasedOn($aliasMember);
776
	}
777
778
779
	/**
780
	 * if 'getData' is true, will returns 'inheritanceBy': the Member at the end of a sub-chain of
781
	 * memberships (based on $field for Top Circle's singleId)
782
	 *
783
	 * @param string $alias
784
	 * @param string $field
785
	 * @param string $aliasInheritedBy
786
	 *
787
	 * @throws RequestBuilderException
788
	 */
789
	public function leftJoinInheritedMembers(string $alias, string $field = '', string $aliasInheritedBy = ''
790
	): void {
791
		$expr = $this->expr();
792
793
		$field = ($field === '') ? 'circle_id' : $field;
794
		$aliasMembership = $this->generateAlias($alias, self::MEMBERSHIPS, $options);
795
796
		$this->leftJoin(
797
			$alias, CoreRequestBuilder::TABLE_MEMBERSHIP, $aliasMembership,
798
			$expr->eq($aliasMembership . '.circle_id', $alias . '.' . $field)
799
		);
800
801
//		if (!$this->getBool('getData', $options, false)) {
802
//			return;
803
//		}
804
805
		if ($aliasInheritedBy === '') {
806
			$aliasInheritedBy = $this->generateAlias($alias, self::INHERITED_BY);
807
		}
808
		$this->generateMemberSelectAlias($aliasInheritedBy)
809
			 ->leftJoin(
810
				 $alias, CoreRequestBuilder::TABLE_MEMBER, $aliasInheritedBy,
811
				 $expr->andX(
812
					 $expr->eq($aliasMembership . '.inheritance_last', $aliasInheritedBy . '.circle_id'),
813
					 $expr->eq($aliasMembership . '.single_id', $aliasInheritedBy . '.single_id')
814
				 )
815
			 );
816
817
		$this->leftJoinBasedOn($aliasInheritedBy);
818
	}
819
820
821
	/**
822
	 * @throws RequestBuilderException
823
	 */
824
	public function limitToInheritedMemberships(string $alias, string $singleId, string $field = ''): void {
825
		$expr = $this->expr();
826
		$field = ($field === '') ? 'circle_id' : $field;
827
		$aliasUpstreamMembership = $this->generateAlias($alias, self::UPSTREAM_MEMBERSHIPS, $options);
828
		$this->leftJoin(
829
			$alias, CoreRequestBuilder::TABLE_MEMBERSHIP, $aliasUpstreamMembership,
830
			$expr->eq($aliasUpstreamMembership . '.single_id', $this->createNamedParameter($singleId))
831
		);
832
833
		$orX = $expr->orX(
834
			$expr->eq($aliasUpstreamMembership . '.circle_id', $alias . '.' . $field),
835
			$expr->eq($alias . '.' . $field, $this->createNamedParameter($singleId))
836
		);
837
838
		$this->andWhere($orX);
839
	}
840
841
842
	/**
843
	 * limit the request to Members and Sub Members of a Circle.
844
	 *
845
	 * @param string $alias
846
	 * @param string $singleId
847
	 *
848
	 * @throws RequestBuilderException
849
	 */
850
	public function limitToMembersByInheritance(string $alias, string $singleId): void {
851
		$this->leftJoinMembersByInheritance($alias);
852
853
		$expr = $this->expr();
854
		$aliasMembership = $this->generateAlias($alias, self::MEMBERSHIPS);
855
		$this->andWhere($expr->eq($aliasMembership . '.circle_id', $this->createNamedParameter($singleId)));
856
	}
857
858
859
	/**
860
	 * if 'getData' is true, will returns 'inheritanceFrom': the Circle-As-Member of the Top Circle
861
	 * that explain the membership of a Member (based on $field for singleId) to a specific Circle
862
	 *
863
	 * // TODO: returns the link/path ?
864
	 *
865
	 * @param string $alias
866
	 * @param string $field
867
	 *
868
	 * @throws RequestBuilderException
869
	 */
870
	public function leftJoinMembersByInheritance(string $alias, string $field = ''): void {
871
		$expr = $this->expr();
872
873
		$field = ($field === '') ? 'circle_id' : $field;
874
		$aliasMembership = $this->generateAlias($alias, self::MEMBERSHIPS, $options);
875
876
		$this->leftJoin(
877
			$alias, CoreRequestBuilder::TABLE_MEMBERSHIP, $aliasMembership,
878
//			$expr->andX(
879
			$expr->eq($aliasMembership . '.inheritance_last', $alias . '.' . $field)
880
//				$expr->eq($aliasMembership . '.single_id', $alias . '.single_id')
881
//			)
882
		);
883
884
		if (!$this->getBool('getData', $options, false)) {
885
			return;
886
		}
887
888
		$aliasInheritanceFrom = $this->generateAlias($alias, self::INHERITANCE_FROM);
889
		$this->generateMemberSelectAlias($aliasInheritanceFrom)
890
			 ->leftJoin(
891
				 $aliasMembership, CoreRequestBuilder::TABLE_MEMBER, $aliasInheritanceFrom,
892
				 $expr->andX(
893
					 $expr->eq($aliasMembership . '.circle_id', $aliasInheritanceFrom . '.circle_id'),
894
					 $expr->eq($aliasMembership . '.inheritance_first', $aliasInheritanceFrom . '.single_id')
895
				 )
896
			 );
897
	}
898
899
900
	/**
901
	 * limit the result to the point of view of a FederatedUser
902
	 *
903
	 * @param string $alias
904
	 * @param IFederatedUser $user
905
	 * @param string $field
906
	 *
907
	 * @throws RequestBuilderException
908
	 */
909
	public function limitToInitiator(string $alias, IFederatedUser $user, string $field = ''): void {
910
		$this->leftJoinInitiator($alias, $user, $field);
911
		$this->limitInitiatorVisibility($alias);
912
913
		$aliasInitiator = $this->generateAlias($alias, self::INITIATOR, $options);
914
		if ($this->getBool('getData', $options, false)) {
915
			$this->leftJoinBasedOn($aliasInitiator);
916
		}
917
	}
918
919
920
	/**
921
	 * Left join members to filter userId as initiator.
922
	 *
923
	 * @param string $alias
924
	 * @param IFederatedUser $initiator
925
	 * @param string $field
926
	 *
927
	 * @throws RequestBuilderException
928
	 */
929
	public function leftJoinInitiator(string $alias, IFederatedUser $initiator, string $field = ''): void {
930
		if ($this->getType() !== QueryBuilder::SELECT) {
931
			return;
932
		}
933
934
		$expr = $this->expr();
935
		$field = ($field === '') ? 'unique_id' : $field;
936
		$aliasMembership = $this->generateAlias($alias, self::MEMBERSHIPS, $options);
937
938
		$this->leftJoin(
939
			$alias, CoreRequestBuilder::TABLE_MEMBERSHIP, $aliasMembership,
940
			$expr->andX(
941
				$expr->eq(
942
					$aliasMembership . '.single_id',
943
					$this->createNamedParameter($initiator->getSingleId())
944
				),
945
				$expr->eq($aliasMembership . '.circle_id', $alias . '.' . $field)
946
			)
947
		);
948
949
		if (!$this->getBool('getData', $options, false)) {
950
			return;
951
		}
952
953
		try {
954
			$aliasInitiator = $this->generateAlias($alias, self::INITIATOR, $options);
955
			$this->leftJoin(
956
				$aliasMembership, CoreRequestBuilder::TABLE_MEMBER, $aliasInitiator,
957
				$expr->andX(
958
					$expr->eq($aliasMembership . '.inheritance_first', $aliasInitiator . '.single_id'),
959
					$expr->eq($aliasMembership . '.circle_id', $aliasInitiator . '.circle_id')
960
				)
961
			);
962
963
			$aliasInheritedBy = $this->generateAlias($aliasInitiator, self::INHERITED_BY);
964
			$this->leftJoin(
965
				$aliasInitiator, CoreRequestBuilder::TABLE_MEMBER, $aliasInheritedBy,
966
				$expr->andX(
967
					$expr->eq($aliasMembership . '.single_id', $aliasInheritedBy . '.single_id'),
968
					$expr->eq($aliasMembership . '.inheritance_last', $aliasInheritedBy . '.circle_id')
969
				)
970
			);
971
972
			$default = [];
973
			if ($this->getBool('canBeVisitor', $options)) {
974
				$default = [
975
					'user_id'   => $initiator->getUserId(),
976
					'single_id' => $initiator->getSingleId(),
977
					'user_type' => $initiator->getUserType(),
978
					'instance'  => $initiator->getInstance()
979
				];
980
			}
981
			$this->generateMemberSelectAlias($aliasInitiator, $default);
982
983
			$this->generateMemberSelectAlias($aliasInheritedBy);
984
			$aliasInheritedByMembership = $this->generateAlias($aliasInheritedBy, self::MEMBERSHIPS);
985
			$this->generateMembershipSelectAlias($aliasMembership, $aliasInheritedByMembership);
986
		} catch (RequestBuilderException $e) {
987
			\OC::$server->getLogger()->log(3, '-- ' . $e->getMessage());
988
		}
989
	}
990
991
992
	/**
993
	 * @param string $alias
994
	 *
995
	 * @throws RequestBuilderException
996
	 */
997
	protected function limitInitiatorVisibility(string $alias) {
998
		$aliasMembership = $this->generateAlias($alias, self::MEMBERSHIPS, $options);
999
		$getPersonalCircle = $this->getBool('getPersonalCircle', $options, false);
1000
		$mustBeMember = $this->getBool('mustBeMember', $options, true);
1001
		$canBeVisitor = $this->getBool('canBeVisitor', $options, false);
1002
1003
		$expr = $this->expr();
1004
1005
		// Visibility to non-member is
1006
		// - 0 (default), if initiator is member
1007
		// - 2 (Personal), if initiator is owner)
1008
		// - 4 (Visible to everyone)
1009
		$orX = $expr->orX();
1010
		$orX->add(
1011
			$expr->andX(
1012
				$expr->gte($aliasMembership . '.level', $this->createNamedParameter(Member::LEVEL_MEMBER))
1013
			)
1014
		);
1015
1016
		if ($getPersonalCircle) {
1017
			$orX->add(
1018
				$expr->andX(
1019
					$expr->bitwiseAnd($alias . '.config', Circle::CFG_PERSONAL),
1020
					$expr->eq($aliasMembership . '.level', $this->createNamedParameter(Member::LEVEL_OWNER))
1021
				)
1022
			);
1023
		}
1024
		if (!$mustBeMember) {
1025
			$orX->add($expr->bitwiseAnd($alias . '.config', Circle::CFG_VISIBLE));
1026
		}
1027
		if ($canBeVisitor) {
1028
			// TODO: should find a better way, also filter on remote initiator on non-federated ?
1029
			$orX->add($expr->gte($alias . '.config', $this->createNamedParameter(0)));
1030
		}
1031
		$this->andWhere($orX);
1032
1033
1034
//		$orTypes = $this->generateLimit($qb, $circleUniqueId, $userId, $type, $name, $forceAll);
1035
//		if (sizeof($orTypes) === 0) {
1036
//			throw new ConfigNoCircleAvailableException(
1037
//				$this->l10n->t(
1038
//					'You cannot use the Circles Application until your administrator has allowed at least one type of circles'
1039
//				)
1040
//			);
1041
//		}
1042
1043
//		$orXTypes = $this->expr()
1044
//						 ->orX();
1045
//		foreach ($orTypes as $orType) {
1046
//			$orXTypes->add($orType);
1047
//		}
1048
//
1049
//		$qb->andWhere($orXTypes);
1050
	}
1051
1052
1053
	/**
1054
	 * CFG_SINGLE, CFG_HIDDEN and CFG_BACKEND means hidden from listing.
1055
	 *
1056
	 * @param string $aliasCircle
1057
	 * @param int $flag
1058
	 */
1059
	public function filterCircles(
1060
		string $aliasCircle,
1061
		int $flag = Circle::CFG_SINGLE | Circle::CFG_HIDDEN | Circle::CFG_BACKEND
1062
	): void {
1063
		if ($flag === 0) {
1064
			return;
1065
		}
1066
1067
		$expr = $this->expr();
1068
		$hide = $expr->andX();
1069
		foreach (Circle::$DEF_CFG as $cfg => $v) {
1070
			if ($flag & $cfg) {
1071
				$hide->add($this->createFunction('NOT') . $expr->bitwiseAnd($aliasCircle . '.config', $cfg));
1072
			}
1073
		}
1074
1075
		$this->andWhere($hide);
1076
	}
1077
1078
1079
	/**
1080
	 * Limit visibility on Sensitive information when search for members.
1081
	 *
1082
	 * @param string $alias
1083
	 *
1084
	 * @return ICompositeExpression
1085
	 */
1086
	private function limitRemoteVisibility_Sensitive_Members(string $alias = 'ri'): ICompositeExpression {
1087
		$expr = $this->expr();
1088
		$andPassive = $expr->andX();
1089
		$andPassive->add(
1090
			$expr->eq($alias . '.type', $this->createNamedParameter(RemoteInstance::TYPE_PASSIVE))
1091
		);
1092
1093
		$orMemberOrLevel = $expr->orX();
1094
		$orMemberOrLevel->add(
1095
			$expr->eq($this->getDefaultSelectAlias() . '.instance', $alias . '.instance')
1096
		);
1097
		// TODO: do we need this ? (display members from the local instance)
1098
		$orMemberOrLevel->add(
1099
			$expr->emptyString($this->getDefaultSelectAlias() . '.instance')
1100
		);
1101
1102
		$orMemberOrLevel->add(
1103
			$expr->eq(
1104
				$this->getDefaultSelectAlias() . '.level',
1105
				$this->createNamedParameter(Member::LEVEL_OWNER)
1106
			)
1107
		);
1108
		$andPassive->add($orMemberOrLevel);
1109
1110
		return $andPassive;
1111
	}
1112
1113
1114
	/**
1115
	 *
1116
	 * @param string $aliasCircle
1117
	 * @param int $flag
1118
	 */
1119
	public function filterConfig(string $aliasCircle, int $flag): void {
1120
		$this->andWhere($this->expr()->bitwiseAnd($aliasCircle . '.config', $flag));
1121
	}
1122
1123
1124
	/**
1125
	 * Link to storage/filecache
1126
	 *
1127
	 * @param string $aliasShare
1128
	 *
1129
	 * @throws RequestBuilderException
1130
	 */
1131
	public function leftJoinFileCache(string $aliasShare) {
1132
		$expr = $this->expr();
1133
1134
		$aliasFileCache = $this->generateAlias($aliasShare, self::FILE_CACHE);
1135
		$aliasStorages = $this->generateAlias($aliasFileCache, self::STORAGES);
1136
1137
		$fieldsFileCache = [
1138
			'fileid', 'path', 'permissions', 'storage', 'path_hash', 'parent', 'name', 'mimetype', 'mimepart',
1139
			'size', 'mtime', 'storage_mtime', 'encrypted', 'unencrypted_size', 'etag', 'checksum'
1140
		];
1141
1142
		$this->generateSelectAlias($fieldsFileCache, $aliasFileCache, $aliasFileCache, [])
1143
			 ->generateSelectAlias(['id'], $aliasStorages, $aliasStorages, [])
1144
			 ->leftJoin(
1145
				 $aliasShare, CoreRequestBuilder::TABLE_FILE_CACHE, $aliasFileCache,
1146
				 $expr->eq($aliasShare . '.file_source', $aliasFileCache . '.fileid')
1147
			 )
1148
			 ->leftJoin(
1149
				 $aliasFileCache, CoreRequestBuilder::TABLE_STORAGES, $aliasStorages,
1150
				 $expr->eq($aliasFileCache . '.storage', $aliasStorages . '.numeric_id')
1151
			 );
1152
	}
1153
1154
1155
	/**
1156
	 * @param string $aliasShare
1157
	 * @param string $aliasShareMemberships
1158
	 *
1159
	 * @throws RequestBuilderException
1160
	 */
1161
	public function leftJoinShareChild(string $aliasShare, string $aliasShareMemberships = '') {
1162
		$expr = $this->expr();
1163
1164
		$aliasShareChild = $this->generateAlias($aliasShare, self::SHARE);
1165
		if ($aliasShareMemberships === '') {
1166
			$aliasShareMemberships = $this->generateAlias($aliasShare, self::MEMBERSHIPS, $options);
1167
		}
1168
1169
		$this->leftJoin(
1170
			$aliasShareMemberships, CoreRequestBuilder::TABLE_SHARE, $aliasShareChild,
1171
			$expr->andX(
1172
				$expr->eq($aliasShareChild . '.parent', $aliasShare . '.id'),
1173
				$expr->eq($aliasShareChild . '.share_with', $aliasShareMemberships . '.single_id')
1174
			)
1175
		);
1176
1177
		$this->selectAlias($aliasShareChild . '.id', 'child_id');
1178
		$this->selectAlias($aliasShareChild . '.file_target', 'child_file_target');
1179
//		$this->selectAlias($aliasShareParent . '.permissions', 'parent_perms');
1180
	}
1181
1182
1183
	/**
1184
	 * @param string $alias
1185
	 * @param FederatedUser $federatedUser
1186
	 * @param bool $reshares
1187
	 */
1188
	public function limitToShareOwner(string $alias, FederatedUser $federatedUser, bool $reshares): void {
1189
		$expr = $this->expr();
1190
1191
		$orX = $expr->orX(
1192
			$expr->eq($alias . '.uid_initiator', $this->createNamedParameter($federatedUser->getUserId()))
1193
		);
1194
1195
		if ($reshares) {
1196
			$orX->add(
1197
				$expr->eq($alias . '.uid_owner', $this->createNamedParameter($federatedUser->getUserId()))
1198
			);
1199
		}
1200
1201
		$this->andWhere($orX);
1202
	}
1203
1204
1205
	/**
1206
	 * @param string $aliasMount
1207
	 * @param string $aliasMountMemberships
1208
	 *
1209
	 * @throws RequestBuilderException
1210
	 */
1211
	public function leftJoinMountpoint(string $aliasMount, string $aliasMountMemberships = '') {
1212
		$expr = $this->expr();
1213
1214
		$aliasMountpoint = $this->generateAlias($aliasMount, self::MOUNTPOINT);
1215
		if ($aliasMountMemberships === '') {
1216
			$aliasMountMemberships = $this->generateAlias($aliasMount, self::MEMBERSHIPS, $options);
1217
		}
1218
1219
		$this->leftJoin(
1220
			$aliasMountMemberships, CoreRequestBuilder::TABLE_MOUNTPOINT, $aliasMountpoint,
1221
			$expr->andX(
1222
				$expr->eq($aliasMountpoint . '.mount_id', $aliasMount . '.mount_id'),
1223
				$expr->eq($aliasMountpoint . '.single_id', $aliasMountMemberships . '.single_id')
1224
			)
1225
		);
1226
1227
		$this->selectAlias($aliasMountpoint . '.mountpoint', $aliasMountpoint . '_mountpoint');
1228
		$this->selectAlias($aliasMountpoint . '.mountpoint_hash', $aliasMountpoint . '_mountpoint_hash');
1229
	}
1230
1231
1232
	/**
1233
	 * @param string $alias
1234
	 * @param array $default
1235
	 *
1236
	 * @return CoreQueryBuilder
1237
	 */
1238
	private function generateCircleSelectAlias(string $alias, array $default = []): self {
1239
		$fields = [
1240
			'unique_id', 'name', 'display_name', 'source', 'description', 'settings', 'config',
1241
			'contact_addressbook', 'contact_groupname', 'creation'
1242
		];
1243
1244
		$this->generateSelectAlias($fields, $alias, $alias, $default);
1245
1246
		return $this;
1247
	}
1248
1249
	/**
1250
	 * @param string $alias
1251
	 * @param array $default
1252
	 *
1253
	 * @return $this
1254
	 */
1255
	private function generateMemberSelectAlias(string $alias, array $default = []): self {
1256
		$fields = [
1257
			'circle_id', 'single_id', 'user_id', 'user_type', 'member_id', 'instance', 'cached_name',
1258
			'cached_update', 'status', 'level', 'note', 'contact_id', 'contact_meta', 'joined'
1259
		];
1260
1261
		$this->generateSelectAlias($fields, $alias, $alias, $default);
1262
1263
		return $this;
1264
	}
1265
1266
1267
	/**
1268
	 * @param string $alias
1269
	 * @param array $default
1270
	 * @param string $prefix
1271
	 *
1272
	 * @return $this
1273
	 */
1274
	private function generateMembershipSelectAlias(
1275
		string $alias,
1276
		string $prefix = '',
1277
		array $default = []
1278
	): self {
1279
		$fields = [
1280
			'single_id', 'circle_id', 'level', 'inheritance_first', 'inheritance_last', 'inheritance_path',
1281
			'inheritance_depth'
1282
		];
1283
		$this->generateSelectAlias($fields, $alias, ($prefix === '') ? $alias : $prefix, $default);
1284
1285
		return $this;
1286
	}
1287
1288
1289
	/**
1290
	 * @param string $alias
1291
	 * @param array $default
1292
	 *
1293
	 * @return $this
1294
	 */
1295
	private function generateRemoteInstanceSelectAlias(string $alias, array $default = []): self {
1296
		$fields = ['id', 'type', 'uid', 'instance', 'href', 'item', 'creation'];
1297
1298
		$this->generateSelectAlias($fields, $alias, $alias, $default);
1299
1300
		return $this;
1301
	}
1302
1303
1304
	/**
1305
	 * @param array $path
1306
	 * @param array $options
1307
	 */
1308
	public function setOptions(array $path, array $options): void {
1309
		$options = [self::OPTIONS => $options];
1310
		foreach (array_reverse($path) as $item) {
1311
			$options = [$item => $options];
1312
		}
1313
1314
		$this->options = $options;
1315
	}
1316
1317
1318
	/**
1319
	 * @param string $base
1320
	 * @param string $extension
1321
	 * @param array|null $options
1322
	 *
1323
	 * @return string
1324
	 * @throws RequestBuilderException
1325
	 */
1326
	public function generateAlias(string $base, string $extension, ?array &$options = []): string {
1327
		$search = str_replace('_', '.', $base);
1328
		$path = $search . '.' . $extension;
1329
		if (!$this->validKey($path, self::$SQL_PATH)
1330
			&& !in_array($extension, $this->getArray($search, self::$SQL_PATH))) {
1331
			throw new RequestBuilderException($extension . ' not found in ' . $search);
1332
		}
1333
1334
		if (!is_array($options)) {
1335
			$options = [];
1336
		}
1337
1338
		$optionPath = '';
1339
		foreach (explode('.', $path) as $p) {
1340
			$optionPath = trim($optionPath . '.' . $p, '.');
1341
			$options = array_merge(
1342
				$options,
1343
				$this->getArray($optionPath . '.' . self::OPTIONS, self::$SQL_PATH),
1344
				$this->getArray($optionPath . '.' . self::OPTIONS, $this->options)
1345
			);
1346
		}
1347
1348
		return $base . '_' . $extension;
1349
	}
1350
1351
1352
	/**
1353
	 * @param string $prefix
1354
	 *
1355
	 * @return array
1356
	 */
1357
	public function getAvailablePath(string $prefix): array {
1358
		$prefix = trim($prefix, '_');
1359
		$search = str_replace('_', '.', $prefix);
1360
1361
		$path = [];
1362
		foreach ($this->getArray($search, self::$SQL_PATH) as $arr => $item) {
1363
			if (is_numeric($arr)) {
1364
				$k = $item;
1365
			} else {
1366
				$k = $arr;
1367
			}
1368
			$path[$k] = $prefix . '_' . $k . '_';
1369
		}
1370
1371
		return $path;
1372
	}
1373
1374
}
1375
1376