Event_Builder::get_rsvp_response()   A
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 18
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 10
nc 4
nop 1
dl 0
loc 18
rs 9.2
c 0
b 0
f 0
1
<?php
2
/**
3
 * Event Builder
4
 *
5
 * @package SimpleCalendar/Events
6
 */
7
namespace SimpleCalendar\Events;
8
9
use Carbon\Carbon;
10
use SimpleCalendar\Abstracts\Calendar;
11
12
if ( ! defined( 'ABSPATH' ) ) {
13
	exit;
14
}
15
16
/**
17
 * The Event Builder.
18
 *
19
 * Parses event templates from a feed post type for printing on calendar views.
20
 *
21
 * @since 3.0.0
22
 */
23
class Event_Builder {
24
25
	/**
26
	 * Event.
27
	 *
28
	 * @access public
29
	 * @var Event
30
	 */
31
	public $event = null;
32
33
	/**
34
	 * Calendar.
35
	 *
36
	 * @access public
37
	 * @var Calendar
38
	 */
39
	public $calendar = null;
40
41
	/**
42
	 * Tags.
43
	 *
44
	 * @access public
45
	 * @var array
46
	 */
47
	public $tags = array();
48
49
	/**
50
	 * Constructor.
51
	 *
52
	 * @since 3.0.0
53
	 *
54
	 * @param Event    $event
55
	 * @param Calendar $calendar
56
	 */
57
	public function __construct( Event $event, Calendar $calendar ) {
58
		$this->event    = $event;
59
		$this->calendar = $calendar;
60
		$this->tags     = $this->get_content_tags();
61
	}
62
63
	/**
64
	 * Get content tags.
65
	 *
66
	 * @since  3.0.0
67
	 *
68
	 * @return array
69
	 */
70
	public function get_content_tags() {
71
		return array_merge( array(
72
73
			/* ============ *
74
			 * Content Tags *
75
			 * ============ */
76
77
			'title',
78
			// The event title.
79
			'event-title',
80
			// @deprecated An alias for 'title' tag.
81
			'description',
82
			// The event description.
83
84
			'when',
85
			// Date and time of the event.
86
			'start-time',
87
			// Start time of the event.
88
			'start-date',
89
			// Start date of the event.
90
			'start-custom',
91
			// @deprecated Start time in a user defined format (set by tag 'format' attribute).
92
			'start-human',
93
			// Start time in a human friendly format.
94
			'end-time',
95
			// End time of the event.
96
			'end-date',
97
			// End date of the event.
98
			'end-custom',
99
			// @deprecated End date-time in a user defined format (set by tag 'format' attribute).
100
			'end-human',
101
			// End date-time in a human friendly format.
102
103
			'duration',
104
			// How long the events lasts, in a human-readable format.
105
			'length',
106
			// @deprecated An alias of 'duration' tag.
107
108
			'location',
109
			// Alias of start-location.
110
			'start-location',
111
			// Location name where the event starts.
112
			'maps-link',
113
			// @deprecated An alias for 'start-location-link' tag.
114
			'start-location-link',
115
			// Link to Google Maps querying the event start location address.
116
			'end-location',
117
			// Location name where the event ends.
118
			'end-location-link',
119
			// Link to Google Maps querying the event end location address.
120
121
			'link',
122
			// An HTML link to the event URL.
123
			'url',
124
			// A string with the raw event link URL.
125
			'add-to-gcal-link',
126
			// Link for viewers to add to their GCals.
127
128
			'calendar',
129
			// The title of the source calendar.
130
			'feed-title',
131
			// @deprecated An alias of 'calendar'.
132
133
			'id',
134
			// The event unique ID.
135
			'uid',
136
			// An alias of ID.
137
			'ical-id',
138
			// iCal ID.
139
			'event-id',
140
			// @deprecated An alias for 'id' tag.
141
			'calendar-id',
142
			// The calendar ID.
143
			'feed-id',
144
			// @deprecated An alias for 'calendar-id' tag.
145
			'cal-id',
146
			// @deprecated An alias for 'calendar-id' tag.
147
148
			/* ========= *
149
			 * Meta Tags *
150
			 * ========= */
151
152
			'attachments',
153
			// List of attachments.
154
			'attendees',
155
			// List of attendees.
156
			'organizer',
157
			// Creator info.
158
159
			/* ================ *
160
			 * Conditional Tags *
161
			 * ================ */
162
163
			'if-title',
164
			// If the event has a title.
165
			'if-description',
166
			// If the event has a description.
167
168
			'if-now',
169
			// If the event is taking place now.
170
			'if-not-now',
171
			// If the event is not taking place now (may have ended or just not started yet).
172
			'if-started',
173
			// If the event has started (and may as well as ended).
174
			'if-not-started',
175
			// If the event has NOT started yet (event could be any time in the future).
176
			'if-ended',
177
			// If the event has ended (event could be any time in the past).
178
			'if-not-ended',
179
			// If the event has NOT ended (may as well as not started yet).
180
181
			'if-today',
182
			// If the event is taking place today
183
			'if-not-today',
184
			// If the event is NOT taking place today
185
186
			'if-whole-day',
187
			// If the event lasts the whole day.
188
			'if-all-day',
189
			// @deprecated Alias for 'if-whole-day'.
190
			'if-not-whole-day',
191
			// If the event does NOT last the whole day.
192
			'if-not-all-day',
193
			// @deprecated Alias for 'if-not-whole-day'.
194
			'if-end-time',
195
			// If the event has a set end time.
196
			'if-no-end-time',
197
			// If the event has NOT a set end time.
198
199
			'if-multi-day',
200
			// If the event spans multiple days.
201
			'if-single-day',
202
			// If the event does not span multiple days.
203
204
			'if-recurring',
205
			// If the event is a recurring event.
206
			'if-not-recurring',
207
			// If the event is NOT a recurring event.
208
209
			'if-location',
210
			// @deprecated Alias for 'if-start-location'.
211
			'if-start-location',
212
			// Does the event has a start location?
213
			'if-end-location',
214
			// Does the event has an end location?
215
			'if-not-location',
216
			// @deprecated Alias for 'if-not-start-location'.
217
			'if-not-start-location',
218
			// Does the event has NOT a start location?
219
			'if-not-end-location',
220
			// Does the event has NOT an end location?
221
222
		), (array) $this->add_custom_event_tags() );
223
	}
224
225
	/**
226
	 * Get event content.
227
	 *
228
	 * @since  3.0.0
229
	 *
230
	 * @param  string $template_tags
231
	 *
232
	 * @return string
233
	 */
234
	public function parse_event_template_tags( $template_tags = '' ) {
235
236
		// Process tags.
237
		$result = preg_replace_callback( $this->get_regex(), array( $this, 'process_event_content' ), $template_tags );
238
239
		// Removes extra consecutive <br> tags.
240
		// TODO: Doesn't seem to work but going to remove it to allow multiple <br> tags in the editor
241
		/*return preg_replace( '#(<br *//*?>\s*)+#i', '<br />', trim( $result ) );*/
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
242
243
		return do_shortcode( trim( $result ) );
244
	}
245
246
	/**
247
	 * Process event content.
248
	 *
249
	 * @since  3.0.0
250
	 *
251
	 * @param  string $match
252
	 *
253
	 * @return string
254
	 */
255
	public function process_event_content( $match ) {
256
257
		if ( $match[1] == '[' && $match[6] == ']' ) {
258
			return substr( $match[0], 1, -1 );
259
		}
260
261
		$tag     = $match[2]; // Tag name without square brackets.
262
		$before  = $match[1]; // Before tag.
263
		$partial = $match[5]; // HTML content between tags.
264
		$after   = $match[6]; // After tag.
265
		$attr    = $match[3]; // Tag attributes in quotes.
266
267
		$calendar = $this->calendar;
268
		$event    = $this->event;
269
270
		if ( ( $calendar instanceof Calendar ) && ( $event instanceof Event ) ) {
271
272
			switch ( $tag ) {
273
274
				/* ============ *
275
				 * Content Tags *
276
				 * ============ */
277
278
				case 'title' :
279
				case 'event-title' :
280
					return $this->get_title( $event->title, $attr );
281
282
				case 'description' :
283
					return $this->get_description( $event->description, $attr );
284
285
				case 'when' :
286
					return $this->get_when( $event );
287
288
				case 'end-date' :
289
				case 'end-custom' :
290
				case 'end-human' :
291
				case 'end-time' :
292
				case 'start-custom' :
293
				case 'start-date' :
294
				case 'start-human' :
295
				case 'start-time' :
296
					return $this->get_dt( $tag, $event, $attr );
297
298
				case 'length' :
299
				case 'duration' :
300
					if ( false !== $event->end ) {
301
						$duration = $event->start - $event->end;
302
						$value    = human_time_diff( $event->start, $event->end );
303
					} else {
304
						$duration = '-1';
305
						$value    = __( 'No end time', 'google-calendar-events' );
306
					}
307
308
					return ' <span class="simcal-event-duration" data-event-duration="' . $duration . '">' . $value . '</span>';
309
310
				case 'location' :
311
				case 'start-location' :
312
				case 'end-location' :
313
					$location       = ( 'end-location' == $tag ) ? $event->end_location['address'] : $event->start_location['address'];
314
					$location_class = ( 'end-location' == $tag ) ? 'end' : 'start';
315
316
					// location, location.name, location.address (type PostalAddress) all required for schema data.
317
					// Need to use event name where location data doesn't exist.
318
					// Since we have 1 location field, use it as the name and address.
319
					// If the location is blank, use the event title as the name and address.
320
					// Wrap with wp_strip_all_tags().
321
					$meta_location_name_and_address = empty( $location ) ? wp_strip_all_tags( $event->title ) : wp_strip_all_tags( $location );
322
323
					return ' <span class="simcal-event-address simcal-event-' . $location_class . '-location" itemprop="location" itemscope itemtype="http://schema.org/Place">' . '<meta itemprop="name" content="' . $meta_location_name_and_address . '" />' . '<meta itemprop="address" content="' . $meta_location_name_and_address . '" />' . wp_strip_all_tags( $location ) . '</span>';
324
325
				case 'start-location-link':
326
				case 'end-location-link' :
327
				case 'maps-link' :
328
					$location = ( 'end-location-link' == $tag ) ? $event->end_location['address'] : $event->start_location['address'];
329
					if ( ! empty( $location ) ) {
330
						$url = '//maps.google.com?q=' . urlencode( $location );
331
332
						return $this->make_link( $tag, $url, $calendar->get_event_html( $event, $partial ), $attr );
333
					}
334
					break;
335
336
				case 'link' :
337
				case 'url' :
338
					$content = ( 'link' == $tag ) ? $calendar->get_event_html( $event, $partial ) : '';
339
340
					return $this->make_link( $tag, $event->link, $content, $attr );
341
342
				case 'add-to-gcal-link';
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
343
					$content = ( 'add-to-gcal-link' == $tag ) ? $calendar->get_event_html( $event, $partial ) : '';
344
					if ( ! empty( $content ) ) {
345
						$url = $calendar->get_add_to_gcal_url( $event );
346
347
						return $this->make_link( $tag, $url, $content, $attr );
348
					}
349
					break;
350
351
				case 'calendar' :
352
				case 'feed-title' :
353
					return $event->source;
354
355
				case 'id' :
356
				case 'uid' :
357
				case 'event-id' :
358
					return $event->uid;
359
360
				case 'ical-id' :
361
					return $event->ical_id;
362
363
				case 'calendar-id' :
364
				case 'cal-id' :
365
				case 'feed-id' :
366
					return $event->calendar;
367
368
				/* ========= *
369
				 * Meta Tags *
370
				 * ========= */
371
372
				case 'attachments' :
373
					$attachments = $event->get_attachments();
374
					if ( ! empty( $attachments ) ) {
375
						return $this->get_attachments( $attachments );
376
					}
377
					break;
378
379
				case 'attendees' :
380
					$attendees = $event->get_attendees();
381
					if ( ! empty( $attendees ) ) {
382
						return $this->get_attendees( $attendees, $attr );
383
					}
384
					break;
385
386
				case 'organizer' :
387
					$organizer = $event->get_organizer();
388
					if ( ! empty( $organizer ) ) {
389
						return $this->get_organizer( $organizer, $attr );
390
					}
391
					break;
392
393
				/* ================ *
394
				 * Conditional Tags *
395
				 * ================ */
396
397
				case 'if-title':
398
					if ( ! empty( $event->title ) ) {
399
						return $calendar->get_event_html( $event, $partial );
400
					}
401
					break;
402
403
				case 'if-description':
404
					if ( ! empty( $event->description ) ) {
405
						return $calendar->get_event_html( $event, $partial );
406
					}
407
					break;
408
409
				case 'if-now' :
410
				case 'if-not-now' :
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
411
412
					$start_dt = $event->start_dt->setTimezone( $calendar->timezone );
413
					$start    = $start_dt->getTimestamp();
414
415
					if ( $event->end_dt instanceof Carbon ) {
416
						$end = $event->end_dt->setTimezone( $calendar->timezone )->getTimestamp();
417
					} else {
418
						return '';
419
					}
420
421
					$now = ( $start <= $calendar->now ) && ( $end >= $calendar->now );
422
423
					if ( ( 'if-now' == $tag ) && $now ) {
424
						return $calendar->get_event_html( $event, $partial );
425
					} elseif ( ( 'if-not-now' == $tag ) && ( false == $now ) ) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
426
						return $calendar->get_event_html( $event, $partial );
427
					}
428
429
					break;
430
431
				case 'if-started' :
432 View Code Duplication
				case 'if-not-started' :
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
433
434
					$start = $event->start_dt->setTimezone( $calendar->timezone )->getTimestamp();
435
436
					if ( 'if-started' == $tag ) {
437
						if ( $start < $calendar->now ) {
438
							return $calendar->get_event_html( $event, $partial );
439
						}
440
					} elseif ( 'if-not-started' == $tag ) {
441
						if ( $start > $calendar->now ) {
442
							return $calendar->get_event_html( $event, $partial );
443
						}
444
					}
445
446
					break;
447
448
				case 'if-ended' :
449 View Code Duplication
				case 'if-not-ended' :
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
450
451
					if ( false !== $event->end ) {
452
453
						$end = $event->end_dt->setTimezone( $calendar->timezone )->getTimestamp();
454
455
						if ( 'if-ended' == $tag ) {
456
							if ( $end < $calendar->now ) {
457
								return $calendar->get_event_html( $event, $partial );
458
							}
459
						} elseif ( 'if-not-ended' == $tag ) {
460
							if ( $end > $calendar->now ) {
461
								return $calendar->get_event_html( $event, $partial );
462
							}
463
						}
464
465
					}
466
467
					break;
468
469
				case 'if-today' :
470
				case 'if-not-today' :
471
					$start_dt   = $event->start_dt->setTimezone( $calendar->timezone );
472
					$startOfDay = $start_dt->startOfDay()->getTimestamp();
473
					$endOfDay   = $start_dt->endOfDay()->getTimestamp();
474
475
					$today = ( $startOfDay <= $calendar->now ) && ( $calendar->now <= $endOfDay );
476
477
					if ( ( 'if-today' == $tag ) && $today ) {
478
						return $calendar->get_event_html( $event, $partial );
479
					} elseif ( ( 'if-not-today' == $tag ) && ( false == $today ) ) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
480
						return $calendar->get_event_html( $event, $partial );
481
					}
482
483
					break;
484
485
				case 'if-end-time' :
486
					if ( false !== $event->end ) {
487
						return $calendar->get_event_html( $event, $partial );
488
					}
489
					break;
490
491
				case 'if-no-end-time' :
492
					if ( false === $event->end ) {
493
						return $calendar->get_event_html( $event, $partial );
494
					}
495
					break;
496
497
				case 'if-all-day' :
498
				case 'if-whole-day' :
499
				case 'if-not-all-day' :
500
				case 'if-not-whole-day' :
501
					$bool = strstr( $tag, 'not' ) ? false : true;
502
					if ( $bool === $event->whole_day ) {
503
						return $calendar->get_event_html( $event, $partial );
504
					}
505
					break;
506
507
				case 'if-recurring' :
508
					if ( ! empty( $event->recurrence ) ) {
509
						return $calendar->get_event_html( $event, $partial );
510
					}
511
					break;
512
513
				case 'if-not-recurring' :
514
					if ( false === $event->recurrence ) {
515
						return $calendar->get_event_html( $event, $partial );
516
					}
517
					break;
518
519
				case 'if-multi-day' :
520
					if ( false !== $event->multiple_days ) {
521
						return $calendar->get_event_html( $event, $partial );
522
					}
523
					break;
524
525
				case 'if-single-day' :
526
					if ( false === $event->multiple_days ) {
527
						return $calendar->get_event_html( $event, $partial );
528
					}
529
					break;
530
531
				case 'if-location' :
532
				case 'if-start-location' :
533
					if ( ! empty( $event->start_location['address'] ) ) {
534
						return $calendar->get_event_html( $event, $partial );
535
					}
536
537
					return false;
538
539
				case 'if-not-location' :
540
				case 'if-not-start-location' :
541
					if ( empty( $event->start_location['address'] ) ) {
542
						return $calendar->get_event_html( $event, $partial );
543
					}
544
545
					return '';
546
547
				case 'if-not-end-location' :
548
					if ( empty( $event->end_location['address'] ) ) {
549
						return $calendar->get_event_html( $event, $partial );
550
					}
551
552
					return '';
553
554
				case 'if-end-location' :
555
					if ( ! empty( $event->end_location['address'] ) ) {
556
						return $calendar->get_event_html( $event, $partial );
557
					}
558
559
					return '';
560
561
				/* ======= *
562
				 * Custom Event Tags or Default *
563
				 * ======= */
564
565
				default :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a DEFAULT statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in the default statement.

switch ($expr) {
    default : //wrong
        doSomething();
        break;
}

switch ($expr) {
    default: //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
566
					$resultCustom = $this->do_custom_event_tag( $tag, $partial, $attr, $event );
567
					if ( $resultCustom != "" ) {
568
						return $resultCustom;
569
					}
570
571
					return wp_kses_post( $before . $partial . $after );
572
			}
573
		}
574
575
		return '';
576
	}
577
578
	/**
579
	 * Limit words in text string.
580
	 *
581
	 * @since  3.0.0
582
	 * @access private
583
	 *
584
	 * @param  string $text
585
	 * @param  int    $limit
586
	 *
587
	 * @return string
588
	 */
589
	private function limit_words( $text, $limit ) {
590
591
		$limit = max( absint( $limit ), 0 );
592
593
		if ( $limit > 0 && ( str_word_count( $text, 0 ) > $limit ) ) {
594
			$words = str_word_count( $text, 2 );
595
			$pos   = array_keys( $words );
596
			$text  = trim( substr( $text, 0, $pos[ $limit ] ) ) . '&hellip;';
597
		}
598
599
		return $text;
600
	}
601
602
	/**
603
	 * Get event title.
604
	 *
605
	 * @since  3.0.0
606
	 * @access private
607
	 *
608
	 * @param  $title
609
	 * @param  $attr
610
	 *
611
	 * @return string
612
	 */
613
	private function get_title( $title, $attr ) {
614
615
		if ( empty( $title ) ) {
616
			return '';
617
		}
618
619
		$attr = array_merge( array(
620
			'html'  => '',  // Parse HTML
621
			'limit' => 0,   // Trim length to amount of words
622
		), (array) shortcode_parse_atts( $attr ) );
623
624
		if ( ! empty( $attr['html'] ) ) {
625
			$title = wp_kses_post( $title );
626
			$tag   = 'div';
627
		} else {
628
			$title = $this->limit_words( $title, $attr['limit'] );
629
			$tag   = 'span';
630
		}
631
632
		return '<' . $tag . ' class="simcal-event-title" itemprop="name">' . $title . '</' . $tag . '>';
633
	}
634
635
	/**
636
	 * Get event description.
637
	 *
638
	 * @since  3.0.0
639
	 * @access private
640
	 *
641
	 * @param  string $description
642
	 * @param  string $attr
643
	 *
644
	 * @return string
645
	 */
646
	private function get_description( $description, $attr ) {
647
648
		if ( empty( $description ) ) {
649
			return '';
650
		}
651
652
		$attr = array_merge( array(
653
			'limit'    => 0,       // Trim length to number of words
654
			'html'     => 'no',    // Parse HTML content
655
			'markdown' => 'no',    // Parse Markdown content
656
			'autolink' => 'no',    // Automatically convert plaintext URIs to anchors
657
		), (array) shortcode_parse_atts( $attr ) );
658
659
		$allow_html = 'no' != $attr['html'] ? true : false;
660
		$allow_md   = 'no' != $attr['markdown'] ? true : false;
661
662
		$html = '<div class="simcal-event-description" itemprop="description">';
663
664
		// Markdown and HTML don't play well together, use one or the other in the same tag.
665
		if ( $allow_html || $allow_md ) {
666
			if ( $allow_html ) {
667
				$description = wp_kses_post( $description );
668
			} elseif ( $allow_md ) {
669
				$markdown    = new \Parsedown();
670
				$description = $markdown->text( wp_strip_all_tags( $description ) );
671
			}
672
		} else {
673
			$description = wpautop( $description );
674
		}
675
676
		$description = $this->limit_words( $description, $attr['limit'] );
677
678
		$html .= $description . '</div>';
679
680
		if ( 'no' != $attr['autolink'] ) {
681
			$html = ' ' . make_clickable( $html );
682
		}
683
684
		return $html;
685
	}
686
687
	/**
688
	 * Get event start and end date and time.
689
	 *
690
	 * @since  3.0.0
691
	 * @access private
692
	 *
693
	 * @param  Event $event
694
	 *
695
	 * @return string
696
	 */
697
	private function get_when( Event $event ) {
698
699
		$start = $event->start_dt;
700
		$end   = $event->end_dt;
701
702
		$time_start = '';
703
		$time_end   = '';
704
		$start_ts   = $start->timestamp;
705
		$end_ts     = ! is_null( $end ) ? $end->timestamp : null;
706
		$start_iso  = $start->toIso8601String();
707
		$end_iso    = ! is_null( $end ) ? $end->toIso8601String() : null;;
708
709
		if ( ! $event->whole_day ) {
710
711
			$time_start = $this->calendar->datetime_separator . ' <span class="simcal-event-start simcal-event-start-time" ' . 'data-event-start="' . $start_ts . '" ' . 'data-event-format="' . $this->calendar->time_format . '" ' . 'itemprop="startDate" content="' . $start_iso . '">' . date_i18n( $this->calendar->time_format, strtotime( $start->toDateTimeString() ) ) . '</span> ';
712
713 View Code Duplication
			if ( $end instanceof Carbon ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
714
715
				$time_end = ' <span class="simcal-event-end simcal-event-end-time" ' . 'data-event-end="' . $end_ts . '" ' . 'data-event-format="' . $this->calendar->time_format . '" ' . 'itemprop="endDate" content="' . $end_iso . '">' . date_i18n( $this->calendar->time_format, strtotime( $end->toDateTimeString() ) ) . '</span> ';
716
717
			}
718
719
		}
720
721
		if ( $event->multiple_days ) {
722
723
			$output = ' <span class="simcal-event-start simcal-event-start-date" ' . 'data-event-start="' . $start_ts . '" ' . 'data-event-format="' . $this->calendar->date_format . '" ' . 'itemprop="startDate" content="' . $start_iso . '">' . date_i18n( $this->calendar->date_format, strtotime( $start->toDateTimeString() ) ) . '</span> ' . $time_start;
724
725 View Code Duplication
			if ( $end instanceof Carbon ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
726
727
				$output .= '-' . ' <span class="simcal-event-start simcal-event-end-date" ' . 'data-event-start="' . $end_ts . '" ' . 'data-event-format="' . $this->calendar->date_format . '" ' . 'itemprop="endDate" content="' . $end_iso . '">' . date_i18n( $this->calendar->date_format, strtotime( $end->toDateTimeString() ) ) . '</span> ' . $time_end;
728
			}
729
730
		} else {
731
732
			$time_end = ! empty( $time_start ) && ! empty( $time_end ) ? ' - ' . $time_end : '';
733
734
			// All-day events also need startDate for schema data.
735
			$output = ' <span class="simcal-event-start simcal-event-start-date" ' . 'data-event-start="' . $start_ts . '" ' . 'data-event-format="' . $this->calendar->date_format . '" ' . 'itemprop="startDate" content="' . $start_iso . '">' . date_i18n( $this->calendar->date_format, strtotime( $start->toDateTimeString() ) ) . '</span> ' . $time_start . $time_end;
736
737
		}
738
739
		return trim( $output );
740
	}
741
742
	/**
743
	 * Get event date or time.
744
	 *
745
	 * @since  3.0.0
746
	 * @access private
747
	 *
748
	 * @param  string $tag
749
	 * @param  Event  $event
750
	 * @param  string $attr
751
	 *
752
	 * @return string
753
	 */
754
	private function get_dt( $tag, Event $event, $attr ) {
755
756
		$bound = 0 === strpos( $tag, 'end' ) ? 'end' : 'start';
757
758
		if ( ( 'end' == $bound ) && ( false === $event->end ) ) {
759
			return '';
760
		}
761
762
		$dt = $bound . '_dt';
763
764
		if ( ! $event->$dt instanceof Carbon ) {
765
			return '';
766
		}
767
768
		$event_dt = $event->$dt;
769
770
		$attr = array_merge( array(
771
			'format' => '',
772
		), (array) shortcode_parse_atts( $attr ) );
773
774
		$format    = ltrim( strstr( $tag, '-' ), '-' );
775
		$dt_format = '';
776
777
		if ( ! empty( $attr['format'] ) ) {
778
			$dt_format = esc_attr( wp_strip_all_tags( $attr['format'] ) );
779
		} elseif ( 'date' == $format ) {
780
			$dt_format = $this->calendar->date_format;
781
		} elseif ( 'time' == $format ) {
782
			$dt_format = $this->calendar->time_format;
783
		}
784
785
		$dt_ts = $event_dt->timestamp;
786
787
		if ( 'human' == $format ) {
788
			$value = human_time_diff( $dt_ts, Carbon::now( $event->timezone )->getTimestamp() );
789
790
			if ( $dt_ts < Carbon::now( $event->timezone )->getTimestamp() ) {
791
				$value .= ' ' . _x( 'ago', 'human date event builder code modifier', 'google-calendar-events' );
792
			} else {
793
				$value .= ' ' . _x( 'from now', 'human date event builder code modifier', 'google-calendar-events' );
794
			}
795
		} else {
796
			$value = date_i18n( $dt_format, strtotime( $event_dt->toDateTimeString() ) );
797
		}
798
799
		return '<span class="simcal-event-' . $bound . ' ' . 'simcal-event-' . $bound . '-' . $format . '" ' . 'data-event-' . $bound . '="' . $dt_ts . '" ' . 'data-event-format="' . $dt_format . '" ' . 'itemprop="' . $bound . 'Date" content="' . $event_dt->toIso8601String() . '">' . $value . '</span>';
800
	}
801
802
	/**
803
	 * Make a link.
804
	 *
805
	 * @since  3.0.0
806
	 * @access private
807
	 *
808
	 * @param  string $tag
809
	 * @param  string $url
810
	 * @param  string $content
811
	 * @param  string $attr
812
	 *
813
	 * @return string
814
	 */
815
	private function make_link( $tag, $url, $content, $attr ) {
816
817
		if ( empty( $url ) ) {
818
			return '';
819
		}
820
821
		$text = empty( $content ) ? $url : $content;
822
823
		$attr = array_merge( array(
824
			'autolink'  => false,   // Convert url to link anchor
825
			'newwindow' => false,   // If autolink attribute is true, open link in new window
826
		), (array) shortcode_parse_atts( $attr ) );
827
828
		$anchor = $tag != 'url' ? 'yes' : $attr['autolink'];
829
		$target = $attr['newwindow'] !== false ? 'target="_blank"' : '';
830
831
		return $anchor !== false ? ' <a href="' . esc_url( $url ) . '" ' . $target . '>' . $text . '</a>' : ' ' . $text;
832
	}
833
834
	/**
835
	 * Get event attachments.
836
	 *
837
	 * @since  3.0.0
838
	 * @access private
839
	 *
840
	 * @param  array $attachments
841
	 *
842
	 * @return string
843
	 */
844
	private function get_attachments( $attachments ) {
845
846
		$html = '<ul class="simcal-attachments">' . "\n\t";
847
848
		foreach ( $attachments as $attachment ) {
849
			$html .= '<li class="simcal-attachment">';
850
			$html .= '<a href="' . $attachment['url'] . '" target="_blank">';
851
			$html .= ! empty( $attachment['icon'] ) ? '<img src="' . $attachment['icon'] . '" />' : '';
852
			$html .= '<span>' . $attachment['name'] . '</span>';
853
			$html .= '</a>';
854
			$html .= '</li>' . "\n";
855
		}
856
857
		$html .= '</ul>' . "\n";
858
859
		return $html;
860
	}
861
862
	/**
863
	 * Get attendees.
864
	 *
865
	 * @since  3.0.0
866
	 * @access private
867
	 *
868
	 * @param  array  $attendees
869
	 * @param  string $attr
870
	 *
871
	 * @return string
872
	 */
873
	private function get_attendees( $attendees, $attr ) {
874
875
		$attr = array_merge( array(
876
			'photo'    => 'show',  // show/hide attendee photo
877
			'email'    => 'hide',  // show/hide attendee email address
878
			'rsvp'     => 'hide',  // show/hide rsvp response status
879
			'response' => '',      // filter attendees by rsvp response (yes/no/maybe)
880
		), (array) shortcode_parse_atts( $attr ) );
881
882
		$html = '<ul class="simcal-attendees" itemprop="attendees">' . "\n\t";
883
884
		$known   = 0;
885
		$unknown = 0;
886
887
		foreach ( $attendees as $attendee ) {
888
889
			if ( 'yes' == $attr['response'] && 'yes' != $attendee['response'] ) {
890
				continue;
891
			} elseif ( 'no' == $attr['response'] && 'no' != $attendee['response'] ) {
892
				continue;
893
			} elseif ( 'maybe' == $attr['response'] && ! in_array( $attendee['response'], array( 'yes', 'maybe' ) ) ) {
894
				continue;
895
			}
896
897
			if ( ! empty( $attendee['name'] ) ) {
898
899
				$photo    = 'hide' != $attr['photo'] ? '<img class="avatar avatar-128 photo" src="' . $attendee['photo'] . '" itemprop="image" />' : '';
900
				$response = 'hide' != $attr['rsvp'] ? $this->get_rsvp_response( $attendee['response'] ) : '';
901
				$guest    = $photo . '<span itemprop="name">' . $attendee['name'] . $response . '</span>';
902
903
				if ( ! empty( $attendee['email'] ) && ( 'show' == $attr['email'] ) ) {
904
					$guest = sprintf( '<a href="mailto:' . $attendee['email'] . '" itemprop="email">%s</a>', $guest );
905
				}
906
907
				$html .= '<li class="simcal-attendee" itemprop="attendee" itemscope itemtype="http://schema.org/Person">' . $guest . '</li>' . "\n";
908
909
				$known++;
910
911
			} else {
912
913
				$unknown++;
914
915
			}
916
		}
917
918
		if ( $unknown > 0 ) {
919
			if ( $known > 0 ) {
920
				/* translators: One more person attending the event. */
921
				$others = sprintf( _n( '1 more attendee', '%s more attendees', $unknown, 'google-calendar-events' ), $unknown );
922
			} else {
923
				/* translators: One or more persons attending the event whose name is unknown. */
924
				$others = sprintf( _n( '1 anonymous attendee', '%s anonymous attendees', $unknown, 'google-calendar-events' ), $unknown );
925
			}
926
			$photo = $attr['photo'] !== 'hide' ? get_avatar( '', 128 ) : '';
927
			$html .= '<li class="simcal-attendee simcal-attendee-anonymous">' . $photo . '<span>' . $others . '</span></li>' . "\n";
928
		} elseif ( $known === 0 ) {
929
			$html .= '<li class="simcal-attendee">' . _x( 'No one yet', 'No one yet rsvp to attend the event.', 'google-calendar-events' ) . '</li>' . "\n";
930
		}
931
932
		$html .= '</ul>' . "\n";
933
934
		return $html;
935
	}
936
937
	/**
938
	 * Format attendee rsvp response.
939
	 *
940
	 * @since  3.0.0
941
	 *
942
	 * @param  $response
943
	 *
944
	 * @return string
945
	 */
946
	private function get_rsvp_response( $response ) {
947
948
		if ( 'yes' == $response ) {
949
			/* translators: Someone replied with 'yes' to a rsvp request. */
950
			$rsvp = __( 'Attending', 'google-calendar-events' );
951
		} elseif ( 'no' == $response ) {
952
			/* translators: Someone replied with 'no' to a rsvp request. */
953
			$rsvp = __( 'Not attending', 'google-calendar-events' );
954
		} elseif ( 'maybe' == $response ) {
955
			/* translators: Someone replied with 'maybe' to a rsvp request. */
956
			$rsvp = __( 'Maybe attending', 'google-calendar-events' );
957
		} else {
958
			/* translators: Someone did not send yet a rsvp confirmation to join an event. */
959
			$rsvp = __( 'Response pending', 'google-calendar-events' );
960
		}
961
962
		return ' <small>(' . $rsvp . ')</small>';
963
	}
964
965
	/**
966
	 * Get event organizer.
967
	 *
968
	 * @since  3.0.0
969
	 * @access private
970
	 *
971
	 * @param  array  $organizer
972
	 * @param  string $attr
973
	 *
974
	 * @return string
975
	 */
976
	private function get_organizer( $organizer, $attr ) {
977
978
		$attr = array_merge( array(
979
			'photo' => 'show',  // show/hide attendee photo
980
			'email' => 'hide',  // show/hide attendee email address
981
		), (array) shortcode_parse_atts( $attr ) );
982
983
		$photo          = 'hide' != $attr['photo'] ? '<img class="avatar avatar-128 photo" src="' . $organizer['photo'] . '" itemprop="image"  />' : '';
984
		$organizer_html = $photo . '<span itemprop="name">' . $organizer['name'] . '</span>';
985
986
		if ( ! empty( $organizer['email'] ) && ( 'show' == $attr['email'] ) ) {
987
			$organizer_html = sprintf( '<a href="mailto:' . $organizer['email'] . '" itemprop="email">%s</a>', $organizer_html );
988
		}
989
990
		return '<div class="simcal-organizer" itemprop="organizer" itemscope itemtype="https://schema.org/Person">' . $organizer_html . '</div>';
991
	}
992
993
	/**
994
	 * Retrieve the event builder tag regular expression for searching.
995
	 *
996
	 * Combines the event builder tags in the regular expression in a regex class.
997
	 * The regular expression contains 6 different sub matches to help with parsing:
998
	 *
999
	 *  1 - An extra [ to allow for escaping tags with double square brackets [[]]
1000
	 *  2 - The tag name
1001
	 *  3 - The tag argument list
1002
	 *  4 - The self closing /
1003
	 *  5 - The content of a tag when it wraps some content.
1004
	 *  6 - An extra ] to allow for escaping tags with double square brackets [[]]
1005
	 *
1006
	 * @since  3.0.0
1007
	 *
1008
	 * @return string The tag search regular expression result
1009
	 */
1010
	private function get_regex() {
1011
1012
		// This is largely borrowed on get_shortcode_regex() from WordPress Core.
1013
		// @see /wp-includes/shortcodes.php (with some modification)
1014
1015
		$tagregexp = implode( '|', array_values( $this->tags ) );
1016
1017
		return '/' . '\\['                              // Opening bracket
1018
		       . '(\\[?)'                           // 1: Optional second opening bracket for escaping tags: [[tag]]
1019
		       . "($tagregexp)"                     // 2: Tag name
1020
		       . '(?![\\w-])'                       // Not followed by word character or hyphen
1021
		       . '('                                // 3: Unroll the loop: Inside the opening tag
1022
		       . '[^\\]\\/]*'                   // Not a closing bracket or forward slash
1023
		       . '(?:' . '\\/(?!\\])'               // A forward slash not followed by a closing bracket
1024
		       . '[^\\]\\/]*'               // Not a closing bracket or forward slash
1025
		       . ')*?' . ')' . '(?:' . '(\\/)'                        // 4: Self closing tag ...
0 ignored issues
show
Unused Code Comprehensibility introduced by
37% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1026
		       . '\\]'                          // ... and closing bracket
1027
		       . '|' . '\\]'                          // Closing bracket
1028
		       . '(?:' . '('                        // 5: Unroll the loop: Optionally, anything between the opening and closing tags
1029
		       . '[^\\[]*+'             // Not an opening bracket
1030
		       . '(?:' . '\\[(?!\\/\\2\\])' // An opening bracket not followed by the closing tag
1031
		       . '[^\\[]*+'         // Not an opening bracket
1032
		       . ')*+' . ')' . '\\[\\/\\2\\]'             // Closing tag
1033
		       . ')?' . ')' . '(\\]?)'                           // 6: Optional second closing bracket for escaping tags: [[tag]]
1034
		       . '/s';
1035
	}
1036
1037
	//allow other plugins to register own event tags
1038
	private function add_custom_event_tags() {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
1039
		$array = apply_filters( 'simcal_event_tags_add_custom', array() );
1040
1041
		return $array;
1042
	}
1043
1044
	//allow other plugins to replace own (registered) event tags with their value
1045
	private function do_custom_event_tag( $tag, $partial, $attr, $event ) {
1046
		$returnvalue = apply_filters( 'simcal_event_tags_do_custom', "", $tag, $partial, $attr, $event );
1047
1048
		return $returnvalue;
1049
	}
1050
}
1051