Completed
Push — master ( 159268...c329b8 )
by Tim
24s queued 11s
created

TimeTimeTable::addRecurrenceItems()   C

Complexity

Conditions 14
Paths 27

Size

Total Lines 53

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 53
rs 6.2666
c 0
b 0
f 0
cc 14
nc 27
nop 4

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * Time service.
5
 */
6
declare(strict_types=1);
7
8
namespace HDNET\Calendarize\Service\TimeTable;
9
10
use HDNET\Calendarize\Domain\Model\Configuration;
11
use HDNET\Calendarize\Service\RecurrenceService;
12
use HDNET\Calendarize\Utility\ConfigurationUtility;
13
use HDNET\Calendarize\Utility\DateTimeUtility;
14
use HDNET\Calendarize\Utility\HelperUtility;
15
use TYPO3\CMS\Core\Messaging\FlashMessage;
16
use TYPO3\CMS\Core\Messaging\FlashMessageService;
17
use TYPO3\CMS\Core\Utility\GeneralUtility;
18
use TYPO3\CMS\Core\Utility\MathUtility;
19
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
20
21
/**
22
 * Time service.
23
 */
24
class TimeTimeTable extends AbstractTimeTable
25
{
26
    /**
27
     * Modify the given times via the configuration.
28
     *
29
     * @param array         $times
30
     * @param Configuration $configuration
31
     */
32
    public function handleConfiguration(array &$times, Configuration $configuration)
33
    {
34
        $startTime = $configuration->isAllDay() ? null : $configuration->getStartTime();
35
        $endTime = $configuration->isAllDay() ? null : $configuration->getEndTime();
36
        $baseEntry = [
37
            'pid' => $configuration->getPid(),
38
            'start_date' => $configuration->getStartDate(),
39
            'end_date' => $configuration->getEndDate() ?: $configuration->getStartDate(),
40
            'start_time' => $startTime,
41
            'end_time' => $endTime,
42
            'all_day' => $configuration->isAllDay(),
43
            'open_end_time' => $configuration->isOpenEndTime(),
44
            'state' => $configuration->getState(),
45
        ];
46
        if (!$this->validateBaseEntry($baseEntry)) {
47
            return;
48
        }
49
        $times[$this->calculateEntryKey($baseEntry)] = $baseEntry;
50
        $tillDateConfiguration = $this->getTillDateConfiguration($configuration, $baseEntry);
51
        $this->addFrequencyItems($times, $configuration, $baseEntry, $tillDateConfiguration);
52
        $this->addRecurrenceItems($times, $configuration, $baseEntry, $tillDateConfiguration);
53
        $this->respectDynamicEndDates($times, $configuration);
54
        $this->removeBaseEntryIfNecessary($times, $configuration, $baseEntry, $tillDateConfiguration);
55
    }
56
57
    /**
58
     * Respect the selection of dynamic enddates.
59
     *
60
     * @param array         $times
61
     * @param Configuration $configuration
62
     */
63
    protected function respectDynamicEndDates(array &$times, Configuration $configuration)
64
    {
65
        switch ($configuration->getEndDateDynamic()) {
66
            case Configuration::END_DYNAMIC_1_DAY:
67
                $callback = function ($entry) {
68
                    if ($entry['start_date'] instanceof \DateTime) {
69
                        $entry['end_date'] = clone $entry['start_date'];
70
                        $entry['end_date']->modify('+1 day');
71
                    }
72
73
                    return $entry;
74
                };
75
                break;
76
            case Configuration::END_DYNAMIC_1_WEEK:
77
                $callback = function ($entry) {
78
                    if ($entry['start_date'] instanceof \DateTime) {
79
                        $entry['end_date'] = clone $entry['start_date'];
80
                        $entry['end_date']->modify('+1 week');
81
                    }
82
83
                    return $entry;
84
                };
85
                break;
86
            case Configuration::END_DYNAMIC_END_WEEK:
87
                $callback = function ($entry) {
88
                    if ($entry['start_date'] instanceof \DateTime) {
89
                        $entry['end_date'] = clone $entry['start_date'];
90
                        $entry['end_date']->modify('monday next week');
91
                        $entry['end_date']->modify('-1 day');
92
                    }
93
94
                    return $entry;
95
                };
96
                break;
97
            case Configuration::END_DYNAMIC_END_MONTH:
98
                $callback = function ($entry) {
99
                    if ($entry['start_date'] instanceof \DateTime) {
100
                        $entry['end_date'] = clone $entry['start_date'];
101
                        $entry['end_date']->modify('last day of this month');
102
                    }
103
104
                    return $entry;
105
                };
106
                break;
107
            case Configuration::END_DYNAMIC_END_YEAR:
108
109
                $callback = function ($entry) {
110
                    if ($entry['start_date'] instanceof \DateTime) {
111
                        $entry['end_date'] = clone $entry['start_date'];
112
                        $entry['end_date']->setDate((int) $entry['end_date']->format('Y'), 12, 31);
113
                    }
114
115
                    return $entry;
116
                };
117
                break;
118
        }
119
120
        if (!isset($callback)) {
121
            return;
122
        }
123
124
        $new = [];
125
        foreach ($times as $hash => $record) {
126
            $target = $callback($record);
127
            $new[$this->calculateEntryKey($target)] = $target;
128
        }
129
        $times = $new;
130
    }
131
132
    /**
133
     * Validate the base entry, if there are logica mistakes.
134
     *
135
     * @param array $baseEntry
136
     *
137
     * @return bool
138
     */
139
    protected function validateBaseEntry(array $baseEntry): bool
140
    {
141
        $message = null;
142
        if (!($baseEntry['start_date'] instanceof \DateTimeInterface)) {
143
            $message = GeneralUtility::makeInstance(
144
                FlashMessage::class,
145
                'There is no usage for a event configuration without start date?!',
146
                'No start date?',
147
                FlashMessage::ERROR
148
            );
149
        } elseif ($baseEntry['end_date'] instanceof \DateTimeInterface && $baseEntry['start_date'] > $baseEntry['end_date']) {
150
            $message = GeneralUtility::makeInstance(
151
                FlashMessage::class,
152
                LocalizationUtility::translate(
153
                    'LLL:EXT:calendarize/Resources/Private/Language/locallang.xlf:wrong.date.message',
154
                    'calendarize'
155
                ),
156
                LocalizationUtility::translate(
157
                    'LLL:EXT:calendarize/Resources/Private/Language/locallang.xlf:wrong.date',
158
                    'calendarize'
159
                ),
160
                FlashMessage::ERROR
161
            );
162
        } elseif ($baseEntry['end_date'] instanceof \DateTimeInterface && !$baseEntry['all_day'] && $baseEntry['start_date']->format('d.m.Y') === $baseEntry['end_date']->format('d.m.Y') && $baseEntry['start_time'] % DateTimeUtility::SECONDS_DAY > $baseEntry['end_time'] % DateTimeUtility::SECONDS_DAY && $baseEntry['end_time'] > 0) {
163
            $message = GeneralUtility::makeInstance(
164
                FlashMessage::class,
165
                LocalizationUtility::translate(
166
                    'LLL:EXT:calendarize/Resources/Private/Language/locallang.xlf:wrong.time.message',
167
                    'calendarize'
168
                ),
169
                LocalizationUtility::translate(
170
                    'LLL:EXT:calendarize/Resources/Private/Language/locallang.xlf:wrong.time',
171
                    'calendarize'
172
                ),
173
                FlashMessage::ERROR
174
            );
175
        }
176
        if ($message) {
177
            $flashMessageService = HelperUtility::create(FlashMessageService::class);
178
            $messageQueue = $flashMessageService->getMessageQueueByIdentifier();
179
            $messageQueue->addMessage($message);
180
181
            return false;
182
        }
183
184
        return true;
185
    }
186
187
    /**
188
     * Add frequency items.
189
     *
190
     * @param array         $times
191
     * @param Configuration $configuration
192
     * @param array         $baseEntry
193
     * @param array         $tillDateConfiguration
194
     */
195
    protected function addFrequencyItems(array &$times, Configuration $configuration, array $baseEntry, array $tillDateConfiguration)
196
    {
197
        $frequencyIncrement = $this->getFrequencyIncrement($configuration);
198
        if (!$frequencyIncrement) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $frequencyIncrement of type false|string is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
199
            return;
200
        }
201
        $amountCounter = $configuration->getCounterAmount();
202
        $maxLimit = $this->getFrequencyLimitPerItem();
203
        $lastLoop = $baseEntry;
204
        for ($i = 0; $i < $maxLimit && (0 === $amountCounter || $i < $amountCounter); ++$i) {
205
            $loopEntry = $this->createNextLoopEntry($lastLoop, $frequencyIncrement);
206
207
            if ($tillDateConfiguration['tillDate'] instanceof \DateTimeInterface && $loopEntry['start_date'] > $tillDateConfiguration['tillDate']) {
208
                break;
209
            }
210
211
            $lastLoop = $loopEntry;
212
213
            if ($tillDateConfiguration['tillDatePast'] instanceof \DateTimeInterface && $loopEntry['end_date'] < $tillDateConfiguration['tillDatePast']) {
214
                continue;
215
            }
216
217
            $times[$this->calculateEntryKey($loopEntry)] = $loopEntry;
218
        }
219
    }
220
221
    /**
222
     * Create the next loop entry.
223
     *
224
     * @param array  $loopEntry
225
     * @param string $modification
226
     *
227
     * @return array
228
     */
229
    protected function createNextLoopEntry(array $loopEntry, string $modification): array
230
    {
231
        /** @var $startDate \DateTime */
232
        $startDate = clone $loopEntry['start_date'];
233
        $startDate->modify($modification);
234
        $loopEntry['start_date'] = $startDate;
235
236
        /** @var $endDate \DateTime */
237
        $endDate = clone $loopEntry['end_date'];
238
        $endDate->modify($modification);
239
        $loopEntry['end_date'] = $endDate;
240
241
        return $loopEntry;
242
    }
243
244
    /**
245
     * Get the frequency date increment.
246
     *
247
     * @param Configuration $configuration
248
     *
249
     * @return string
250
     */
251
    protected function getFrequencyIncrement(Configuration $configuration)
252
    {
253
        $interval = $configuration->getCounterInterval() <= 1 ? 1 : $configuration->getCounterInterval();
254
        switch ($configuration->getFrequency()) {
255
            case Configuration::FREQUENCY_DAILY:
256
                $intervalValue = '+' . $interval . ' days';
257
                break;
258
            case Configuration::FREQUENCY_WEEKLY:
259
                $intervalValue = '+' . $interval . ' weeks';
260
                break;
261
            case Configuration::FREQUENCY_MONTHLY:
262
                if (Configuration::RECURRENCE_NONE !== $configuration->getRecurrence()) {
263
                    return false;
264
                }
265
                $intervalValue = '+' . $interval . ' months';
266
                break;
267
            case Configuration::FREQUENCY_YEARLY:
268
                if (Configuration::RECURRENCE_NONE !== $configuration->getRecurrence()) {
269
                    return false;
270
                }
271
                $intervalValue = '+' . $interval . ' years';
272
                break;
273
            default:
274
                $intervalValue = false;
275
        }
276
277
        return $intervalValue;
278
    }
279
280
    /**
281
     * Add recurrence items.
282
     *
283
     * @param array         $times
284
     * @param Configuration $configuration
285
     * @param array         $baseEntry
286
     * @param array         $tillDateConfiguration
287
     */
288
    protected function addRecurrenceItems(array &$times, Configuration $configuration, array $baseEntry, array $tillDateConfiguration)
289
    {
290
        if (Configuration::RECURRENCE_NONE === $configuration->getRecurrence() || Configuration::DAY_NONE === $configuration->getDay()) {
291
            return;
292
        }
293
294
        $recurrenceService = GeneralUtility::makeInstance(RecurrenceService::class);
295
        $amountCounter = $configuration->getCounterAmount();
296
        $maxLimit = $this->getFrequencyLimitPerItem();
297
        $lastLoop = $baseEntry;
298
        $intervalCounter = $configuration->getCounterInterval() <= 1 ? 1 : $configuration->getCounterInterval();
299
        for ($i = 0; $i < $maxLimit && (0 === $amountCounter || $i < $amountCounter); ++$i) {
300
            $loopEntry = $lastLoop;
301
302
            $dateTime = false;
303
            if (Configuration::FREQUENCY_MONTHLY === $configuration->getFrequency()) {
304
                $dateTime = $recurrenceService->getRecurrenceForNextMonth(
305
                    $loopEntry['start_date'],
306
                    $configuration->getRecurrence(),
307
                    $configuration->getDay(),
308
                    $intervalCounter
309
                );
310
            } elseif (Configuration::FREQUENCY_YEARLY === $configuration->getFrequency()) {
311
                $dateTime = $recurrenceService->getRecurrenceForNextYear(
312
                    $loopEntry['start_date'],
313
                    $configuration->getRecurrence(),
314
                    $configuration->getDay(),
315
                    $intervalCounter
316
                );
317
            }
318
            if (false === $dateTime) {
319
                break;
320
            }
321
322
            /** @var \DateInterval $interval */
323
            $interval = $loopEntry['start_date']->diff($dateTime);
324
            $frequencyIncrement = $interval->format('%R%a days');
325
326
            $loopEntry = $this->createNextLoopEntry($loopEntry, $frequencyIncrement);
327
328
            if ($tillDateConfiguration['tillDate'] instanceof \DateTimeInterface && $loopEntry['start_date'] > $tillDateConfiguration['tillDate']) {
329
                break;
330
            }
331
332
            $lastLoop = $loopEntry;
333
334
            if ($tillDateConfiguration['tillDatePast'] instanceof \DateTimeInterface && $loopEntry['end_date'] < $tillDateConfiguration['tillDatePast']) {
335
                continue;
336
            }
337
338
            $times[$this->calculateEntryKey($loopEntry)] = $loopEntry;
339
        }
340
    }
341
342
    /**
343
     * Remove the base entry if necessary.
344
     *
345
     * @param array         $times
346
     * @param Configuration $configuration
347
     * @param array         $baseEntry
348
     * @param array         $tillDateConfiguration
349
     */
350
    protected function removeBaseEntryIfNecessary(array &$times, Configuration $configuration, array $baseEntry, array $tillDateConfiguration)
351
    {
352
        $baseEntryKey = $this->calculateEntryKey($baseEntry);
353
        $tillDate = $configuration->getTillDate();
354
355
        if (!isset($times[$baseEntryKey])) {
356
            return;
357
        }
358
359
        // if the till date is set via the till day feature and if the base entry does not match the till date condition remove it from times
360
        if (!$tillDate instanceof \DateTimeInterface && $tillDateConfiguration['tillDate'] instanceof \DateTimeInterface && $baseEntry['start_date'] > $tillDateConfiguration['tillDate']) {
361
            unset($times[$baseEntryKey]);
362
        } elseif ($tillDateConfiguration['tillDatePast'] instanceof \DateTimeInterface && $baseEntry['end_date'] < $tillDateConfiguration['tillDatePast']) {
363
            // till date past can only be set via the till date day feature, if the base entry does not match the till date past condition remove it from times
364
            unset($times[$baseEntryKey]);
365
        }
366
    }
367
368
    /**
369
     * @param  Configuration  $configuration
370
     * @param  array  $baseEntry
371
     * @return array
372
     */
373
    protected function getTillDateConfiguration(Configuration $configuration, array $baseEntry): array
374
    {
375
        // get values from item configuration
376
        $tillDate = $configuration->getTillDate();
377
        $tillDays = $configuration->getTillDays();
378
        $tillDaysRelative = $configuration->isTillDaysRelative();
379
        $tillDaysPast = $configuration->getTillDaysPast();
380
        $tillDatePast = null;
381
382
        // if not set get values from extension configuration
383
        if ($tillDays === null && $tillDaysPast === null) {
384
            $tillDays = ConfigurationUtility::get('tillDays');
385
            $tillDays = MathUtility::canBeInterpretedAsInteger($tillDays) ? (int)$tillDays : null;
386
            $tillDaysPast = ConfigurationUtility::get('tillDaysPast');
387
            $tillDaysPast = MathUtility::canBeInterpretedAsInteger($tillDaysPast) ? (int)$tillDaysPast : null;
388
            $tillDaysRelative = (bool)ConfigurationUtility::get('tillDaysRelative');
389
        }
390
391
        // get base date for till tillDate and tillDatePast calculation
392
        /** @var \DateTime $tillDaysBaseDate */
393
        $tillDaysBaseDate = $baseEntry['start_date'];
394
        if ($tillDaysRelative) {
395
            $tillDaysBaseDate = DateTimeUtility::resetTime();
396
        }
397
398
        // get actual tillDate
399
        if (!$tillDate instanceof \DateTimeInterface && (is_int($tillDays))) {
400
            --$tillDays; // - 1 day because we already take the current day into account
401
            $tillDate = clone $tillDaysBaseDate;
402
            $tillDate->modify('+' . $tillDays . ' day');
403
        }
404
405
        // get actual tillDatePast
406
        if (is_int($tillDaysPast)) {
407
            $tillDatePast = clone $tillDaysBaseDate;
408
            $tillDatePast->modify('-' . $tillDaysPast . ' day');
409
        }
410
411
        return [
412
            'tillDate' => $tillDate,
413
            'tillDatePast' => $tillDatePast,
414
        ];
415
    }
416
417
    /**
418
     * Get the limit of the frequency.
419
     *
420
     * @return int
421
     */
422
    protected function getFrequencyLimitPerItem(): int
423
    {
424
        $maxLimit = (int) ConfigurationUtility::get('frequencyLimitPerItem');
425
        if ($maxLimit <= 0) {
426
            $maxLimit = 300;
427
        }
428
429
        return $maxLimit;
430
    }
431
}
432