1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Charcoal\Cms; |
4
|
|
|
|
5
|
|
|
use DateTime; |
6
|
|
|
use DateTimeInterface; |
7
|
|
|
use InvalidArgumentException; |
8
|
|
|
|
9
|
|
|
// From PSR-7 |
10
|
|
|
use Psr\Http\Message\RequestInterface; |
11
|
|
|
use Psr\Http\Message\ResponseInterface; |
12
|
|
|
|
13
|
|
|
// From 'charcoal-object' |
14
|
|
|
use Charcoal\Object\Content; |
15
|
|
|
use Charcoal\Object\CategorizableInterface; |
16
|
|
|
use Charcoal\Object\CategorizableTrait; |
17
|
|
|
use Charcoal\Object\PublishableInterface; |
18
|
|
|
use Charcoal\Object\PublishableTrait; |
19
|
|
|
use Charcoal\Object\RoutableInterface; |
20
|
|
|
use Charcoal\Object\RoutableTrait; |
21
|
|
|
|
22
|
|
|
// From 'charcoal-translator' |
23
|
|
|
use Charcoal\Translator\Translation; |
24
|
|
|
|
25
|
|
|
// From 'charcoal-cms' |
26
|
|
|
use Charcoal\Cms\MetatagInterface; |
27
|
|
|
use Charcoal\Cms\NewsInterface; |
28
|
|
|
use Charcoal\Cms\SearchableInterface; |
29
|
|
|
use Charcoal\Cms\SearchableTrait; |
30
|
|
|
use Charcoal\Cms\TemplateableInterface; |
31
|
|
|
|
32
|
|
|
// Local dependencies |
33
|
|
|
use Charcoal\Cms\Support\Helpers\DateHelper; |
34
|
|
|
|
35
|
|
|
// Pimple dependencies |
36
|
|
|
use Pimple\Container; |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* News |
40
|
|
|
*/ |
41
|
|
|
abstract class AbstractNews extends Content implements |
42
|
|
|
CategorizableInterface, |
43
|
|
|
MetatagInterface, |
44
|
|
|
NewsInterface, |
45
|
|
|
PublishableInterface, |
46
|
|
|
RoutableInterface, |
47
|
|
|
SearchableInterface, |
48
|
|
|
TemplateableInterface |
49
|
|
|
{ |
50
|
|
|
use CategorizableTrait; |
51
|
|
|
use PublishableTrait; |
52
|
|
|
use MetatagTrait; |
53
|
|
|
use RoutableTrait; |
54
|
|
|
use SearchableTrait; |
55
|
|
|
use TemplateableTrait; |
56
|
|
|
|
57
|
|
|
/** |
58
|
|
|
* @var Translation|string|null |
59
|
|
|
*/ |
60
|
|
|
private $title; |
61
|
|
|
|
62
|
|
|
/** |
63
|
|
|
* @var Translation|string|null |
64
|
|
|
*/ |
65
|
|
|
private $subtitle; |
66
|
|
|
|
67
|
|
|
/** |
68
|
|
|
* @var Translation|string|null |
69
|
|
|
*/ |
70
|
|
|
private $summary; |
71
|
|
|
|
72
|
|
|
/** |
73
|
|
|
* @var Translation|string|null |
74
|
|
|
*/ |
75
|
|
|
private $content; |
76
|
|
|
|
77
|
|
|
/** |
78
|
|
|
* @var Translation|string|null |
79
|
|
|
*/ |
80
|
|
|
private $image; |
81
|
|
|
|
82
|
|
|
/** |
83
|
|
|
* @var DateTimeInterface|null |
84
|
|
|
*/ |
85
|
|
|
private $newsDate; |
86
|
|
|
|
87
|
|
|
/** |
88
|
|
|
* @var Translation|string|null |
89
|
|
|
*/ |
90
|
|
|
private $infoUrl; |
91
|
|
|
|
92
|
|
|
/** |
93
|
|
|
* @var array |
94
|
|
|
*/ |
95
|
|
|
protected $keywords; |
96
|
|
|
|
97
|
|
|
// ========================================================================== |
98
|
|
|
// INIT |
99
|
|
|
// ========================================================================== |
100
|
|
|
|
101
|
|
|
/** |
102
|
|
|
* Section constructor. |
103
|
|
|
* @param array $data The data. |
104
|
|
|
*/ |
105
|
|
View Code Duplication |
public function __construct(array $data = null) |
|
|
|
|
106
|
|
|
{ |
107
|
|
|
parent::__construct($data); |
108
|
|
|
|
109
|
|
|
if (is_callable([ $this, 'defaultData' ])) { |
110
|
|
|
$this->setData($this->defaultData()); |
111
|
|
|
} |
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
// ========================================================================== |
115
|
|
|
// FUNCTIONS |
116
|
|
|
// ========================================================================== |
117
|
|
|
|
118
|
|
|
/** |
119
|
|
|
* In the datetime attribute of the <time> tag |
120
|
|
|
* @return string The datetime attribute formatted. |
121
|
|
|
*/ |
122
|
|
|
public function dateTimeDate() |
123
|
|
|
{ |
124
|
|
|
$newsDate = $this->newsDate(); |
125
|
|
|
|
126
|
|
|
return $newsDate->format('Y-m-d H:i:s'); |
127
|
|
|
} |
128
|
|
|
|
129
|
|
|
/** |
130
|
|
|
* Some dates cannot be null |
131
|
|
|
* @return void |
132
|
|
|
*/ |
133
|
|
|
public function verifyDates() |
134
|
|
|
{ |
135
|
|
|
if (!$this->newsDate()) { |
136
|
|
|
$this->setNewsDate('now'); |
137
|
|
|
} |
138
|
|
|
|
139
|
|
|
if (!$this->publishDate()) { |
140
|
|
|
$this->setPublishDate('now'); |
141
|
|
|
} |
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
/** |
145
|
|
|
* @return string The date filtered for admin dual select input and others. |
146
|
|
|
*/ |
147
|
|
|
public function adminDateFilter() |
148
|
|
|
{ |
149
|
|
|
return $this->newsDate()->format('Y-m-d'); |
150
|
|
|
} |
151
|
|
|
|
152
|
|
|
// ========================================================================== |
153
|
|
|
// SETTERS |
154
|
|
|
// ========================================================================== |
155
|
|
|
|
156
|
|
|
/** |
157
|
|
|
* @param mixed $title The news title (localized). |
158
|
|
|
* @return self |
159
|
|
|
*/ |
160
|
|
|
public function setTitle($title) |
161
|
|
|
{ |
162
|
|
|
$this->title = $this->translator()->translation($title); |
163
|
|
|
|
164
|
|
|
return $this; |
165
|
|
|
} |
166
|
|
|
|
167
|
|
|
/** |
168
|
|
|
* @param mixed $subtitle The news subtitle (localized). |
169
|
|
|
* @return self |
170
|
|
|
*/ |
171
|
|
|
public function setSubtitle($subtitle) |
172
|
|
|
{ |
173
|
|
|
$this->subtitle = $this->translator()->translation($subtitle); |
174
|
|
|
|
175
|
|
|
return $this; |
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
/** |
179
|
|
|
* @param mixed $summary The news summary (localized). |
180
|
|
|
* @return self |
181
|
|
|
*/ |
182
|
|
|
public function setSummary($summary) |
183
|
|
|
{ |
184
|
|
|
$this->summary = $this->translator()->translation($summary); |
185
|
|
|
|
186
|
|
|
return $this; |
187
|
|
|
} |
188
|
|
|
|
189
|
|
|
/** |
190
|
|
|
* @param mixed $content The news content (localized). |
191
|
|
|
* @return self |
192
|
|
|
*/ |
193
|
|
|
public function setContent($content) |
194
|
|
|
{ |
195
|
|
|
$this->content = $this->translator()->translation($content); |
196
|
|
|
|
197
|
|
|
return $this; |
198
|
|
|
} |
199
|
|
|
|
200
|
|
|
/** |
201
|
|
|
* @param mixed $image The section main image (localized). |
202
|
|
|
* @return self |
203
|
|
|
*/ |
204
|
|
|
public function setImage($image) |
205
|
|
|
{ |
206
|
|
|
$this->image = $this->translator()->translation($image); |
207
|
|
|
|
208
|
|
|
return $this; |
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
/** |
212
|
|
|
* @param mixed $url The info URL (news source or where to find more information; localized). |
213
|
|
|
* @return self |
214
|
|
|
*/ |
215
|
|
|
public function setInfoUrl($url) |
216
|
|
|
{ |
217
|
|
|
$this->infoUrl = $this->translator()->translation($url); |
218
|
|
|
|
219
|
|
|
return $this; |
220
|
|
|
} |
221
|
|
|
|
222
|
|
|
/** |
223
|
|
|
* @param string|DateTimeInterface $newsDate The news date. |
224
|
|
|
* @throws InvalidArgumentException If the timestamp is invalid. |
225
|
|
|
* @return self |
226
|
|
|
*/ |
227
|
|
View Code Duplication |
public function setNewsDate($newsDate) |
|
|
|
|
228
|
|
|
{ |
229
|
|
|
if ($newsDate === null || $newsDate === '') { |
230
|
|
|
$this->newsDate = null; |
231
|
|
|
|
232
|
|
|
return $this; |
233
|
|
|
} |
234
|
|
|
if (is_string($newsDate)) { |
235
|
|
|
$newsDate = new DateTime($newsDate); |
236
|
|
|
} |
237
|
|
|
if (!($newsDate instanceof DateTimeInterface)) { |
238
|
|
|
throw new InvalidArgumentException( |
239
|
|
|
'Invalid "Revision Date" value. Must be a date/time string or a DateTimeInterface object.' |
240
|
|
|
); |
241
|
|
|
} |
242
|
|
|
$this->newsDate = $newsDate; |
243
|
|
|
|
244
|
|
|
return $this; |
245
|
|
|
} |
246
|
|
|
|
247
|
|
|
/** |
248
|
|
|
* Set the object's keywords. |
249
|
|
|
* |
250
|
|
|
* @param string|string[] $keywords One or more entries. |
251
|
|
|
* @return self |
252
|
|
|
*/ |
253
|
|
|
public function setKeywords($keywords) |
254
|
|
|
{ |
255
|
|
|
$this->keywords = $this->parseAsMultiple($keywords); |
256
|
|
|
|
257
|
|
|
return $this; |
258
|
|
|
} |
259
|
|
|
|
260
|
|
|
// ========================================================================== |
261
|
|
|
// GETTERS |
262
|
|
|
// ========================================================================== |
263
|
|
|
|
264
|
|
|
/** |
265
|
|
|
* @return Translation|string|null |
266
|
|
|
*/ |
267
|
|
|
public function title() |
268
|
|
|
{ |
269
|
|
|
return $this->title; |
270
|
|
|
} |
271
|
|
|
|
272
|
|
|
/** |
273
|
|
|
* @return Translation|string|null |
274
|
|
|
*/ |
275
|
|
|
public function subtitle() |
276
|
|
|
{ |
277
|
|
|
return $this->subtitle; |
278
|
|
|
} |
279
|
|
|
|
280
|
|
|
/** |
281
|
|
|
* @return Translation|string|null |
282
|
|
|
*/ |
283
|
|
|
public function summary() |
284
|
|
|
{ |
285
|
|
|
return $this->summary; |
286
|
|
|
} |
287
|
|
|
|
288
|
|
|
/** |
289
|
|
|
* @return Translation|string|null |
290
|
|
|
*/ |
291
|
|
|
public function infoUrl() |
292
|
|
|
{ |
293
|
|
|
return $this->infoUrl; |
294
|
|
|
} |
295
|
|
|
|
296
|
|
|
/** |
297
|
|
|
* @return DateTimeInterface|null |
298
|
|
|
*/ |
299
|
|
|
public function newsDate() |
300
|
|
|
{ |
301
|
|
|
return $this->newsDate; |
302
|
|
|
} |
303
|
|
|
|
304
|
|
|
/** |
305
|
|
|
* @return Translation|string|null |
306
|
|
|
*/ |
307
|
|
|
public function content() |
308
|
|
|
{ |
309
|
|
|
return $this->content; |
310
|
|
|
} |
311
|
|
|
|
312
|
|
|
/** |
313
|
|
|
* @return Translation|string|null |
314
|
|
|
*/ |
315
|
|
|
public function image() |
316
|
|
|
{ |
317
|
|
|
return $this->image; |
318
|
|
|
} |
319
|
|
|
|
320
|
|
|
// ========================================================================== |
321
|
|
|
// META TAGS |
322
|
|
|
// ========================================================================== |
323
|
|
|
|
324
|
|
|
/** |
325
|
|
|
* MetatagTrait > canonical_url |
326
|
|
|
* |
327
|
|
|
* @return string |
328
|
|
|
* @todo |
329
|
|
|
*/ |
330
|
|
|
public function canonicalUrl() |
331
|
|
|
{ |
332
|
|
|
return ''; |
333
|
|
|
} |
334
|
|
|
|
335
|
|
|
/** |
336
|
|
|
* @return Translation|string|null |
337
|
|
|
*/ |
338
|
|
|
public function defaultMetaTitle() |
339
|
|
|
{ |
340
|
|
|
return $this->title(); |
341
|
|
|
} |
342
|
|
|
|
343
|
|
|
/** |
344
|
|
|
* @return Translation|string|null |
345
|
|
|
*/ |
346
|
|
View Code Duplication |
public function defaultMetaDescription() |
|
|
|
|
347
|
|
|
{ |
348
|
|
|
$content = $this->translator()->translation($this->content()); |
349
|
|
|
if ($content instanceof Translation) { |
350
|
|
|
$desc = []; |
351
|
|
|
foreach ($content->data() as $lang => $text) { |
352
|
|
|
$desc[$lang] = strip_tags($text); |
353
|
|
|
} |
354
|
|
|
|
355
|
|
|
return $this->translator()->translation($desc); |
356
|
|
|
} |
357
|
|
|
|
358
|
|
|
return null; |
359
|
|
|
} |
360
|
|
|
|
361
|
|
|
/** |
362
|
|
|
* @return Translation|string|null |
363
|
|
|
*/ |
364
|
|
|
public function defaultMetaImage() |
365
|
|
|
{ |
366
|
|
|
return $this->image(); |
367
|
|
|
} |
368
|
|
|
|
369
|
|
|
/** |
370
|
|
|
* Retrieve the object's keywords. |
371
|
|
|
* |
372
|
|
|
* @return string[] |
373
|
|
|
*/ |
374
|
|
|
public function keywords() |
375
|
|
|
{ |
376
|
|
|
return $this->keywords; |
377
|
|
|
} |
378
|
|
|
|
379
|
|
|
// ========================================================================== |
380
|
|
|
// Utils |
381
|
|
|
// ========================================================================== |
382
|
|
|
|
383
|
|
|
/** |
384
|
|
|
* Parse the property value as a "multiple" value type. |
385
|
|
|
* |
386
|
|
|
* @param mixed $value The value being converted to an array. |
387
|
|
|
* @param string|PropertyInterface $separator The boundary string. |
388
|
|
|
* @return array |
389
|
|
|
*/ |
390
|
|
View Code Duplication |
public function parseAsMultiple($value, $separator = ',') |
|
|
|
|
391
|
|
|
{ |
392
|
|
|
if (!isset($value) || |
393
|
|
|
(is_string($value) && !strlen(trim($value))) || |
394
|
|
|
(is_array($value) && !count(array_filter($value, 'strlen'))) |
395
|
|
|
) { |
396
|
|
|
return []; |
397
|
|
|
} |
398
|
|
|
|
399
|
|
|
/** |
400
|
|
|
* This property is marked as "multiple". |
401
|
|
|
* Manually handling the resolution to array |
402
|
|
|
* until the property itself manages this. |
403
|
|
|
*/ |
404
|
|
|
if (is_string($value)) { |
405
|
|
|
return explode($separator, $value); |
406
|
|
|
} |
407
|
|
|
|
408
|
|
|
/** |
409
|
|
|
* If the parameter isn't an array yet, |
410
|
|
|
* means we might be dealing with an integer, |
411
|
|
|
* an empty string, or an object. |
412
|
|
|
*/ |
413
|
|
|
if (!is_array($value)) { |
414
|
|
|
return [ $value ]; |
415
|
|
|
} |
416
|
|
|
|
417
|
|
|
return $value; |
418
|
|
|
} |
419
|
|
|
|
420
|
|
|
// ========================================================================== |
421
|
|
|
// EVENTS |
422
|
|
|
// ========================================================================== |
423
|
|
|
|
424
|
|
|
/** |
425
|
|
|
* {@inheritdoc} |
426
|
|
|
* |
427
|
|
|
* @return boolean |
428
|
|
|
*/ |
429
|
|
|
public function preSave() |
430
|
|
|
{ |
431
|
|
|
$this->verifyDates(); |
432
|
|
|
$this->setSlug($this->generateSlug()); |
433
|
|
|
$this->generateDefaultMetaTags(); |
434
|
|
|
|
435
|
|
|
return parent::preSave(); |
436
|
|
|
} |
437
|
|
|
|
438
|
|
|
/** |
439
|
|
|
* {@inheritdoc} |
440
|
|
|
* |
441
|
|
|
* @param array $properties Optional properties to update. |
442
|
|
|
* @return boolean |
443
|
|
|
*/ |
444
|
|
|
public function preUpdate(array $properties = null) |
445
|
|
|
{ |
446
|
|
|
$this->verifyDates(); |
447
|
|
|
$this->setSlug($this->generateSlug()); |
448
|
|
|
$this->generateDefaultMetaTags(); |
449
|
|
|
|
450
|
|
|
return parent::preUpdate($properties); |
451
|
|
|
} |
452
|
|
|
|
453
|
|
|
/** |
454
|
|
|
* @return boolean Parent postSave(). |
455
|
|
|
*/ |
456
|
|
|
public function postSave() |
457
|
|
|
{ |
458
|
|
|
// RoutableTrait |
459
|
|
|
$this->generateObjectRoute($this->slug()); |
460
|
|
|
|
461
|
|
|
return parent::postSave(); |
462
|
|
|
} |
463
|
|
|
|
464
|
|
|
/** |
465
|
|
|
* @param array|null $properties Properties. |
466
|
|
|
* @return boolean |
467
|
|
|
*/ |
468
|
|
|
public function postUpdate(array $properties = null) |
469
|
|
|
{ |
470
|
|
|
// RoutableTrait |
471
|
|
|
$this->generateObjectRoute($this->slug()); |
472
|
|
|
|
473
|
|
|
return parent::postUpdate($properties); |
|
|
|
|
474
|
|
|
} |
475
|
|
|
|
476
|
|
|
/** |
477
|
|
|
* GenericRoute checks if the route is active. |
478
|
|
|
* Default in RoutableTrait. |
479
|
|
|
* |
480
|
|
|
* @return boolean |
481
|
|
|
*/ |
482
|
|
|
public function isActiveRoute() |
483
|
|
|
{ |
484
|
|
|
return ( |
485
|
|
|
$this->active() && |
486
|
|
|
$this->isPublished() |
487
|
|
|
); |
488
|
|
|
} |
489
|
|
|
} |
490
|
|
|
|
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.