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\SearchableInterface;
28: use Charcoal\Cms\TemplateableInterface;
29:
30: use Charcoal\Cms\MetatagTrait;
31: use Charcoal\Cms\SearchableTrait;
32: use Charcoal\Cms\TemplateableTrait;
33:
34: /**
35: *
36: */
37: abstract class AbstractEvent extends Content implements
38: CategorizableInterface,
39: EventInterface,
40: MetatagInterface,
41: PublishableInterface,
42: RoutableInterface,
43: SearchableInterface,
44: TemplateableInterface
45: {
46: use CategorizableTrait;
47: use PublishableTrait;
48: use MetatagTrait;
49: use RoutableTrait;
50: use SearchableTrait;
51: use TemplateableTrait;
52:
53: /**
54: * @var Translation|string|null
55: */
56: private $title;
57:
58: /**
59: * @var Translation|string|null
60: */
61: private $subtitle;
62:
63: /**
64: * @var Translation|string|null
65: */
66: private $summary;
67:
68: /**
69: * @var Translation|string|null
70: */
71: private $content;
72:
73: /**
74: * @var Translation|string|null
75: */
76: private $image;
77:
78: /**
79: * @var DateTimeInterface|null
80: */
81: private $startDate;
82:
83: /**
84: * @var DateTimeInterface|null
85: */
86: private $endDate;
87:
88: /**
89: * @var array
90: */
91: protected $keywords;
92:
93: // ==========================================================================
94: // INIT
95: // ==========================================================================
96:
97: /**
98: * Section constructor.
99: * @param array $data The data.
100: */
101: public function __construct(array $data = null)
102: {
103: parent::__construct($data);
104:
105: if (is_callable([ $this, 'defaultData' ])) {
106: $this->setData($this->defaultData());
107: }
108: }
109:
110: // ==========================================================================
111: // FUNCTIONS
112: // ==========================================================================
113:
114: /**
115: * Some dates cannot be null
116: * @return void
117: */
118: public function verifyDates()
119: {
120: if (!$this->startDate()) {
121: $this->setStartDate('now');
122: }
123:
124: if (!$this->endDate()) {
125: $this->setEndDate($this->startDate());
126: }
127:
128: if (!$this->publishDate()) {
129: $this->setPublishDate('now');
130: }
131: }
132:
133: /**
134: * @return string The date filtered for admin dual select input and others.
135: */
136: public function adminDateFilter()
137: {
138: $start = $this->startDate()->format('Y-m-d');
139: $end = $this->endDate()->format('Y-m-d');
140:
141: if ($start === $end) {
142: $date = $start;
143: } else {
144: $date = $start.' - '.$end;
145: }
146:
147: return $date;
148: }
149:
150: // ==========================================================================
151: // SETTERS and GETTERS
152: // ==========================================================================
153:
154: /**
155: * @param mixed $title The event title (localized).
156: * @return self
157: */
158: public function setTitle($title)
159: {
160: $this->title = $this->translator()->translation($title);
161:
162: return $this;
163: }
164:
165: /**
166: * @return Translation|string|null
167: */
168: public function title()
169: {
170: return $this->title;
171: }
172:
173: /**
174: * @param mixed $subtitle The event subtitle (localized).
175: * @return self
176: */
177: public function setSubtitle($subtitle)
178: {
179: $this->subtitle = $this->translator()->translation($subtitle);
180:
181: return $this;
182: }
183:
184: /**
185: * @return Translation|string|null
186: */
187: public function subtitle()
188: {
189: return $this->subtitle;
190: }
191:
192: /**
193: * @param mixed $summary The news summary (localized).
194: * @return self
195: */
196: public function setSummary($summary)
197: {
198: $this->summary = $this->translator()->translation($summary);
199:
200: return $this;
201: }
202:
203: /**
204: * @return Translation|string|null
205: */
206: public function summary()
207: {
208: return $this->summary;
209: }
210:
211: /**
212: * @param mixed $content The event content (localized).
213: * @return self
214: */
215: public function setContent($content)
216: {
217: $this->content = $this->translator()->translation($content);
218:
219: return $this;
220: }
221:
222: /**
223: * @return Translation|string|null
224: */
225: public function content()
226: {
227: return $this->content;
228: }
229:
230: /**
231: * @param mixed $image The section main image (localized).
232: * @return self
233: */
234: public function setImage($image)
235: {
236: $this->image = $this->translator()->translation($image);
237:
238: return $this;
239: }
240:
241: /**
242: * @return Translation|string|null
243: */
244: public function image()
245: {
246: return $this->image;
247: }
248:
249: /**
250: * @param string|DateTimeInterface $startDate Event starting date.
251: * @throws InvalidArgumentException If the timestamp is invalid.
252: * @return self
253: */
254: public function setStartDate($startDate)
255: {
256: if ($startDate === null || $startDate === '') {
257: $this->startDate = null;
258:
259: return $this;
260: }
261: if (is_string($startDate)) {
262: $startDate = new DateTime($startDate);
263: }
264: if (!($startDate instanceof DateTimeInterface)) {
265: throw new InvalidArgumentException(
266: 'Invalid "Start Date" value. Must be a date/time string or a DateTime object.'
267: );
268: }
269: $this->startDate = $startDate;
270:
271: return $this;
272: }
273:
274: /**
275: * @return DateTimeInterface|null
276: */
277: public function startDate()
278: {
279: return $this->startDate;
280: }
281:
282: /**
283: * @param string|DateTimeInterface $endDate Event end date.
284: * @throws InvalidArgumentException If the timestamp is invalid.
285: * @return self
286: */
287: public function setEndDate($endDate)
288: {
289: if ($endDate === null || $endDate === '') {
290: $this->endDate = null;
291:
292: return $this;
293: }
294: if (is_string($endDate)) {
295: $endDate = new DateTime($endDate);
296: }
297: if (!($endDate instanceof DateTimeInterface)) {
298: throw new InvalidArgumentException(
299: 'Invalid "End Date" value. Must be a date/time string or a DateTime object.'
300: );
301: }
302: $this->endDate = $endDate;
303:
304: return $this;
305: }
306:
307: /**
308: * @return DateTimeInterface|null
309: */
310: public function endDate()
311: {
312: return $this->endDate;
313: }
314:
315: // ==========================================================================
316: // META TAGS
317: // ==========================================================================
318:
319: /**
320: * MetatagTrait > canonical_url
321: *
322: * @todo
323: * @return string
324: */
325: public function canonicalUrl()
326: {
327: return '';
328: }
329:
330: /**
331: * @return Translation|string|null
332: */
333: public function defaultMetaTitle()
334: {
335: return $this->title();
336: }
337:
338: /**
339: * @return Translation|string|null
340: */
341: public function defaultMetaDescription()
342: {
343: $content = $this->translator()->translation($this->content());
344: if ($content instanceof Translation) {
345: $desc = [];
346: foreach ($content->data() as $lang => $text) {
347: $desc[$lang] = strip_tags($text);
348: }
349:
350: return $this->translator()->translation($desc);
351: }
352:
353: return null;
354: }
355:
356: /**
357: * @return Translation|string|null
358: */
359: public function defaultMetaImage()
360: {
361: return $this->image();
362: }
363:
364: /**
365: * Retrieve the object's keywords.
366: *
367: * @return string[]
368: */
369: public function keywords()
370: {
371: return $this->keywords;
372: }
373:
374: // ==========================================================================
375: // EVENTS
376: // ==========================================================================
377:
378: /**
379: * {@inheritdoc}
380: *
381: * @return boolean
382: */
383: public function preSave()
384: {
385: $this->verifyDates();
386: $this->setSlug($this->generateSlug());
387: $this->generateDefaultMetaTags();
388:
389: return parent::preSave();
390: }
391:
392: /**
393: * {@inheritdoc}
394: *
395: * @param array $properties Optional properties to update.
396: * @return boolean
397: */
398: public function preUpdate(array $properties = null)
399: {
400: $this->verifyDates();
401: $this->setSlug($this->generateSlug());
402: $this->generateDefaultMetaTags();
403:
404: return parent::preUpdate($properties);
405: }
406:
407: /**
408: * @return boolean Parent postSave().
409: */
410: public function postSave()
411: {
412: // RoutableTrait
413: $this->generateObjectRoute($this->slug());
414:
415: return parent::postSave();
416: }
417:
418: /**
419: * @param array|null $properties Properties.
420: * @return boolean
421: */
422: public function postUpdate(array $properties = null)
423: {
424: // RoutableTrait
425: $this->generateObjectRoute($this->slug());
426:
427: return parent::postUpdate($properties);
428: }
429:
430: /**
431: * GenericRoute checks if the route is active.
432: * Default in RoutableTrait.
433: *
434: * @return boolean
435: */
436: public function isActiveRoute()
437: {
438: return (
439: $this->active() &&
440: $this->isPublished()
441: );
442: }
443: }
444: