Passed
Pull Request — master (#1365)
by René
04:08
created

OptionService   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 316
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 4
Bugs 3 Features 0
Metric Value
wmc 43
eloc 135
c 4
b 3
f 0
dl 0
loc 316
ccs 0
cts 162
cp 0
rs 8.96

14 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 10 1
A list() 0 15 4
A get() 0 8 2
A confirm() 0 11 2
A delete() 0 6 1
A moveModifier() 0 12 6
A add() 0 11 2
A setOrder() 0 25 6
A getHighestOrder() 0 8 3
A clone() 0 15 2
A update() 0 6 1
A reorder() 0 22 5
A setOption() 0 16 4
A sequence() 0 26 4

How to fix   Complexity   

Complex Class

Complex classes like OptionService often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use OptionService, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @copyright Copyright (c) 2017 Vinzenz Rosenkranz <[email protected]>
4
 *
5
 * @author René Gieling <[email protected]>
6
 *
7
 * @license GNU AGPL version 3 or any later version
8
 *
9
 *  This program is free software: you can redistribute it and/or modify
10
 *  it under the terms of the GNU Affero General Public License as
11
 *  published by the Free Software Foundation, either version 3 of the
12
 *  License, or (at your option) any later version.
13
 *
14
 *  This program is distributed in the hope that it will be useful,
15
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
 *  GNU Affero General Public License for more details.
18
 *
19
 *  You should have received a copy of the GNU Affero General Public License
20
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
21
 *
22
 */
23
24
namespace OCA\Polls\Service;
25
26
use DateTime;
27
use OCP\AppFramework\Db\DoesNotExistException;
28
use OCA\Polls\Exceptions\NotAuthorizedException;
29
use OCA\Polls\Exceptions\BadRequestException;
30
use OCA\Polls\Exceptions\DuplicateEntryException;
31
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
32
33
use OCA\Polls\Db\OptionMapper;
34
use OCA\Polls\Db\Option;
35
use OCA\Polls\Db\PollMapper;
36
use OCA\Polls\Db\Poll;
37
use OCA\Polls\Model\Acl;
38
39
class OptionService {
40
41
	/** @var OptionMapper */
42
	private $optionMapper;
43
44
	/** @var Option */
45
	private $option;
46
47
	/** @var PollMapper */
48
	private $pollMapper;
49
50
	/** @var Acl */
51
	private $acl;
52
53
	public function __construct(
54
		OptionMapper $optionMapper,
55
		Option $option,
56
		PollMapper $pollMapper,
57
		Acl $acl
58
	) {
59
		$this->optionMapper = $optionMapper;
60
		$this->option = $option;
61
		$this->pollMapper = $pollMapper;
62
		$this->acl = $acl;
63
	}
64
65
	/**
66
	 * 	 * Get all options of given poll
67
	 *
68
	 * @return Option[]
69
	 *
70
	 * @psalm-return array<array-key, Option>
71
	 */
72
	public function list(?int $pollId = 0, string $token = ''): array {
73
		if ($token) {
74
			$this->acl->setToken($token);
75
		} else {
76
			$this->acl->setPollId($pollId)->request(Acl::PERMISSION_VIEW);
77
		}
78
79
		if (!$this->acl->isAllowed(Acl::PERMISSION_VIEW)) {
80
			throw new NotAuthorizedException;
81
		}
82
83
		try {
84
			return $this->optionMapper->findByPoll($this->acl->getPollId());
85
		} catch (DoesNotExistException $e) {
86
			return [];
87
		}
88
	}
89
90
	/**
91
	 * 	 * Get option
92
	 *
93
	 * @return Option
94
	 */
95
	public function get(int $optionId): Option {
96
		$this->acl->setPollId($this->optionMapper->find($optionId)->getPollId())->request(Acl::PERMISSION_VIEW);
97
98
		if (!$this->acl->isAllowed(Acl::PERMISSION_VIEW)) {
99
			throw new NotAuthorizedException;
100
		}
101
102
		return $this->optionMapper->find($optionId);
103
	}
104
105
106
	/**
107
	 * 	 * Add a new option
108
	 *
109
	 * @return Option
110
	 */
111
	public function add(int $pollId, int $timestamp = 0, string $pollOptionText = '', ?int $duration = 0): Option {
112
		$this->acl->setPollId($pollId)->request(Acl::PERMISSION_EDIT);
113
		$this->option = new Option();
114
		$this->option->setPollId($pollId);
115
		$this->option->setOrder($this->getHighestOrder($this->option->getPollId()) + 1);
116
		$this->setOption($timestamp, $pollOptionText, $duration);
117
118
		try {
119
			return $this->optionMapper->insert($this->option);
120
		} catch (UniqueConstraintViolationException $e) {
121
			throw new DuplicateEntryException('This option already exists');
122
		}
123
	}
124
125
	/**
126
	 * 	 * Update option
127
	 *
128
	 * @return Option
129
	 */
130
	public function update(int $optionId, int $timestamp = 0, ?string $pollOptionText = '', ?int $duration = 0): Option {
131
		$this->option = $this->optionMapper->find($optionId);
132
		$this->acl->setPollId($this->option->getPollId())->request(Acl::PERMISSION_EDIT);
133
		$this->setOption($timestamp, $pollOptionText, $duration);
134
135
		return $this->optionMapper->update($this->option);
136
	}
137
138
	/**
139
	 * 	 * Delete option
140
	 *
141
	 * @return Option
142
	 */
143
	public function delete(int $optionId): Option {
144
		$this->option = $this->optionMapper->find($optionId);
145
		$this->acl->setPollId($this->option->getPollId())->request(Acl::PERMISSION_EDIT);
146
		$this->optionMapper->delete($this->option);
147
148
		return $this->option;
149
	}
150
151
	/**
152
	 * 	 * Switch optoin confirmation
153
	 *
154
	 * @return Option
155
	 */
156
	public function confirm(int $optionId): Option {
157
		$this->option = $this->optionMapper->find($optionId);
158
		$this->acl->setPollId($this->option->getPollId())->request(Acl::PERMISSION_EDIT);
159
160
		if ($this->option->getConfirmed()) {
161
			$this->option->setConfirmed(0);
162
		} else {
163
			$this->option->setConfirmed(time());
164
		}
165
166
		return $this->optionMapper->update($this->option);
167
	}
168
169
	/**
170
	 * 	 * Make a sequence of date poll options
171
	 *
172
	 * @return Option[]
173
	 *
174
	 * @psalm-return array<array-key, Option>
175
	 */
176
	public function sequence(int $optionId, int $step, string $unit, int $amount): array {
177
		$baseDate = new DateTime;
178
		$this->option = $this->optionMapper->find($optionId);
179
		$this->acl->setPollId($this->option->getPollId())->request(Acl::PERMISSION_EDIT);
180
181
		if ($step === 0) {
182
			return $this->optionMapper->findByPoll($this->option->getPollId());
183
		}
184
185
		$baseDate->setTimestamp($this->option->getTimestamp());
186
187
		for ($i = 0; $i < $amount; $i++) {
188
			$clonedOption = new Option();
189
			$clonedOption->setPollId($this->option->getPollId());
190
			$clonedOption->setDuration($this->option->getDuration());
191
			$clonedOption->setConfirmed(0);
192
			$clonedOption->setTimestamp($baseDate->modify($step . ' ' . $unit)->getTimestamp());
193
			$clonedOption->setOrder($clonedOption->getTimestamp());
194
			$clonedOption->setPollOptionText($baseDate->format('c'));
195
			try {
196
				$this->optionMapper->insert($clonedOption);
197
			} catch (UniqueConstraintViolationException $e) {
198
				\OC::$server->getLogger()->warning('skip adding ' . $baseDate->format('c') . 'for pollId' . $this->option->getPollId() . '. Option alredy exists.');
199
			}
200
		}
201
		return $this->optionMapper->findByPoll($this->option->getPollId());
202
	}
203
204
	/**
205
	 * 	 * Copy options from $fromPoll to $toPoll
206
	 *
207
	 * @return Option[]
208
	 *
209
	 * @psalm-return array<array-key, Option>
210
	 */
211
	public function clone(int $fromPollId, int $toPollId): array {
212
		$this->acl->setPollId($fromPollId);
213
214
		foreach ($this->optionMapper->findByPoll($fromPollId) as $origin) {
215
			$option = new Option();
216
			$option->setPollId($toPollId);
217
			$option->setConfirmed(0);
218
			$option->setPollOptionText($origin->getPollOptionText());
219
			$option->setTimestamp($origin->getTimestamp());
220
			$option->setDuration($origin->getDuration());
221
			$option->setOrder($option->getOrder());
222
			$this->optionMapper->insert($option);
223
		}
224
225
		return $this->optionMapper->findByPoll($toPollId);
226
	}
227
228
	/**
229
	 * 	 * Reorder options with the order specified by $options
230
	 *
231
	 * @return Option[]
232
	 *
233
	 * @psalm-return array<array-key, Option>
234
	 */
235
	public function reorder(int $pollId, array $options): array {
236
		try {
237
			$poll = $this->pollMapper->find($pollId);
238
			$this->acl->setPoll($poll)->request(Acl::PERMISSION_EDIT);
239
240
			if ($poll->getType() === Poll::TYPE_DATE) {
241
				throw new BadRequestException("Not allowed in date polls");
242
			}
243
		} catch (DoesNotExistException $e) {
244
			throw new NotAuthorizedException;
245
		}
246
247
		$i = 0;
248
		foreach ($options as $option) {
249
			$this->option = $this->optionMapper->find($option['id']);
250
			if ($pollId === intval($this->option->getPollId())) {
251
				$this->option->setOrder(++$i);
252
				$this->optionMapper->update($this->option);
253
			}
254
		}
255
256
		return $this->optionMapper->findByPoll($pollId);
257
	}
258
259
	/**
260
	 * 	 * Change order for $optionId and reorder the options
261
	 *
262
	 * @NoAdminRequired
263
	 *
264
	 * @return Option[]
265
	 *
266
	 * @psalm-return array<array-key, Option>
267
	 */
268
	public function setOrder(int $optionId, int $newOrder): array {
269
		try {
270
			$this->option = $this->optionMapper->find($optionId);
271
			$poll = $this->pollMapper->find($this->option->getPollId());
272
			$this->acl->setPoll($poll)->request(Acl::PERMISSION_EDIT);
273
274
			if ($poll->getType() === Poll::TYPE_DATE) {
275
				throw new BadRequestException("Not allowed in date polls");
276
			}
277
		} catch (DoesNotExistException $e) {
278
			throw new NotAuthorizedException;
279
		}
280
281
		if ($newOrder < 1) {
282
			$newOrder = 1;
283
		} elseif ($newOrder > $this->getHighestOrder($poll->getId())) {
284
			$newOrder = $this->getHighestOrder($poll->getId());
285
		}
286
287
		foreach ($this->optionMapper->findByPoll($poll->getId()) as $option) {
288
			$option->setOrder($this->moveModifier($this->option->getOrder(), $newOrder, $option->getOrder()));
289
			$this->optionMapper->update($option);
290
		}
291
292
		return $this->optionMapper->findByPoll($this->option->getPollId());
293
	}
294
295
	/**
296
	 * 	 * moveModifier - evaluate new order
297
	 * 	 * depending on the old and the new position of a moved array item
298
	 * 	 * $moveFrom - old position of the moved item
299
	 * 	 * $moveTo   - target posotion of the moved item
300
	 * 	 * $value    - current position of the current item
301
	 * 	 * Returns the modified new new position of the current item
302
	 *
303
	 * @return int
304
	 */
305
	private function moveModifier(int $moveFrom, int $moveTo, int $currentPosition): int {
306
		$moveModifier = 0;
307
		if ($moveFrom < $currentPosition && $currentPosition <= $moveTo) {
308
			// moving forward
309
			$moveModifier = -1;
310
		} elseif ($moveTo <= $currentPosition && $currentPosition < $moveFrom) {
311
			//moving backwards
312
			$moveModifier = 1;
313
		} elseif ($moveFrom === $currentPosition) {
314
			return $moveTo;
315
		}
316
		return $currentPosition + $moveModifier;
317
	}
318
319
	/**
320
	 * Set option entities validated
321
	 */
322
	private function setOption(int $timestamp = 0, ?string $pollOptionText = '', ?int $duration = 0): void {
323
		$poll = $this->pollMapper->find($this->option->getPollId());
324
325
		if ($poll->getType() === Poll::TYPE_DATE) {
326
			$this->option->setTimestamp($timestamp);
327
			$this->option->setOrder($timestamp);
328
			$this->option->setDuration($duration);
329
			if ($duration === 0) {
330
				$this->option->setPollOptionText(date('c', $timestamp));
331
			} elseif ($duration > 0) {
332
				$this->option->setPollOptionText(date('c', $timestamp) .' - ' . date('c', $timestamp + $duration));
333
			} else {
334
				$this->option->setPollOptionText($pollOptionText);
335
			}
336
		} else {
337
			$this->option->setPollOptionText($pollOptionText);
338
		}
339
	}
340
341
	/**
342
	 * 	 * Get the highest order number in $pollId
343
	 * 	 * Return Highest order number
344
	 *
345
	 * @return int
346
	 */
347
	private function getHighestOrder(int $pollId): int {
348
		$highestOrder = 0;
349
		foreach ($this->optionMapper->findByPoll($pollId) as $option) {
350
			if ($option->getOrder() > $highestOrder) {
351
				$highestOrder = $option->getOrder();
352
			}
353
		}
354
		return $highestOrder;
355
	}
356
}
357