Passed
Pull Request — master (#1219)
by René
08:31
created

OptionService   C

Complexity

Total Complexity 55

Size/Duplication

Total Lines 364
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 2
Bugs 1 Features 0
Metric Value
wmc 55
eloc 142
c 2
b 1
f 0
dl 0
loc 364
ccs 0
cts 193
cp 0
rs 6

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 10 1
A list() 0 11 3
A get() 0 6 2
A add() 0 13 3
C setOrder() 0 45 14
A confirm() 0 14 3
A clone() 0 20 4
A update() 0 10 2
A reorder() 0 23 6
A sequence() 0 28 5
A delete() 0 10 2
A getHighestOrder() 0 8 3
B setOption() 0 16 7

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 OCA\Polls\Exceptions\NotFoundException;
32
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
33
34
use OCA\Polls\Db\OptionMapper;
35
use OCA\Polls\Db\Option;
36
use OCA\Polls\Db\PollMapper;
37
use OCA\Polls\Db\Poll;
38
use OCA\Polls\Model\Acl;
39
40
class OptionService {
41
42
	/** @var OptionMapper */
43
	private $optionMapper;
44
45
	/** @var Option */
46
	private $option;
47
48
	/** @var PollMapper */
49
	private $pollMapper;
50
51
	/** @var Acl */
52
	private $acl;
53
54
	/**
55
	 * OptionService constructor.
56
	 * @param OptionMapper $optionMapper
57
	 * @param Option $option
58
	 * @param PollMapper $pollMapper
59
	 * @param Acl $acl
60
	 */
61
62
	public function __construct(
63
		OptionMapper $optionMapper,
64
		Option $option,
65
		PollMapper $pollMapper,
66
		Acl $acl
67
	) {
68
		$this->optionMapper = $optionMapper;
69
		$this->option = $option;
70
		$this->pollMapper = $pollMapper;
71
		$this->acl = $acl;
72
	}
73
74
	/**
75
	 * Get all options of given poll
76
	 * @NoAdminRequired
77
	 * @param int $pollId
78
	 * @param string $token
79
	 * @return array Array of Option objects
80
	 * @throws NotAuthorizedException
81
	 */
82
	public function list($pollId = 0, $token = '') {
83
		$acl = $this->acl->set($pollId, $token);
84
85
		if (!$acl->getAllowView()) {
86
			throw new NotAuthorizedException;
87
		}
88
89
		try {
90
			return $this->optionMapper->findByPoll($acl->getPollId());
91
		} catch (DoesNotExistException $e) {
92
			return [];
93
		}
94
	}
95
96
	/**
97
	 * Get option
98
	 * @NoAdminRequired
99
	 * @param int $optionId
100
	 * @return Option
101
	 * @throws NotAuthorizedException
102
	 */
103
	public function get($optionId) {
104
		if (!$this->acl->set($this->optionMapper->find($optionId)->getPollId())->getAllowView()) {
105
			throw new NotAuthorizedException;
106
		}
107
108
		return $this->optionMapper->find($optionId);
109
	}
110
111
112
	/**
113
	 * Add a new option
114
	 * @NoAdminRequired
115
	 * @param int $pollId
116
	 * @param int $timestamp
117
	 * @param string $pollOptionText
118
	 * @return Option
119
	 * @throws NotAuthorizedException
120
	 */
121
	public function add($pollId, $timestamp = 0, $pollOptionText = '') {
122
		if (!$this->acl->set($pollId)->getAllowEdit()) {
123
			throw new NotAuthorizedException;
124
		}
125
126
		$this->option = new Option();
127
		$this->option->setPollId($pollId);
128
		$this->setOption($timestamp, $pollOptionText);
129
130
		try {
131
			return $this->optionMapper->insert($this->option);
132
		} catch (UniqueConstraintViolationException $e) {
133
			throw new DuplicateEntryException('This option already exists');
134
		}
135
	}
136
137
	/**
138
	 * Update option
139
	 * @NoAdminRequired
140
	 * @param int $optionId
141
	 * @param int $timestamp
142
	 * @param string $pollOptionText
143
	 * @param int $order
144
	 * @return Option
145
	 * @throws NotAuthorizedException
146
	 */
147
	public function update($optionId, $timestamp = 0, $pollOptionText = '', $order = 0) {
148
		$this->option = $this->optionMapper->find($optionId);
149
150
		if (!$this->acl->set($this->option->getPollId())->getAllowEdit()) {
151
			throw new NotAuthorizedException;
152
		}
153
154
		$this->setOption($timestamp, $pollOptionText, $order);
155
156
		return $this->optionMapper->update($this->option);
157
	}
158
159
	/**
160
	 * Delete option
161
	 * @NoAdminRequired
162
	 * @param int $optionId
163
	 * @return Option deleted Option
164
	 * @throws NotAuthorizedException
165
	 */
166
	public function delete($optionId) {
167
		$this->option = $this->optionMapper->find($optionId);
168
169
		if (!$this->acl->set($this->option->getPollId())->getAllowEdit()) {
170
			throw new NotAuthorizedException;
171
		}
172
173
		$this->optionMapper->delete($this->option);
174
175
		return $this->option;
176
	}
177
178
	/**
179
	 * Switch optoin confirmation
180
	 * @NoAdminRequired
181
	 * @param int $optionId
182
	 * @return Option confirmed Option
183
	 * @throws NotAuthorizedException
184
	 */
185
	public function confirm($optionId) {
186
		$this->option = $this->optionMapper->find($optionId);
187
188
		if (!$this->acl->set($this->option->getPollId())->getAllowEdit()) {
189
			throw new NotAuthorizedException;
190
		}
191
192
		if ($this->option->getConfirmed()) {
193
			$this->option->setConfirmed(0);
194
		} else {
195
			$this->option->setConfirmed(time());
196
		}
197
198
		return $this->optionMapper->update($this->option);
199
	}
200
201
	/**
202
	 * Make a sequence of date poll options
203
	 * @NoAdminRequired
204
	 * @param int $optionId
205
	 * @param int $step
206
	 * @param string $unit
207
	 * @param int $amount
208
	 * @return array Array of Option objects
209
	 * @throws NotAuthorizedException
210
	 */
211
	public function sequence($optionId, $step, $unit, $amount) {
212
		$baseDate = new DateTime;
213
		$origin = $this->optionMapper->find($optionId);
214
215
		if (!$this->acl->set($origin->getPollId())->getAllowEdit()) {
216
			throw new NotAuthorizedException;
217
		}
218
219
		if ($step === 0) {
220
			return $this->optionMapper->findByPoll($origin->getPollId());
221
		}
222
223
		$baseDate->setTimestamp($origin->getTimestamp());
224
225
		for ($i = 0; $i < $amount; $i++) {
226
			$this->option = new Option();
227
			$this->option->setPollId($origin->getPollId());
228
			$this->option->setConfirmed(0);
229
			$this->option->setTimestamp($baseDate->modify($step . ' ' . $unit)->getTimestamp());
230
			$this->option->setPollOptionText($baseDate->format('c'));
231
			$this->option->setOrder($baseDate->getTimestamp());
232
			try {
233
				$this->optionMapper->insert($this->option);
234
			} catch (UniqueConstraintViolationException $e) {
235
				\OC::$server->getLogger()->warning('skip adding ' . $baseDate->format('c') . 'for pollId' . $origin->getPollId() . '. Option alredy exists.');
236
			}
237
		}
238
		return $this->optionMapper->findByPoll($origin->getPollId());
239
	}
240
241
	/**
242
	 * Copy options from $fromPoll to $toPoll
243
	 * @NoAdminRequired
244
	 * @param int $fromPollId
245
	 * @param int $toPollId
246
	 * @return array Array of Option objects
247
	 * @throws NotAuthorizedException
248
	 */
249
	public function clone($fromPollId, $toPollId) {
250
		try {
251
			if (!$this->acl->set($fromPollId)->getAllowView()) {
252
				throw new NotAuthorizedException;
253
			}
254
		} catch (DoesNotExistException $e) {
255
			throw new NotFoundException('Poll ' . $fromPollId .' does not exist');
256
		}
257
258
		foreach ($this->optionMapper->findByPoll($fromPollId) as $origin) {
259
			$option = new Option();
260
			$option->setPollId($toPollId);
261
			$option->setConfirmed(0);
262
			$option->setPollOptionText($origin->getPollOptionText());
263
			$option->setTimestamp($origin->getTimestamp());
264
			$option->setOrder($origin->getOrder());
265
			$this->optionMapper->insert($option);
266
		}
267
268
		return $this->optionMapper->findByPoll($toPollId);
269
	}
270
271
	/**
272
	 * Reorder options with the order specified by $options
273
	 * @NoAdminRequired
274
	 * @param int $pollId
275
	 * @param array $options - Array of options
276
	 * @return array Array of Option objects
277
	 * @throws NotAuthorizedException
278
	 * @throws BadRequestException
279
	 * @throws NotFoundException
280
	 */
281
	public function reorder($pollId, $options) {
282
		try {
283
			if (!$this->acl->set($pollId)->getAllowEdit()) {
284
				throw new NotAuthorizedException;
285
			}
286
287
			if ($this->pollMapper->find($pollId)->getType() === Poll::TYPE_DATE) {
288
				throw new BadRequestException("Not allowed in date polls");
289
			}
290
		} catch (DoesNotExistException $e) {
291
			throw new NotAuthorizedException;
292
		}
293
294
		$i = 0;
295
		foreach ($options as $option) {
296
			$this->option = $this->optionMapper->find($option['id']);
297
			if ($pollId === intval($this->option->getPollId())) {
298
				$this->option->setOrder(++$i);
299
				$this->optionMapper->update($this->option);
300
			}
301
		}
302
303
		return $this->optionMapper->findByPoll($pollId);
304
	}
305
306
	/**
307
	 * Change order for $optionId and reorder the options
308
	 * @NoAdminRequired
309
	 * @param int $optionId
310
	 * @param int $newOrder
311
	 * @return array Array of Option objects
312
	 * @throws NotAuthorizedException
313
	 * @throws BadRequestException
314
	 * @throws NotFoundException
315
	 */
316
	public function setOrder($optionId, $newOrder) {
317
		try {
318
			$this->option = $this->optionMapper->find($optionId);
319
			$pollId = $this->option->getPollId();
320
321
			if ($this->pollMapper->find($pollId)->getType() === Poll::TYPE_DATE) {
322
				throw new BadRequestException("Not allowed in date polls");
323
			}
324
325
			if (!$this->acl->set($pollId)->getAllowEdit()) {
326
				throw new NotAuthorizedException;
327
			}
328
		} catch (DoesNotExistException $e) {
329
			throw new NotAuthorizedException;
330
		}
331
332
333
		if ($newOrder < 1) {
334
			$newOrder = 1;
335
		} elseif ($newOrder > $this->getHighestOrder($pollId)) {
336
			$newOrder = $this->getHighestOrder($pollId);
337
		}
338
339
		$oldOrder = $this->option->getOrder();
340
341
		foreach ($this->optionMapper->findByPoll($pollId) as $option) {
342
			$currentOrder = $option->getOrder();
343
			if ($currentOrder > $oldOrder && $currentOrder <= $newOrder) {
344
				$option->setOrder($currentOrder - 1);
345
				$this->optionMapper->update($option);
346
			} elseif (
347
					   ($currentOrder < $oldOrder && $currentOrder >= $newOrder)
348
					|| ($currentOrder < $oldOrder && $currentOrder = $newOrder)
349
				) {
350
				$option->setOrder($currentOrder + 1);
351
				$this->optionMapper->update($option);
352
			} elseif ($currentOrder === $oldOrder) {
353
				$option->setOrder($newOrder);
354
				$this->optionMapper->update($option);
355
			} else {
356
				continue;
357
			}
358
		}
359
360
		return $this->optionMapper->findByPoll($this->option->getPollId());
361
	}
362
363
	/**
364
	 * Set option entities validated
365
	 * @NoAdminRequired
366
	 * @param int $timestamp
367
	 * @param string $pollOptionText
368
	 * @param int $order
369
	 * @throws BadRequestException
370
	 */
371
	private function setOption($timestamp = 0, $pollOptionText = '', $order = 0) {
372
		$poll = $this->pollMapper->find($this->option->getPollId());
373
374
		if ($poll->getType() === Poll::TYPE_DATE && $timestamp) {
375
			$this->option->setTimestamp($timestamp);
376
			$this->option->setOrder($timestamp);
377
			$this->option->setPollOptionText(date('c', $timestamp));
378
		} elseif ($poll->getType() === Poll::TYPE_TEXT && $pollOptionText) {
379
			$this->option->setPollOptionText($pollOptionText);
380
381
			if (!$order && !$this->option->getOrder()) {
382
				$order = $this->getHighestOrder($this->option->getPollId()) + 1;
383
				$this->option->setOrder($order);
384
			}
385
		} else {
386
			throw new BadRequestException("Text or timestamp is missing");
387
		}
388
	}
389
390
	/**
391
	 * Get the highest order number in $pollId
392
	 * @NoAdminRequired
393
	 * @param int $pollId
394
	 * @return int Highest order number
395
	 */
396
	private function getHighestOrder($pollId) {
397
		$order = 0;
398
		foreach ($this->optionMapper->findByPoll($pollId) as $option) {
399
			if ($option->getOrder() > $order) {
400
				$order = $option->getOrder();
401
			}
402
		}
403
		return $order;
404
	}
405
}
406