SimpleCalendar   C
last analyzed

Complexity

Total Complexity 54

Size/Duplication

Total Lines 357
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 142
c 1
b 0
f 0
dl 0
loc 357
rs 6.4799
wmc 54

14 Methods

Rating   Name   Duplication   Size   Complexity  
A weekdays() 0 12 3
A __construct() 0 4 1
A setCalendarClasses() 0 7 3
A clearDailyHtml() 0 1 1
F render() 0 88 16
A rotate() 0 8 3
A setToday() 0 7 3
A setWeekDayNames() 0 6 4
A show() 0 7 2
A addDailyHtml() 0 31 6
A setPermSubmit() 0 3 1
A setStartOfWeek() 0 12 5
A parseDate() 0 12 4
A setDate() 0 2 2

How to fix   Complexity   

Complex Class

Complex classes like SimpleCalendar 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 SimpleCalendar, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace XoopsModules\Wgevents\SimpleCalendar;
4
5
/**
6
 * Simple Calendar
7
 *
8
 * @author Jesse G. Donat <[email protected]>
9
 * @see http://donatstudios.com
10
 * @license http://opensource.org/licenses/mit-license.php
11
 */
12
class SimpleCalendar {
13
14
    /**
15
     * Array of Week Day Names
16
     *
17
     * @var string[]|null
18
     */
19
    private $weekDayNames;
20
21
    /**
22
     * @var \DateTimeInterface
23
     */
24
    private $now;
25
26
    /**
27
     * @var \DateTimeInterface|null
28
     */
29
    private $today;
30
31
    private $classes = [
32
        'calendar'     => 'SimpleCalendar',
33
        'leading_day'  => 'SCprefix',
34
        'trailing_day' => 'SCsuffix',
35
        'today'        => 'today',
36
        'event'        => 'event',
37
        'events'       => 'events',
38
    ];
39
40
    private $dailyHtml = [];
41
    private $offset = 0;
42
43
    /**
44
     * @var bool
45
     */
46
    private $permSubmit;
47
48
49
    /**
50
     * @param \DateTimeInterface|int|string|null $calendarDate
51
     * @param \DateTimeInterface|false|int|string|null $today
52
     *
53
     * @throws \Exception
54
     * @throws \Exception
55
     * @see setToday
56
     * @see setDate
57
     */
58
    public function __construct( $calendarDate = null, $today = null ) {
59
        $this->setDate($calendarDate);
60
        $this->setToday($today);
61
        $this->setCalendarClasses();
62
    }
63
64
    /**
65
     * Sets the date for the calendar.
66
     *
67
     * @param \DateTimeInterface|int|string|null $date DateTimeInterface or Date string parsed by strtotime for the
68
     *     calendar date. If null set to current timestamp.
69
     * @throws \Exception
70
     */
71
    public function setDate( $date = null ) {
72
        $this->now = $this->parseDate($date) ?: new \DateTimeImmutable();
73
    }
74
75
    /**
76
     * @param \DateTimeInterface|int|string|null $date
77
     * @return \DateTimeInterface|null
78
     * @throws \Exception
79
     */
80
    private function parseDate( $date = null ) {
81
        if( $date instanceof \DateTimeInterface ) {
82
            return $date;
83
        }
84
        if( \is_int($date) ) {
85
            return (new \DateTimeImmutable())->setTimestamp($date);
86
        }
87
        if( \is_string($date) ) {
88
            return new \DateTimeImmutable($date);
89
        }
90
91
        return null;
92
    }
93
94
    /**
95
     * Sets the class names used in the calendar
96
     *
97
     * ```php
98
     * [
99
     *    'calendar'     => 'SimpleCalendar',
100
     *    'leading_day'  => 'SCprefix',
101
     *    'trailing_day' => 'SCsuffix',
102
     *    'today'        => 'today',
103
     *    'event'        => 'event',
104
     *    'events'       => 'events',
105
     * ]
106
     * ```
107
     *
108
     * @param array $classes Map of element to class names used by the calendar.
109
     */
110
    public function setCalendarClasses( array $classes = [] ) {
111
        foreach( $classes as $key => $value ) {
112
            if( !isset($this->classes[$key]) ) {
113
                throw new \InvalidArgumentException("class '$key' not supported");
114
            }
115
116
            $this->classes[$key] = $value;
117
        }
118
    }
119
120
    /**
121
     * Sets "today"'s date. Defaults to today.
122
     *
123
     * @param \DateTimeInterface|false|string|null $today `null` will default to today, `false` will disable the
124
     *     rendering of Today.
125
     * @throws \Exception
126
     */
127
    public function setToday( $today = null ) {
128
        if( $today === false ) {
129
            $this->today = null;
130
        } elseif( $today === null ) {
131
            $this->today = new \DateTimeImmutable();
132
        } else {
133
            $this->today = $this->parseDate($today);
134
        }
135
    }
136
137
    /**
138
     * @param string[]|null $weekDayNames
139
     */
140
    public function setWeekDayNames( array $weekDayNames = null ) {
141
        if( \is_array($weekDayNames) && \count($weekDayNames) !== 7 ) {
142
            throw new \InvalidArgumentException('week array must have exactly 7 values');
143
        }
144
145
        $this->weekDayNames = $weekDayNames ? \array_values($weekDayNames) : null;
146
    }
147
148
    /**
149
     * @param bool $permSubmit
150
     */
151
    public function setPermSubmit( $permSubmit = false ) {
152
153
        $this->permSubmit = $permSubmit;
154
155
    }
156
157
158
    /**
159
     * Add a daily event to the calendar
160
     *
161
     * @param string $html The raw HTML to place on the calendar for this event
162
     * @param \DateTimeInterface|int|string $startDate Date string for when the event starts
163
     * @param null $endDate Date string for when the event ends. Defaults to start date
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $endDate is correct as it would always require null to be passed?
Loading history...
164
     * @param string $style Style for this event
165
     * @throws \Exception
166
     */
167
    public function addDailyHtml(string $html, $startDate, $endDate = null, $style = '') {
168
        static $htmlCount = 0;
169
170
        $start = $this->parseDate($startDate);
171
        if( !$start ) {
0 ignored issues
show
introduced by
$start is of type DateTimeInterface, thus it always evaluated to true.
Loading history...
172
            throw new \InvalidArgumentException('invalid start time');
173
        }
174
175
        $end = $start;
176
        if( $endDate ) {
0 ignored issues
show
introduced by
$endDate is of type null, thus it always evaluated to false.
Loading history...
177
            $end = $this->parseDate($endDate);
178
        }
179
        if( !$end ) {
0 ignored issues
show
introduced by
$end is of type DateTimeInterface, thus it always evaluated to true.
Loading history...
180
            throw new \InvalidArgumentException('invalid end time');
181
        }
182
183
        if( $end->getTimestamp() < $start->getTimestamp() ) {
184
            throw new \InvalidArgumentException('end must come after start');
185
        }
186
187
        $working = (new \DateTimeImmutable())->setTimestamp($start->getTimestamp());
188
        do {
189
            $tDate = getdate($working->getTimestamp());
190
191
            $this->dailyHtml[$tDate['year']][$tDate['mon']][$tDate['mday']][$htmlCount]['link'] = $html;
192
            $this->dailyHtml[$tDate['year']][$tDate['mon']][$tDate['mday']][$htmlCount]['style'] = $style;
193
194
            $working = $working->add(new \DateInterval('P1D'));
195
        } while( $working->getTimestamp() < $end->getTimestamp() + 1 );
196
197
        $htmlCount++;
198
    }
199
200
    /**
201
     * Clear all daily events for the calendar
202
     */
203
    public function clearDailyHtml() { $this->dailyHtml = []; }
204
205
    /**
206
     * Sets the first day of the week
207
     *
208
     * @param int|string $offset Day the week starts on. ex: "Monday" or 0-6 where 0 is Sunday
209
     */
210
    public function setStartOfWeek($offset) {
211
        if( \is_int($offset) ) {
212
            $this->offset = $offset % 7;
213
        } elseif( $this->weekDayNames !== null && ($weekOffset = \array_search($offset, $this->weekDayNames, true)) !== false ) {
214
            $this->offset = $weekOffset;
215
        } else {
216
            $weekTime = \strtotime($offset);
217
            if( $weekTime === 0 ) {
218
                throw new \InvalidArgumentException('invalid offset');
219
            }
220
221
            $this->offset = date('N', $weekTime) % 7;
222
        }
223
    }
224
225
    /**
226
     * Returns/Outputs the Calendar
227
     *
228
     * @param bool $echo Whether to echo resulting calendar
229
     * @return string HTML of the Calendar
230
     * @throws \Exception
231
     * @throws \Exception
232
     * @deprecated Use `render()` method instead.
233
     */
234
    public function show( $echo = true ) {
235
        $out = $this->render();
236
        if( $echo ) {
237
            echo $out;
238
        }
239
240
        return $out;
241
    }
242
243
    /**
244
     * Returns the generated Calendar
245
     *
246
     * @return string
247
     * @throws \Exception
248
     */
249
    public function render() {
250
        $now   = getdate($this->now->getTimestamp());
251
        $today = [ 'mday' => -1, 'mon' => -1, 'year' => -1 ];
252
        if( $this->today !== null ) {
253
            $today = getdate($this->today->getTimestamp());
254
        }
255
256
        $daysOfWeek = $this->weekdays();
257
        $this->rotate($daysOfWeek, $this->offset);
258
259
        $weekDayIndex = date('N', \mktime(0, 0, 1, $now['mon'], 1, $now['year'])) - $this->offset;
260
        $daysInMonth  = cal_days_in_month(CAL_GREGORIAN, $now['mon'], $now['year']);
261
262
        $out = <<<TAG
263
<table cellpadding="0" cellspacing="0" class="{$this->classes['calendar']}"><thead><tr>
264
TAG;
265
266
        foreach( $daysOfWeek as $key => $dayName ) {
267
            $out .= "<th class='day$key'>$dayName</th>";
268
        }
269
270
        $out .= <<<'TAG'
271
</tr></thead>
272
<tbody>
273
<tr>
274
TAG;
275
276
        $weekDayIndex = ($weekDayIndex + 7) % 7;
277
278
        if( $weekDayIndex === 7 ) {
279
            $weekDayIndex = 0;
280
        } else {
281
            $out .= \str_repeat(<<<TAG
282
<td class="{$this->classes['leading_day']}">&nbsp;</td>
283
TAG
284
                , $weekDayIndex);
285
        }
286
287
        $count = $weekDayIndex + 1;
288
        for( $i = 1; $i <= $daysInMonth; $i++ ) {
289
            $date = (new \DateTimeImmutable())->setDate($now['year'], $now['mon'], $i);
290
291
            $isToday = false;
292
            if( $this->today !== null ) {
293
                $isToday = $i == $today['mday']
294
                    && $today['mon'] == $date->format('n')
295
                    && $today['year'] == $date->format('Y');
296
            }
297
298
            $out .= '<td' . ($isToday ? ' class="' . $this->classes['today'] . '"' : '') . '>';
299
300
            // line removed by goffy
301
            //$out .= \sprintf('<time datetime="%s">%d</time>', $date->format('Y-m-d'), $i);
302
303
            $dailyHTML = null;
304
            if( isset($this->dailyHtml[$now['year']][$now['mon']][$i]) ) {
305
                $dailyHTML = $this->dailyHtml[$now['year']][$now['mon']][$i];
306
            }
307
308
            if( \is_array($dailyHTML) ) {
309
                $out .= \sprintf('<time datetime="%s"><span class="badge badge-primary">%d</span></time>', $date->format('Y-m-d'), $i);
310
                $out .= '<div class="' . $this->classes['events'] . '">';
311
                foreach( $dailyHTML as $dHtml ) {
312
                    $out .= \sprintf('<div class="%s wg-cal-item" style="' . $dHtml['style'] . '">%s</div>', $this->classes['event'], $dHtml['link']);
313
                }
314
                $out .= '</div>';
315
            } else {
316
                $out .= \sprintf('<time datetime="%s">%d</time>', $date->format('Y-m-d'), $i);
317
            }
318
            if ($this->permSubmit) {
319
                $out .= '<div class="addnew pull-right"><a href="event.php?op=new&eventDate=' . $date->getTimestamp() . '"><i class="fa fa-plus-square wg-cal-icon pull-right" title="' . \_MA_WGEVENTS_CAL_ADDITEM . '"></i></a></div>';
320
            }
321
            $out .= '</td>';
322
323
            if( $count > 6 ) {
324
                $out   .= "</tr>\n" . ($i < $daysInMonth ? '<tr>' : '');
325
                $count = 0;
326
            }
327
            $count++;
328
        }
329
330
        if( $count !== 1 ) {
331
            $out .= \str_repeat('<td class="' . $this->classes['trailing_day'] . '">&nbsp;</td>', 8 - $count) . '</tr>';
332
        }
333
334
        $out .= "\n</tbody></table>\n";
335
336
        return $out;
337
    }
338
339
    /**
340
     * @param array $data
341
     * @param int $steps
342
     */
343
    private function rotate(array &$data, int $steps) {
344
        $count = \count($data);
345
        if( $steps < 0 ) {
346
            $steps = $count + $steps;
347
        }
348
        $steps %= $count;
349
        for( $i = 0; $i < $steps; $i++ ) {
350
            $data[] = \array_shift($data);
351
        }
352
    }
353
354
    /**
355
     * @return string[]
356
     */
357
    private function weekdays() {
358
        if( $this->weekDayNames !== null ) {
359
            $wDays = $this->weekDayNames;
360
        } else {
361
            $today = (86400 * (date('N')));
362
            $wDays = [];
363
            for( $n = 0; $n < 7; $n++ ) {
364
                $wDays[] = \strftime('%a', \time() - $today + ($n * 86400));
365
            }
366
        }
367
368
        return $wDays;
369
    }
370
371
}
372