PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 7.2.2
Jetpack – WP Security, Backup, Speed, & Growth v7.2.2
16.3 16.3-beta 16.3-a.5 16.3-a.7 16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 All 508 releases
jetpack / modules / widgets / wordpress-post-widget / class.jetpack-display-posts-widget-base.php

class.jetpack-display-posts-widget-base.php in Jetpack – WP Security, Backup, Speed, & Growth 7.2.2, at modules/widgets/wordpress-post-widget/class.jetpack-display-posts-widget-base.php

844 lines 24.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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 * That this widget is currently implemented as these two classes
18 * is an implementation detail and should not be depended on :)
19 */
20 abstract class Jetpack_Display_Posts_Widget__Base extends WP_Widget {
21 /**
22 * @var string Remote service API URL prefix.
23 */
24 public $service_url = 'https://public-api.wordpress.com/rest/v1.1/';
25
26 public function __construct() {
27 parent::__construct(
28 // internal id
29 'jetpack_display_posts_widget',
30 /** This filter is documented in modules/widgets/facebook-likebox.php */
31 apply_filters( 'jetpack_widget_name', __( 'Display WordPress Posts', 'jetpack' ) ),
32 array(
33 'description' => __( 'Displays a list of recent posts from another WordPress.com or Jetpack-enabled blog.', 'jetpack' ),
34 'customize_selective_refresh' => true,
35 )
36 );
37
38 if ( is_customize_preview() ) {
39 add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
40 }
41 }
42
43 /**
44 * Enqueue CSS and JavaScript.
45 *
46 * @since 4.0.0
47 */
48 public function enqueue_scripts() {
49 wp_enqueue_style( 'jetpack_display_posts_widget', plugins_url( 'style.css', __FILE__ ) );
50 }
51
52
53 // DATA STORE: Must implement
54
55 /**
56 * Gets blog data from the cache.
57 *
58 * @param string $site
59 *
60 * @return array|WP_Error
61 */
62 abstract public function get_blog_data( $site );
63
64 /**
65 * Update a widget instance.
66 *
67 * @param string $site The site to fetch the latest data for.
68 *
69 * @return array - the new data
70 */
71 abstract public function update_instance( $site );
72
73
74 // WIDGET API
75
76 /**
77 * Set up the widget display on the front end.
78 *
79 * @param array $args
80 * @param array $instance
81 */
82 public function widget( $args, $instance ) {
83 /** This action is documented in modules/widgets/gravatar-profile.php */
84 do_action( 'jetpack_stats_extra', 'widget_view', 'display_posts' );
85
86 // Enqueue front end assets.
87 $this->enqueue_scripts();
88
89 $content = $args['before_widget'];
90
91 if ( empty( $instance['url'] ) ) {
92 if ( current_user_can( 'manage_options' ) ) {
93 $content .= '<p>';
94 /* Translators: the "Blog URL" field mentioned is the input field labeled as such in the widget form. */
95 $content .= esc_html__( 'The Blog URL is not properly setup in the widget.', 'jetpack' );
96 $content .= '</p>';
97 }
98 $content .= $args['after_widget'];
99
100 echo $content;
101 return;
102 }
103
104 $data = $this->get_blog_data( $instance['url'] );
105 // check for errors
106 if ( is_wp_error( $data ) || empty( $data['site_info']['data'] ) ) {
107 $content .= '<p>' . __( 'Cannot load blog information at this time.', 'jetpack' ) . '</p>';
108 $content .= $args['after_widget'];
109
110 echo $content;
111 return;
112 }
113
114 $site_info = $data['site_info']['data'];
115
116 if ( ! empty( $instance['title'] ) ) {
117 /** This filter is documented in core/src/wp-includes/default-widgets.php */
118 $instance['title'] = apply_filters( 'widget_title', $instance['title'] );
119 $content .= $args['before_title'] . esc_html( $instance['title'] . ': ' . $site_info->name ) . $args['after_title'];
120 }
121 else {
122 $content .= $args['before_title'] . esc_html( $site_info->name ) . $args['after_title'];
123 }
124
125 $content .= '<div class="jetpack-display-remote-posts">';
126
127 if ( is_wp_error( $data['posts']['data'] ) || empty( $data['posts']['data'] ) ) {
128 $content .= '<p>' . __( 'Cannot load blog posts at this time.', 'jetpack' ) . '</p>';
129 $content .= '</div><!-- .jetpack-display-remote-posts -->';
130 $content .= $args['after_widget'];
131
132 echo $content;
133 return;
134 }
135
136 $posts_list = $data['posts']['data'];
137
138 /**
139 * Show only as much posts as we need. If we have less than configured amount,
140 * we must show only that much posts.
141 */
142 $number_of_posts = min( $instance['number_of_posts'], count( $posts_list ) );
143
144 for ( $i = 0; $i < $number_of_posts; $i ++ ) {
145 $single_post = $posts_list[ $i ];
146 $post_title = ( $single_post['title'] ) ? $single_post['title'] : '( No Title )';
147
148 $target = '';
149 if ( isset( $instance['open_in_new_window'] ) && $instance['open_in_new_window'] == true ) {
150 $target = ' target="_blank" rel="noopener"';
151 }
152 $content .= '<h4><a href="' . esc_url( $single_post['url'] ) . '"' . $target . '>' . esc_html( $post_title ) . '</a></h4>' . "\n";
153 if ( ( $instance['featured_image'] == true ) && ( ! empty ( $single_post['featured_image'] ) ) ) {
154 $featured_image = $single_post['featured_image'];
155 /**
156 * Allows setting up custom Photon parameters to manipulate the image output in the Display Posts widget.
157 *
158 * @see https://developer.wordpress.com/docs/photon/
159 *
160 * @module widgets
161 *
162 * @since 3.6.0
163 *
164 * @param array $args Array of Photon Parameters.
165 */
166 $image_params = apply_filters( 'jetpack_display_posts_widget_image_params', array() );
167 $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>';
168 }
169
170 if ( $instance['show_excerpts'] == true ) {
171 $content .= $single_post['excerpt'];
172 }
173 }
174
175 $content .= '</div><!-- .jetpack-display-remote-posts -->';
176 $content .= $args['after_widget'];
177
178 /**
179 * Filter the WordPress Posts widget content.
180 *
181 * @module widgets
182 *
183 * @since 4.7.0
184 *
185 * @param string $content Widget content.
186 */
187 echo apply_filters( 'jetpack_display_posts_widget_content', $content );
188 }
189
190 /**
191 * Display the widget administration form.
192 *
193 * @param array $instance Widget instance configuration.
194 *
195 * @return string|void
196 */
197 public function form( $instance ) {
198
199 /**
200 * Initialize widget configuration variables.
201 */
202 $title = ( isset( $instance['title'] ) ) ? $instance['title'] : __( 'Recent Posts', 'jetpack' );
203 $url = ( isset( $instance['url'] ) ) ? $instance['url'] : '';
204 $number_of_posts = ( isset( $instance['number_of_posts'] ) ) ? $instance['number_of_posts'] : 5;
205 $open_in_new_window = ( isset( $instance['open_in_new_window'] ) ) ? $instance['open_in_new_window'] : false;
206 $featured_image = ( isset( $instance['featured_image'] ) ) ? $instance['featured_image'] : false;
207 $show_excerpts = ( isset( $instance['show_excerpts'] ) ) ? $instance['show_excerpts'] : false;
208
209
210 /**
211 * Check if the widget instance has errors available.
212 *
213 * Only do so if a URL is set.
214 */
215 $update_errors = array();
216
217 if ( ! empty( $url ) ) {
218 $data = $this->get_blog_data( $url );
219 $update_errors = $this->extract_errors_from_blog_data( $data );
220 }
221
222 ?>
223 <p>
224 <label for="<?php echo $this->get_field_id( 'title' ); ?>"><?php _e( 'Title:', 'jetpack' ); ?></label>
225 <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 ); ?>" />
226 </p>
227
228 <p>
229 <label for="<?php echo $this->get_field_id( 'url' ); ?>"><?php _e( 'Blog URL:', 'jetpack' ); ?></label>
230 <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 ); ?>" />
231 <i>
232 <?php _e( "Enter a WordPress.com or Jetpack WordPress site URL.", 'jetpack' ); ?>
233 </i>
234 <?php
235 /**
236 * Show an error if the URL field was left empty.
237 *
238 * The error is shown only when the widget was already saved.
239 */
240 if ( empty( $url ) && ! preg_match( '/__i__|%i%/', $this->id ) ) {
241 ?>
242 <br />
243 <i class="error-message"><?php echo __( 'You must specify a valid blog URL!', 'jetpack' ); ?></i>
244 <?php
245 }
246 ?>
247 </p>
248 <p>
249 <label for="<?php echo $this->get_field_id( 'number_of_posts' ); ?>"><?php _e( 'Number of Posts to Display:', 'jetpack' ); ?></label>
250 <select name="<?php echo $this->get_field_name( 'number_of_posts' ); ?>">
251 <?php
252 for ( $i = 1; $i <= 10; $i ++ ) {
253 echo '<option value="' . $i . '" ' . selected( $number_of_posts, $i ) . '>' . $i . '</option>';
254 }
255 ?>
256 </select>
257 </p>
258 <p>
259 <label for="<?php echo $this->get_field_id( 'open_in_new_window' ); ?>"><?php _e( 'Open links in new window/tab:', 'jetpack' ); ?></label>
260 <input type="checkbox" name="<?php echo $this->get_field_name( 'open_in_new_window' ); ?>" <?php checked( $open_in_new_window, 1 ); ?> />
261 </p>
262 <p>
263 <label for="<?php echo $this->get_field_id( 'featured_image' ); ?>"><?php _e( 'Show Featured Image:', 'jetpack' ); ?></label>
264 <input type="checkbox" name="<?php echo $this->get_field_name( 'featured_image' ); ?>" <?php checked( $featured_image, 1 ); ?> />
265 </p>
266 <p>
267 <label for="<?php echo $this->get_field_id( 'show_excerpts' ); ?>"><?php _e( 'Show Excerpts:', 'jetpack' ); ?></label>
268 <input type="checkbox" name="<?php echo $this->get_field_name( 'show_excerpts' ); ?>" <?php checked( $show_excerpts, 1 ); ?> />
269 </p>
270
271 <?php
272
273 /**
274 * Show error messages.
275 */
276 if ( ! empty( $update_errors['message'] ) ) {
277
278 /**
279 * Prepare the error messages.
280 */
281
282 $where_message = '';
283 switch ( $update_errors['where'] ) {
284 case 'posts':
285 $where_message .= __( 'An error occurred while downloading blog posts list', 'jetpack' );
286 break;
287
288 /**
289 * If something else, beside `posts` and `site_info` broke,
290 * don't handle it and default to blog `information`,
291 * as it is generic enough.
292 */
293 case 'site_info':
294 default:
295 $where_message .= __( 'An error occurred while downloading blog information', 'jetpack' );
296 break;
297 }
298
299 ?>
300 <p class="error-message">
301 <?php echo esc_html( $where_message ); ?>:
302 <br />
303 <i>
304 <?php echo esc_html( $update_errors['message'] ); ?>
305 <?php
306 /**
307 * If there is any debug - show it here.
308 */
309 if ( ! empty( $update_errors['debug'] ) ) {
310 ?>
311 <br />
312 <br />
313 <?php esc_html_e( 'Detailed information', 'jetpack' ); ?>:
314 <br />
315 <?php echo esc_html( $update_errors['debug'] ); ?>
316 <?php
317 }
318 ?>
319 </i>
320 </p>
321
322 <?php
323 }
324 }
325
326 public function update( $new_instance, $old_instance ) {
327
328 $instance = array();
329 $instance['title'] = ( ! empty( $new_instance['title'] ) ) ? strip_tags( $new_instance['title'] ) : '';
330 $instance['url'] = ( ! empty( $new_instance['url'] ) ) ? strip_tags( trim( $new_instance['url'] ) ) : '';
331 $instance['url'] = preg_replace( "!^https?://!is", "", $instance['url'] );
332 $instance['url'] = untrailingslashit( $instance['url'] );
333
334
335 /**
336 * Check if the URL should be with or without the www prefix before saving.
337 */
338 if ( ! empty( $instance['url'] ) ) {
339 $blog_data = $this->fetch_blog_data( $instance['url'], array(), true );
340
341 if ( is_wp_error( $blog_data['site_info']['error'] ) && 'www.' === substr( $instance['url'], 0, 4 ) ) {
342 $blog_data = $this->fetch_blog_data( substr( $instance['url'], 4 ), array(), true );
343
344 if ( ! is_wp_error( $blog_data['site_info']['error'] ) ) {
345 $instance['url'] = substr( $instance['url'], 4 );
346 }
347 }
348 }
349
350 $instance['number_of_posts'] = ( ! empty( $new_instance['number_of_posts'] ) ) ? intval( $new_instance['number_of_posts'] ) : '';
351 $instance['open_in_new_window'] = ( ! empty( $new_instance['open_in_new_window'] ) ) ? true : '';
352 $instance['featured_image'] = ( ! empty( $new_instance['featured_image'] ) ) ? true : '';
353 $instance['show_excerpts'] = ( ! empty( $new_instance['show_excerpts'] ) ) ? true : '';
354
355 /**
356 * If there is no cache entry for the specified URL, run a forced update.
357 *
358 * @see get_blog_data Returns WP_Error if the cache is empty, which is what is needed here.
359 */
360 $cached_data = $this->get_blog_data( $instance['url'] );
361
362 if ( is_wp_error( $cached_data ) ) {
363 $this->update_instance( $instance['url'] );
364 }
365
366 return $instance;
367 }
368
369
370 // DATA PROCESSING
371
372 /**
373 * Expiring transients have a name length maximum of 45 characters,
374 * so this function returns an abbreviated MD5 hash to use instead of
375 * the full URI.
376 *
377 * @param string $site Site to get the hash for.
378 *
379 * @return string
380 */
381 public function get_site_hash( $site ) {
382 return substr( md5( $site ), 0, 21 );
383 }
384
385 /**
386 * Fetch a remote service endpoint and parse it.
387 *
388 * Timeout is set to 15 seconds right now, because sometimes the WordPress API
389 * takes more than 5 seconds to fully respond.
390 *
391 * Caching is used here so we can avoid re-downloading the same endpoint
392 * in a single request.
393 *
394 * @param string $endpoint Parametrized endpoint to call.
395 *
396 * @param int $timeout How much time to wait for the API to respond before failing.
397 *
398 * @return array|WP_Error
399 */
400 public function fetch_service_endpoint( $endpoint, $timeout = 15 ) {
401
402 /**
403 * Holds endpoint request cache.
404 */
405 static $cache = array();
406
407 if ( ! isset( $cache[ $endpoint ] ) ) {
408 $raw_data = $this->wp_wp_remote_get( $this->service_url . ltrim( $endpoint, '/' ), array( 'timeout' => $timeout ) );
409 $cache[ $endpoint ] = $this->parse_service_response( $raw_data );
410 }
411
412 return $cache[ $endpoint ];
413 }
414
415 /**
416 * Parse data from service response.
417 * Do basic error handling for general service and data errors
418 *
419 * @param array $service_response Response from the service.
420 *
421 * @return array|WP_Error
422 */
423 public function parse_service_response( $service_response ) {
424 /**
425 * If there is an error, we add the error message to the parsed response
426 */
427 if ( is_wp_error( $service_response ) ) {
428 return new WP_Error(
429 'general_error',
430 __( 'An error occurred fetching the remote data.', 'jetpack' ),
431 $service_response->get_error_messages()
432 );
433 }
434
435 /**
436 * Validate HTTP response code.
437 */
438 if ( 200 !== wp_remote_retrieve_response_code( $service_response ) ) {
439 return new WP_Error(
440 'http_error',
441 __( 'An error occurred fetching the remote data.', 'jetpack' ),
442 wp_remote_retrieve_response_message( $service_response )
443 );
444 }
445
446
447 /**
448 * Extract service response body from the request.
449 */
450
451 $service_response_body = wp_remote_retrieve_body( $service_response );
452
453
454 /**
455 * No body has been set in the response. This should be pretty bad.
456 */
457 if ( ! $service_response_body ) {
458 return new WP_Error(
459 'no_body',
460 __( 'Invalid remote response.', 'jetpack' ),
461 'No body in response.'
462 );
463 }
464
465 /**
466 * Parse the JSON response from the API. Convert to associative array.
467 */
468 $parsed_data = json_decode( $service_response_body );
469
470 /**
471 * If there is a problem with parsing the posts return an empty array.
472 */
473 if ( is_null( $parsed_data ) ) {
474 return new WP_Error(
475 'no_body',
476 __( 'Invalid remote response.', 'jetpack' ),
477 'Invalid JSON from remote.'
478 );
479 }
480
481 /**
482 * Check for errors in the parsed body.
483 */
484 if ( isset( $parsed_data->error ) ) {
485 return new WP_Error(
486 'remote_error',
487 __( 'It looks like the WordPress site URL is incorrectly configured. Please check it in your widget settings.', 'jetpack' ),
488 $parsed_data->error
489 );
490 }
491
492 /**
493 * No errors found, return parsed data.
494 */
495 return $parsed_data;
496 }
497
498 /**
499 * Fetch site information from the WordPress public API
500 *
501 * @param string $site URL of the site to fetch the information for.
502 *
503 * @return array|WP_Error
504 */
505 public function fetch_site_info( $site ) {
506
507 $response = $this->fetch_service_endpoint( sprintf( '/sites/%s', urlencode( $site ) ) );
508
509 return $response;
510 }
511
512 /**
513 * Parse external API response from the site info call and handle errors if they occur.
514 *
515 * @param array|WP_Error $service_response The raw response to be parsed.
516 *
517 * @return array|WP_Error
518 */
519 public function parse_site_info_response( $service_response ) {
520
521 /**
522 * If the service returned an error, we pass it on.
523 */
524 if ( is_wp_error( $service_response ) ) {
525 return $service_response;
526 }
527
528 /**
529 * Check if the service returned proper site information.
530 */
531 if ( ! isset( $service_response->ID ) ) {
532 return new WP_Error(
533 'no_site_info',
534 __( 'Invalid site information returned from remote.', 'jetpack' ),
535 'No site ID present in the response.'
536 );
537 }
538
539 return $service_response;
540 }
541
542 /**
543 * Fetch list of posts from the WordPress public API.
544 *
545 * @param int $site_id The site to fetch the posts for.
546 *
547 * @return array|WP_Error
548 */
549 public function fetch_posts_for_site( $site_id ) {
550
551 $response = $this->fetch_service_endpoint(
552 sprintf(
553 '/sites/%1$d/posts/%2$s',
554 $site_id,
555 /**
556 * Filters the parameters used to fetch for posts in the Display Posts Widget.
557 *
558 * @see https://developer.wordpress.com/docs/api/1.1/get/sites/%24site/posts/
559 *
560 * @module widgets
561 *
562 * @since 3.6.0
563 *
564 * @param string $args Extra parameters to filter posts returned from the WordPress.com REST API.
565 */
566 apply_filters( 'jetpack_display_posts_widget_posts_params', '' )
567 )
568 );
569
570 return $response;
571 }
572
573 /**
574 * Parse external API response from the posts list request and handle errors if any occur.
575 *
576 * @param object|WP_Error $service_response The raw response to be parsed.
577 *
578 * @return array|WP_Error
579 */
580 public function parse_posts_response( $service_response ) {
581
582 /**
583 * If the service returned an error, we pass it on.
584 */
585 if ( is_wp_error( $service_response ) ) {
586 return $service_response;
587 }
588
589 /**
590 * Check if the service returned proper posts array.
591 */
592 if ( ! isset( $service_response->posts ) || ! is_array( $service_response->posts ) ) {
593 return new WP_Error(
594 'no_posts',
595 __( 'No posts data returned by remote.', 'jetpack' ),
596 'No posts information set in the returned data.'
597 );
598 }
599
600 /**
601 * Format the posts to preserve storage space.
602 */
603
604 return $this->format_posts_for_storage( $service_response );
605 }
606
607 /**
608 * Format the posts for better storage. Drop all the data that is not used.
609 *
610 * @param object $parsed_data Array of posts returned by the APIs.
611 *
612 * @return array Formatted posts or an empty array if no posts were found.
613 */
614 public function format_posts_for_storage( $parsed_data ) {
615
616 $formatted_posts = array();
617
618 /**
619 * Only go through the posts list if we have valid posts array.
620 */
621 if ( isset( $parsed_data->posts ) && is_array( $parsed_data->posts ) ) {
622
623 /**
624 * Loop through all the posts and format them appropriately.
625 */
626 foreach ( $parsed_data->posts as $single_post ) {
627
628 $prepared_post = array(
629 'title' => $single_post->title ? $single_post->title : '',
630 'excerpt' => $single_post->excerpt ? $single_post->excerpt : '',
631 'featured_image' => $single_post->featured_image ? $single_post->featured_image : '',
632 'url' => $single_post->URL,
633 );
634
635 /**
636 * Append the formatted post to the results.
637 */
638 $formatted_posts[] = $prepared_post;
639 }
640 }
641
642 return $formatted_posts;
643 }
644
645 /**
646 * Fetch site information and posts list for a site.
647 *
648 * @param string $site Site to fetch the data for.
649 * @param array $original_data Optional original data to updated.
650 *
651 * @param bool $site_data_only Fetch only site information, skip posts list.
652 *
653 * @return array Updated or new data.
654 */
655 public function fetch_blog_data( $site, $original_data = array(), $site_data_only = false ) {
656
657 /**
658 * If no optional data is supplied, initialize a new structure
659 */
660 if ( ! empty( $original_data ) ) {
661 $widget_data = $original_data;
662 }
663 else {
664 $widget_data = array(
665 'site_info' => array(
666 'last_check' => null,
667 'last_update' => null,
668 'error' => null,
669 'data' => array(),
670 ),
671 'posts' => array(
672 'last_check' => null,
673 'last_update' => null,
674 'error' => null,
675 'data' => array(),
676 )
677 );
678 }
679
680 /**
681 * Update check time and fetch site information.
682 */
683 $widget_data['site_info']['last_check'] = time();
684
685 $site_info_raw_data = $this->fetch_site_info( $site );
686 $site_info_parsed_data = $this->parse_site_info_response( $site_info_raw_data );
687
688
689 /**
690 * If there is an error with the fetched site info, save the error and update the checked time.
691 */
692 if ( is_wp_error( $site_info_parsed_data ) ) {
693 $widget_data['site_info']['error'] = $site_info_parsed_data;
694
695 return $widget_data;
696 }
697 /**
698 * If data is fetched successfully, update the data and set the proper time.
699 *
700 * Data is only updated if we have valid results. This is done this way so we can show
701 * something if external service is down.
702 *
703 */
704 else {
705 $widget_data['site_info']['last_update'] = time();
706 $widget_data['site_info']['data'] = $site_info_parsed_data;
707 $widget_data['site_info']['error'] = null;
708 }
709
710
711 /**
712 * If only site data is needed, return it here, don't fetch posts data.
713 */
714 if ( true === $site_data_only ) {
715 return $widget_data;
716 }
717
718 /**
719 * Update check time and fetch posts list.
720 */
721 $widget_data['posts']['last_check'] = time();
722
723 $site_posts_raw_data = $this->fetch_posts_for_site( $site_info_parsed_data->ID );
724 $site_posts_parsed_data = $this->parse_posts_response( $site_posts_raw_data );
725
726
727 /**
728 * If there is an error with the fetched posts, save the error and update the checked time.
729 */
730 if ( is_wp_error( $site_posts_parsed_data ) ) {
731 $widget_data['posts']['error'] = $site_posts_parsed_data;
732
733 return $widget_data;
734 }
735 /**
736 * If data is fetched successfully, update the data and set the proper time.
737 *
738 * Data is only updated if we have valid results. This is done this way so we can show
739 * something if external service is down.
740 *
741 */
742 else {
743 $widget_data['posts']['last_update'] = time();
744 $widget_data['posts']['data'] = $site_posts_parsed_data;
745 $widget_data['posts']['error'] = null;
746 }
747
748 return $widget_data;
749 }
750
751 /**
752 * Scan and extract first error from blog data array.
753 *
754 * @param array|WP_Error $blog_data Blog data to scan for errors.
755 *
756 * @return string First error message found
757 */
758 public function extract_errors_from_blog_data( $blog_data ) {
759
760 $errors = array(
761 'message' => '',
762 'debug' => '',
763 'where' => '',
764 );
765
766
767 /**
768 * When the cache result is an error. Usually when the cache is empty.
769 * This is not an error case for now.
770 */
771 if ( is_wp_error( $blog_data ) ) {
772 return $errors;
773 }
774
775 /**
776 * Loop through `site_info` and `posts` keys of $blog_data.
777 */
778 foreach ( array( 'site_info', 'posts' ) as $info_key ) {
779
780 /**
781 * Contains information on which stage the error ocurred.
782 */
783 $errors['where'] = $info_key;
784
785 /**
786 * If an error is set, we want to check it for usable messages.
787 */
788 if ( isset( $blog_data[ $info_key ]['error'] ) && ! empty( $blog_data[ $info_key ]['error'] ) ) {
789
790 /**
791 * Extract error message from the error, if possible.
792 */
793 if ( is_wp_error( $blog_data[ $info_key ]['error'] ) ) {
794 /**
795 * In the case of WP_Error we want to have the error message
796 * and the debug information available.
797 */
798 $error_messages = $blog_data[ $info_key ]['error']->get_error_messages();
799 $errors['message'] = reset( $error_messages );
800
801 $extra_data = $blog_data[ $info_key ]['error']->get_error_data();
802 if ( is_array( $extra_data ) ) {
803 $errors['debug'] = implode( '; ', $extra_data );
804 }
805 else {
806 $errors['debug'] = $extra_data;
807 }
808
809 break;
810 }
811 elseif ( is_array( $blog_data[ $info_key ]['error'] ) ) {
812 /**
813 * In this case we don't have debug information, because
814 * we have no way to know the format. The widget works with
815 * WP_Error objects only.
816 */
817 $errors['message'] = reset( $blog_data[ $info_key ]['error'] );
818 break;
819 }
820
821 /**
822 * We do nothing if no usable error is found.
823 */
824 }
825 }
826
827 return $errors;
828 }
829
830 /**
831 * This is just to make method mocks in the unit tests easier.
832 *
833 * @param string $url The URL to fetch
834 * @param array $args Optional. Request arguments.
835 *
836 * @return array|WP_Error
837 *
838 * @codeCoverageIgnore
839 */
840 public function wp_wp_remote_get( $url, $args = array() ) {
841 return wp_remote_get( $url, $args );
842 }
843 }
844