Completed
Push — master ( 71159f...86ffa1 )
by
unknown
06:54
created

Google   C

Complexity

Total Complexity 57

Size/Duplication

Total Lines 420
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 8

Importance

Changes 5
Bugs 3 Features 0
Metric Value
wmc 57
lcom 2
cbo 8
dl 0
loc 420
rs 6.433
c 5
b 3
f 0

6 Methods

Rating   Name   Duplication   Size   Complexity  
C __construct() 0 32 8
A esc_google_calendar_id() 0 3 1
D get_events() 0 183 34
C make_request() 0 68 11
A get_client() 0 10 1
A get_service() 0 3 2

How to fix   Complexity   

Complex Class

Complex classes like Google often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Google, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Google Calendar Feed
4
 *
5
 * @package SimpleCalendar/Feeds
6
 */
7
namespace SimpleCalendar\Feeds;
8
9
use Carbon\Carbon;
10
use Carbon\CarbonInterval;
11
use SimpleCalendar\Abstracts\Calendar;
12
use SimpleCalendar\Abstracts\Feed;
13
use SimpleCalendar\Feeds\Admin\Google_Admin as Admin;
14
15
if ( ! defined( 'ABSPATH' ) ) {
16
	exit;
17
}
18
19
/**
20
 * Google Calendar feed.
21
 *
22
 * A feed using a simple Google API key to pull events from public calendars.
23
 *
24
 * @since 3.0.0
25
 */
26
class Google extends Feed {
27
28
29
	/**
30
	 * Google API Client.
31
	 *
32
	 * @access private
33
	 * @var \Google_Client
34
	 */
35
	protected $google_client = null;
36
37
	/**
38
	 * Client scopes.
39
	 *
40
	 * @access private
41
	 * @var array
42
	 */
43
	protected $google_client_scopes = array();
44
45
	/**
46
	 * Google Calendar API key.
47
	 *
48
	 * @access protected
49
	 * @var string
50
	 */
51
	protected $google_api_key = '';
52
53
	/**
54
	 * Google Calendar ID.
55
	 *
56
	 * @access protected
57
	 * @var string
58
	 */
59
	protected $google_calendar_id = '';
60
61
	/**
62
	 * Google recurring events query setting.
63
	 *
64
	 * @access protected
65
	 * @var string
66
	 */
67
	protected $google_events_recurring = '';
68
69
	/**
70
	 * Google search query setting.
71
	 *
72
	 * @access protected
73
	 * @var string
74
	 */
75
	protected $google_search_query = '';
76
77
	/**
78
	 * Google max results query setting.
79
	 *
80
	 * @access protected
81
	 * @var int
82
	 */
83
	protected $google_max_results = 2500;
84
85
	/**
86
	 * Set properties.
87
	 *
88
	 * @since 3.0.0
89
	 *
90
	 * @param string|Calendar $calendar
91
	 * @param bool $load_admin
92
	 */
93
	public function __construct( $calendar = '', $load_admin = true ) {
94
95
		parent::__construct( $calendar );
96
97
		$this->type = 'google';
98
		$this->name = __( 'Google Calendar', 'google-calendar-events' );
99
100
		// Google client config.
101
		$settings = get_option( 'simple-calendar_settings_feeds' );
102
		$this->google_api_key = isset( $settings['google']['api_key'] ) ? esc_attr( $settings['google']['api_key'] ) : '';
103
		$this->google_client_scopes = array( \Google_Service_Calendar::CALENDAR_READONLY );
104
		$this->google_client = $this->get_client();
105
106
		if ( $this->post_id > 0 ) {
107
108
			// Google query args.
109
			$this->google_calendar_id       = $this->esc_google_calendar_id( get_post_meta( $this->post_id, '_google_calendar_id', true ) );
110
			$this->google_events_recurring  = esc_attr( get_post_meta( $this->post_id, '_google_events_recurring', true ) );
111
            // note that google_search_query is used in a URL param and not as HTML output, so don't use esc_attr() on it
0 ignored issues
show
Coding Style introduced by
Tabs must be used to indent lines; spaces are not allowed
Loading history...
112
			$this->google_search_query      = get_post_meta( $this->post_id, '_google_events_search_query', true );
113
			$this->google_max_results       = max( absint( get_post_meta( $this->post_id, '_google_events_max_results', true ) ), 1 );
114
115
			if ( ! is_admin() || defined( 'DOING_AJAX' ) ) {
116
				$this->events = ! empty( $this->google_api_key ) ? $this->get_events() : array();
0 ignored issues
show
Documentation Bug introduced by
It seems like !empty($this->google_api...>get_events() : array() can also be of type string. However, the property $events is declared as type array. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
117
			}
118
		}
119
120
		if ( is_admin() && $load_admin ) {
121
			$admin = new Admin( $this, $this->google_api_key, $this->google_calendar_id );
122
			$this->settings = $admin->settings_fields();
123
		}
124
	}
125
126
	/**
127
	 * Decode a calendar id.
128
	 *
129
	 * @since  3.0.0
130
	 *
131
	 * @param  string $id Base64 encoded id.
132
	 *
133
	 * @return string
134
	 */
135
	public function esc_google_calendar_id( $id ) {
136
		return base64_decode( $id );
137
	}
138
139
	/**
140
	 * Get events feed.
141
	 *
142
	 * Normalizes Google data into a standard array object to list events.
143
	 *
144
	 * @since  3.0.0
145
	 *
146
	 * @return string|array
147
	 */
148
	public function get_events() {
149
150
		$calendar = get_transient( '_simple-calendar_feed_id_' . strval( $this->post_id ) . '_' . $this->type );
151
152
		if ( empty( $calendar ) && ! empty( $this->google_calendar_id ) ) {
153
154
			$error = '';
155
156
			try {
157
				$response = $this->make_request( $this->google_calendar_id );
158
			} catch ( \Exception $e ) {
159
				$error .= $e->getMessage();
160
			}
161
162
			if ( empty( $error ) && isset( $response['events'] ) && isset( $response['timezone'] ) ) {
163
164
				$calendar = array_merge( $response, array( 'events' => array() ) );
165
166
				// If no timezone has been set, use calendar feed.
167
				if ( 'use_calendar' == $this->timezone_setting ) {
168
					$this->timezone = $calendar['timezone'];
169
				}
170
171
				$source = isset( $response['title'] ) ? sanitize_text_field( $response['title'] ) : '';
172
173
				if ( ! empty( $response['events'] ) && is_array( $response['events'] ) ) {
174
					foreach ( $response['events'] as $event ) {
175
						if ( $event instanceof \Google_Service_Calendar_Event ) {
176
177
							// Visibility and status.
178
							// Public calendars may have private events which can't be properly accessed by simple api key method.
179
							// Also want to skip cancelled events (single occurences deleted from repeating events)
180
							$visibility = $event->getVisibility();
181
							$status = $event->getStatus();
182
							if ( $this->type == 'google' && ( $visibility == 'private' || $visibility == 'confidential' || $status == 'cancelled' ) ) {
0 ignored issues
show
introduced by
Found "== '". Use Yoda Condition checks, you must
Loading history...
183
								continue;
184
							}
185
186
							// Event title & description.
187
							$title = strip_tags( $event->getSummary() );
188
							$title = sanitize_text_field( iconv( mb_detect_encoding( $title, mb_detect_order(), true ), 'UTF-8', $title ) );
189
							$description = wp_kses_post( iconv( mb_detect_encoding( $event->getDescription(), mb_detect_order(), true ), 'UTF-8', $event->getDescription() ) );
190
191
							$whole_day = false;
192
193
							// Event start properties.
194
							if( 'use_calendar' == $this->timezone_setting ) {
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
introduced by
No space before opening parenthesis is prohibited
Loading history...
195
								$start_timezone = ! $event->getStart()->timeZone ? $calendar['timezone'] : $event->getStart()->timeZone;
196
							} else {
197
								$start_timezone = $this->timezone;
198
							}
199
200
							if ( is_null( $event->getStart()->dateTime ) ) {
201
								// Whole day event.
202
								$date = Carbon::parse( $event->getStart()->date );
203
								$google_start = Carbon::createFromDate( $date->year, $date->month, $date->day, $start_timezone )->startOfDay()->addSeconds( 59 );
204
								$google_start_utc = Carbon::createFromDate( $date->year, $date->month, $date->day, 'UTC' )->startOfDay()->addSeconds( 59 );
205
								$whole_day = true;
206
							} else {
207
								$date = Carbon::parse( $event->getStart()->dateTime );
208
								$google_start     = Carbon::create( $date->year, $date->month, $date->day, $date->hour, $date->minute, $date->second, $start_timezone );
209
								$google_start_utc = Carbon::create( $date->year, $date->month, $date->day, $date->hour, $date->minute, $date->second, 'UTC' );
210
							}
211
							// Start.
212
							$start = $google_start->getTimestamp();
213
							// Start UTC.
214
							$start_utc = $google_start_utc->getTimestamp();
215
216
							$end = $end_utc = $end_timezone = '';
217
							$span = 0;
218
							if ( false == $event->getEndTimeUnspecified() ) {
219
220
								// Event end properties.
221
								if( 'use_calendar' == $this->timezone_setting ) {
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
introduced by
No space before opening parenthesis is prohibited
Loading history...
222
									$end_timezone = ! $event->getEnd()->timeZone ? $calendar['timezone'] : $event->getEnd()->timeZone;
223
								} else {
224
									$end_timezone = $this->timezone;
225
								}
226
227
								if ( is_null( $event->getEnd()->dateTime ) ) {
228
									// Whole day event.
229
									$date           = Carbon::parse( $event->getEnd()->date );
230
									$google_end     = Carbon::createFromDate( $date->year, $date->month, $date->day, $end_timezone )->startOfDay()->subSeconds( 59 );
231
									$google_end_utc = Carbon::createFromDate( $date->year, $date->month, $date->day, 'UTC' )->startOfDay()->subSeconds( 59 );
232
								} else {
233
									$date           = Carbon::parse( $event->getEnd()->dateTime );
234
									$google_end     = Carbon::create( $date->year, $date->month, $date->day, $date->hour, $date->minute, $date->second, $end_timezone );
235
									$google_end_utc = Carbon::create( $date->year, $date->month, $date->day, $date->hour, $date->minute, $date->second, 'UTC' );
236
								}
237
								// End.
238
								$end = $google_end->getTimestamp();
239
								// End UTC.
240
								$end_utc = $google_end_utc->getTimestamp();
241
242
								// Count multiple days.
243
								$span = $google_start->setTimezone( $calendar['timezone'] )->diffInDays( $google_end->setTimezone( $calendar['timezone'] ) );
244
245
								if ( $span == 0 ) {
0 ignored issues
show
introduced by
Found "== 0". Use Yoda Condition checks, you must
Loading history...
246
									if ( $google_start->toDateString() !== $google_end->toDateString() ) {
247
										$span = 1;
248
									}
249
								}
250
							}
251
252
							// Multiple days.
253
							$multiple_days = $span > 0 ? $span : false;
254
255
							// Google cannot have two different locations for start and end time.
256
							$start_location = $end_location = $event->getLocation();
257
258
							// Recurring event.
259
							$recurrence = $event->getRecurrence();
260
							$recurring_id = $event->getRecurringEventId();
261
							if ( ! $recurrence && $recurring_id ) {
262
								$recurrence = true;
263
							}
264
265
							// Event link.
266
							if ( 'use_calendar' == $this->timezone_setting ) {
267
								$link = add_query_arg( array( 'ctz' => $this->timezone ), $event->getHtmlLink() );
268
							} else {
269
								$link = $event->getHtmlLink();
270
							}
271
272
							// Build the event.
273
							$calendar['events'][ intval( $start ) ][] = array(
274
								'type'           => 'google-calendar',
275
								'source'         => $source,
276
								'title'          => $title,
277
								'description'    => $description,
278
								'link'           => $link,
279
								'visibility'     => $visibility,
280
								'uid'            => $event->getICalUID(),
281
								'calendar'       => $this->post_id,
282
								'timezone'       => $this->timezone,
283
								'start'          => $start,
284
								'start_utc'      => $start_utc,
285
								'start_timezone' => $start_timezone,
286
								'start_location' => $start_location,
287
								'end'            => $end,
288
								'end_utc'        => $end_utc,
289
								'end_timezone'   => $end_timezone,
290
								'end_location'   => $end_location,
291
								'whole_day'      => $whole_day,
292
								'multiple_days'  => $multiple_days,
293
								'recurrence'     => $recurrence,
294
								'template'       => $this->events_template,
295
							);
296
297
						}
298
					}
299
300
					if ( ! empty( $calendar['events'] ) ) {
301
302
						ksort( $calendar['events'], SORT_NUMERIC );
303
304
						set_transient(
305
							'_simple-calendar_feed_id_' . strval( $this->post_id ) . '_' . $this->type,
306
							$calendar,
307
							max( absint( $this->cache ), 1 ) // Since a value of 0 means forever we set the minimum here to 1 if the user has set it to be 0
308
						);
309
					}
310
				}
311
312
			} else {
313
314
				$message  = __( 'While trying to retrieve events, Google returned an error:', 'google-calendar-events' );
315
				$message .= '<br><br>' . $error . '<br><br>';
316
				$message .= __( 'Please ensure that both your Google Calendar ID and API Key are valid and that the Google Calendar you want to display is public.', 'google-calendar-events' ) . '<br><br>';
317
				$message .= __( 'Only you can see this notice.', 'google-calendar-events' );
318
319
				return $message;
320
			}
321
322
		}
323
324
		// If no timezone has been set, use calendar feed.
325
		if ( 'use_calendar' == $this->timezone_setting && isset( $calendar['timezone'] ) ) {
326
			$this->timezone = $calendar['timezone'];
327
		}
328
329
		return isset( $calendar['events'] ) ? $calendar['events'] : array();
330
	}
331
332
	/**
333
	 * Query Google Calendar.
334
	 *
335
	 * @since  3.0.0
336
	 *
337
	 * @param  string $id        A valid Google Calendar ID.
338
	 * @param  int    $time_min  Lower bound timestamp.
339
	 * @param  int    $time_max  Upper bound timestamp.
340
	 *
341
	 * @return array
342
	 *
343
	 * @throws \Exception On request failure will throw an exception from Google.
344
	 */
345
	public function make_request( $id = '', $time_min = 0, $time_max = 0 ) {
346
347
		$calendar = array();
348
		$google = $this->get_service();
349
350
		if ( ! is_null( $google ) && ! empty( $id ) ) {
351
352
			// Build the request args.
353
			$args = array();
354
355
			// Expand recurring events.
356
			if ( $this->google_events_recurring == 'show' ) {
0 ignored issues
show
introduced by
Found "== '". Use Yoda Condition checks, you must
Loading history...
357
				$args['singleEvents'] = true;
358
			}
359
360
			// Query events using search terms.
361
			if ( ! empty( $this->google_search_query ) ) {
362
				$args['q'] = rawurlencode( $this->google_search_query );
363
			}
364
365
			// Max results to query.
366
			$args['maxResults'] = strval( min( absint( $this->google_max_results ), 2500 ) );
367
368
			// Specify a timezone.
369
			$timezone = '';
370
			if ( 'use_calendar' != get_post_meta( $this->post_id, '_feed_timezone_setting', true ) ) {
371
				$args['timeZone'] = $timezone = $this->timezone;
372
			}
373
374
			// Lower bound (inclusive) for an event's end time to filter by.
375
			$earliest_event = intval( $this->time_min );
376
			if ( $earliest_event > 0 ) {
377
				$timeMin = Carbon::now();
378
				if ( ! empty( $timezone ) ) {
379
					$timeMin->setTimezone( $timezone );
380
				}
381
				$timeMin->setTimestamp( $earliest_event );
382
				$args['timeMin'] = $timeMin->toRfc3339String();
383
			}
384
385
			// Upper bound (exclusive) for an event's start time to filter by.
386
			$latest_event = intval( $this->time_max );
387
			if ( $latest_event > 0 ) {
388
				$timeMax = Carbon::now();
389
				if ( ! empty( $timezone ) ) {
390
					$timeMax->setTimezone( $timezone );
391
				}
392
				$timeMax->setTimestamp( $latest_event );
393
				$args['timeMax'] = $timeMax->toRfc3339String();
394
			}
395
396
			// Query events in calendar.
397
			$response = $google->events->listEvents( $id, $args );
398
399
			if ( $response instanceof \Google_Service_Calendar_Events ) {
400
				$calendar = array(
401
					'id'            => $id,
402
					'title'         => $response->getSummary(),
403
					'description'   => $response->getDescription(),
404
					'timezone'      => $response->getTimeZone(),
405
					'url'           => esc_url( '//www.google.com/calendar/embed?src=' . $id ),
406
					'events'        => $response->getItems(),
407
				);
408
			}
409
		}
410
411
		return $calendar;
412
	}
413
414
	/**
415
	 * Google API Client.
416
	 *
417
	 * @since  3.0.0
418
	 * @access private
419
	 *
420
	 * @return \Google_Client
421
	 */
422
	private function get_client() {
423
424
		$client = new \Google_Client();
425
		$client->setApplicationName( 'Simple Calendar' );
426
		$client->setScopes( $this->google_client_scopes );
427
		$client->setDeveloperKey( $this->google_api_key );
428
		$client->setAccessType( 'online' );
429
430
		return $client;
431
	}
432
433
	/**
434
	 * Google Calendar Service.
435
	 *
436
	 * @since  3.0.0
437
	 * @access protected
438
	 *
439
	 * @return null|\Google_Service_Calendar
440
	 */
441
	protected function get_service() {
442
		return $this->google_client instanceof \Google_Client ? new \Google_Service_Calendar( $this->google_client ) : null;
443
	}
444
445
}
446