Completed
Pull Request — master (#490)
by Maxence
02:09
created

GSUpstreamService::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 19
rs 9.6333
c 0
b 0
f 0
cc 1
nc 1
nop 8

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php declare(strict_types=1);
2
3
4
/**
5
 * Circles - Bring cloud-users closer together.
6
 *
7
 * This file is licensed under the Affero General Public License version 3 or
8
 * later. See the COPYING file.
9
 *
10
 * @author Maxence Lange <[email protected]>
11
 * @copyright 2017
12
 * @license GNU AGPL version 3 or any later version
13
 *
14
 * This program is free software: you can redistribute it and/or modify
15
 * it under the terms of the GNU Affero General Public License as
16
 * published by the Free Software Foundation, either version 3 of the
17
 * License, or (at your option) any later version.
18
 *
19
 * This program is distributed in the hope that it will be useful,
20
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22
 * GNU Affero General Public License for more details.
23
 *
24
 * You should have received a copy of the GNU Affero General Public License
25
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
26
 *
27
 */
28
29
30
namespace OCA\Circles\Service;
31
32
33
use daita\MySmallPhpTools\Exceptions\RequestContentException;
34
use daita\MySmallPhpTools\Exceptions\RequestNetworkException;
35
use daita\MySmallPhpTools\Exceptions\RequestResultNotJsonException;
36
use daita\MySmallPhpTools\Exceptions\RequestResultSizeException;
37
use daita\MySmallPhpTools\Exceptions\RequestServerException;
38
use daita\MySmallPhpTools\Model\Nextcloud\NC19Request;
39
use daita\MySmallPhpTools\Model\Request;
40
use daita\MySmallPhpTools\Model\SimpleDataStore;
41
use daita\MySmallPhpTools\Traits\TArrayTools;
42
use daita\MySmallPhpTools\Traits\TRequest;
43
use Exception;
44
use OCA\Circles\Db\CirclesRequest;
45
use OCA\Circles\Db\GSEventsRequest;
46
use OCA\Circles\Db\MembersRequest;
47
use OCA\Circles\Exceptions\GlobalScaleEventException;
48
use OCA\Circles\Exceptions\GSStatusException;
49
use OCA\Circles\Exceptions\JsonException;
50
use OCA\Circles\Exceptions\ModelException;
51
use OCA\Circles\GlobalScale\CircleStatus;
52
use OCA\Circles\Model\Circle;
53
use OCA\Circles\Model\GlobalScale\GSEvent;
54
use OCA\Circles\Model\GlobalScale\GSWrapper;
55
use OCP\IURLGenerator;
56
57
58
/**
59
 * Class GSUpstreamService
60
 *
61
 * @package OCA\Circles\Service
62
 */
63
class GSUpstreamService {
64
65
66
	use TRequest;
67
	use TArrayTools;
68
69
70
	/** @var string */
71
	private $userId = '';
72
73
	/** @var IURLGenerator */
74
	private $urlGenerator;
75
76
	/** @var GSEventsRequest */
77
	private $gsEventsRequest;
78
79
	/** @var CirclesRequest */
80
	private $circlesRequest;
81
82
	/** @var MembersRequest */
83
	private $membersRequest;
84
85
	/** @var GlobalScaleService */
86
	private $globalScaleService;
87
88
	/** @var ConfigService */
89
	private $configService;
90
91
	/** @var MiscService */
92
	private $miscService;
93
94
95
	/**
96
	 * GSUpstreamService constructor.
97
	 *
98
	 * @param $userId
99
	 * @param IURLGenerator $urlGenerator
100
	 * @param GSEventsRequest $gsEventsRequest
101
	 * @param CirclesRequest $circlesRequest
102
	 * @param MembersRequest $membersRequest
103
	 * @param GlobalScaleService $globalScaleService
104
	 * @param ConfigService $configService
105
	 * @param MiscService $miscService
106
	 */
107
	public function __construct(
108
		$userId,
109
		IURLGenerator $urlGenerator,
110
		GSEventsRequest $gsEventsRequest,
111
		CirclesRequest $circlesRequest,
112
		MembersRequest $membersRequest,
113
		GlobalScaleService $globalScaleService,
114
		ConfigService $configService,
115
		MiscService $miscService
116
	) {
117
		$this->userId = $userId;
118
		$this->urlGenerator = $urlGenerator;
119
		$this->gsEventsRequest = $gsEventsRequest;
120
		$this->circlesRequest = $circlesRequest;
121
		$this->membersRequest = $membersRequest;
122
		$this->globalScaleService = $globalScaleService;
123
		$this->configService = $configService;
124
		$this->miscService = $miscService;
125
	}
126
127
128
	/**
129
	 * @param GSEvent $event
130
	 *
131
	 * @return string
132
	 * @throws Exception
133
	 */
134
	public function newEvent(GSEvent $event): string {
135
		$event->setSource($this->configService->getLocalCloudId());
136
137
		try {
138
			$gs = $this->globalScaleService->getGlobalScaleEvent($event);
139
140
			if ($this->isLocalEvent($event)) {
141
				$gs->verify($event, true);
142
				if (!$event->isAsync()) {
143
					$gs->manage($event);
144
				}
145
146
				return $this->globalScaleService->asyncBroadcast($event);
147
			} else {
148
				$this->confirmEvent($event);
149
150
				return '';
151
			}
152
		} catch (Exception $e) {
153
			$this->miscService->log(
154
				get_class($e) . ' on new event: ' . $e->getMessage() . ' - ' . json_encode($event), 1
155
			);
156
			throw $e;
157
		}
158
159
	}
160
161
162
	/**
163
	 * @param GSWrapper $wrapper
164
	 * @param string $protocol
165
	 */
166
	public function broadcastWrapper(GSWrapper $wrapper, string $protocol): void {
167
		$status = GSWrapper::STATUS_FAILED;
168
169
		try {
170
			$this->broadcastEvent($wrapper->getEvent(), $wrapper->getInstance(), $protocol);
171
			$status = GSWrapper::STATUS_DONE;
172
		} catch (RequestContentException | RequestNetworkException | RequestResultSizeException | RequestServerException | RequestResultNotJsonException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
173
		}
174
175
		if ($wrapper->getSeverity() === GSEvent::SEVERITY_HIGH) {
176
			$wrapper->setStatus($status);
177
		} else {
178
			$wrapper->setStatus(GSWrapper::STATUS_OVER);
179
		}
180
181
		$this->gsEventsRequest->update($wrapper);
182
	}
183
184
185
	/**
186
	 * @param GSEvent $event
187
	 * @param string $instance
188
	 * @param string $protocol
189
	 *
190
	 * @throws RequestContentException
191
	 * @throws RequestNetworkException
192
	 * @throws RequestResultNotJsonException
193
	 * @throws RequestResultSizeException
194
	 * @throws RequestServerException
195
	 */
196
	public function broadcastEvent(GSEvent $event, string $instance, string $protocol = ''): void {
197
		$this->signEvent($event);
198
199
		$path = $this->urlGenerator->linkToRoute('circles.GlobalScale.broadcast');
200
		$request = new NC19Request($path, Request::TYPE_POST);
201
		$this->configService->configureRequest($request);
202
		$protocols = ['https', 'http'];
203
		if ($protocol !== '') {
204
			$protocols = [$protocol];
205
		}
206
207
		$request->setProtocols($protocols);
208
		$request->setDataSerialize($event);
209
210
		$request->setAddress($instance);
211
212
		$data = $this->retrieveJson($request);
213
		$event->setResult(new SimpleDataStore($this->getArray('result', $data, [])));
214
	}
215
216
217
	/**
218
	 * @param GSEvent $event
219
	 *
220
	 * @throws RequestContentException
221
	 * @throws RequestNetworkException
222
	 * @throws RequestResultSizeException
223
	 * @throws RequestServerException
224
	 * @throws RequestResultNotJsonException
225
	 * @throws GlobalScaleEventException
226
	 */
227
	public function confirmEvent(GSEvent &$event): void {
228
		$this->signEvent($event);
229
230
		$circle = $event->getCircle();
231
		$owner = $circle->getOwner();
232
		$path = $this->urlGenerator->linkToRoute('circles.GlobalScale.event');
233
234
		$request = new NC19Request($path, Request::TYPE_POST);
235
		$this->configService->configureRequest($request);
236
237
		if ($this->get('REQUEST_SCHEME', $_SERVER) !== '') {
238
			$request->setProtocols([$_SERVER['REQUEST_SCHEME']]);
239
		} else {
240
			$request->setProtocols(['https', 'http']);
241
		}
242
		$request->setAddressFromUrl($owner->getInstance());
243
		$request->setDataSerialize($event);
244
245
		$result = $this->retrieveJson($request);
246
		$this->miscService->log('result ' . json_encode($result), 0);
247
		if ($this->getInt('status', $result) === 0) {
248
			throw new GlobalScaleEventException($this->get('error', $result));
249
		}
250
251
		$updatedData = $this->getArray('event', $result);
252
		$this->miscService->log('updatedEvent: ' . json_encode($updatedData), 0);
253
		if (!empty($updatedData)) {
254
			$updated = new GSEvent();
255
			try {
256
				$updated->import($updatedData);
257
				$event = $updated;
258
			} catch (Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
259
			}
260
		}
261
	}
262
263
264
	/**
265
	 * @param GSEvent $event
266
	 */
267
	private function signEvent(GSEvent $event) {
268
		$event->setKey($this->globalScaleService->getKey());
269
	}
270
271
272
	/**
273
	 * We check that the event can be managed/checked locally or if the owner of the circle belongs to
274
	 * an other instance of Nextcloud
275
	 *
276
	 * @param GSEvent $event
277
	 *asyncBroadcast
278
	 *
279
	 * @return bool
280
	 */
281
	private function isLocalEvent(GSEvent $event): bool {
282
		if ($event->isLocal()) {
283
			return true;
284
		}
285
286
		$circle = $event->getCircle();
287
		$owner = $circle->getOwner();
288
		if ($owner->getInstance() === ''
0 ignored issues
show
Unused Code introduced by
This if statement, and the following return statement can be replaced with return $owner->getInstan...->getTrustedDomains());.
Loading history...
289
			|| in_array($owner->getInstance(), $this->configService->getTrustedDomains())) {
290
			return true;
291
		}
292
293
		return false;
294
	}
295
296
297
	/**
298
	 * @param string $token
299
	 *
300
	 * @return GSWrapper[]
301
	 * @throws JsonException
302
	 * @throws ModelException
303
	 */
304
	public function getEventsByToken(string $token): array {
305
		return $this->gsEventsRequest->getByToken($token);
306
	}
307
308
309
	/**
310
	 * should be used to manage results from events, like sending mails on user creation
311
	 *
312
	 * @param string $token
313
	 */
314
	public function manageResults(string $token): void {
315
		try {
316
			$wrappers = $this->gsEventsRequest->getByToken($token);
317
		} catch (JsonException | ModelException $e) {
318
			return;
319
		}
320
321
		$event = null;
322
		$events = [];
323
		foreach ($wrappers as $wrapper) {
324
			if ($wrapper->getStatus() !== GSWrapper::STATUS_DONE) {
325
				return;
326
			}
327
328
			$events[$wrapper->getInstance()] = $event = $wrapper->getEvent();
329
		}
330
331
		if ($event === null) {
332
			return;
333
		}
334
335
		try {
336
			$gs = $this->globalScaleService->getGlobalScaleEvent($event);
337
			$gs->result($events);
338
		} catch (GlobalScaleEventException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
339
		}
340
	}
341
342
343
	/**
344
	 * @throws GSStatusException
345
	 */
346
	public function synchronize() {
347
		$this->configService->getGSStatus();
348
349
		$sync = $this->getCirclesToSync();
350
		$this->synchronizeCircles($sync);
351
		$this->removeDeprecatedCircles();
352
353
		$this->removeDeprecatedEvents();
0 ignored issues
show
Unused Code introduced by
The call to the method OCA\Circles\Service\GSUp...emoveDeprecatedEvents() seems un-needed as the method has no side-effects.

PHP Analyzer performs a side-effects analysis of your code. A side-effect is basically anything that might be visible after the scope of the method is left.

Let’s take a look at an example:

class User
{
    private $email;

    public function getEmail()
    {
        return $this->email;
    }

    public function setEmail($email)
    {
        $this->email = $email;
    }
}

If we look at the getEmail() method, we can see that it has no side-effect. Whether you call this method or not, no future calls to other methods are affected by this. As such code as the following is useless:

$user = new User();
$user->getEmail(); // This line could safely be removed as it has no effect.

On the hand, if we look at the setEmail(), this method _has_ side-effects. In the following case, we could not remove the method call:

$user = new User();
$user->setEmail('email@domain'); // This line has a side-effect (it changes an
                                 // instance variable).
Loading history...
354
	}
355
356
357
	/**
358
	 * @param array $circles
359
	 */
360
	public function synchronizeCircles(array $circles): void {
361
		$event = new GSEvent(GSEvent::GLOBAL_SYNC, true);
362
		$event->setSource($this->configService->getLocalCloudId());
363
		$event->setData(new SimpleDataStore($circles));
364
365
		foreach ($this->globalScaleService->getInstances() as $instance) {
366
			try {
367
				$this->broadcastEvent($event, $instance);
368
			} catch (RequestContentException | RequestNetworkException | RequestResultSizeException | RequestServerException | RequestResultNotJsonException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
369
			}
370
		}
371
	}
372
373
374
	/**
375
	 * @return Circle[]
376
	 */
377
	private function getCirclesToSync(): array {
378
		$circles = $this->circlesRequest->forceGetCircles();
379
380
		$sync = [];
381
		foreach ($circles as $circle) {
382
			if ($circle->getOwner()
383
					   ->getInstance() !== ''
384
				|| $circle->getType() === Circle::CIRCLES_PERSONAL) {
385
				continue;
386
			}
387
388
			$members = $this->membersRequest->forceGetMembers($circle->getUniqueId());
389
			$circle->setMembers($members);
390
391
			$sync[] = $circle;
392
		}
393
394
		return $sync;
395
	}
396
397
398
	/**
399
	 *
400
	 */
401
	private function removeDeprecatedCircles() {
402
		$knownCircles = $this->circlesRequest->forceGetCircles();
403
		foreach ($knownCircles as $knownItem) {
404
			if ($knownItem->getOwner()
405
						  ->getInstance() === '') {
406
				continue;
407
			}
408
409
			try {
410
				$this->checkCircle($knownItem);
411
			} catch (GSStatusException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
412
			}
413
		}
414
	}
415
416
417
	/**
418
	 * @param Circle $circle
419
	 *
420
	 * @throws GSStatusException
421
	 */
422
	private function checkCircle(Circle $circle): void {
423
		$status = $this->confirmCircleStatus($circle);
424
425
		if (!$status) {
426
			$this->circlesRequest->destroyCircle($circle->getUniqueId());
427
			$this->membersRequest->removeAllFromCircle($circle->getUniqueId());
428
		}
429
	}
430
431
432
	/**
433
	 * @param Circle $circle
434
	 *
435
	 * @return bool
436
	 * @throws GSStatusException
437
	 */
438
	public function confirmCircleStatus(Circle $circle): bool {
439
		$event = new GSEvent(GSEvent::CIRCLE_STATUS, true);
440
		$event->setSource($this->configService->getLocalCloudId());
441
		$event->setCircle($circle);
442
443
		$this->signEvent($event);
444
445
		$path = $this->urlGenerator->linkToRoute('circles.GlobalScale.status');
446
		$request = new NC19Request($path, Request::TYPE_POST);
447
		$this->configService->configureRequest($request);
448
		$request->setProtocols(['https', 'http']);
449
		$request->setDataSerialize($event);
450
451
		$requestIssue = false;
452
		$notFound = false;
453
		$foundWithNoOwner = false;
454
		foreach ($this->globalScaleService->getInstances() as $instance) {
455
			$request->setAddress($instance);
456
457
			try {
458
				$result = $this->retrieveJson($request);
459
//				$this->miscService->log('result: ' . json_encode($result));
460
				if ($this->getInt('status', $result, 0) !== 1) {
461
					throw new RequestContentException('result status is not good');
462
				}
463
464
				$status = $this->getInt('success.data.status', $result);
465
466
				// if error, we assume the circle might still exist.
467
				if ($status === CircleStatus::STATUS_ERROR) {
468
					return true;
469
				}
470
471
				if ($status === CircleStatus::STATUS_OK) {
472
					return true;
473
				}
474
475
				// TODO: check the data.supposedOwner entry.
476
				if ($status === CircleStatus::STATUS_NOT_OWNER) {
477
					$foundWithNoOwner = true;
478
				}
479
480
				if ($status === CircleStatus::STATUS_NOT_FOUND) {
481
					$notFound = true;
482
				}
483
484
			} catch (RequestContentException
485
			| RequestNetworkException
486
			| RequestResultNotJsonException
487
			| RequestResultSizeException
488
			| RequestServerException $e) {
489
				$requestIssue = true;
490
				// TODO: log instances that have network issue, after too many tries (7d), remove this circle.
491
				continue;
492
			}
493
		}
494
495
		// if no request issue, we can imagine that the instance that owns the circle is down.
496
		// We'll wait for more information (cf request exceptions management);
497
		if ($requestIssue) {
498
			return true;
499
		}
500
501
		// circle were not found in any other instances, we can easily says that the circle does not exists anymore
502
		if ($notFound && !$foundWithNoOwner) {
503
			return false;
504
		}
505
506
		// circle were found everywhere but with no owner on every instance. we need to assign a new owner.
507
		// This should be done by checking admin rights. if no admin rights, let's assume that circle should be removed.
508
		if (!$notFound && $foundWithNoOwner) {
0 ignored issues
show
Unused Code introduced by
This if statement, and the following return statement can be replaced with return !$notFound && $foundWithNoOwner;.
Loading history...
509
			// TODO: assign a new owner and check that when changing owner, we do check that the destination instance is updated FOR SURE!
510
			return true;
511
		}
512
513
		// some instances returned notFound, some returned circle with no owner. let's assume the circle is deprecated.
514
		return false;
515
	}
516
517
	/**
518
	 * @throws GSStatusException
519
	 */
520
	public function syncEvents() {
521
522
	}
523
524
	/**
525
	 *
526
	 */
527
	private function removeDeprecatedEvents() {
528
//		$this->deprecatedEvents();
529
530
	}
531
532
533
}
534
535