1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace app\models\types; |
4
|
|
|
|
5
|
|
|
use Yii; |
6
|
|
|
use ICal\ICal; |
7
|
|
|
use app\models\ContentType; |
8
|
|
|
|
9
|
|
|
/** |
10
|
|
|
* This is the model class for Agenda content type. |
11
|
|
|
*/ |
12
|
|
|
class Agenda extends ContentType |
13
|
|
|
{ |
14
|
|
|
const BASE_CACHE_TIME = 7200; // 2 hours |
15
|
|
|
const DAYS = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']; |
16
|
|
|
const HOUR_MIN = 8; |
17
|
|
|
const HOUR_MAX = 18; |
18
|
|
|
|
19
|
|
|
public $html = '<div class="agenda">%data%</div>'; |
20
|
|
|
public $css = <<<'EO1' |
21
|
|
|
%field% .agenda { width: 100%; height: 100%; text-align: center; background-color: white; color: black; font-size: 1.1em; } |
22
|
|
|
%field% .agenda-header { font-weight: bold; } |
23
|
|
|
%field% .agenda-contents { width: 100%; height: calc(100% - 1.3em); display: table; table-layout: fixed; border-collapse: collapse; } |
24
|
|
|
%field% .agenda-time { display: table-cell; width: 2.2em; border: solid 1px black; } |
25
|
|
|
%field% .agenda-time-header { } |
26
|
|
|
%field% .agenda-time-contents { width: 100%; height: calc(100% - 1.3em); display: table; position: relative; } |
27
|
|
|
%field% .agenda-time-h { position: absolute; border-top: solid 1px black; width: 100%; } |
28
|
|
|
%field% .agenda-time-m { position: absolute; border-top: dotted 1px black; right: 0; } |
29
|
|
|
%field% .agenda-time-trace { position: absolute; border-top: dotted 1px gray; width: 100%; } |
30
|
|
|
%field% .agenda-day { display: table-cell; border: solid 1px black; } |
31
|
|
|
%field% .agenda-day-header { border-bottom: solid 1px black; } |
32
|
|
|
%field% .agenda-day-contents { width: 100%; height: calc(100% - 1.3em); display: table; position: relative; } |
33
|
|
|
%field% .agenda-event { position: absolute; overflow: hidden; border-bottom: solid 1px black; z-index: 10; } |
34
|
|
|
%field% .agenda-event-desc { font-weight: bold; font-size: 1.1em; } |
35
|
|
|
%field% .agenda-event-location { font-size: 1.1em; white-space: nowrap; } |
36
|
|
|
%field% .agenda-event-name { word-break: break-all; display: block; } |
37
|
|
|
EO1; |
38
|
|
|
public $input = 'url'; |
39
|
|
|
public $output = 'raw'; |
40
|
|
|
public $usable = true; |
41
|
|
|
public $exemple = '@web/images/agenda.preview.jpg'; |
42
|
|
|
public $canPreview = true; |
43
|
|
|
|
44
|
|
|
private static $translit; |
45
|
|
|
private $color = []; |
46
|
|
|
private $overlapScanOffset = 0.1; |
47
|
|
|
private $tz; |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* {@inheritdoc} |
51
|
|
|
*/ |
52
|
|
|
public function __construct($config = []) |
53
|
|
|
{ |
54
|
|
|
parent::__construct($config); |
55
|
|
|
$this->name = Yii::t('app', 'Agenda'); |
56
|
|
|
$this->description = Yii::t('app', 'Display an agenda from an ICal feed.'); |
57
|
|
|
} |
58
|
|
|
|
59
|
|
|
/** |
60
|
|
|
* {@inheritdoc} |
61
|
|
|
*/ |
62
|
|
|
public function processData($data) |
63
|
|
|
{ |
64
|
|
|
$content = self::fromCache($data); |
65
|
|
|
if ($content === false) { |
66
|
|
|
if (!$data) { |
67
|
|
|
return null; |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
$content = self::downloadContent($data); |
71
|
|
|
self::toCache($data, $content); |
72
|
|
|
} |
73
|
|
|
|
74
|
|
|
if (!$content) { |
75
|
|
|
return null; |
76
|
|
|
} |
77
|
|
|
|
78
|
|
|
$agenda = $this->genAgenda($content); |
79
|
|
|
|
80
|
|
|
return $agenda; |
81
|
|
|
} |
82
|
|
|
|
83
|
|
|
/** |
84
|
|
|
* Extract data from ical event. |
85
|
|
|
* |
86
|
|
|
* @param array $e ical event |
87
|
|
|
* |
88
|
|
|
* @return array parsed event |
89
|
|
|
*/ |
90
|
|
|
private function parseEvent($e) |
91
|
|
|
{ |
92
|
|
|
// Convert timezones |
93
|
|
|
$start = (new \DateTime($e->dtstart))->setTimeZone($this->tz); |
94
|
|
|
$end = (new \DateTime($e->dtend))->setTimeZone($this->tz); |
95
|
|
|
|
96
|
|
|
// Event info |
97
|
|
|
$event = [ |
98
|
|
|
'dow' => $start->format('w') - 1, |
99
|
|
|
'dayMonth' => $start->format('d/m'), |
100
|
|
|
'start' => $start->format('G') + ($start->format('i') / 60.0), |
101
|
|
|
'startStr' => $start->format('G:i'), |
102
|
|
|
'end' => $end->format('G') + ($end->format('i') / 60.0), |
103
|
|
|
'endStr' => $end->format('G:i'), |
104
|
|
|
'name' => self::filter($e->summary, 'name'), |
105
|
|
|
'locations' => self::arrayFilter(explode(',', $e->location), 'location'), |
106
|
|
|
'desc' => self::arrayFilter(explode(PHP_EOL, $e->description), 'description'), |
107
|
|
|
]; |
108
|
|
|
$event['duration'] = $event['end'] - $event['start']; |
109
|
|
|
|
110
|
|
|
return $event; |
111
|
|
|
} |
112
|
|
|
|
113
|
|
|
/** |
114
|
|
|
* Read .ical data and parse to day-based array. |
115
|
|
|
* |
116
|
|
|
* @param string $data ical raw data |
117
|
|
|
* |
118
|
|
|
* @return array agenda |
119
|
|
|
*/ |
120
|
|
|
public function parseIcal($data) |
121
|
|
|
{ |
122
|
|
|
// Init ICal parser |
123
|
|
|
$ical = new ICal(); |
124
|
|
|
$ical->initString($data); |
125
|
|
|
|
126
|
|
|
// Retrieve event for this week only |
127
|
|
|
$events = $ical->eventsFromRange(self::DAYS[0].' this week', self::DAYS[count(self::DAYS) - 1].' this week 23:59'); |
128
|
|
|
|
129
|
|
|
if (!is_array($events) || !count($events)) { |
130
|
|
|
return null; |
131
|
|
|
} |
132
|
|
|
|
133
|
|
|
// Use own timezone to display |
134
|
|
|
$this->tz = new \DateTimeZone(ini_get('date.timezone')); |
135
|
|
|
// Always transliterate text contents |
136
|
|
|
self::$translit = \Transliterator::create('Latin-ASCII'); |
137
|
|
|
if (!self::$translit) { |
138
|
|
|
return null; |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
// Base agenda format info |
142
|
|
|
$info = [ |
143
|
|
|
'minHour' => self::HOUR_MIN, |
144
|
|
|
'maxHour' => self::HOUR_MAX, |
145
|
|
|
'days' => [], |
146
|
|
|
]; |
147
|
|
|
|
148
|
|
|
$parsedEvents = []; |
149
|
|
|
|
150
|
|
|
foreach ($events as $event) { |
151
|
|
|
$e = $this->parseEvent($event); |
152
|
|
|
|
153
|
|
|
// Adjust agenda format based on events |
154
|
|
|
if ($e['start'] < $info['minHour']) { |
155
|
|
|
$info['minHour'] = $e['start']; |
156
|
|
|
} |
157
|
|
|
if ($e['end'] > $info['maxHour']) { |
158
|
|
|
$info['maxHour'] = $e['end']; |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
// Only add days with events |
162
|
|
|
if (!array_key_exists($e['dow'], $parsedEvents)) { |
163
|
|
|
$parsedEvents[$e['dow']] = []; |
164
|
|
|
$info['days'][$e['dow']] = $e['dayMonth']; |
165
|
|
|
} |
166
|
|
|
|
167
|
|
|
$parsedEvents[$e['dow']][] = $e; |
168
|
|
|
} |
169
|
|
|
|
170
|
|
|
return ['info' => $info, 'events' => $parsedEvents]; |
171
|
|
|
} |
172
|
|
|
|
173
|
|
|
/** |
174
|
|
|
* Loop through day events and tag with overlaps. |
175
|
|
|
* |
176
|
|
|
* @param array $events |
177
|
|
|
* @param int $from start hour |
178
|
|
|
* @param int $to end hour |
179
|
|
|
* |
180
|
|
|
* @return array tagged events |
181
|
|
|
*/ |
182
|
|
|
private function tagOverlaps($events, $from, $to) |
183
|
|
|
{ |
184
|
|
|
// Scan each 0.1h for overlapping events |
185
|
|
|
for ($i = $from; $i <= $to; $i += $this->overlapScanOffset) { |
186
|
|
|
// $overlap is every overlapping event |
187
|
|
|
$overlap = $this->scanOverlap($events, $i); |
188
|
|
|
|
189
|
|
|
// $overlaps is maximum concurrent overlappings |
190
|
|
|
// Used to fix block width |
191
|
|
|
$overlaps = count($overlap); |
192
|
|
|
|
193
|
|
|
foreach ($events as $k => $e) { |
194
|
|
|
if ($e['start'] < $i && $i < $e['end']) { |
195
|
|
|
if (!array_key_exists('overlaps', $e)) { |
196
|
|
|
$e['overlaps'] = $overlaps; |
197
|
|
|
$e['overlap'] = $overlap; |
198
|
|
|
} else { |
199
|
|
|
if ($overlaps >= $e['overlaps']) { |
200
|
|
|
$e['overlaps'] = $overlaps; |
201
|
|
|
} |
202
|
|
|
// Merge overlap to always get full range of overlapping events |
203
|
|
|
// Used to calculate block position |
204
|
|
|
$e['overlap'] = array_unique(array_merge($e['overlap'], $overlap)); |
205
|
|
|
} |
206
|
|
|
|
207
|
|
|
$events[$k] = $e; |
208
|
|
|
} |
209
|
|
|
} |
210
|
|
|
} |
211
|
|
|
|
212
|
|
|
// Second pass, fixing non-contiguous overlaps |
213
|
|
|
for ($i = $from; $i <= $to; $i += $this->overlapScanOffset) { |
214
|
|
|
// Find maximum overlap for a given line |
215
|
|
|
$maxOverlaps = 0; |
216
|
|
|
foreach ($events as $k => $e) { |
217
|
|
|
if ($e['start'] < $i && $i < $e['end']) { |
218
|
|
|
if ($e['overlaps'] > $maxOverlaps) { |
219
|
|
|
$maxOverlaps = $e['overlaps']; |
220
|
|
|
} |
221
|
|
|
} |
222
|
|
|
} |
223
|
|
|
|
224
|
|
|
// Apply it to all events on this line |
225
|
|
|
foreach ($events as $k => $e) { |
226
|
|
|
if ($e['start'] < $i && $i < $e['end']) { |
227
|
|
View Code Duplication |
if ($e['overlaps'] < $maxOverlaps) { |
|
|
|
|
228
|
|
|
$e['overlaps'] = $maxOverlaps; |
229
|
|
|
$events[$k] = $e; |
230
|
|
|
} |
231
|
|
|
} |
232
|
|
|
} |
233
|
|
|
} |
234
|
|
|
|
235
|
|
|
return $events; |
236
|
|
|
} |
237
|
|
|
|
238
|
|
|
/** |
239
|
|
|
* Scan for overlap at precise time. |
240
|
|
|
* |
241
|
|
|
* @param array $events |
242
|
|
|
* @param int $at scan hour |
243
|
|
|
* |
244
|
|
|
* @return array overlap |
245
|
|
|
*/ |
246
|
|
|
private function scanOverlap($events, $at) |
247
|
|
|
{ |
248
|
|
|
$overlap = []; |
249
|
|
|
foreach ($events as $k => $e) { |
250
|
|
|
if ($e['start'] < $at && $at < $e['end']) { |
251
|
|
|
$overlap[] = $k; |
252
|
|
|
} |
253
|
|
|
} |
254
|
|
|
|
255
|
|
|
return $overlap; |
256
|
|
|
} |
257
|
|
|
|
258
|
|
|
/** |
259
|
|
|
* Loop through day events and scan for open position. |
260
|
|
|
* |
261
|
|
|
* @param array $events |
262
|
|
|
* |
263
|
|
|
* @return array positionned blocks |
264
|
|
|
*/ |
265
|
|
|
private function positionBlocks($events) |
266
|
|
|
{ |
267
|
|
|
foreach ($events as $k => $e) { |
268
|
|
View Code Duplication |
if ($e['overlaps'] < 2) { |
|
|
|
|
269
|
|
|
// No overlap, easy mode |
270
|
|
|
$e['position'] = 0; |
271
|
|
|
$events[$k] = $e; |
272
|
|
|
continue; |
273
|
|
|
} |
274
|
|
|
|
275
|
|
|
if (array_key_exists('position', $e)) { |
276
|
|
|
// Position already set, don't touch |
277
|
|
|
continue; |
278
|
|
|
} |
279
|
|
|
|
280
|
|
|
// Find available spots for this event |
281
|
|
|
$spots = range(0, $e['overlaps'] - 1); |
282
|
|
|
$overlapCount = count($e['overlap']); |
283
|
|
|
for ($i = 0; $i < $overlapCount; ++$i) { |
284
|
|
|
$overlaped = $events[$e['overlap'][$i]]; |
285
|
|
|
if (array_key_exists('position', $overlaped)) { |
286
|
|
|
unset($spots[$overlaped['position']]); |
287
|
|
|
} |
288
|
|
|
} |
289
|
|
|
|
290
|
|
|
// Take first one |
291
|
|
|
$e['position'] = array_shift($spots); |
292
|
|
|
|
293
|
|
|
$events[$k] = $e; |
294
|
|
|
} |
295
|
|
|
|
296
|
|
|
return $events; |
297
|
|
|
} |
298
|
|
|
|
299
|
|
|
/** |
300
|
|
|
* Use agenda events data to build blocks for rendering. |
301
|
|
|
* |
302
|
|
|
* @param array $agenda |
303
|
|
|
* |
304
|
|
|
* @return array blocks |
305
|
|
|
*/ |
306
|
|
|
public function blockize($agenda) |
307
|
|
|
{ |
308
|
|
|
$blocks = []; |
309
|
|
|
|
310
|
|
|
foreach ($agenda['events'] as $day => $events) { |
311
|
|
|
// Sort by desc first line |
312
|
|
|
usort($events, function ($a, $b) { |
313
|
|
|
return strcmp($a['desc'][0], $b['desc'][0]); |
314
|
|
|
}); |
315
|
|
|
|
316
|
|
|
$events = $this->tagOverlaps($events, $agenda['info']['minHour'], $agenda['info']['maxHour']); |
317
|
|
|
|
318
|
|
|
$blocks[$day] = $this->positionBlocks($events); |
319
|
|
|
} |
320
|
|
|
|
321
|
|
|
return $blocks; |
322
|
|
|
} |
323
|
|
|
|
324
|
|
|
/** |
325
|
|
|
* Render agenda left column with hours. |
326
|
|
|
* |
327
|
|
|
* @param array $agenda |
328
|
|
|
* |
329
|
|
|
* @return string HTML column |
330
|
|
|
*/ |
331
|
|
|
private function renderHoursColumn($agenda) |
332
|
|
|
{ |
333
|
|
|
$hourColumnIntervals = 0.25; |
334
|
|
|
$min = $agenda['info']['minHour']; |
335
|
|
|
$max = $agenda['info']['maxHour']; |
336
|
|
|
$len = $max - $min; |
337
|
|
|
|
338
|
|
|
$h = '<div class="agenda-time"><div class="agenda-time-header"> </div><div class="agenda-time-contents">'; |
339
|
|
|
for ($i = floor($min); $i < ceil($max); $i += $hourColumnIntervals) { |
340
|
|
|
if (fmod($i, 1) == 0) { |
341
|
|
|
$h .= '<div class="agenda-time-h" style="top: '.((($i - $min) / $len) * 100).'%;">'.$i.'h</div>'; |
342
|
|
|
} else { |
343
|
|
|
$width = fmod($i, 0.5) == 0 ? 40 : 20; |
344
|
|
|
$h .= '<div class="agenda-time-m" style="top: '.((($i - $min) / $len) * 100).'%; width: '.$width.'%;"></div>'; |
345
|
|
|
} |
346
|
|
|
} |
347
|
|
|
$h .= '</div></div>'; |
348
|
|
|
|
349
|
|
|
return $h; |
350
|
|
|
} |
351
|
|
|
|
352
|
|
|
/** |
353
|
|
|
* Render agenda events blocks columns. |
354
|
|
|
* |
355
|
|
|
* @param array $agenda |
356
|
|
|
* |
357
|
|
|
* @return string HTML blocks columns |
358
|
|
|
*/ |
359
|
|
|
private function renderEvents($agenda) |
360
|
|
|
{ |
361
|
|
|
$hourIntervals = 1; |
362
|
|
|
$min = $agenda['info']['minHour']; |
363
|
|
|
$max = $agenda['info']['maxHour']; |
364
|
|
|
$len = $max - $min; |
365
|
|
|
|
366
|
|
|
$h = ''; |
367
|
|
|
foreach ($agenda['events'] as $day => $events) { |
368
|
|
|
// Draw day header |
369
|
|
|
$h .= '<div class="agenda-day" id="day-'.$day.'">'. |
370
|
|
|
'<div class="agenda-day-header">'.\Yii::t('app', self::DAYS[$day]).' '.$agenda['info']['days'][$day].'</div>'. |
371
|
|
|
'<div class="agenda-day-contents">'; |
372
|
|
|
|
373
|
|
|
// Draw events |
374
|
|
|
foreach ($events as $e) { |
375
|
|
|
$style = [ |
376
|
|
|
'top' => ((($e['start'] - $min) / $len) * 100).'%', |
377
|
|
|
'bottom' => ((($max - $e['end']) / $len) * 100).'%', |
378
|
|
|
'left' => ($e['position'] / $e['overlaps'] * 100).'%', |
379
|
|
|
'right' => ((1 - ($e['position'] + 1) / $e['overlaps']) * 100).'%', |
380
|
|
|
'background-color' => $this->getColor($e['desc'][0]), |
381
|
|
|
]; |
382
|
|
|
$styleStr = implode('; ', array_map(function ($k, $v) { |
383
|
|
|
return $k.':'.$v; |
384
|
|
|
}, array_keys($style), $style)); |
385
|
|
|
|
386
|
|
|
$content = []; |
387
|
|
|
$content[] = '<div class="agenda-event-header">'; |
388
|
|
|
if (count($e['desc']) && $e['desc'][0]) { |
389
|
|
|
$content[] = '<span class="agenda-event-desc">'.$e['desc'][0].'</span>'; |
390
|
|
|
} |
391
|
|
|
foreach ($e['locations'] as $l) { |
392
|
|
|
$content[] = ' <span class="agenda-event-location">'.$l.'</span>'; |
393
|
|
|
} |
394
|
|
|
$content[] = '</div>'; |
395
|
|
|
if ($e['name']) { |
396
|
|
|
$content[] = '<div class="agenda-event-name">'.$e['name'].'</div>'; |
397
|
|
|
} |
398
|
|
|
$content[] = '<div class="agenda-event-time">'; |
399
|
|
|
$content[] = '<span class="agenda-event-time-start">'.$e['startStr'].'</span>'; |
400
|
|
|
$content[] = ' - '; |
401
|
|
|
$content[] = '<span class="agenda-event-time-end">'.$e['endStr'].'</span>'; |
402
|
|
|
$content[] = '</div>'; |
403
|
|
|
|
404
|
|
|
$h .= '<div class="agenda-event" style="'.$styleStr.'">'.implode('', $content).'</div>'; |
405
|
|
|
} |
406
|
|
|
|
407
|
|
|
// Draw background hour traces |
408
|
|
|
for ($i = floor($min) + 1; $i < ceil($max); $i += $hourIntervals) { |
409
|
|
|
$h .= '<div class="agenda-time-trace" style="top: '.((($i - $min) / $len) * 100).'%;"></div>'; |
410
|
|
|
} |
411
|
|
|
|
412
|
|
|
$h .= '</div></div>'; |
413
|
|
|
} |
414
|
|
|
|
415
|
|
|
return $h; |
416
|
|
|
} |
417
|
|
|
|
418
|
|
|
/** |
419
|
|
|
* Render agenda to HTML. |
420
|
|
|
* |
421
|
|
|
* @param array $agenda |
422
|
|
|
* |
423
|
|
|
* @return string HTML result |
424
|
|
|
*/ |
425
|
|
|
public function render($agenda) |
426
|
|
|
{ |
427
|
|
|
$h = '<div class="agenda-header">%name%</div><div class="agenda-contents">'; |
428
|
|
|
|
429
|
|
|
$h .= $this->renderHoursColumn($agenda); |
430
|
|
|
|
431
|
|
|
$h .= $this->renderEvents($agenda); |
432
|
|
|
|
433
|
|
|
$h .= '</div>'; |
434
|
|
|
|
435
|
|
|
return $h; |
436
|
|
|
} |
437
|
|
|
|
438
|
|
|
/** |
439
|
|
|
* Generate agenda HTML from .ical raw data. |
440
|
|
|
* |
441
|
|
|
* @param string $content ical raw data |
442
|
|
|
* |
443
|
|
|
* @return string|null HTML agenda |
444
|
|
|
*/ |
445
|
|
|
public function genAgenda($content) |
446
|
|
|
{ |
447
|
|
|
$agenda = $this->parseIcal($content); |
448
|
|
|
if (!$agenda) { |
449
|
|
|
return null; |
450
|
|
|
} |
451
|
|
|
|
452
|
|
|
$agenda['events'] = $this->blockize($agenda); |
453
|
|
|
|
454
|
|
|
return $this->render($agenda); |
455
|
|
|
} |
456
|
|
|
|
457
|
|
|
/** |
458
|
|
|
* Apply self::filter() to each array member. |
459
|
|
|
* |
460
|
|
|
* @param array $arr input |
461
|
|
|
* @param string $type array type |
462
|
|
|
* |
463
|
|
|
* @return array filtered output |
464
|
|
|
*/ |
465
|
|
|
private static function arrayFilter(array $arr, $type) |
466
|
|
|
{ |
467
|
|
|
$res = []; |
468
|
|
|
foreach ($arr as $v) { |
469
|
|
|
$res[] = self::filter($v, $type); |
470
|
|
|
} |
471
|
|
|
|
472
|
|
|
return array_values(array_filter($res)); |
473
|
|
|
} |
474
|
|
|
|
475
|
|
|
/** |
476
|
|
|
* Filter string from feed. |
477
|
|
|
* |
478
|
|
|
* @param string $str input string |
479
|
|
|
* @param string $type string type |
480
|
|
|
* |
481
|
|
|
* @return string filtered string |
482
|
|
|
*/ |
483
|
|
|
private static function filter($str, $type) |
484
|
|
|
{ |
485
|
|
|
$str = html_entity_decode($str); |
486
|
|
|
|
487
|
|
|
if (self::$translit) { |
488
|
|
|
$str = self::$translit->transliterate($str); |
489
|
|
|
} |
490
|
|
|
|
491
|
|
|
$str = preg_replace([ |
492
|
|
|
'/\s{2,}/', |
493
|
|
|
'/\s*\\\,\s*/', |
494
|
|
|
'/\s*\([^\)]*\)/', |
495
|
|
|
], [ |
496
|
|
|
' ', |
497
|
|
|
', ', |
498
|
|
|
'', |
499
|
|
|
], trim($str)); |
500
|
|
|
|
501
|
|
|
switch ($type) { |
502
|
|
|
case 'name': |
503
|
|
|
return preg_replace([ |
504
|
|
|
'/^\d+\s*-/', |
505
|
|
|
'/^\d+\s+/', |
506
|
|
|
], [ |
507
|
|
|
'', |
508
|
|
|
'', |
509
|
|
|
], $str); |
510
|
|
|
case 'location': |
511
|
|
|
return preg_replace([ |
512
|
|
|
'/(\d) (\d{3}).*/', |
513
|
|
|
], [ |
514
|
|
|
'\\1-\\2', |
515
|
|
|
], $str); |
516
|
|
|
case 'description': |
517
|
|
|
return preg_replace([ |
518
|
|
|
'/(modif).*/', |
519
|
|
|
], [ |
520
|
|
|
'', |
521
|
|
|
], $str); |
522
|
|
|
default: |
523
|
|
|
return $str; |
524
|
|
|
} |
525
|
|
|
} |
526
|
|
|
|
527
|
|
|
/** |
528
|
|
|
* Generate color based on string |
529
|
|
|
* Using MD5 to always get the same color for a given string. |
530
|
|
|
* |
531
|
|
|
* @param string $str |
532
|
|
|
* |
533
|
|
|
* @return string color hexcode |
534
|
|
|
*/ |
535
|
|
|
private function getColor($str) |
536
|
|
|
{ |
537
|
|
|
if (array_key_exists($str, $this->color)) { |
538
|
|
|
return $this->color[$str]; |
539
|
|
|
} |
540
|
|
|
|
541
|
|
|
// %140 + 95 make colors brighter |
542
|
|
|
$hash = md5($str); |
543
|
|
|
$this->color[$str] = sprintf( |
544
|
|
|
'#%X%X%X', |
545
|
|
|
hexdec(substr($hash, 0, 2)) % 140 + 95, |
546
|
|
|
hexdec(substr($hash, 2, 2)) % 140 + 95, |
547
|
|
|
hexdec(substr($hash, 4, 2)) % 140 + 95 |
548
|
|
|
); |
549
|
|
|
|
550
|
|
|
return $this->color[$str]; |
551
|
|
|
} |
552
|
|
|
} |
553
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.