Completed
Push — master ( 574ca3...69f17d )
by Maxence
04:30 queued 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\Nextcloud\TNC19Request;
42
use daita\MySmallPhpTools\Traits\TArrayTools;
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 TNC19Request;
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->getLocalInstance());
136
137
		try {
138
			$gs = $this->globalScaleService->getGlobalScaleEvent($event);
139
			if ($this->isLocalEvent($event)) {
140
				$gs->verify($event, true);
141
				if (!$event->isAsync()) {
142
					$gs->manage($event);
143
				}
144
145
				return $this->globalScaleService->asyncBroadcast($event);
146
			} else {
147
				$this->confirmEvent($event);
148
149
				return '';
150
			}
151
		} catch (Exception $e) {
152
			$this->miscService->log(
153
				get_class($e) . ' on new event: ' . $e->getMessage() . ' - ' . json_encode($event), 1
154
			);
155
			throw $e;
156
		}
157
158
	}
159
160
161
	/**
162
	 * @param GSWrapper $wrapper
163
	 * @param string $protocol
164
	 */
165
	public function broadcastWrapper(GSWrapper $wrapper, string $protocol): void {
166
		$status = GSWrapper::STATUS_FAILED;
167
168
		try {
169
			$this->broadcastEvent($wrapper->getEvent(), $wrapper->getInstance(), $protocol);
170
			$status = GSWrapper::STATUS_DONE;
171
		} 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...
172
		}
173
174
		if ($wrapper->getSeverity() === GSEvent::SEVERITY_HIGH) {
175
			$wrapper->setStatus($status);
176
		} else {
177
			$wrapper->setStatus(GSWrapper::STATUS_OVER);
178
		}
179
180
		$this->gsEventsRequest->update($wrapper);
181
	}
182
183
184
	/**
185
	 * @param GSEvent $event
186
	 * @param string $instance
187
	 * @param string $protocol
188
	 *
189
	 * @throws RequestContentException
190
	 * @throws RequestNetworkException
191
	 * @throws RequestResultNotJsonException
192
	 * @throws RequestResultSizeException
193
	 * @throws RequestServerException
194
	 */
195
	public function broadcastEvent(GSEvent $event, string $instance, string $protocol = ''): void {
196
		$this->signEvent($event);
197
198
		if ($this->configService->isLocalInstance($instance)) {
199
			$request = new NC19Request('', Request::TYPE_POST);
200
			$this->configService->configureRequest($request, 'circles.GlobalScale.broadcast');
201
		} else {
202
			$path = $this->urlGenerator->linkToRoute('circles.GlobalScale.broadcast');
203
			$request = new NC19Request($path, Request::TYPE_POST);
204
			$this->configService->configureRequest($request);
205
			$protocols = ['https', 'http'];
206
			if ($protocol !== '') {
207
				$protocols = [$protocol];
208
			}
209
			$request->setInstance($instance);
0 ignored issues
show
Bug introduced by
The method setInstance() does not seem to exist on object<daita\MySmallPhpT...\Nextcloud\NC19Request>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
210
			$request->setProtocols($protocols);
211
		}
212
213
		$request->setDataSerialize($event);
214
215
		$data = $this->retrieveJson($request);
216
		$event->setResult(new SimpleDataStore($this->getArray('result', $data, [])));
217
	}
218
219
220
	/**
221
	 * @param GSEvent $event
222
	 *
223
	 * @throws RequestContentException
224
	 * @throws RequestNetworkException
225
	 * @throws RequestResultSizeException
226
	 * @throws RequestServerException
227
	 * @throws RequestResultNotJsonException
228
	 * @throws GlobalScaleEventException
229
	 */
230
	public function confirmEvent(GSEvent &$event): void {
231
		$this->signEvent($event);
232
233
		$circle = $event->getCircle();
234
		$owner = $circle->getOwner();
235
		$path = $this->urlGenerator->linkToRoute('circles.GlobalScale.event');
236
237
		$request = new NC19Request($path, Request::TYPE_POST);
238
		$request->basedOnUrl($owner->getInstance());
239
		$this->configService->configureRequest($request);
240
241
		if ($this->get('REQUEST_SCHEME', $_SERVER) !== '') {
242
			$request->setProtocols([$_SERVER['REQUEST_SCHEME']]);
243
		} else {
244
			$request->setProtocols(['https', 'http']);
245
		}
246
		$request->setDataSerialize($event);
247
248
		$result = $this->retrieveJson($request);
249
		$this->miscService->log('result ' . json_encode($result), 0);
250
		if ($this->getInt('status', $result) === 0) {
251
			throw new GlobalScaleEventException($this->get('error', $result));
252
		}
253
254
		$updatedData = $this->getArray('event', $result);
255
		$this->miscService->log('updatedEvent: ' . json_encode($updatedData), 0);
256
		if (!empty($updatedData)) {
257
			$updated = new GSEvent();
258
			try {
259
				$updated->import($updatedData);
260
				$event = $updated;
261
			} catch (Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
262
			}
263
		}
264
	}
265
266
267
	/**
268
	 * @param GSEvent $event
269
	 */
270
	private function signEvent(GSEvent $event) {
271
		$event->setKey($this->globalScaleService->getKey());
272
	}
273
274
275
	/**
276
	 * We check that the event can be managed/checked locally or if the owner of the circle belongs to
277
	 * an other instance of Nextcloud
278
	 *
279
	 * @param GSEvent $event
280
	 *
281
	 * @return bool
282
	 */
283
	private function isLocalEvent(GSEvent $event): bool {
284
		if ($event->isLocal()) {
285
			return true;
286
		}
287
288
		$circle = $event->getCircle();
289
		$owner = $circle->getOwner();
290
		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...
291
			|| in_array($owner->getInstance(), $this->configService->getTrustedDomains())) {
292
			return true;
293
		}
294
295
		return false;
296
	}
297
298
299
	/**
300
	 * @param string $token
301
	 *
302
	 * @return GSWrapper[]
303
	 * @throws JsonException
304
	 * @throws ModelException
305
	 */
306
	public function getEventsByToken(string $token): array {
307
		return $this->gsEventsRequest->getByToken($token);
308
	}
309
310
311
	/**
312
	 * should be used to manage results from events, like sending mails on user creation
313
	 *
314
	 * @param string $token
315
	 */
316
	public function manageResults(string $token): void {
317
		try {
318
			$wrappers = $this->gsEventsRequest->getByToken($token);
319
		} catch (JsonException | ModelException $e) {
320
			return;
321
		}
322
323
		$event = null;
324
		$events = [];
325
		foreach ($wrappers as $wrapper) {
326
			if ($wrapper->getStatus() !== GSWrapper::STATUS_DONE) {
327
				return;
328
			}
329
330
			$events[$wrapper->getInstance()] = $event = $wrapper->getEvent();
331
		}
332
333
		if ($event === null) {
334
			return;
335
		}
336
337
		try {
338
			$gs = $this->globalScaleService->getGlobalScaleEvent($event);
339
			$gs->result($events);
340
		} catch (GlobalScaleEventException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
341
		}
342
	}
343
344
345
	/**
346
	 * @param array $sync
347
	 *
348
	 * @throws GSStatusException
349
	 */
350
	public function synchronize(array $sync) {
351
		$this->configService->getGSStatus();
352
353
		$this->synchronizeCircles($sync);
354
		$this->removeDeprecatedCircles();
355
		$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...
356
	}
357
358
359
	/**
360
	 * @param array $circles
361
	 */
362
	public function synchronizeCircles(array $circles): void {
363
		$event = new GSEvent(GSEvent::GLOBAL_SYNC, true);
364
		$event->setSource($this->configService->getLocalInstance());
365
		$event->setData(new SimpleDataStore($circles));
366
367
		foreach ($this->globalScaleService->getInstances() as $instance) {
368
			try {
369
				$this->broadcastEvent($event, $instance);
370
			} 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...
371
			}
372
		}
373
	}
374
375
376
	/**
377
	 *
378
	 */
379
	private function removeDeprecatedCircles() {
380
		$knownCircles = $this->circlesRequest->forceGetCircles();
381
		foreach ($knownCircles as $knownItem) {
382
			if ($knownItem->getOwner()
383
						  ->getInstance() === '') {
384
				continue;
385
			}
386
387
			try {
388
				$this->checkCircle($knownItem);
389
			} catch (GSStatusException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
390
			}
391
		}
392
	}
393
394
395
	/**
396
	 * @param Circle $circle
397
	 *
398
	 * @throws GSStatusException
399
	 */
400
	private function checkCircle(Circle $circle): void {
401
		$status = $this->confirmCircleStatus($circle);
402
403
		if (!$status) {
404
			$this->circlesRequest->destroyCircle($circle->getUniqueId());
405
			$this->membersRequest->removeAllFromCircle($circle->getUniqueId());
406
		}
407
	}
408
409
410
	/**
411
	 * @param Circle $circle
412
	 *
413
	 * @return bool
414
	 * @throws GSStatusException
415
	 */
416
	public function confirmCircleStatus(Circle $circle): bool {
417
		$event = new GSEvent(GSEvent::CIRCLE_STATUS, true);
418
		$event->setSource($this->configService->getLocalInstance());
419
		$event->setCircle($circle);
420
421
		$this->signEvent($event);
422
423
		$path = $this->urlGenerator->linkToRoute('circles.GlobalScale.status');
424
		$request = new NC19Request($path, Request::TYPE_POST);
425
		$this->configService->configureRequest($request);
426
		$request->setProtocols(['https', 'http']);
427
		$request->setDataSerialize($event);
428
429
		$requestIssue = false;
430
		$notFound = false;
431
		$foundWithNoOwner = false;
432
		foreach ($this->globalScaleService->getInstances() as $instance) {
433
			$request->setHost($instance);
434
435
			try {
436
				$result = $this->retrieveJson($request);
437
//				$this->miscService->log('result: ' . json_encode($result));
438
				if ($this->getInt('status', $result, 0) !== 1) {
439
					throw new RequestContentException('result status is not good');
440
				}
441
442
				$status = $this->getInt('success.data.status', $result);
443
444
				// if error, we assume the circle might still exist.
445
				if ($status === CircleStatus::STATUS_ERROR) {
446
					return true;
447
				}
448
449
				if ($status === CircleStatus::STATUS_OK) {
450
					return true;
451
				}
452
453
				// TODO: check the data.supposedOwner entry.
454
				if ($status === CircleStatus::STATUS_NOT_OWNER) {
455
					$foundWithNoOwner = true;
456
				}
457
458
				if ($status === CircleStatus::STATUS_NOT_FOUND) {
459
					$notFound = true;
460
				}
461
462
			} catch (RequestContentException
463
			| RequestNetworkException
464
			| RequestResultNotJsonException
465
			| RequestResultSizeException
466
			| RequestServerException $e) {
467
				$requestIssue = true;
468
				// TODO: log instances that have network issue, after too many tries (7d), remove this circle.
469
				continue;
470
			}
471
		}
472
473
		// if no request issue, we can imagine that the instance that owns the circle is down.
474
		// We'll wait for more information (cf request exceptions management);
475
		if ($requestIssue) {
476
			return true;
477
		}
478
479
		// circle were not found in any other instances, we can easily says that the circle does not exists anymore
480
		if ($notFound && !$foundWithNoOwner) {
481
			return false;
482
		}
483
484
		// circle were found everywhere but with no owner on every instance. we need to assign a new owner.
485
		// This should be done by checking admin rights. if no admin rights, let's assume that circle should be removed.
486
		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...
487
			// TODO: assign a new owner and check that when changing owner, we do check that the destination instance is updated FOR SURE!
488
			return true;
489
		}
490
491
		// some instances returned notFound, some returned circle with no owner. let's assume the circle is deprecated.
492
		return false;
493
	}
494
495
	/**
496
	 * @throws GSStatusException
497
	 */
498
	public function syncEvents() {
499
500
	}
501
502
	/**
503
	 *
504
	 */
505
	private function removeDeprecatedEvents() {
506
//		$this->deprecatedEvents();
507
508
	}
509
510
511
}
512
513