Completed
Pull Request — master (#384)
by Tortue
02:31
created

MembersRequest::unlinkFromGroup()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
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
28
namespace OCA\Circles\Db;
29
30
31
use daita\MySmallPhpTools\Traits\TStringTools;
32
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
33
use OCA\Circles\Exceptions\MemberAlreadyExistsException;
34
use OCA\Circles\Exceptions\MemberDoesNotExistException;
35
use OCA\Circles\Model\Member;
36
use OCP\IGroup;
37
38
class MembersRequest extends MembersRequestBuilder {
39
40
41
	use TStringTools;
42
43
44
	/**
45
	 * Returns information about a member.
46
	 *
47
	 * WARNING: This function does not filters data regarding the current user/viewer.
48
	 *          In case of interaction with users, Please use MembersService->getMember() instead.
49
	 *
50
	 * @param string $circleUniqueId
51
	 * @param string $userId
52
	 * @param $type
53
	 *
54
	 * @return Member
55
	 * @throws MemberDoesNotExistException
56
	 */
57
	public function forceGetMember($circleUniqueId, $userId, $type) {
58
		$qb = $this->getMembersSelectSql();
59
60
		$this->limitToUserId($qb, $userId);
61
		$this->limitToUserType($qb, $type);
62
		$this->limitToCircleId($qb, $circleUniqueId);
63
64
		$cursor = $qb->execute();
65
		$data = $cursor->fetch();
66
		$cursor->closeCursor();
67
68
		if ($data === false) {
69
			throw new MemberDoesNotExistException($this->l10n->t('This member does not exist'));
70
		}
71
72
		return $this->parseMembersSelectSql($data);
73
	}
74
75
76
	/**
77
	 * @param string $memberId
78
	 *
79
	 * @return Member
80
	 * @throws MemberDoesNotExistException
81
	 */
82
	public function forceGetMemberById(string $memberId): Member {
83
		$qb = $this->getMembersSelectSql();
84
85
		$this->limitToMemberId($qb, $memberId);
86
87
		$cursor = $qb->execute();
88
		$data = $cursor->fetch();
89
		$cursor->closeCursor();
90
91
		if ($data === false) {
92
			throw new MemberDoesNotExistException($this->l10n->t('This member does not exist'));
93
		}
94
95
		return $this->parseMembersSelectSql($data);
96
	}
97
98
99
	/**
100
	 * Returns members list of a circle, based on their level.
101
	 *
102
	 * WARNING: This function does not filters data regarding the current user/viewer.
103
	 *          In case of interaction with users, Please use getMembers() instead.
104
	 *
105
	 * @param string $circleUniqueId
106
	 * @param int $level
107
	 * @param bool $incGroup
108
	 *
109
	 * @return Member[]
110
	 */
111
	public function forceGetMembers($circleUniqueId, $level = Member::LEVEL_MEMBER, $incGroup = false) {
112
113
		$qb = $this->getMembersSelectSql();
114
		$this->limitToMembersAndAlmost($qb);
115
		$this->limitToLevel($qb, $level);
116
117
		$this->limitToCircleId($qb, $circleUniqueId);
118
119
		$members = [];
120
		$cursor = $qb->execute();
121
		while ($data = $cursor->fetch()) {
122
			$members[] = $this->parseMembersSelectSql($data);
123
		}
124
		$cursor->closeCursor();
125
126
		if ($this->configService->isLinkedGroupsAllowed() && $incGroup === true) {
127
			$this->includeGroupMembers($members, $circleUniqueId, $level);
128
		}
129
130
		return $members;
131
	}
132
133
134
	/**
135
	 * Returns all members.
136
	 *
137
	 * WARNING: This function does not filters data regarding the current user/viewer.
138
	 *          In case of interaction with users, Please use getMembers() instead.
139
	 *
140
	 *
141
	 * @return Member[]
142
	 */
143
	public function forceGetAllMembers() {
144
145
		$qb = $this->getMembersSelectSql();
146
147
		$members = [];
148
		$cursor = $qb->execute();
149
		while ($data = $cursor->fetch()) {
150
			$members[] = $this->parseMembersSelectSql($data);
151
		}
152
		$cursor->closeCursor();
153
154
		return $members;
155
	}
156
157
158
	/**
159
	 * Returns members generated from Contacts that are not 'checked' (as not sent existing shares).
160
	 *
161
	 *
162
	 * @return Member[]
163
	 */
164
	public function forceGetAllRecentContactEdit() {
165
		$qb = $this->getMembersSelectSql();
166
		$this->limitToUserType($qb, Member::TYPE_CONTACT);
167
168
		$expr = $qb->expr();
169
		$orX = $expr->orX();
170
		$orX->add($expr->isNull('contact_checked'));
171
		$orX->add($expr->neq('contact_checked', $qb->createNamedParameter('1')));
172
		$qb->andWhere($orX);
173
174
		$members = [];
175
		$cursor = $qb->execute();
176
		while ($data = $cursor->fetch()) {
177
			$members[] = $this->parseMembersSelectSql($data);
178
		}
179
		$cursor->closeCursor();
180
181
		return $members;
182
	}
183
184
185
	/**
186
	 * @param Member $member
187
	 * @param bool $check
188
	 */
189
	public function checkMember(Member $member, bool $check) {
190
		$qb = $this->getMembersUpdateSql($member->getCircleId(), $member);
191
		$qb->set('contact_checked', $qb->createNamedParameter(($check) ? 1 : 0));
192
193
		$qb->execute();
194
	}
195
196
197
	/**
198
	 * @param string $circleUniqueId
199
	 * @param Member $viewer
200
	 * @param bool $force
201
	 *
202
	 * @return Member[]
203
	 */
204
	public function getMembers($circleUniqueId, ?Member $viewer, bool $force = false) {
205
		try {
206
			if ($force === false) {
207
				$viewer->hasToBeMember();
208
			}
209
210
			$members = $this->forceGetMembers($circleUniqueId, Member::LEVEL_NONE);
211
			if ($force === false) {
212
				if (!$viewer->isLevel(Member::LEVEL_MODERATOR)) {
213
					array_map(
214
						function(Member $m) {
215
							$m->setNote('');
216
						}, $members
217
					);
218
				}
219
			}
220
221
			return $members;
222
		} catch (\Exception $e) {
223
			return [];
224
		}
225
	}
226
227
228
	/**
229
	 * forceGetGroup();
230
	 *
231
	 * returns group information as a member within a Circle.
232
	 *
233
	 * WARNING: This function does not filters data regarding the current user/viewer.
234
	 *          In case of interaction with users, Please use getGroup() instead.
235
	 *
236
	 * @param string $circleUniqueId
237
	 * @param string $groupId
238
	 *
239
	 * @return Member
240
	 * @throws MemberDoesNotExistException
241
	 */
242
	public function forceGetGroup($circleUniqueId, $groupId) {
243
		$qb = $this->getGroupsSelectSql();
244
245
		$this->limitToGroupId($qb, $groupId);
246
		$this->limitToCircleId($qb, $circleUniqueId);
247
248
		$cursor = $qb->execute();
249
		$data = $cursor->fetch();
250
		if ($data === false) {
251
			throw new MemberDoesNotExistException($this->l10n->t('This member does not exist'));
252
		}
253
254
		$group = $this->parseGroupsSelectSql($data);
255
		$cursor->closeCursor();
256
257
		return $group;
258
	}
259
260
261
	/**
262
	 * includeGroupMembers();
263
	 *
264
	 * This function will get members of a circle throw NCGroups and fill the result an existing
265
	 * Members List. In case of duplicate, higher level will be kept.
266
	 *
267
	 * @param Member[] $members
268
	 * @param string $circleUniqueId
269
	 * @param int $level
270
	 */
271
	private function includeGroupMembers(array &$members, $circleUniqueId, $level) {
272
273
		$groupMembers = $this->forceGetGroupMembers($circleUniqueId, $level);
274
		$this->avoidDuplicateMembers($members, $groupMembers);
275
	}
276
277
278
	/**
279
	 * avoidDuplicateMembers();
280
	 *
281
	 * Use this function to add members to the list (1st argument), keeping the higher level in case
282
	 * of duplicate
283
	 *
284
	 * @param Member[] $members
285
	 * @param Member[] $groupMembers
286
	 */
287
	public function avoidDuplicateMembers(array &$members, array $groupMembers) {
288
		foreach ($groupMembers as $member) {
289
			$index = $this->indexOfMember($members, $member->getUserId());
290
			if ($index === -1) {
291
				array_push($members, $member);
292
			} else if ($members[$index]->getLevel() < $member->getLevel()) {
293
				$members[$index] = $member;
294
			}
295
		}
296
	}
297
298
299
	/**
300
	 * returns the index of a specific UserID in a Members List
301
	 *
302
	 * @param array $members
303
	 * @param $userId
304
	 *
305
	 * @return int
306
	 */
307
	private function indexOfMember(array $members, $userId) {
308
309
		foreach ($members as $k => $member) {
310
			if ($member->getUserId() === $userId) {
311
				return intval($k);
312
			}
313
		}
314
315
		return -1;
316
	}
317
318
319
	/**
320
	 * Check if a fresh member can be generated (by addMember/joinCircle)
321
	 *
322
	 * @param string $circleUniqueId
323
	 * @param string $name
324
	 * @param $type
325
	 *
326
	 * @return Member
327
	 * @throws MemberAlreadyExistsException
328
	 * @throws \Exception
329
	 */
330
	public function getFreshNewMember($circleUniqueId, $name, $type) {
331
332
		try {
333
334
			$member = $this->forceGetMember($circleUniqueId, $name, $type);
335
336
		} catch (MemberDoesNotExistException $e) {
337
			$member = new Member($name, $type, $circleUniqueId);
338
			$this->createMember($member);
339
		}
340
341
//		if ($member->alreadyExistOrJoining()) {
342
//			throw new MemberAlreadyExistsException(
343
//				$this->l10n->t('This user is already a member of the circle')
344
//			);
345
//		}
346
347
		return $member;
348
	}
349
350
351
	/**
352
	 * Returns members list of all Group Members of a Circle. The Level of the linked group will be
353
	 * assigned to each entry
354
	 *
355
	 * NOTE: Can contains duplicate.
356
	 *
357
	 * WARNING: This function does not filters data regarding the current user/viewer.
358
	 *          Do not use in case of direct interaction with users.
359
	 *
360
	 * @param string $circleUniqueId
361
	 * @param int $level
362
	 *
363
	 * @return Member[]
364
	 */
365
	public function forceGetGroupMembers($circleUniqueId, $level = Member::LEVEL_MEMBER) {
366
		$qb = $this->getGroupsSelectSql();
367
368
		$this->limitToLevel($qb, $level);
369
		$this->limitToCircleId($qb, $circleUniqueId);
370
		$this->limitToNCGroupUser($qb);
371
372
		$members = [];
373
		$cursor = $qb->execute();
374
		while ($data = $cursor->fetch()) {
375
			$members[] = $this->parseGroupsSelectSql($data);
376
		}
377
		$cursor->closeCursor();
378
379
		return $members;
380
	}
381
382
383
	/**
384
	 * returns all users from a Group as a list of Members.
385
	 *
386
	 * @param Member $group
387
	 *
388
	 * @return Member[]
389
	 */
390
	public function getGroupMemberMembers(Member $group) {
391
		/** @var IGroup $grp */
392
		$grp = $this->groupManager->get($group->getUserId());
393
		if ($grp === null) {
394
			return [];
395
		}
396
397
		$members = [];
398
		$users = $grp->getUsers();
399
		foreach ($users as $user) {
400
			$member = clone $group;
401
			//Member::fromJSON($this->l10n, json_encode($group));
402
			$member->setType(Member::TYPE_USER);
403
			$member->setUserId($user->getUID());
404
			$members[] = $member;
405
		}
406
407
		return $members;
408
	}
409
410
411
	/**
412
	 * return the higher level group linked to a circle, that include the userId.
413
	 *
414
	 * WARNING: This function does not filters data regarding the current user/viewer.
415
	 *          In case of direct interaction with users, Please don't use this.
416
	 *
417
	 * @param string $circleUniqueId
418
	 * @param string $userId
419
	 *
420
	 * @return Member
421
	 */
422
	public function forceGetHigherLevelGroupFromUser($circleUniqueId, $userId) {
423
		$qb = $this->getGroupsSelectSql();
424
425
		$this->limitToCircleId($qb, $circleUniqueId);
426
		$this->limitToNCGroupUser($qb, $userId);
427
428
		/** @var Member $group */
429
		$group = null;
430
431
		$cursor = $qb->execute();
432
		while ($data = $cursor->fetch()) {
433
			$entry = $this->parseGroupsSelectSql($data);
434
			if ($group === null || $entry->getLevel() > $group->getLevel()) {
435
				$group = $entry;
436
			}
437
		}
438
		$cursor->closeCursor();
439
440
		return $group;
441
	}
442
443
444
	/**
445
	 * Insert Member into database.
446
	 *
447
	 * @param Member $member
448
	 *
449
	 * @throws MemberAlreadyExistsException
450
	 */
451
	public function createMember(Member $member) {
452
453
		if ($member->getMemberId() === '') {
454
			$member->setMemberId($this->token(14));
455
		}
456
457
		try {
458
			$qb = $this->getMembersInsertSql();
459
			$qb->setValue('circle_id', $qb->createNamedParameter($member->getCircleId()))
460
			   ->setValue('user_id', $qb->createNamedParameter($member->getUserId()))
461
			   ->setValue('member_id', $qb->createNamedParameter($member->getMemberId()))
462
			   ->setValue('user_type', $qb->createNamedParameter($member->getType()))
463
			   ->setValue('level', $qb->createNamedParameter($member->getLevel()))
464
			   ->setValue('status', $qb->createNamedParameter($member->getStatus()))
465
			   ->setValue('contact_id', $qb->createNamedParameter($member->getContactId()))
466
			   ->setValue('note', $qb->createNamedParameter($member->getNote()));
467
468
			$qb->execute();
469
		} catch (UniqueConstraintViolationException $e) {
0 ignored issues
show
Bug introduced by
The class Doctrine\DBAL\Exception\...raintViolationException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
470
			throw new MemberAlreadyExistsException(
471
				$this->l10n->t('This user is already a member of the circle')
472
			);
473
		}
474
	}
475
476
477
	/**
478
	 * @param string $circleUniqueId
479
	 * @param Member $viewer
480
	 *
481
	 * @return Member[]
482
	 * @throws MemberDoesNotExistException
483
	 */
484
	public function getGroupsFromCircle($circleUniqueId, Member $viewer) {
485
486
		if ($viewer->getLevel() < Member::LEVEL_MEMBER) {
487
			return [];
488
		}
489
490
		$qb = $this->getGroupsSelectSql();
491
		$this->limitToCircleId($qb, $circleUniqueId);
492
		$this->limitToLevel($qb, Member::LEVEL_MEMBER);
493
494
		$cursor = $qb->execute();
495
		$groups = [];
496
		while ($data = $cursor->fetch()) {
497
			if ($viewer->getLevel() < Member::LEVEL_MODERATOR) {
498
				$data['note'] = '';
499
			}
500
			$groups[] = $this->parseGroupsSelectSql($data);
501
		}
502
		$cursor->closeCursor();
503
504
		return $groups;
505
	}
506
507
508
	/**
509
	 * Insert Member into database.
510
	 *
511
	 * @param Member $member
512
	 *
513
	 * @throws MemberAlreadyExistsException
514
	 */
515
	public function insertGroup(Member $member) {
516
		try {
517
			$qb = $this->getGroupsInsertSql();
518
			$qb->setValue('circle_id', $qb->createNamedParameter($member->getCircleId()))
519
			   ->setValue('group_id', $qb->createNamedParameter($member->getUserId()))
520
			   ->setValue('level', $qb->createNamedParameter($member->getLevel()))
521
			   ->setValue('note', $qb->createNamedParameter($member->getNote()));
522
523
			$qb->execute();
524
		} catch (UniqueConstraintViolationException $e) {
525
			throw new MemberAlreadyExistsException(
526
				$this->l10n->t('This user is already a member of the circle')
527
			);
528
		}
529
	}
530
531
532
	/**
533
	 * update database entry for a specific Member.
534
	 *
535
	 * @param Member $member
536
	 */
537
	public function updateMember(Member $member) {
538
		$qb = $this->getMembersUpdateSql($member->getCircleId(), $member);
539
		$qb->set('level', $qb->createNamedParameter($member->getLevel()))
540
		   ->set('status', $qb->createNamedParameter($member->getStatus()));
541
542
		$qb->execute();
543
	}
544
545
	/**
546
	 * update database entry for a specific Member.
547
	 *
548
	 * @param Member $member
549
	 */
550
	public function updateContactMeta(Member $member) {
551
		$qb = $this->getMembersUpdateSql($member->getCircleId(), $member);
552
		$qb->set('contact_meta', $qb->createNamedParameter(json_encode($member->getContactMeta())));
553
554
		$qb->execute();
555
	}
556
557
558
	/**
559
	 * removeAllFromCircle();
560
	 *
561
	 * Remove All members from a Circle. Used when deleting a Circle.
562
	 *
563
	 * @param string $uniqueCircleId
564
	 */
565
	public function removeAllFromCircle($uniqueCircleId) {
566
		$qb = $this->getMembersDeleteSql();
567
		$expr = $qb->expr();
568
569
		$qb->where($expr->eq('circle_id', $qb->createNamedParameter($uniqueCircleId)));
570
		$qb->execute();
571
	}
572
573
574
	/**
575
	 * removeAllMembershipsFromUser();
576
	 *
577
	 * remove All membership from a User. Used when removing a User from the Cloud.
578
	 *
579
	 * @param string $userId
580
	 */
581
	public function removeAllMembershipsFromUser($userId) {
582
		if ($userId === '') {
583
			return;
584
		}
585
586
		$qb = $this->getMembersDeleteSql();
587
		$expr = $qb->expr();
588
589
		/** @noinspection PhpMethodParametersCountMismatchInspection */
590
		$qb->where(
591
			$expr->andX(
592
				$expr->eq('user_id', $qb->createNamedParameter($userId)),
593
				$expr->eq('user_type', $qb->createNamedParameter(Member::TYPE_USER))
594
			)
595
		);
596
597
		$qb->execute();
598
	}
599
600
601
	/**
602
	 * remove member, identified by its id, type and circleId
603
	 *
604
	 * @param Member $member
605
	 */
606
	public function removeMember(Member $member) {
607
		$qb = $this->getMembersDeleteSql();
608
		$this->limitToCircleId($qb, $member->getCircleId());
609
		$this->limitToUserId($qb, $member->getUserId());
610
		$this->limitToUserType($qb, $member->getType());
611
		if ($member->getContactId() !== '') {
612
			$this->limitToContactId($qb, $member->getContactId());
613
		}
614
615
		$qb->execute();
616
	}
617
618
	/**
619
	 * update database entry for a specific Group.
620
	 *
621
	 * @param Member $member
622
	 *
623
	 * @return bool
624
	 */
625
	public function updateGroup(Member $member) {
626
		$qb = $this->getGroupsUpdateSql($member->getCircleId(), $member->getUserId());
627
		$qb->set('level', $qb->createNamedParameter($member->getLevel()));
628
		$qb->execute();
629
630
		return true;
631
	}
632
633
634
	public function unlinkAllFromGroup($groupId) {
635
		$qb = $this->getGroupsDeleteSql($groupId);
636
		$qb->execute();
637
	}
638
639
640
	public function unlinkFromGroup($circleId, $groupId) {
641
		$qb = $this->getGroupsDeleteSql($groupId);
642
		$this->limitToCircleId($qb, $circleId);
643
644
		$qb->execute();
645
	}
646
647
648
	/**
649
	 * @param string $contactId
650
	 *
651
	 * @return Member[]
652
	 */
653
	public function getMembersByContactId(string $contactId = ''): array {
654
		$qb = $this->getMembersSelectSql();
655
		if ($contactId === '') {
656
			$expr = $qb->expr();
657
			$qb->andWhere($expr->neq('contact_id', $qb->createNamedParameter('')));
658
		} else {
659
			$this->limitToContactId($qb, $contactId);
660
		}
661
662
		$members = [];
663
		$cursor = $qb->execute();
664
		while ($data = $cursor->fetch()) {
665
			$member = $this->parseMembersSelectSql($data);
666
			$members[] = $member;
667
		}
668
		$cursor->closeCursor();
669
670
		return $members;
671
	}
672
673
674
	/**
675
	 * @param string $circleId
676
	 * @param string $contactId
677
	 * @param string $userId
0 ignored issues
show
Bug introduced by
There is no parameter named $userId. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
678
	 * @param int $type
0 ignored issues
show
Bug introduced by
There is no parameter named $type. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
679
	 *
680
	 * @return Member
681
	 * @throws MemberDoesNotExistException
682
	 */
683
	public function getContactMember(string $circleId, string $contactId): Member {
684
		$qb = $this->getMembersSelectSql();
685
		$this->limitToContactId($qb, $contactId);
686
		$this->limitToCircleId($qb, $circleId);
687
688
		$cursor = $qb->execute();
689
		$data = $cursor->fetch();
690
		$cursor->closeCursor();
691
692
		if ($data === false) {
693
			throw new MemberDoesNotExistException($this->l10n->t('This member does not exist'));
694
		}
695
696
		return $this->parseMembersSelectSql($data);
697
	}
698
699
700
	/**
701
	 * @param string $contactId
702
	 *
703
	 * @return Member[]
704
	 */
705
	public function getLocalContactMembers(string $contactId): array {
706
		$qb = $this->getMembersSelectSql();
707
		$this->limitToContactId($qb, $contactId);
708
		$this->limitToUserType($qb, Member::TYPE_USER);
709
710
		$members = [];
711
		$cursor = $qb->execute();
712
		while ($data = $cursor->fetch()) {
713
			$members[] = $this->parseMembersSelectSql($data);
714
		}
715
		$cursor->closeCursor();
716
717
		return $members;
718
	}
719
720
721
	/**
722
	 * @param string $contactId
723
	 * @param int $type
724
	 */
725
	public function removeMembersByContactId(string $contactId, int $type = 0) {
726
		$this->miscService->log($contactId);
727
		if ($contactId === '') {
728
			return;
729
		}
730
731
		$qb = $this->getMembersDeleteSql();
732
		$this->limitToContactId($qb, $contactId);
733
		if ($type > 0) {
734
			$this->limitToUserType($qb, $type);
735
		}
736
737
		$qb->execute();
738
	}
739
740
741
}
742