Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
69.23% covered (warning)
69.23%
36 / 52
44.44% covered (danger)
44.44%
4 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
Jetpack_Display_Posts_Widget
72.00% covered (warning)
72.00%
36 / 50
44.44% covered (danger)
44.44%
4 / 9
52.10
0.00% covered (danger)
0.00%
0 / 1
 get_blog_data
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
2
 update_instance
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
2
 update
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 activate_cron
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 deactivate_cron
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 deactivate_cron_static
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 should_cron_be_running
33.33% covered (danger)
33.33%
3 / 9
0.00% covered (danger)
0.00%
0 / 1
26.96
 cron_task
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
5
 get_instances_sites
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
6
 wp_get_option
n/a
0 / 0
n/a
0 / 0
1
 wp_add_option
n/a
0 / 0
n/a
0 / 0
1
 wp_update_option
n/a
0 / 0
n/a
0 / 0
1
1<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2
3use Automattic\Jetpack\Status;
4
5if ( ! defined( 'ABSPATH' ) ) {
6    exit( 0 );
7}
8
9/**
10 * Display a list of recent posts from a WordPress.com or Jetpack-enabled blog.
11 */
12class Jetpack_Display_Posts_Widget extends Jetpack_Display_Posts_Widget__Base {
13    /**
14     * Widget options key prefix.
15     *
16     * @var string
17     */
18    public $widget_options_key_prefix = 'display_posts_site_data_';
19
20    /**
21     * The name of the cron that will update widget data.
22     *
23     * @var string
24     */
25    public static $cron_name = 'jetpack_display_posts_widget_cron_update';
26
27    // DATA STORE.
28
29    /**
30     * Gets blog data from the cache.
31     *
32     * @param string $site Site.
33     *
34     * @return array|WP_Error
35     */
36    public function get_blog_data( $site ) {
37        // Load from cache, if nothing return an error.
38        $site_hash = $this->get_site_hash( $site );
39
40        $cached_data = $this->wp_get_option( $this->widget_options_key_prefix . $site_hash );
41
42        /**
43         * If the cache is empty, return an empty_cache error.
44         */
45        if ( false === $cached_data ) {
46            return new WP_Error(
47                'empty_cache',
48                __( 'Information about this blog is currently being retrieved.', 'jetpack' )
49            );
50        }
51
52        return $cached_data;
53    }
54
55    /**
56     * Update a widget instance.
57     *
58     * @param string $site The site to fetch the latest data for.
59     *
60     * @return array - the new data
61     */
62    public function update_instance( $site ) {
63
64        /**
65         * Fetch current information for a site.
66         */
67        $site_hash = $this->get_site_hash( $site );
68
69        $option_key = $this->widget_options_key_prefix . $site_hash;
70
71        $instance_data = $this->wp_get_option( $option_key );
72
73        /**
74         * Fetch blog data and save it in $instance_data.
75         */
76        $new_data = $this->fetch_blog_data( $site, $instance_data );
77
78        /**
79         * If the option doesn't exist yet - create a new option
80         */
81        if ( false === $instance_data ) {
82            $this->wp_add_option( $option_key, $new_data );
83        } else {
84            $this->wp_update_option( $option_key, $new_data );
85        }
86
87        return $new_data;
88    }
89
90    // WIDGET API.
91
92    /**
93     * Widget update function.
94     *
95     * @param array $new_instance New instance widget settings.
96     * @param array $old_instance Old instance widget settings.
97     */
98    public function update( $new_instance, $old_instance ) {
99        $instance = parent::update( $new_instance, $old_instance );
100
101        /**
102         * Forcefully activate the update cron when saving widget instance.
103         *
104         * So we can be sure that it will be running later.
105         */
106        $this->activate_cron();
107
108        return $instance;
109    }
110
111    // CRON.
112
113    /**
114     * Activates widget update cron task.
115     */
116    public static function activate_cron() {
117        if ( ! wp_next_scheduled( self::$cron_name ) ) {
118            wp_schedule_event( time(), 'minutes_10', self::$cron_name );
119        }
120    }
121
122    /**
123     * Deactivates widget update cron task.
124     *
125     * This is a wrapper over the static method as it provides some syntactic sugar.
126     */
127    public function deactivate_cron() {
128        self::deactivate_cron_static();
129    }
130
131    /**
132     * Deactivates widget update cron task.
133     */
134    public static function deactivate_cron_static() {
135        $next_scheduled_time = wp_next_scheduled( self::$cron_name );
136        wp_unschedule_event( $next_scheduled_time, self::$cron_name );
137    }
138
139    /**
140     * Checks if the update cron should be running and returns appropriate result.
141     *
142     * @return bool If the cron should be running or not.
143     */
144    public function should_cron_be_running() {
145        /**
146         * The cron doesn't need to run empty loops.
147         */
148        $widget_instances = $this->get_instances_sites();
149
150        if ( empty( $widget_instances ) || ! is_array( $widget_instances ) ) {
151            return false;
152        }
153
154        if ( ! defined( 'IS_WPCOM' ) || ! IS_WPCOM ) {
155            /**
156             * If Jetpack is not active or in offline mode, we don't want to update widget data.
157             */
158            if ( ! Jetpack::is_connection_ready() && ! ( new Status() )->is_offline_mode() ) {
159                return false;
160            }
161
162            /**
163             * If Extra Sidebar Widgets module is not active, we don't need to update widget data.
164             */
165            if ( ! Jetpack::is_module_active( 'widgets' ) ) {
166                return false;
167            }
168        }
169
170        /**
171         * If none of the above checks failed, then we definitely want to update widget data.
172         */
173        return true;
174    }
175
176    /**
177     * Main cron code. Updates all instances of the widget.
178     *
179     * @return bool
180     */
181    public function cron_task() {
182
183        /**
184         * If the cron should not be running, disable it.
185         */
186        if ( false === $this->should_cron_be_running() ) {
187            return true;
188        }
189
190        $instances_to_update = $this->get_instances_sites();
191
192        /**
193         * If no instances are found to be updated - stop.
194         */
195        if ( empty( $instances_to_update ) || ! is_array( $instances_to_update ) ) {
196            return true;
197        }
198
199        foreach ( $instances_to_update as $site_url ) {
200            $this->update_instance( $site_url );
201        }
202
203        return true;
204    }
205
206    /**
207     * Get a list of unique sites from all instances of the widget.
208     *
209     * @return array|bool
210     */
211    public function get_instances_sites() {
212
213        $widget_settings = $this->wp_get_option( 'widget_jetpack_display_posts_widget' );
214
215        /**
216         * If the widget still hasn't been added anywhere, the config will not be present.
217         *
218         * In such case we don't want to continue execution.
219         */
220        if ( false === $widget_settings || ! is_array( $widget_settings ) ) {
221            return false;
222        }
223
224        $urls = array();
225
226        foreach ( $widget_settings as $widget_instance_data ) {
227            if ( isset( $widget_instance_data['url'] ) && ! empty( $widget_instance_data['url'] ) ) {
228                $urls[] = $widget_instance_data['url'];
229            }
230        }
231
232        /**
233         * Make sure only unique URLs are returned.
234         */
235        $urls = array_unique( $urls );
236
237        return $urls;
238    }
239
240    // MOCKABLES.
241
242    /**
243     * This is just to make method mocks in the unit tests easier.
244     *
245     * @param string $param Option key to get.
246     *
247     * @return mixed
248     *
249     * @codeCoverageIgnore
250     */
251    public function wp_get_option( $param ) {
252        return get_option( $param );
253    }
254
255    /**
256     * This is just to make method mocks in the unit tests easier.
257     *
258     * @param string $option_name  Option name to be added.
259     * @param mixed  $option_value Option value.
260     *
261     * @return mixed
262     *
263     * @codeCoverageIgnore
264     */
265    public function wp_add_option( $option_name, $option_value ) {
266        return add_option( $option_name, $option_value );
267    }
268
269    /**
270     * This is just to make method mocks in the unit tests easier.
271     *
272     * @param string $option_name  Option name to be updated.
273     * @param mixed  $option_value Option value.
274     *
275     * @return mixed
276     *
277     * @codeCoverageIgnore
278     */
279    public function wp_update_option( $option_name, $option_value ) {
280        return update_option( $option_name, $option_value );
281    }
282}