Completed
Push — unify/wordpress-post-widget ( 9319c3 )
by
unknown
09:34
created

Jetpack_Display_Posts_Widget__Base::form()   F

Complexity

Conditions 15
Paths 3584

Size

Total Lines 128
Code Lines 65

Duplication

Lines 6
Ratio 4.69 %

Importance

Changes 0
Metric Value
cc 15
eloc 65
nc 3584
nop 1
dl 6
loc 128
rs 2
c 0
b 0
f 0

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
 * For back-compat, the final widget class must be named
5
 * Jetpack_Display_Posts_Widget.
6
 *
7
 * For convenience, it's nice to have a widget class constructor with no
8
 * arguments. Otherwise, we have to register the widget with an instance
9
 * instead of a class name. This makes unregistering annoying.
10
 *
11
 * Both WordPress.com and Jetpack implement the final widget class by
12
 * extending this __Base class and adding data fetching and storage.
13
 *
14
 * This would be a bit cleaner with dependency injection, but we already
15
 * use mocking to test, so it's not a big win.
16
 */
17
abstract class Jetpack_Display_Posts_Widget__Base extends WP_Widget {
18
	/**
19
	 * @var string Remote service API URL prefix.
20
	 */
21
	public $service_url = 'https://public-api.wordpress.com/rest/v1.1/';
22
23 View Code Duplication
	public function __construct() {
24
		parent::__construct(
25
		// internal id
26
			'jetpack_display_posts_widget',
27
			/** This filter is documented in modules/widgets/facebook-likebox.php */
28
			apply_filters( 'jetpack_widget_name', __( 'Display WordPress Posts', 'jetpack' ) ),
29
			array(
30
				'description' => __( 'Displays a list of recent posts from another WordPress.com or Jetpack-enabled blog.', 'jetpack' ),
31
				'customize_selective_refresh' => true,
32
			)
33
		);
34
35
		if ( is_customize_preview() ) {
36
			add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
37
		}
38
	}
39
40
	/**
41
	 * Enqueue CSS and JavaScript.
42
	 *
43
	 * @since 4.0.0
44
	 */
45
	public function enqueue_scripts() {
46
		wp_enqueue_style( 'jetpack_display_posts_widget', plugins_url( 'wordpress-post-widget/style.css', __FILE__ ) );
47
	}
48
49
50
	// FETCH: Must implement
51
52
	/**
53
	 * @param string $url  The URL to fetch
54
	 * @param array  $args Optional. Request arguments.
55
	 *
56
	 * @return array|WP_Error
57
	 */
58
	abstract public function wpcom_json_api_get( $url, $args = array() );
59
60
	/**
61
	 * @param array $service_response
62
	 *
63
	 * @return array|WP_Error
64
	 */
65
	abstract public function wpcom_json_api_parse( $service_response );
66
67
68
	// DATA STORE: Must implement
69
70
	/**
71
	 * Gets blog data from the cache.
72
	 *
73
	 * @param string $site
74
	 *
75
	 * @return array|WP_Error
76
	 */
77
	abstract public function get_blog_data( $site );
78
79
	/**
80
	 * Update a widget instance.
81
	 *
82
	 * @param string $site The site to fetch the latest data for.
83
	 */
84
	abstract public function update_instance( $site );
85
86
87
	// WIDGET API
88
89
	/**
90
	 * Set up the widget display on the front end.
91
	 *
92
	 * @param array $args
93
	 * @param array $instance
94
	 */
95
	public function widget( $args, $instance ) {
96
		/** This action is documented in modules/widgets/gravatar-profile.php */
97
		do_action( 'jetpack_stats_extra', 'widget_view', 'display_posts' );
98
99
		// Enqueue front end assets.
100
		$this->enqueue_scripts();
101
102
		$content = $args['before_widget'];
103
104
		if ( empty( $instance['url'] ) ) {
105
			if ( current_user_can( 'manage_options' ) ) {
106
				$content .= '<p>';
107
				/* Translators: the "Blog URL" field mentioned is the input field labeled as such in the widget form. */
108
				$content .= esc_html__( 'The Blog URL is not properly setup in the widget.', 'jetpack' );
109
				$content .= '</p>';
110
			}
111
			$content .= $args['after_widget'];
112
113
			echo $content;
114
			return;
115
		}
116
117
		$data = $this->get_blog_data( $instance['url'] );
118
		// check for errors
119
		if ( is_wp_error( $data ) || empty( $data['site_info']['data'] ) ) {
120
			$content .= '<p>' . __( 'Cannot load blog information at this time.', 'jetpack' ) . '</p>';
121
			$content .= $args['after_widget'];
122
123
			echo $content;
124
			return;
125
		}
126
127
		$site_info = $data['site_info']['data'];
128
129
		if ( ! empty( $instance['title'] ) ) {
130
			/** This filter is documented in core/src/wp-includes/default-widgets.php */
131
			$instance['title'] = apply_filters( 'widget_title', $instance['title'] );
132
			$content .= $args['before_title'] . esc_html( $instance['title'] . ': ' . $site_info->name ) . $args['after_title'];
133
		}
134
		else {
135
			$content .= $args['before_title'] . esc_html( $site_info->name ) . $args['after_title'];
136
		}
137
138
		$content .= '<div class="jetpack-display-remote-posts">';
139
140
		if ( is_wp_error( $data['posts']['data'] ) || empty( $data['posts']['data'] ) ) {
141
			$content .= '<p>' . __( 'Cannot load blog posts at this time.', 'jetpack' ) . '</p>';
142
			$content .= '</div><!-- .jetpack-display-remote-posts -->';
143
			$content .= $args['after_widget'];
144
145
			echo $content;
146
			return;
147
		}
148
149
		$posts_list = $data['posts']['data'];
150
151
		/**
152
		 * Show only as much posts as we need. If we have less than configured amount,
153
		 * we must show only that much posts.
154
		 */
155
		$number_of_posts = min( $instance['number_of_posts'], count( $posts_list ) );
156
157
		for ( $i = 0; $i < $number_of_posts; $i ++ ) {
158
			$single_post = $posts_list[ $i ];
159
			$post_title  = ( $single_post['title'] ) ? $single_post['title'] : '( No Title )';
160
161
			$target = '';
162
			if ( isset( $instance['open_in_new_window'] ) && $instance['open_in_new_window'] == true ) {
163
				$target = ' target="_blank" rel="noopener"';
164
			}
165
			$content .= '<h4><a href="' . esc_url( $single_post['url'] ) . '"' . $target . '>' . esc_html( $post_title ) . '</a></h4>' . "\n";
166
			if ( ( $instance['featured_image'] == true ) && ( ! empty ( $single_post['featured_image'] ) ) ) {
167
				$featured_image = $single_post['featured_image'];
168
				/**
169
				 * Allows setting up custom Photon parameters to manipulate the image output in the Display Posts widget.
170
				 *
171
				 * @see    https://developer.wordpress.com/docs/photon/
172
				 *
173
				 * @module widgets
174
				 *
175
				 * @since  3.6.0
176
				 *
177
				 * @param array $args Array of Photon Parameters.
178
				 */
179
				$image_params = apply_filters( 'jetpack_display_posts_widget_image_params', array() );
180
				$content .= '<a title="' . esc_attr( $post_title ) . '" href="' . esc_url( $single_post['url'] ) . '"' . $target . '><img src="' . jetpack_photon_url( $featured_image, $image_params ) . '" alt="' . esc_attr( $post_title ) . '"/></a>';
181
			}
182
183
			if ( $instance['show_excerpts'] == true ) {
184
				$content .= $single_post['excerpt'];
185
			}
186
		}
187
188
		$content .= '</div><!-- .jetpack-display-remote-posts -->';
189
		$content .= $args['after_widget'];
190
191
		/**
192
		 * Filter the WordPress Posts widget content.
193
		 *
194
		 * @module widgets
195
		 *
196
		 * @since 4.7.0
197
		 *
198
		 * @param string $content Widget content.
199
		 */
200
		echo apply_filters( 'jetpack_display_posts_widget_content', $content );
201
	}
202
203
	/**
204
	 * Display the widget administration form.
205
	 *
206
	 * @param array $instance Widget instance configuration.
207
	 *
208
	 * @return string|void
209
	 */
210
	public function form( $instance ) {
211
212
		/**
213
		 * Initialize widget configuration variables.
214
		 */
215
		$title              = ( isset( $instance['title'] ) ) ? $instance['title'] : __( 'Recent Posts', 'jetpack' );
216
		$url                = ( isset( $instance['url'] ) ) ? $instance['url'] : '';
217
		$number_of_posts    = ( isset( $instance['number_of_posts'] ) ) ? $instance['number_of_posts'] : 5;
218
		$open_in_new_window = ( isset( $instance['open_in_new_window'] ) ) ? $instance['open_in_new_window'] : false;
219
		$featured_image     = ( isset( $instance['featured_image'] ) ) ? $instance['featured_image'] : false;
220
		$show_excerpts      = ( isset( $instance['show_excerpts'] ) ) ? $instance['show_excerpts'] : false;
221
222
223
		/**
224
		 * Check if the widget instance has errors available.
225
		 *
226
		 * Only do so if a URL is set.
227
		 */
228
		$update_errors = array();
229
230
		if ( ! empty( $url ) ) {
231
			$data          = $this->get_blog_data( $url );
232
			$update_errors = $this->extract_errors_from_blog_data( $data );
233
		}
234
235
		?>
236
		<p>
237
			<label for="<?php echo $this->get_field_id( 'title' ); ?>"><?php _e( 'Title:', 'jetpack' ); ?></label>
238
			<input class="widefat" id="<?php echo $this->get_field_id( 'title' ); ?>" name="<?php echo $this->get_field_name( 'title' ); ?>" type="text" value="<?php echo esc_attr( $title ); ?>" />
239
		</p>
240
241
		<p>
242
			<label for="<?php echo $this->get_field_id( 'url' ); ?>"><?php _e( 'Blog URL:', 'jetpack' ); ?></label>
243
			<input class="widefat" id="<?php echo $this->get_field_id( 'url' ); ?>" name="<?php echo $this->get_field_name( 'url' ); ?>" type="text" value="<?php echo esc_attr( $url ); ?>" />
244
			<i>
245
				<?php _e( "Enter a WordPress.com or Jetpack WordPress site URL.", 'jetpack' ); ?>
246
			</i>
247
			<?php
248
			/**
249
			 * Show an error if the URL field was left empty.
250
			 *
251
			 * The error is shown only when the widget was already saved.
252
			 */
253 View Code Duplication
			if ( empty( $url ) && ! preg_match( '/__i__|%i%/', $this->id ) ) {
254
				?>
255
				<br />
256
				<i class="error-message"><?php echo __( 'You must specify a valid blog URL!', 'jetpack' ); ?></i>
257
				<?php
258
			}
259
			?>
260
		</p>
261
		<p>
262
			<label for="<?php echo $this->get_field_id( 'number_of_posts' ); ?>"><?php _e( 'Number of Posts to Display:', 'jetpack' ); ?></label>
263
			<select name="<?php echo $this->get_field_name( 'number_of_posts' ); ?>">
264
				<?php
265
				for ( $i = 1; $i <= 10; $i ++ ) {
266
					echo '<option value="' . $i . '" ' . selected( $number_of_posts, $i ) . '>' . $i . '</option>';
267
				}
268
				?>
269
			</select>
270
		</p>
271
		<p>
272
			<label for="<?php echo $this->get_field_id( 'open_in_new_window' ); ?>"><?php _e( 'Open links in new window/tab:', 'jetpack' ); ?></label>
273
			<input type="checkbox" name="<?php echo $this->get_field_name( 'open_in_new_window' ); ?>" <?php checked( $open_in_new_window, 1 ); ?> />
274
		</p>
275
		<p>
276
			<label for="<?php echo $this->get_field_id( 'featured_image' ); ?>"><?php _e( 'Show Featured Image:', 'jetpack' ); ?></label>
277
			<input type="checkbox" name="<?php echo $this->get_field_name( 'featured_image' ); ?>" <?php checked( $featured_image, 1 ); ?> />
278
		</p>
279
		<p>
280
			<label for="<?php echo $this->get_field_id( 'show_excerpts' ); ?>"><?php _e( 'Show Excerpts:', 'jetpack' ); ?></label>
281
			<input type="checkbox" name="<?php echo $this->get_field_name( 'show_excerpts' ); ?>" <?php checked( $show_excerpts, 1 ); ?> />
282
		</p>
283
284
		<?php
285
286
		/**
287
		 * Show error messages.
288
		 */
289
		if ( ! empty( $update_errors['message'] ) ) {
290
291
			/**
292
			 * Prepare the error messages.
293
			 */
294
295
			$where_message = '';
296
			switch ( $update_errors['where'] ) {
297
				case 'posts':
298
					$where_message .= __( 'An error occurred while downloading blog posts list', 'jetpack' );
299
					break;
300
301
				/**
302
				 * If something else, beside `posts` and `site_info` broke,
303
				 * don't handle it and default to blog `information`,
304
				 * as it is generic enough.
305
				 */
306
				case 'site_info':
307
				default:
308
					$where_message .= __( 'An error occurred while downloading blog information', 'jetpack' );
309
					break;
310
			}
311
312
			?>
313
			<p class="error-message">
314
				<?php echo esc_html( $where_message ); ?>:
315
				<br />
316
				<i>
317
					<?php echo esc_html( $update_errors['message'] ); ?>
318
					<?php
319
					/**
320
					 * If there is any debug - show it here.
321
					 */
322
					if ( ! empty( $update_errors['debug'] ) ) {
323
						?>
324
						<br />
325
						<br />
326
						<?php esc_html_e( 'Detailed information', 'jetpack' ); ?>:
327
						<br />
328
						<?php echo esc_html( $update_errors['debug'] ); ?>
329
						<?php
330
					}
331
					?>
332
				</i>
333
			</p>
334
335
			<?php
336
		}
337
	}
338
339
	public function update( $new_instance, $old_instance ) {
340
341
		$instance          = array();
342
		$instance['title'] = ( ! empty( $new_instance['title'] ) ) ? strip_tags( $new_instance['title'] ) : '';
343
		$instance['url']   = ( ! empty( $new_instance['url'] ) ) ? strip_tags( trim( $new_instance['url'] ) ) : '';
344
		$instance['url']   = preg_replace( "!^https?://!is", "", $instance['url'] );
345
		$instance['url']   = untrailingslashit( $instance['url'] );
346
347
348
		/**
349
		 * Check if the URL should be with or without the www prefix before saving.
350
		 */
351
		if ( ! empty( $instance['url'] ) ) {
352
			$blog_data = $this->fetch_blog_data( $instance['url'], array(), true );
353
354
			if ( is_wp_error( $blog_data['site_info']['error'] ) && 'www.' === substr( $instance['url'], 0, 4 ) ) {
355
				$blog_data = $this->fetch_blog_data( substr( $instance['url'], 4 ), array(), true );
356
357
				if ( ! is_wp_error( $blog_data['site_info']['error'] ) ) {
358
					$instance['url'] = substr( $instance['url'], 4 );
359
				}
360
			}
361
		}
362
363
		$instance['number_of_posts']    = ( ! empty( $new_instance['number_of_posts'] ) ) ? intval( $new_instance['number_of_posts'] ) : '';
364
		$instance['open_in_new_window'] = ( ! empty( $new_instance['open_in_new_window'] ) ) ? true : '';
365
		$instance['featured_image']     = ( ! empty( $new_instance['featured_image'] ) ) ? true : '';
366
		$instance['show_excerpts']      = ( ! empty( $new_instance['show_excerpts'] ) ) ? true : '';
367
368
		/**
369
		 * If there is no cache entry for the specified URL, run a forced update.
370
		 *
371
		 * @see get_blog_data Returns WP_Error if the cache is empty, which is what is needed here.
372
		 */
373
		$cached_data = $this->get_blog_data( $instance['url'] );
374
375
		if ( is_wp_error( $cached_data ) ) {
376
			$this->update_instance( $instance['url'] );
377
		}
378
379
		return $instance;
380
	}
381
382
383
	// DATA PROCESSING
384
385
	/**
386
	 * Expiring transients have a name length maximum of 45 characters,
387
	 * so this function returns an abbreviated MD5 hash to use instead of
388
	 * the full URI.
389
	 *
390
	 * @param string $site Site to get the hash for.
391
	 *
392
	 * @return string
393
	 */
394
	public function get_site_hash( $site ) {
395
		return substr( md5( $site ), 0, 21 );
396
	}
397
398
	/**
399
	 * Fetch a remote service endpoint and parse it.
400
	 *
401
	 * Timeout is set to 15 seconds right now, because sometimes the WordPress API
402
	 * takes more than 5 seconds to fully respond.
403
	 *
404
	 * Caching is used here so we can avoid re-downloading the same endpoint
405
	 * in a single request.
406
	 *
407
	 * @param string $endpoint Parametrized endpoint to call.
408
	 *
409
	 * @param int    $timeout  How much time to wait for the API to respond before failing.
410
	 *
411
	 * @return array|WP_Error
412
	 */
413
	public function fetch_service_endpoint( $endpoint, $timeout = 15 ) {
414
415
		/**
416
		 * Holds endpoint request cache.
417
		 */
418
		static $cache = array();
419
420
		if ( ! isset( $cache[ $endpoint ] ) ) {
421
			$raw_data           = $this->wpcom_json_api_get( $this->service_url . ltrim( $endpoint, '/' ), array( 'timeout' => $timeout ) );
422
			$cache[ $endpoint ] = $this->parse_service_response( $raw_data );
423
		}
424
425
		return $cache[ $endpoint ];
426
	}
427
428
	/**
429
	 * Parse data from service response.
430
	 * Do basic error handling for general service and data errors
431
	 *
432
	 * @param array $service_response Response from the service.
433
	 *
434
	 * @return array|WP_Error
435
	 */
436
	public function parse_service_response( $service_response ) {
437
		/**
438
		 * If there is an error, we add the error message to the parsed response
439
		 */
440
		if ( is_wp_error( $service_response ) ) {
441
			return new WP_Error(
442
				'general_error',
443
				__( 'An error occurred fetching the remote data.', 'jetpack' ),
444
				$service_response->get_error_messages()
0 ignored issues
show
Bug introduced by
The method get_error_messages cannot be called on $service_response (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
445
			);
446
		}
447
448
		$parsed_data = $this->wpcom_json_api_parse( $service_response );
449
450
		/**
451
		 * Check for errors in the parsed body.
452
		 */
453
		if ( isset( $parsed_data->error ) ) {
454
			return new WP_Error(
455
				'remote_error',
456
				__( 'It looks like the WordPress site URL is incorrectly configured. Please check it in your widget settings.', 'jetpack' ),
457
				$parsed_data->error
458
			);
459
		}
460
461
462
		/**
463
		 * No errors found, return parsed data.
464
		 */
465
		return $parsed_data;
466
	}
467
468
	/**
469
	 * Fetch site information from the WordPress public API
470
	 *
471
	 * @param string $site URL of the site to fetch the information for.
472
	 *
473
	 * @return array|WP_Error
474
	 */
475
	public function fetch_site_info( $site ) {
476
477
		$response = $this->fetch_service_endpoint( sprintf( '/sites/%s', urlencode( $site ) ) );
478
479
		return $response;
480
	}
481
482
	/**
483
	 * Parse external API response from the site info call and handle errors if they occur.
484
	 *
485
	 * @param array|WP_Error $service_response The raw response to be parsed.
486
	 *
487
	 * @return array|WP_Error
488
	 */
489 View Code Duplication
	public function parse_site_info_response( $service_response ) {
490
491
		/**
492
		 * If the service returned an error, we pass it on.
493
		 */
494
		if ( is_wp_error( $service_response ) ) {
495
			return $service_response;
496
		}
497
498
		/**
499
		 * Check if the service returned proper site information.
500
		 */
501
		if ( ! isset( $service_response->ID ) ) {
502
			return new WP_Error(
503
				'no_site_info',
504
				__( 'Invalid site information returned from remote.', 'jetpack' ),
505
				'No site ID present in the response.'
506
			);
507
		}
508
509
		return $service_response;
510
	}
511
512
	/**
513
	 * Fetch list of posts from the WordPress public API.
514
	 *
515
	 * @param int $site_id The site to fetch the posts for.
516
	 *
517
	 * @return array|WP_Error
518
	 */
519
	public function fetch_posts_for_site( $site_id ) {
520
521
		$response = $this->fetch_service_endpoint(
522
			sprintf(
523
				'/sites/%1$d/posts/%2$s',
524
				$site_id,
525
				/**
526
				 * Filters the parameters used to fetch for posts in the Display Posts Widget.
527
				 *
528
				 * @see    https://developer.wordpress.com/docs/api/1.1/get/sites/%24site/posts/
529
				 *
530
				 * @module widgets
531
				 *
532
				 * @since  3.6.0
533
				 *
534
				 * @param string $args Extra parameters to filter posts returned from the WordPress.com REST API.
535
				 */
536
				apply_filters( 'jetpack_display_posts_widget_posts_params', '' )
537
			)
538
		);
539
540
		return $response;
541
	}
542
543
	/**
544
	 * Parse external API response from the posts list request and handle errors if any occur.
545
	 *
546
	 * @param object|WP_Error $service_response The raw response to be parsed.
547
	 *
548
	 * @return array|WP_Error
549
	 */
550 View Code Duplication
	public function parse_posts_response( $service_response ) {
551
552
		/**
553
		 * If the service returned an error, we pass it on.
554
		 */
555
		if ( is_wp_error( $service_response ) ) {
556
			return $service_response;
557
		}
558
559
		/**
560
		 * Check if the service returned proper posts array.
561
		 */
562
		if ( ! isset( $service_response->posts ) || ! is_array( $service_response->posts ) ) {
563
			return new WP_Error(
564
				'no_posts',
565
				__( 'No posts data returned by remote.', 'jetpack' ),
566
				'No posts information set in the returned data.'
567
			);
568
		}
569
570
		/**
571
		 * Format the posts to preserve storage space.
572
		 */
573
574
		return $this->format_posts_for_storage( $service_response );
575
	}
576
577
	/**
578
	 * Format the posts for better storage. Drop all the data that is not used.
579
	 *
580
	 * @param object $parsed_data Array of posts returned by the APIs.
581
	 *
582
	 * @return array Formatted posts or an empty array if no posts were found.
583
	 */
584
	public function format_posts_for_storage( $parsed_data ) {
585
586
		$formatted_posts = array();
587
588
		/**
589
		 * Only go through the posts list if we have valid posts array.
590
		 */
591
		if ( isset( $parsed_data->posts ) && is_array( $parsed_data->posts ) ) {
592
593
			/**
594
			 * Loop through all the posts and format them appropriately.
595
			 */
596
			foreach ( $parsed_data->posts as $single_post ) {
597
598
				$prepared_post = array(
599
					'title'          => $single_post->title ? $single_post->title : '',
600
					'excerpt'        => $single_post->excerpt ? $single_post->excerpt : '',
601
					'featured_image' => $single_post->featured_image ? $single_post->featured_image : '',
602
					'url'            => $single_post->URL,
603
				);
604
605
				/**
606
				 * Append the formatted post to the results.
607
				 */
608
				$formatted_posts[] = $prepared_post;
609
			}
610
		}
611
612
		return $formatted_posts;
613
	}
614
615
	/**
616
	 * Fetch site information and posts list for a site.
617
	 *
618
	 * @param string $site           Site to fetch the data for.
619
	 * @param array  $original_data  Optional original data to updated.
620
	 *
621
	 * @param bool   $site_data_only Fetch only site information, skip posts list.
622
	 *
623
	 * @return array Updated or new data.
624
	 */
625
	public function fetch_blog_data( $site, $original_data = array(), $site_data_only = false ) {
626
627
		/**
628
		 * If no optional data is supplied, initialize a new structure
629
		 */
630
		if ( ! empty( $original_data ) ) {
631
			$widget_data = $original_data;
632
		}
633
		else {
634
			$widget_data = array(
635
				'site_info' => array(
636
					'last_check'  => null,
637
					'last_update' => null,
638
					'error'       => null,
639
					'data'        => array(),
640
				),
641
				'posts'     => array(
642
					'last_check'  => null,
643
					'last_update' => null,
644
					'error'       => null,
645
					'data'        => array(),
646
				)
647
			);
648
		}
649
650
		/**
651
		 * Update check time and fetch site information.
652
		 */
653
		$widget_data['site_info']['last_check'] = time();
654
655
		$site_info_raw_data    = $this->fetch_site_info( $site );
656
		$site_info_parsed_data = $this->parse_site_info_response( $site_info_raw_data );
657
658
659
		/**
660
		 * If there is an error with the fetched site info, save the error and update the checked time.
661
		 */
662 View Code Duplication
		if ( is_wp_error( $site_info_parsed_data ) ) {
663
			$widget_data['site_info']['error'] = $site_info_parsed_data;
664
665
			return $widget_data;
666
		}
667
		/**
668
		 * If data is fetched successfully, update the data and set the proper time.
669
		 *
670
		 * Data is only updated if we have valid results. This is done this way so we can show
671
		 * something if external service is down.
672
		 *
673
		 */
674
		else {
675
			$widget_data['site_info']['last_update'] = time();
676
			$widget_data['site_info']['data']        = $site_info_parsed_data;
677
			$widget_data['site_info']['error']       = null;
678
		}
679
680
681
		/**
682
		 * If only site data is needed, return it here, don't fetch posts data.
683
		 */
684
		if ( true === $site_data_only ) {
685
			return $widget_data;
686
		}
687
688
		/**
689
		 * Update check time and fetch posts list.
690
		 */
691
		$widget_data['posts']['last_check'] = time();
692
693
		$site_posts_raw_data    = $this->fetch_posts_for_site( $site_info_parsed_data->ID );
694
		$site_posts_parsed_data = $this->parse_posts_response( $site_posts_raw_data );
695
696
697
		/**
698
		 * If there is an error with the fetched posts, save the error and update the checked time.
699
		 */
700 View Code Duplication
		if ( is_wp_error( $site_posts_parsed_data ) ) {
701
			$widget_data['posts']['error'] = $site_posts_parsed_data;
702
703
			return $widget_data;
704
		}
705
		/**
706
		 * If data is fetched successfully, update the data and set the proper time.
707
		 *
708
		 * Data is only updated if we have valid results. This is done this way so we can show
709
		 * something if external service is down.
710
		 *
711
		 */
712
		else {
713
			$widget_data['posts']['last_update'] = time();
714
			$widget_data['posts']['data']        = $site_posts_parsed_data;
715
			$widget_data['posts']['error']       = null;
716
		}
717
718
		return $widget_data;
719
	}
720
721
	/**
722
	 * Scan and extract first error from blog data array.
723
	 *
724
	 * @param array|WP_Error $blog_data Blog data to scan for errors.
725
	 *
726
	 * @return string First error message found
727
	 */
728
	public function extract_errors_from_blog_data( $blog_data ) {
729
730
		$errors = array(
731
			'message' => '',
732
			'debug'   => '',
733
			'where'   => '',
734
		);
735
736
737
		/**
738
		 * When the cache result is an error. Usually when the cache is empty.
739
		 * This is not an error case for now.
740
		 */
741
		if ( is_wp_error( $blog_data ) ) {
742
			return $errors;
743
		}
744
745
		/**
746
		 * Loop through `site_info` and `posts` keys of $blog_data.
747
		 */
748
		foreach ( array( 'site_info', 'posts' ) as $info_key ) {
749
750
			/**
751
			 * Contains information on which stage the error ocurred.
752
			 */
753
			$errors['where'] = $info_key;
754
755
			/**
756
			 * If an error is set, we want to check it for usable messages.
757
			 */
758
			if ( isset( $blog_data[ $info_key ]['error'] ) && ! empty( $blog_data[ $info_key ]['error'] ) ) {
759
760
				/**
761
				 * Extract error message from the error, if possible.
762
				 */
763
				if ( is_wp_error( $blog_data[ $info_key ]['error'] ) ) {
764
					/**
765
					 * In the case of WP_Error we want to have the error message
766
					 * and the debug information available.
767
					 */
768
					$error_messages    = $blog_data[ $info_key ]['error']->get_error_messages();
769
					$errors['message'] = reset( $error_messages );
770
771
					$extra_data = $blog_data[ $info_key ]['error']->get_error_data();
772
					if ( is_array( $extra_data ) ) {
773
						$errors['debug'] = implode( '; ', $extra_data );
774
					}
775
					else {
776
						$errors['debug'] = $extra_data;
777
					}
778
779
					break;
780
				}
781
				elseif ( is_array( $blog_data[ $info_key ]['error'] ) ) {
782
					/**
783
					 * In this case we don't have debug information, because
784
					 * we have no way to know the format. The widget works with
785
					 * WP_Error objects only.
786
					 */
787
					$errors['message'] = reset( $blog_data[ $info_key ]['error'] );
788
					break;
789
				}
790
791
				/**
792
				 * We do nothing if no usable error is found.
793
				 */
794
			}
795
		}
796
797
		return $errors;
798
	}
799
}
800