Completed
Push — some-scrutinizing ( d3de44...c6cac2 )
by Maxence
02:32
created

CirclesMapper::buildWithOrXTypes()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 16
rs 9.4285
cc 3
eloc 9
nc 3
nop 5
1
<?php
2
/**
3
 * Circles - Bring cloud-users closer together.
4
 *
5
 * This file is licensed under the Affero General Public License version 3 or
6
 * later. See the COPYING file.
7
 *
8
 * @author Maxence Lange <[email protected]>
9
 * @copyright 2017
10
 * @license GNU AGPL version 3 or any later version
11
 *
12
 * This program is free software: you can redistribute it and/or modify
13
 * it under the terms of the GNU Affero General Public License as
14
 * published by the Free Software Foundation, either version 3 of the
15
 * License, or (at your option) any later version.
16
 *
17
 * This program is distributed in the hope that it will be useful,
18
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20
 * GNU Affero General Public License for more details.
21
 *
22
 * You should have received a copy of the GNU Affero General Public License
23
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
24
 *
25
 */
26
27
namespace OCA\Circles\Db;
28
29
use OCA\Circles\Exceptions\CircleAlreadyExistsException;
30
use OCA\Circles\Exceptions\CircleDoesNotExistException;
31
use OCA\Circles\Exceptions\ConfigNoCircleAvailable;
32
use OCA\Circles\Model\Circle;
33
use OCA\Circles\Model\Member;
34
35
use OCP\DB\QueryBuilder\IQueryBuilder;
36
use OCP\IDBConnection;
37
use OCP\AppFramework\Db\Mapper;
38
39
class CirclesMapper extends Mapper {
40
41
	const TABLENAME = 'circles_circles';
42
43
	private $miscService;
44
45
	public function __construct(IDBConnection $db, $miscService) {
46
		parent::__construct($db, self::TABLENAME, 'OCA\Circles\Db\Circles');
47
		$this->miscService = $miscService;
48
49
	}
50
51
52
	/**
53
	 * Returns all circle from a user point-of-view
54
	 *
55
	 * @param $userId
56
	 * @param $type
57
	 * @param string $name
58
	 * @param int $level
59
	 * @param int $circleId
60
	 *
61
	 * @return Circle[]
62
	 * @throws ConfigNoCircleAvailable
63
	 */
64
	public function findCirclesByUser($userId, $type, $name = '', $level = 0, $circleId = -1) {
65
66
		$type = (int)$type;
67
		$level = (int)$level;
68
		$circleId = (int)$circleId;
69
		$qb = $this->findCirclesByUserSql($userId, $type, $name, $level, $circleId);
70
		$cursor = $qb->execute();
71
72
		$result = [];
73
		while ($data = $cursor->fetch()) {
74
			if ($name === '' || stripos($data['name'], $name) !== false) {
75
				$circle = new Circle();
76
				$result[] = $circle->fromArray($data);
77
			}
78
		}
79
		$cursor->closeCursor();
80
81
		return $result;
82
	}
83
84
85
	/**
86
	 * Returns SQL for findCirclesByUser
87
	 *
88
	 * @param $userId
89
	 * @param $type
90
	 * @param $name
91
	 * @param $level
92
	 * @param $circleId
93
	 *
94
	 * @return IQueryBuilder
95
	 * @throws ConfigNoCircleAvailable
96
	 */
97
	private function findCirclesByUserSql($userId, $type, $name, $level, $circleId) {
98
		$qb = $this->db->getQueryBuilder();
99
		$expr = $qb->expr();
100
101
		/** @noinspection PhpMethodParametersCountMismatchInspection */
102
		$qb->select(
103
			'c.id', 'c.name', 'c.description', 'c.type', 'c.creation',
104
			'u.joined', 'u.level', 'u.status'
105
		)
106
		   ->selectAlias('o.user_id', 'owner')
107
		   ->from(self::TABLENAME, 'c')
108
		   ->from(MembersMapper::TABLENAME, 'o')
109
		   ->where(
110
			   $expr->eq('c.id', 'o.circle_id'),
111
			   $expr->eq('o.level', $qb->createNamedParameter(Member::LEVEL_OWNER))
112
		   )
113
		   ->leftJoin(
114
			   'c', MembersMapper::TABLENAME, 'u',
115
			   $expr->andX(
116
				   $expr->eq('c.id', 'u.circle_id'),
117
				   $expr->eq('u.user_id', $qb->createNamedParameter($userId))
118
			   )
119
		   );
120
121
		$this->buildWithMemberLevel($qb, 'u.level', $level);
122
		$this->buildWithCircleId($qb, 'c.id', $circleId);
123
		$this->buildWithOrXTypes($qb, $userId, $type, $name, $circleId);
124
125
		$qb->groupBy('c.id');
126
		$qb->orderBy('c.name', 'ASC');
127
128
		return $qb;
129
	}
130
131
132
	private function buildWithOrXTypes(&$qb, $userId, $type, $name, $circleId) {
133
134
		$orTypesArray = $this->fillOrXTypes($qb, $userId, $type, $name, $circleId);
135
		if (sizeof($orTypesArray) === 0) {
136
			throw new ConfigNoCircleAvailable();
137
		}
138
139
		$orXTypes = $qb->expr()
140
					   ->orX();
141
142
		foreach ($orTypesArray as $orTypes) {
143
			$orXTypes->add($orTypes);
144
		}
145
146
		$qb->andWhere($orXTypes);
147
	}
148
149
150
	/**
151
	 * fill with sql conditions for each type of circles.
152
	 *
153
	 * @param $qb
154
	 * @param $userId
155
	 * @param $type
156
	 * @param $name
157
	 * @param $circleId
158
	 *
159
	 * @return array
160
	 */
161
	private function fillOrXTypes(&$qb, $userId, $type, $name, $circleId) {
162
163
		$orTypesArray = [];
164
		array_push($orTypesArray, $this->generateTypeEntryForCirclePersonal($qb, $type, $userId));
165
		array_push(
166
			$orTypesArray, $this->generateTypeEntryForCircleHidden($qb, $type, $circleId, $name)
167
		);
168
		array_push($orTypesArray, $this->generateTypeEntryForCirclePrivate($qb, $type));
169
		array_push($orTypesArray, $this->generateTypeEntryForCirclePublic($qb, $type));
170
171
		return array_filter($orTypesArray);
172
	}
173
174
	/**
175
	 * @param IQueryBuilder $qb
176
	 * @param string $field
177
	 * @param int $circleId
178
	 */
179
	private function buildWithCircleId(IQueryBuilder & $qb, string $field, int $circleId) {
180
		if ($circleId > 0) {
181
			$qb->andWhere(
182
				$qb->expr()
183
				   ->eq($field, $qb->createNamedParameter($circleId))
184
			);
185
		}
186
	}
187
188
189
	/**
190
	 * @param IQueryBuilder $qb
191
	 * @param string $field
192
	 * @param int $level
193
	 */
194
	private function buildWithMemberLevel(IQueryBuilder & $qb, string $field, int $level) {
195
		if ($level > 0) {
196
			$qb->andWhere(
197
				$qb->expr()
198
				   ->gte($field, $qb->createNamedParameter($level))
199
			);
200
		}
201
	}
202
203
204
	/**
205
	 * @param IQueryBuilder $qb
206
	 * @param int $type
207
	 * @param int|string $userId
208
	 *
209
	 * @return \OCP\DB\QueryBuilder\ICompositeExpression
210
	 */
211
	private function generateTypeEntryForCirclePersonal(IQueryBuilder $qb, int $type, string $userId
212
	) {
213
		if (Circle::CIRCLES_PERSONAL & (int)$type) {
214
215
			/** @noinspection PhpMethodParametersCountMismatchInspection */
216
			return $qb->expr()
217
					  ->andX(
218
						  $qb->expr()
219
							 ->eq('c.type', $qb->createNamedParameter(Circle::CIRCLES_PERSONAL)),
220
						  $qb->expr()
221
							 ->eq('o.user_id', $qb->createNamedParameter($userId))
222
223
					  );
224
		}
225
226
		return null;
227
	}
228
229
	/**
230
	 * @param IQueryBuilder $qb
231
	 * @param int $type
232
	 * @param int $circleId
233
	 * @param string $name
234
	 *
235
	 * @return string
236
	 */
237
	private function generateTypeEntryForCircleHidden(
238
		IQueryBuilder $qb, int $type, int $circleId, string $name
239
	) {
240
		if (!(Circle::CIRCLES_HIDDEN & (int)$type)) {
241
			return null;
242
		}
243
244
		$expr = $qb->expr();
245
		/** @noinspection PhpMethodParametersCountMismatchInspection */
246
		$sqb = $expr->andX(
247
			$expr->eq('c.type', $qb->createNamedParameter(Circle::CIRCLES_HIDDEN)),
248
			$expr->orX(
249
				$expr->gte(
250
					'u.level', $qb->createNamedParameter(Member::LEVEL_MEMBER)
251
				),
252
				$expr->eq('c.id', $qb->createNamedParameter($circleId)),
253
				$expr->eq('c.name', $qb->createNamedParameter($name))
254
			)
255
		);
256
257
		return $sqb;
258
	}
259
260
261
	/**
262
	 * @param IQueryBuilder $qb
263
	 * @param int $type
264
	 *
265
	 * @return string
266
	 */
267 View Code Duplication
	private function generateTypeEntryForCirclePrivate(IQueryBuilder $qb, int $type) {
268
		if (Circle::CIRCLES_PRIVATE & (int)$type) {
269
			return $qb->expr()
270
					  ->eq(
271
						  'c.type',
272
						  $qb->createNamedParameter(Circle::CIRCLES_PRIVATE)
273
					  );
274
		}
275
276
		return null;
277
	}
278
279
280
	/**
281
	 * @param IQueryBuilder $qb
282
	 * @param int $type
283
	 *
284
	 * @return string
285
	 */
286 View Code Duplication
	private function generateTypeEntryForCirclePublic(IQueryBuilder $qb, int $type) {
287
		if (Circle::CIRCLES_PUBLIC & (int)$type) {
288
			return $qb->expr()
289
					  ->eq(
290
						  'c.type',
291
						  $qb->createNamedParameter(Circle::CIRCLES_PUBLIC)
292
					  );
293
		}
294
295
		return null;
296
	}
297
298
	/**
299
	 * Returns details about a circle.
300
	 *
301
	 * @param string $userId
302
	 * @param int $circleId
303
	 *
304
	 * @return Circle
305
	 * @throws CircleDoesNotExistException
306
	 * @throws ConfigNoCircleAvailable
307
	 */
308
	public function getDetailsFromCircle($circleId, $userId) {
309
310
		try {
311
			$result = $this->findCirclesByUser($userId, Circle::CIRCLES_ALL, '', 0, $circleId);
312
		} catch (ConfigNoCircleAvailable $e) {
313
			throw $e;
314
		}
315
316
		if (sizeof($result) !== 1) {
317
			throw new CircleDoesNotExistException(
318
				"The circle does not exist or is hidden to the user"
319
			);
320
		}
321
322
		return $result[0];
323
	}
324
325
326
	/**
327
	 * @param Circle $circle
328
	 * @param Member $owner
329
	 *
330
	 * @return bool
331
	 * @throws CircleAlreadyExistsException
332
	 */
333
	public function create(Circle & $circle, Member & $owner) {
334
335
		if (!$this->isCircleUnique($circle, $owner)) {
336
			throw new CircleAlreadyExistsException();
337
		}
338
339
		$qb = $this->db->getQueryBuilder();
340
		$qb->insert(self::TABLENAME)
341
		   ->setValue('name', $qb->createNamedParameter($circle->getName()))
342
		   ->setValue('description', $qb->createNamedParameter($circle->getDescription()))
343
		   ->setValue('type', $qb->createNamedParameter($circle->getType()));
344
		$qb->execute();
345
		$circleId = $qb->getLastInsertId();
346
347
		$circle->setId($circleId);
348
		$owner->setLevel(Member::LEVEL_OWNER)
349
			  ->setStatus(Member::STATUS_MEMBER)
350
			  ->setCircleId($circleId);
351
352
		return true;
353
	}
354
355
356
	/**
357
	 * remove a circle
358
	 *
359
	 * @param Circle $circle
360
	 */
361 View Code Duplication
	public function destroy(Circle $circle) {
362
		$qb = $this->db->getQueryBuilder();
363
		$qb->delete(self::TABLENAME)
364
		   ->where(
365
			   $qb->expr()
366
				  ->eq(
367
					  'id', $qb->createNamedParameter($circle->getId())
368
				  )
369
		   );
370
371
		$qb->execute();
372
	}
373
374
375
	/**
376
	 * returns if the circle is already in database
377
	 *
378
	 * @param Circle $circle
379
	 * @param Member $owner
380
	 *
381
	 * @return bool
382
	 */
383
	public function isCircleUnique(Circle $circle, Member $owner) {
384
385
		if ($circle->getType() === Circle::CIRCLES_PERSONAL) {
386
			return $this->isPersonalCircleUnique($circle, $owner);
387
		}
388
389
		$qb = $this->isCircleUniqueSql();
390
		$cursor = $qb->execute();
391
392
		while ($data = $cursor->fetch()) {
393
			if (strtolower($data['name']) === strtolower($circle->getName())) {
394
				return false;
395
			}
396
		}
397
		$cursor->closeCursor();
398
399
		return true;
400
	}
401
402
403
	/**
404
	 * Return SQL for isCircleUnique();
405
	 *
406
	 * @return IQueryBuilder
407
	 */
408
	private function isCircleUniqueSql() {
409
		$qb = $this->db->getQueryBuilder();
410
411
		/** @noinspection PhpMethodParametersCountMismatchInspection */
412
		$qb->select(
413
			'c.id', 'c.name', 'c.type'
414
		)
415
		   ->from(self::TABLENAME, 'c')
416
		   ->where(
417
			   $qb->expr()
418
				  ->neq('c.type', $qb->createNamedParameter(Circle::CIRCLES_PERSONAL))
419
		   );
420
421
		return $qb;
422
	}
423
424
425
	/**
426
	 * return if the personal circle is unique
427
	 *
428
	 * @param Circle $circle
429
	 * @param Member $owner
430
	 *
431
	 * @return bool
432
	 */
433
	private function isPersonalCircleUnique(Circle $circle, Member $owner) {
434
435
		$list = $this->findCirclesByUser(
436
			$owner->getUserId(), Circle::CIRCLES_PERSONAL, $circle->getName(),
437
			Member::LEVEL_OWNER
438
		);
439
440
		foreach ($list as $test) {
441
			if ($test->getName() === $circle->getName()) {
442
				return false;
443
			}
444
		}
445
446
		return true;
447
	}
448
}
449
450