Completed
Push — remove/wp-admin-publicize-conn... ( d708bd...e84ae6 )
by
unknown
64:00 queued 52:22
created

iCalendarReader::formatted_date()   C

Complexity

Conditions 13
Paths 240

Size

Total Lines 55
Code Lines 30

Duplication

Lines 10
Ratio 18.18 %
Metric Value
dl 10
loc 55
rs 5.1577
cc 13
eloc 30
nc 240
nop 1

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * Gets and renders iCal feeds for the Upcoming Events widget and shortcode
5
 */
6
7
class iCalendarReader {
8
9
	public $todo_count = 0;
10
	public $event_count = 0;
11
	public $cal = array();
12
	public $_lastKeyWord = '';
13
	public $timezone = null;
14
15
	/**
16
	 * Class constructor
17
	 *
18
	 * @return void
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
19
	 */
20
	public function __construct() {}
21
22
	/**
23
	 * Return an array of events
24
	 *
25
	 * @param string $url (default: '')
26
	 * @return array | false on failure
27
	 */
28
	public function get_events( $url = '', $count = 5 ) {
29
		$count = (int) $count;
30
		$transient_id = 'icalendar_vcal_' . md5( $url ) . '_' . $count;
31
32
		$vcal = get_transient( $transient_id );
33
34
		if ( ! empty( $vcal ) ) {
35
			if ( isset( $vcal['TIMEZONE'] ) )
36
				$this->timezone = $this->timezone_from_string( $vcal['TIMEZONE'] );
37
38
			if ( isset( $vcal['VEVENT'] ) ) {
39
				$vevent = $vcal['VEVENT'];
40
41
				if ( $count > 0 )
42
					$vevent = array_slice( $vevent, 0, $count );
43
44
				$this->cal['VEVENT'] = $vevent;
45
46
				return $this->cal['VEVENT'];
47
			}
48
		}
49
50
		if ( ! $this->parse( $url ) )
51
			return false;
52
53
		$vcal = array();
54
55
		if ( $this->timezone ) {
56
			$vcal['TIMEZONE'] = $this->timezone->getName();
57
		} else {
58
			$this->timezone = $this->timezone_from_string( '' );
59
		}
60
61
		if ( ! empty( $this->cal['VEVENT'] ) ) {
62
			$vevent = $this->cal['VEVENT'];
63
64
			// check for recurring events
65
			// $vevent = $this->add_recurring_events( $vevent );
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% 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...
66
67
			// remove before caching - no sense in hanging onto the past
68
			$vevent = $this->filter_past_and_recurring_events( $vevent );
69
70
			// order by soonest start date
71
			$vevent = $this->sort_by_recent( $vevent );
72
73
			$vcal['VEVENT'] = $vevent;
74
		}
75
76
		set_transient( $transient_id, $vcal, HOUR_IN_SECONDS );
77
78
		if ( !isset( $vcal['VEVENT'] ) )
79
			return false;
80
81
		if ( $count > 0 )
82
			return array_slice( $vcal['VEVENT'], 0, $count );
83
84
		return $vcal['VEVENT'];
85
	}
86
87
	protected function filter_past_and_recurring_events( $events ) {
88
		$upcoming = array();
89
		$set_recurring_events = array();
90
		$recurrences = array();
0 ignored issues
show
Unused Code introduced by
$recurrences is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
91
		/**
92
		 * This filter allows any time to be passed in for testing or changing timezones, etc...
93
		 *
94
		 * @module widgets
95
		 * 
96
		 * @since 3.4.0
97
		 *
98
		 * @param object time() A time object.
99
		 */
100
		$current = apply_filters( 'ical_get_current_time', time() );
101
102
		foreach ( $events as $event ) {
103
104
			$date_from_ics = strtotime( $event['DTSTART'] );
105 View Code Duplication
			if ( isset( $event['DTEND'] ) ) {
106
				$duration = strtotime( $event['DTEND'] ) - strtotime( $event['DTSTART'] );
107
			} else {
108
				$duration = 0;
109
			}
110
111
			if ( isset( $event['RRULE'] ) && $this->timezone->getName() && 8 != strlen( $event['DTSTART'] ) ) {
112
				try {
113
					$adjusted_time = new DateTime( $event['DTSTART'], new DateTimeZone('UTC') );
114
					$adjusted_time->setTimeZone( new DateTimeZone( $this->timezone->getName() ) );
115
					$event['DTSTART'] = $adjusted_time->format('Ymd\THis');
116
					$date_from_ics = strtotime( $event['DTSTART'] );
117
118
					$event['DTEND'] = date( 'Ymd\THis', strtotime( $event['DTSTART'] ) + $duration );
119
				} catch ( Exception $e ) {
120
					// Invalid argument to DateTime
121
				}
122
123
				if ( isset( $event['EXDATE'] ) ) {
124
					$exdates = array();
125
					foreach ( (array) $event['EXDATE'] as $exdate ) {
126
						try {
127
							$adjusted_time = new DateTime( $exdate, new DateTimeZone('UTC') );
128
							$adjusted_time->setTimeZone( new DateTimeZone( $this->timezone->getName() ) );
129
							if ( 8 == strlen( $event['DTSTART'] ) ) {
130
								$exdates[] = $adjusted_time->format( 'Ymd' );
131
							} else {
132
								$exdates[] = $adjusted_time->format( 'Ymd\THis' );
133
							}
134
						} catch ( Exception $e ) {
135
							// Invalid argument to DateTime
136
						}
137
					}
138
					$event['EXDATE'] = $exdates;
139
				} else {
140
					$event['EXDATE'] = array();
141
				}
142
			}
143
144
			if ( ! isset( $event['DTSTART'] ) ) {
145
				continue;
146
			}
147
148
			// Process events with RRULE before other events
149
			$rrule = isset( $event['RRULE'] ) ? $event['RRULE'] : false ;
150
			$uid = $event['UID'];
151
152
			if ( $rrule && ! in_array( $uid, $set_recurring_events ) ) {
153
154
				// Break down the RRULE into digestible chunks
155
				$rrule_array = array();
156
157
				foreach ( explode( ";", $event['RRULE'] ) as $rline ) {
158
					list( $rkey, $rvalue ) = explode( "=", $rline, 2 );
159
					$rrule_array[$rkey] = $rvalue;
160
				}
161
162
				$interval = ( isset( $rrule_array['INTERVAL'] ) ) ? $rrule_array['INTERVAL'] : 1;
163
				$rrule_count = ( isset( $rrule_array['COUNT'] ) ) ? $rrule_array['COUNT'] : 0;
164
				$until = ( isset( $rrule_array['UNTIL'] ) ) ? strtotime( $rrule_array['UNTIL'] ) : strtotime( '+1 year', $current );
165
166
				// Used to bound event checks
167
				$echo_limit = 10;
168
				$noop = false;
169
170
				// Set bydays for the event
171
				$weekdays = array( 'SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA' );
172
				$bydays = $weekdays;
173
174
				// Calculate a recent start date for incrementing depending on the frequency and interval
175
				switch ( $rrule_array['FREQ'] ) {
176
177
					case 'DAILY':
178
						$frequency = 'day';
179
						$echo_limit = 10;
180
181
						if ( $date_from_ics >= $current ) {
182
							$recurring_event_date_start = date( 'Ymd\THis', strtotime( $event['DTSTART'] ) );
183
						} else {
184
							// Interval and count
185
							$catchup = floor( ( $current - strtotime( $event['DTSTART'] ) ) / ( $interval * DAY_IN_SECONDS ) );
186
							if ( $rrule_count && $catchup > 0 ) {
187
								if ( $catchup < $rrule_count ) {
188
									$rrule_count = $rrule_count - $catchup;
189
									$recurring_event_date_start = date( 'Ymd', strtotime( '+ ' . ( $interval * $catchup ) . ' days', strtotime( $event['DTSTART'] ) ) ) . date( '\THis', strtotime( $event['DTSTART'] ) );
190
								} else {
191
									$noop = true;
192
								}
193 View Code Duplication
							} else {
194
								$recurring_event_date_start = date( 'Ymd', strtotime( '+ ' . ( $interval * $catchup ) . ' days', strtotime( $event['DTSTART'] ) ) ) . date( '\THis', strtotime( $event['DTSTART'] ) );
195
							}
196
						}
197
						break;
198
199
					case 'WEEKLY':
200
						$frequency = 'week';
201
						$echo_limit = 4;
202
203
						// BYDAY exception to current date
204
						$day = false;
205
						if ( ! isset( $rrule_array['BYDAY'] ) ) {
206
							$day = $rrule_array['BYDAY'] = strtoupper( substr( date( 'D', strtotime( $event['DTSTART'] ) ), 0, 2 ) );
207
						}
208
						$bydays = explode( ',', $rrule_array['BYDAY'] );
209
210
						if ( $date_from_ics >= $current ) {
211
							$recurring_event_date_start = date( 'Ymd\THis', strtotime( $event['DTSTART'] ) );
212
						} else {
213
							// Interval and count
214
							$catchup = floor( ( $current - strtotime( $event['DTSTART'] ) ) / ( $interval * WEEK_IN_SECONDS ) );
215
							if ( $rrule_count && $catchup > 0 ) {
216
								if ( ( $catchup * count( $bydays ) ) < $rrule_count ) {
217
									$rrule_count = $rrule_count - ( $catchup * count( $bydays ) ); // Estimate current event count
218
									$recurring_event_date_start = date( 'Ymd', strtotime( '+ ' . ( $interval * $catchup ) . ' weeks', strtotime( $event['DTSTART'] ) ) ) . date( '\THis', strtotime( $event['DTSTART'] ) );
219
								} else {
220
									$noop = true;
221
								}
222 View Code Duplication
							} else {
223
								$recurring_event_date_start = date( 'Ymd', strtotime( '+ ' . ( $interval * $catchup ) . ' weeks', strtotime( $event['DTSTART'] ) ) ) . date( '\THis', strtotime( $event['DTSTART'] ) );
224
							}
225
						}
226
227
						// Set to Sunday start
228
						if ( ! $noop && 'SU' !== strtoupper( substr( date( 'D', strtotime( $recurring_event_date_start ) ), 0, 2 ) ) ) {
229
							$recurring_event_date_start = date( 'Ymd', strtotime( "last Sunday", strtotime( $recurring_event_date_start ) ) ) . date( '\THis', strtotime( $event['DTSTART'] ) );
0 ignored issues
show
Bug introduced by
The variable $recurring_event_date_start does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
230
						}
231
						break;
232
233
					case 'MONTHLY':
234
						$frequency = 'month';
235
						$echo_limit = 1;
236
237
						if ( $date_from_ics >= $current ) {
238
							$recurring_event_date_start = date( 'Ymd\THis', strtotime( $event['DTSTART'] ) );
239
						} else {
240
							// Describe the date in the month
241
							if ( isset( $rrule_array['BYDAY'] ) ) {
242
								$day_number = substr( $rrule_array['BYDAY'], 0, 1 );
243
								$week_day = substr( $rrule_array['BYDAY'], 1 );
244
								$day_cardinals = array( 1 => 'first', 2 => 'second', 3 => 'third', 4 => 'fourth', 5 => 'fifth' );
245
								$weekdays = array( 'SU' => 'Sunday', 'MO' => 'Monday', 'TU' => 'Tuesday', 'WE' => 'Wednesday', 'TH' => 'Thursday', 'FR' => 'Friday', 'SA' => 'Saturday' );
246
								$event_date_desc = "{$day_cardinals[$day_number]} {$weekdays[$week_day]} of ";
247
							} else {
248
								$event_date_desc = date( 'd ', strtotime( $event['DTSTART'] ) );
249
							}
250
251
							// Interval only
252
							if ( $interval > 1 ) {
253
								$catchup = 0;
254
								$maybe = strtotime( $event['DTSTART'] );
255
								while ( $maybe < $current ) {
256
									$maybe = strtotime( '+ ' . ( $interval * $catchup ) . ' months', strtotime( $event['DTSTART'] ) );
257
									$catchup++;
258
								}
259
								$recurring_event_date_start = date( 'Ymd', strtotime( $event_date_desc . date( 'F Y', strtotime( '+ ' . ( $interval * ( $catchup - 1 ) ) . ' months', strtotime( $event['DTSTART'] ) ) ) ) ) . date( '\THis', strtotime( $event['DTSTART'] ) );
260 View Code Duplication
							} else {
261
								$recurring_event_date_start = date( 'Ymd', strtotime( $event_date_desc . date( 'F Y', $current ) ) ) . date( '\THis', strtotime( $event['DTSTART'] ) );
262
							}
263
264
							// Add one interval if necessary
265
							if ( strtotime( $recurring_event_date_start ) < $current ) {
266
								if ( $interval > 1 ) {
267
									$recurring_event_date_start = date( 'Ymd', strtotime( $event_date_desc . date( 'F Y', strtotime( '+ ' . ( $interval * $catchup ) . ' months', strtotime( $event['DTSTART'] ) ) ) ) ) . date( '\THis', strtotime( $event['DTSTART'] ) );
0 ignored issues
show
Bug introduced by
The variable $catchup does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
268 View Code Duplication
								} else {
269
									try {
270
										$adjustment = new DateTime( date( 'Y-m-d', $current ) );
271
										$adjustment->modify( 'first day of next month' );
272
										$recurring_event_date_start = date( 'Ymd', strtotime( $event_date_desc . $adjustment->format( 'F Y' ) ) ) . date( '\THis', strtotime( $event['DTSTART'] ) );
273
									} catch ( Exception $e ) {
274
										// Invalid argument to DateTime
275
									}
276
								}
277
							}
278
						}
279
						break;
280
281
					case 'YEARLY':
282
						$frequency = 'year';
283
						$echo_limit = 1;
284
285
						if ( $date_from_ics >= $current ) {
286
							$recurring_event_date_start = date( "Ymd\THis", strtotime( $event['DTSTART'] ) );
287
						} else {
288
							$recurring_event_date_start = date( 'Y', $current ) . date( "md\THis", strtotime( $event['DTSTART'] ) );
289 View Code Duplication
							if ( strtotime( $recurring_event_date_start ) < $current ) {
290
								try {
291
									$next = new DateTime( date( 'Y-m-d', $current ) );
292
									$next->modify( 'first day of next year' );
293
									$recurring_event_date_start = $next->format( 'Y' ) . date ( 'md\THis', strtotime( $event['DTSTART'] ) );
294
								} catch ( Exception $e ) {
295
									// Invalid argument to DateTime
296
								}
297
							}
298
						}
299
						break;
300
301
					default:
302
						$frequency = false;
303
				}
304
305
				if ( $frequency !== false && ! $noop ) {
306
					$count_counter = 1;
307
308
					// If no COUNT limit, go to 10
309
					if ( empty( $rrule_count ) ) {
310
						$rrule_count = 10;
311
					}
312
313
					// Set up EXDATE handling for the event
314
					$exdates = ( isset( $event['EXDATE'] ) ) ? $event['EXDATE'] : array();
315
316
					for ( $i = 1; $i <= $echo_limit; $i++ ) {
317
318
						// Weeks need a daily loop and must check for inclusion in BYDAYS
319
						if ( 'week' == $frequency ) {
320
							$byday_event_date_start = strtotime( $recurring_event_date_start );
321
322
							foreach ( $weekdays as $day ) {
323
324
								$event_start_timestamp = $byday_event_date_start;
325
								$start_time = date( 'His', $event_start_timestamp );
326
								$event_end_timestamp = $event_start_timestamp + $duration;
327
								$end_time = date( 'His', $event_end_timestamp );
0 ignored issues
show
Unused Code introduced by
$end_time is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
328 View Code Duplication
								if ( 8 == strlen( $event['DTSTART'] ) ) {
329
									$exdate_compare = date( 'Ymd', $event_start_timestamp );
330
								} else {
331
									$exdate_compare = date( 'Ymd\THis', $event_start_timestamp );
332
								}
333
334 View Code Duplication
								if ( in_array( $day, $bydays ) && $event_end_timestamp > $current && $event_start_timestamp < $until && $count_counter <= $rrule_count && $event_start_timestamp >= $date_from_ics && ! in_array( $exdate_compare, $exdates ) ) {
335
									if ( 8 == strlen( $event['DTSTART'] ) ) {
336
										$event['DTSTART'] = date( 'Ymd', $event_start_timestamp );
337
										$event['DTEND'] = date( 'Ymd', $event_end_timestamp );
338
									} else {
339
										$event['DTSTART'] = date( 'Ymd\THis', $event_start_timestamp );
340
										$event['DTEND'] = date( 'Ymd\THis', $event_end_timestamp );
341
									}
342
									if ( $this->timezone->getName() && 8 != strlen( $event['DTSTART'] ) ) {
343
										try {
344
											$adjusted_time = new DateTime( $event['DTSTART'], new DateTimeZone( $this->timezone->getName() ) );
345
											$adjusted_time->setTimeZone( new DateTimeZone( 'UTC' ) );
346
											$event['DTSTART'] = $adjusted_time->format('Ymd\THis');
347
348
											$event['DTEND'] = date( 'Ymd\THis', strtotime( $event['DTSTART'] ) + $duration );
349
										} catch ( Exception $e ) {
350
											// Invalid argument to DateTime
351
										}
352
									}
353
									$upcoming[] = $event;
354
									$count_counter++;
355
								}
356
357
								// Move forward one day
358
								$byday_event_date_start = strtotime( date( 'Ymd\T', strtotime( '+ 1 day', $event_start_timestamp ) ) . $start_time );
359
							}
360
361
							// Restore first event timestamp
362
							$event_start_timestamp = strtotime( $recurring_event_date_start );
363
364
						} else {
365
366
							$event_start_timestamp = strtotime( $recurring_event_date_start );
367
							$start_time = date( 'His', $event_start_timestamp );
368
							$event_end_timestamp = $event_start_timestamp + $duration;
369
							$end_time = date( 'His', $event_end_timestamp );
370 View Code Duplication
							if ( 8 == strlen( $event['DTSTART'] ) ) {
371
								$exdate_compare = date( 'Ymd', $event_start_timestamp );
372
							} else {
373
								$exdate_compare = date( 'Ymd\THis', $event_start_timestamp );
374
							}
375
376 View Code Duplication
							if ( $event_end_timestamp > $current && $event_start_timestamp < $until && $count_counter <= $rrule_count && $event_start_timestamp >= $date_from_ics && ! in_array( $exdate_compare, $exdates ) ) {
377
								if ( 8 == strlen( $event['DTSTART'] ) ) {
378
									$event['DTSTART'] = date( 'Ymd', $event_start_timestamp );
379
									$event['DTEND'] = date( 'Ymd', $event_end_timestamp );
380
								} else {
381
									$event['DTSTART'] = date( 'Ymd\T', $event_start_timestamp ) . $start_time;
382
									$event['DTEND'] = date( 'Ymd\T', $event_end_timestamp ) . $end_time;
383
								}
384
								if ( $this->timezone->getName() && 8 != strlen( $event['DTSTART'] ) ) {
385
									try {
386
										$adjusted_time = new DateTime( $event['DTSTART'], new DateTimeZone( $this->timezone->getName() ) );
387
										$adjusted_time->setTimeZone( new DateTimeZone( 'UTC' ) );
388
										$event['DTSTART'] = $adjusted_time->format('Ymd\THis');
389
390
										$event['DTEND'] = date( 'Ymd\THis', strtotime( $event['DTSTART'] ) + $duration );
391
									} catch ( Exception $e ) {
392
										// Invalid argument to DateTime
393
									}
394
								}
395
								$upcoming[] = $event;
396
								$count_counter++;
397
							}
398
						}
399
400
						// Set up next interval and reset $event['DTSTART'] and $event['DTEND'], keeping timestamps intact
0 ignored issues
show
Unused Code Comprehensibility introduced by
36% 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...
401
						$next_start_timestamp = strtotime( "+ {$interval} {$frequency}s", $event_start_timestamp );
402
						if ( 8 == strlen( $event['DTSTART'] ) ) {
403
							$event['DTSTART'] = date( 'Ymd', $next_start_timestamp );
404
							$event['DTEND'] = date( 'Ymd', strtotime( $event['DTSTART'] ) + $duration );
405 View Code Duplication
						} else {
406
							$event['DTSTART'] = date( 'Ymd\THis', $next_start_timestamp );
407
							$event['DTEND'] = date( 'Ymd\THis', strtotime( $event['DTSTART'] ) + $duration );
408
						}
409
410
						// Move recurring event date forward
411
						$recurring_event_date_start = $event['DTSTART'];
412
					}
413
					$set_recurring_events[] = $uid;
414
415
				}
416
417 View Code Duplication
			} else {
418
				// Process normal events
419
				if ( strtotime( isset( $event['DTEND'] ) ? $event['DTEND'] : $event['DTSTART'] ) >= $current ) {
420
					$upcoming[] = $event;
421
				}
422
			}
423
		}
424
		return $upcoming;
425
	}
426
427
	/**
428
	 * Parse events from an iCalendar feed
429
	 *
430
	 * @param string $url (default: '')
431
	 * @return array | false on failure
432
	 */
433
	public function parse( $url = '' ) {
434
		$cache_group = 'icalendar_reader_parse';
435
		$disable_get_key = 'disable:' . md5( $url );
436
437
		// Check to see if previous attempts have failed
438
		if ( false !== wp_cache_get( $disable_get_key, $cache_group ) )
439
			return false;
440
441
		// rewrite webcal: URI schem to HTTP
442
		$url = preg_replace('/^webcal/', 'http', $url );
443
		// try to fetch
444
		$r = wp_remote_get( $url, array( 'timeout' => 3, 'sslverify' => false ) );
445
		if ( 200 !== wp_remote_retrieve_response_code( $r ) ) {
446
			// We were unable to fetch any content, so don't try again for another 60 seconds
447
			wp_cache_set( $disable_get_key, 1, $cache_group, 60 );
448
			return false;
449
		}
450
451
		$body = wp_remote_retrieve_body( $r );
452
		if ( empty( $body ) )
453
			return false;
454
455
		$body = str_replace( "\r\n", "\n", $body );
456
		$lines = preg_split( "/\n(?=[A-Z])/", $body );
457
458
		if ( empty( $lines ) )
459
			return false;
460
461
		if ( false === stristr( $lines[0], 'BEGIN:VCALENDAR' ) )
462
			return false;
463
464
		foreach ( $lines as $line ) {
465
			$add  = $this->key_value_from_string( $line );
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned correctly; expected 1 space but found 2 spaces

This check looks for improperly formatted assignments.

Every assignment must have exactly one space before and one space after the equals operator.

To illustrate:

$a = "a";
$ab = "ab";
$abc = "abc";

will have no issues, while

$a   = "a";
$ab  = "ab";
$abc = "abc";

will report issues in lines 1 and 2.

Loading history...
466
			if ( ! $add ) {
467
				$this->add_component( $type, false, $line );
0 ignored issues
show
Bug introduced by
The variable $type does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
468
				continue;
469
			}
470
			list( $keyword, $value ) = $add;
471
472
			switch ( $keyword ) {
473
				case 'BEGIN':
474
				case 'END':
475
					switch ( $line ) {
476
						case 'BEGIN:VTODO':
477
							$this->todo_count++;
478
							$type = 'VTODO';
479
							break;
480
						case 'BEGIN:VEVENT':
481
							$this->event_count++;
482
							$type = 'VEVENT';
483
							break;
484
						case 'BEGIN:VCALENDAR':
485
						case 'BEGIN:DAYLIGHT':
486
						case 'BEGIN:VTIMEZONE':
487
						case 'BEGIN:STANDARD':
488
							$type = $value;
489
							break;
490
						case 'END:VTODO':
491
						case 'END:VEVENT':
492
						case 'END:VCALENDAR':
493
						case 'END:DAYLIGHT':
494
						case 'END:VTIMEZONE':
495
						case 'END:STANDARD':
496
							$type = 'VCALENDAR';
497
							break;
498
					}
499
					break;
500
				case 'TZID':
501
					if ( 'VTIMEZONE' == $type && ! $this->timezone )
502
						$this->timezone = $this->timezone_from_string( $value );
503
					break;
504
				case 'X-WR-TIMEZONE':
505
					if ( ! $this->timezone )
506
						$this->timezone = $this->timezone_from_string( $value );
507
					break;
508
				default:
509
					$this->add_component( $type, $keyword, $value );
510
					break;
511
			}
512
		}
513
514
		// Filter for RECURRENCE-IDs
515
		$recurrences = array();
516
		if ( array_key_exists( 'VEVENT', $this->cal ) ) {
517
			foreach ( $this->cal['VEVENT'] as $event ) {
518
				if ( isset( $event['RECURRENCE-ID'] ) ) {
519
					$recurrences[] = $event;
520
				}
521
			}
522
			foreach ( $recurrences as $recurrence ) {
523
				for ( $i = 0; $i < count( $this->cal['VEVENT'] ); $i++ ) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
524
					if ( $this->cal['VEVENT'][ $i ]['UID'] == $recurrence['UID'] && ! isset( $this->cal['VEVENT'][ $i ]['RECURRENCE-ID'] ) ) {
525
						$this->cal['VEVENT'][ $i ]['EXDATE'][] = $recurrence['RECURRENCE-ID'];
526
						break;
527
					}
528
				}
529
			}
530
		}
531
532
		return $this->cal;
533
	}
534
535
	/**
536
	 * Parse key:value from a string
537
	 *
538
	 * @param string $text (default: '')
539
	 * @return array
540
	 */
541
	public function key_value_from_string( $text = '' ) {
542
		preg_match( '/([^:]+)(;[^:]+)?[:]([\w\W]*)/', $text, $matches );
543
544
		if ( 0 == count( $matches ) )
545
			return false;
546
547
		return array( $matches[1], $matches[3] );
548
	}
549
550
	/**
551
	 * Convert a timezone name into a timezone object.
552
	 *
553
	 * @param string $text Timezone name. Example: America/Chicago
554
	 * @return object|null A DateTimeZone object if the conversion was successful.
555
	 */
556
	private function timezone_from_string( $text ) {
557
		try {
558
			$timezone = new DateTimeZone( $text );
559
		} catch ( Exception $e ) {
560
			$blog_timezone = get_option( 'timezone_string' );
561
			if ( ! $blog_timezone ) {
562
				$blog_timezone = 'Etc/UTC';
563
			}
564
565
			$timezone = new DateTimeZone( $blog_timezone );
566
		}
567
568
		return $timezone;
569
	}
570
571
	/**
572
	 * Add a component to the calendar array
573
	 *
574
	 * @param string $component (default: '')
575
	 * @param string $keyword (default: '')
576
	 * @param string $value (default: '')
577
	 * @return void
578
	 */
579
	public function add_component( $component = '', $keyword = '', $value = '' ) {
580
		if ( false == $keyword ) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $keyword of type string to the boolean false. If you are specifically checking for an empty string, consider using the more explicit === '' instead.
Loading history...
581
			$keyword = $this->last_keyword;
0 ignored issues
show
Bug introduced by
The property last_keyword does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
582
			switch ( $component ) {
583
			case 'VEVENT':
584
				$value = $this->cal[ $component ][ $this->event_count - 1 ][ $keyword ] . $value;
585
				break;
586
			case 'VTODO' :
587
				$value = $this->cal[ $component ][ $this->todo_count - 1 ][ $keyword ] . $value;
588
				break;
589
			}
590
		}
591
592
		/*
593
		 * Some events have a specific timezone set in their start/end date,
594
		 * and it may or may not be different than the calendar timzeone.
595
		 * Valid formats include:
596
		 * DTSTART;TZID=Pacific Standard Time:20141219T180000
597
		 * DTEND;TZID=Pacific Standard Time:20141219T200000
598
		 * EXDATE:19960402T010000Z,19960403T010000Z,19960404T010000Z
599
		 * EXDATE;VALUE=DATE:2015050
600
		 * EXDATE;TZID=America/New_York:20150424T170000
601
		 * EXDATE;TZID=Pacific Standard Time:20120615T140000,20120629T140000,20120706T140000
602
		 */
603
604
		// Always store EXDATE as an array
605
		if ( stristr( $keyword, 'EXDATE' ) ) {
606
			$value = explode( ',', $value );
607
		}
608
609
		// Adjust DTSTART, DTEND, and EXDATE according to their TZID if set
610
		if ( strpos( $keyword, ';' ) && ( stristr( $keyword, 'DTSTART' ) || stristr( $keyword, 'DTEND' ) || stristr( $keyword, 'EXDATE' ) || stristr( $keyword, 'RECURRENCE-ID' ) ) ) {
611
			$keyword = explode( ';', $keyword );
612
613
			$tzid = false;
614
			if ( 2 == count( $keyword ) ) {
615
				$tparam = $keyword[1];
616
617
				if ( strpos( $tparam, "TZID" ) !== false ) {
618
					$tzid = $this->timezone_from_string( str_replace( 'TZID=', '', $tparam ) );
619
				}
620
			}
621
622
			// Normalize all times to default UTC
623
			if ( $tzid ) {
624
				$adjusted_times = array();
625
				foreach ( (array) $value as $v ) {
626
					try {
627
						$adjusted_time = new DateTime( $v, $tzid );
628
						$adjusted_time->setTimeZone( new DateTimeZone( 'UTC' ) );
629
						$adjusted_times[] = $adjusted_time->format('Ymd\THis');
630
					} catch ( Exception $e ) {
631
						// Invalid argument to DateTime
632
						return;
633
					}
634
				}
635
				$value = $adjusted_times;
636
			}
637
638
			// Format for adding to event
639
			$keyword = $keyword[0];
640
			if ( 'EXDATE' != $keyword ) {
641
				$value = implode( (array) $value );
642
			}
643
		}
644
645
		foreach ( (array) $value as $v ) {
646
			switch ($component) {
647 View Code Duplication
				case 'VTODO':
648
					if ( 'EXDATE' == $keyword ) {
649
						$this->cal[ $component ][ $this->todo_count - 1 ][ $keyword ][] = $v;
650
					} else {
651
						$this->cal[ $component ][ $this->todo_count - 1 ][ $keyword ] = $v;
652
					}
653
					break;
654 View Code Duplication
				case 'VEVENT':
655
					if ( 'EXDATE' == $keyword ) {
656
						$this->cal[ $component ][ $this->event_count - 1 ][ $keyword ][] = $v;
657
					} else {
658
						$this->cal[ $component ][ $this->event_count - 1 ][ $keyword ] = $v;
659
					}
660
					break;
661
				default:
662
					$this->cal[ $component ][ $keyword ] = $v;
663
					break;
664
			}
665
		}
666
		$this->last_keyword = $keyword;
667
	}
668
669
	/**
670
	 * Escape strings with wp_kses, allow links
671
	 *
672
	 * @param string $string (default: '')
673
	 * @return string
674
	 */
675
	public function escape( $string = '' ) {
676
		// Unfold content lines per RFC 5545
677
		$string = str_replace( "\n\t", '', $string );
678
		$string = str_replace( "\n ", '', $string );
679
680
		$allowed_html = array(
681
			'a' => array(
682
				'href'  => array(),
683
				'title' => array()
684
			)
685
		);
686
687
		$allowed_tags = '';
688
		foreach ( array_keys( $allowed_html ) as $tag ) {
689
			$allowed_tags .= "<{$tag}>";
690
		}
691
692
		// Running strip_tags() first with allowed tags to get rid of remaining gallery markup, etc
693
		// because wp_kses() would only htmlentity'fy that. Then still running wp_kses(), for extra
694
		// safety and good measure.
695
		return wp_kses( strip_tags( $string, $allowed_tags ), $allowed_html );
696
	}
697
698
	/**
699
	 * Render the events
700
	 *
701
	 * @param string $url (default: '')
702
	 * @param string $context (default: 'widget') or 'shortcode'
0 ignored issues
show
Bug introduced by
There is no parameter named $context. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
703
	 * @return mixed bool|string false on failure, rendered HTML string on success.
704
	 */
705
	public function render( $url = '', $args = array() ) {
706
707
		$args = wp_parse_args( $args, array(
708
			'context' => 'widget',
709
			'number' => 5
710
		) );
711
712
		$events = $this->get_events( $url, $args['number'] );
713
714
		if ( empty( $events ) )
715
			return false;
716
717
		ob_start();
718
719
		if ( 'widget' == $args['context'] ) : ?>
720
		<ul class="upcoming-events">
721
			<?php foreach ( $events as $event ) : ?>
722
			<li>
723
				<strong class="event-summary"><?php echo $this->escape( stripslashes( $event['SUMMARY'] ) ); ?></strong>
724
				<span class="event-when"><?php echo $this->formatted_date( $event ); ?></span>
725
				<?php if ( ! empty( $event['LOCATION'] ) ) : ?>
726
					<span class="event-location"><?php echo $this->escape( stripslashes( $event['LOCATION'] ) ); ?></span>
727
				<?php endif; ?>
728
				<?php if ( ! empty( $event['DESCRIPTION'] ) ) : ?>
729
					<span class="event-description"><?php echo wp_trim_words( $this->escape( stripcslashes( $event['DESCRIPTION'] ) ) ); ?></span>
730
				<?php endif; ?>
731
			</li>
732
			<?php endforeach; ?>
733
		</ul>
734
		<?php endif;
735
736
		if ( 'shortcode' == $args['context'] ) : ?>
737
		<table class="upcoming-events">
738
			<thead>
739
				<tr>
740
					<th>Location</th>
741
					<th>When</th>
742
					<th>Summary</th>
743
					<th>Description</th>
744
				</tr>
745
			</thead>
746
			<tbody>
747
			<?php foreach ( $events as $event ) : ?>
748
				<tr>
749
					<td><?php echo $this->escape( stripslashes( $event['LOCATION'] ) ); ?></td>
750
					<td><?php echo $this->formatted_date( $event ); ?></td>
751
					<td><?php echo $this->escape( stripslashes( $event['SUMMARY'] ) ); ?></td>
752
					<td><?php echo wp_trim_words( $this->escape( stripcslashes( $event['DESCRIPTION'] ) ) ); ?></td>
753
				</tr>
754
			<?php endforeach; ?>
755
			</tbody>
756
		</table>
757
		<?php endif;
758
759
		$rendered = ob_get_clean();
760
761
		if ( empty( $rendered ) )
762
			return false;
763
764
		return $rendered;
765
	}
766
767
	public function formatted_date( $event ) {
768
769
		$date_format = get_option( 'date_format' );
770
		$time_format = get_option( 'time_format' );
771
		$start = strtotime( $event['DTSTART'] );
772
		$end = isset( $event['DTEND'] ) ? strtotime( $event['DTEND'] ) : false;
773
774
		$all_day = ( 8 == strlen( $event['DTSTART'] ) );
775
776
		if ( !$all_day && $this->timezone ) {
777
			try {
778
				$start_time = new DateTime( $event['DTSTART'] );
779
				$timezone_offset = $this->timezone->getOffset( $start_time );
780
				$start += $timezone_offset;
781
782
				if ( $end ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $end of type integer|false is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
783
					$end += $timezone_offset;
784
				}
785
			} catch ( Exception $e ) {
786
				// Invalid argument to DateTime
787
			}
788
		}
789
		$single_day = $end ? ( $end - $start ) <= DAY_IN_SECONDS : true;
790
791
		/* Translators: Date and time */
792
		$date_with_time = __( '%1$s at %2$s' , 'jetpack' );
793
		/* Translators: Two dates with a separator */
794
		$two_dates = __( '%1$s &ndash; %2$s' , 'jetpack' );
795
796
		// we'll always have the start date. Maybe with time
797 View Code Duplication
		if ( $all_day )
798
			$date = date_i18n( $date_format, $start );
799
		else
800
			$date = sprintf( $date_with_time, date_i18n( $date_format, $start ), date_i18n( $time_format, $start ) );
801
802
		// single day, timed
803
		if ( $single_day && ! $all_day && false !== $end )
804
			$date = sprintf( $two_dates, $date, date_i18n( $time_format, $end ) );
805
806
		// multi-day
807
		if ( ! $single_day ) {
808
809 View Code Duplication
			if ( $all_day ) {
810
				// DTEND for multi-day events represents "until", not "including", so subtract one minute
811
				$end_date = date_i18n( $date_format, $end - 60 );
812
			} else {
813
				$end_date = sprintf( $date_with_time, date_i18n( $date_format, $end ), date_i18n( $time_format, $end ) );
814
			}
815
816
			$date = sprintf( $two_dates, $date, $end_date );
817
818
		}
819
820
		return $date;
821
	}
822
823
	protected function sort_by_recent( $list ) {
824
		$dates = $sorted_list = array();
825
826
		foreach ( $list as $key => $row ) {
827
			$date = $row['DTSTART'];
828
			// pad some time onto an all day date
829
			if ( 8 === strlen( $date ) )
830
				$date .= 'T000000Z';
831
			$dates[$key] = $date;
832
		}
833
		asort( $dates );
834
		foreach( $dates as $key => $value ) {
835
			$sorted_list[$key] = $list[$key];
836
		}
837
		unset($list);
838
		return $sorted_list;
839
	}
840
841
}
842
843
844
/**
845
 * Wrapper function for iCalendarReader->get_events()
846
 *
847
 * @param string $url (default: '')
848
 * @return array
849
 */
850
function icalendar_get_events( $url = '', $count = 5 ) {
851
	// Find your calendar's address http://support.google.com/calendar/bin/answer.py?hl=en&answer=37103
852
	$ical = new iCalendarReader();
853
	return $ical->get_events( $url, $count );
854
}
855
856
/**
857
 * Wrapper function for iCalendarReader->render()
858
 *
859
 * @param string $url (default: '')
860
 * @param string $context (default: 'widget') or 'shortcode'
0 ignored issues
show
Bug introduced by
There is no parameter named $context. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
861
 * @return mixed bool|string false on failure, rendered HTML string on success.
862
 */
863
function icalendar_render_events( $url = '', $args = array() ) {
864
	$ical = new iCalendarReader();
865
	return $ical->render( $url, $args );
866
}
867