PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3
Jetpack – WP Security, Backup, Speed, & Growth v16.3
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
← All changes | class.jetpack-autoupdate.php +379 -0 16.2-beta → 16.3 View file →
@@ -1,0 +1,379 @@
1 +<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2 +/**
3 + * Handles items that have been selected for automatic updates.
4 + * Hooks into WP_Automatic_Updater
5 + *
6 + * @package automattic/jetpack
7 + */
8 +
9 +if ( ! defined( 'ABSPATH' ) ) {
10 + exit( 0 );
11 +}
12 +
13 +/**
14 + * Handles items that have been selected for automatic updates.
15 + * Hooks into WP_Automatic_Updater
16 + */
17 +class Jetpack_Autoupdate {
18 +
19 + /**
20 + * Results.
21 + *
22 + * @var array
23 + */
24 + private $results = array();
25 +
26 + /**
27 + * Expected updates.
28 + *
29 + * @var array
30 + */
31 + private $expected = array();
32 +
33 + /**
34 + * Successful updates.
35 + *
36 + * @var array
37 + */
38 + private $success = array(
39 + 'plugin' => array(),
40 + 'theme' => array(),
41 + );
42 +
43 + /**
44 + * Failed updates.
45 + *
46 + * @var array
47 + */
48 + private $failed = array(
49 + 'plugin' => array(),
50 + 'theme' => array(),
51 + );
52 +
53 + /**
54 + * Static instance.
55 + *
56 + * @var self
57 + */
58 + private static $instance = null;
59 +
60 + /**
61 + * Initialize and fetch the static instance.
62 + *
63 + * @return self
64 + */
65 + public static function init() {
66 + if ( self::$instance === null ) {
67 + self::$instance = new Jetpack_Autoupdate();
68 + }
69 +
70 + return self::$instance;
71 + }
72 +
73 + /** Constructor. */
74 + private function __construct() {
75 + if (
76 + /** This filter is documented in class.jetpack-json-api-endpoint.php */
77 + apply_filters( 'jetpack_json_manage_api_enabled', true )
78 + ) {
79 + add_filter( 'auto_update_theme', array( $this, 'autoupdate_theme' ), 10, 2 );
80 + add_filter( 'auto_update_core', array( $this, 'autoupdate_core' ), 10, 2 );
81 + add_filter( 'auto_update_translation', array( $this, 'autoupdate_translation' ), 10, 2 );
82 + add_action( 'automatic_updates_complete', array( $this, 'automatic_updates_complete' ), 999, 1 );
83 + }
84 + }
85 +
86 + /**
87 + * Filter function for `auto_update_translation`.
88 + *
89 + * @param bool|null $update Whether to update.
90 + * @param object $item The update offer.
91 + * @return bool|null Whether to update.
92 + */
93 + public function autoupdate_translation( $update, $item ) {
94 + // Autoupdate all translations.
95 + if ( Jetpack_Options::get_option( 'autoupdate_translations', false ) ) {
96 + return true;
97 + }
98 +
99 + if ( ! isset( $item->slug ) || ! isset( $item->type ) ) {
100 + return $update;
101 + }
102 +
103 + // Themes.
104 + $autoupdate_themes_translations = Jetpack_Options::get_option( 'autoupdate_themes_translations', array() );
105 + $autoupdate_theme_list = Jetpack_Options::get_option( 'autoupdate_themes', array() );
106 +
107 + if ( ( in_array( $item->slug, $autoupdate_themes_translations, true ) || in_array( $item->slug, $autoupdate_theme_list, true ) )
108 + && 'theme' === $item->type
109 + ) {
110 + $this->expect( $item->type . ':' . $item->slug, 'translation' );
111 +
112 + return true;
113 + }
114 +
115 + // Plugins.
116 + $autoupdate_plugin_translations = Jetpack_Options::get_option( 'autoupdate_plugins_translations', array() );
117 + $autoupdate_plugin_list = (array) get_site_option( 'auto_update_plugins', array() );
118 + $plugin_files = array_unique( array_merge( $autoupdate_plugin_list, $autoupdate_plugin_translations ) );
119 + $plugin_slugs = array_map( array( __CLASS__, 'get_plugin_slug' ), $plugin_files );
120 +
121 + if ( in_array( $item->slug, $plugin_slugs, true )
122 + && 'plugin' === $item->type
123 + ) {
124 + $this->expect( $item->type . ':' . $item->slug, 'translation' );
125 + return true;
126 + }
127 +
128 + return $update;
129 + }
130 +
131 + /**
132 + * Filter function for `auto_update_theme`.
133 + *
134 + * @param bool|null $update Whether to update.
135 + * @param object $item The update offer.
136 + * @return bool|null Whether to update.
137 + */
138 + public function autoupdate_theme( $update, $item ) {
139 + if ( ! isset( $item->theme ) ) {
140 + return $update;
141 + }
142 + $autoupdate_theme_list = Jetpack_Options::get_option( 'autoupdate_themes', array() );
143 + if ( in_array( $item->theme, $autoupdate_theme_list, true ) ) {
144 + $this->expect( $item->theme, 'theme' );
145 + return true;
146 + }
147 +
148 + return $update;
149 + }
150 +
151 + /**
152 + * Filter function for `auto_update_core`.
153 + *
154 + * @param bool|null $update Whether to update.
155 + * @return bool|null Whether to update.
156 + */
157 + public function autoupdate_core( $update ) {
158 + $autoupdate_core = Jetpack_Options::get_option( 'autoupdate_core', false );
159 + if ( $autoupdate_core ) {
160 + return $autoupdate_core;
161 + }
162 +
163 + return $update;
164 + }
165 +
166 + /**
167 + * Stores the an item identifier to the expected array.
168 + *
169 + * @param string $item Example: 'jetpack/jetpack.php' for type 'plugin' or 'twentyfifteen' for type 'theme'.
170 + * @param string $type 'plugin' or 'theme'.
171 + */
172 + private function expect( $item, $type ) {
173 + if ( ! isset( $this->expected[ $type ] ) ) {
174 + $this->expected[ $type ] = array();
175 + }
176 + $this->expected[ $type ][] = $item;
177 + }
178 +
179 + /**
180 + * On completion of an automatic update, let's store the results.
181 + *
182 + * @param mixed $results - Sent by WP_Automatic_Updater after it completes an autoupdate action. Results may be empty.
183 + */
184 + public function automatic_updates_complete( $results ) {
185 + if ( empty( $this->expected ) ) {
186 + return;
187 + }
188 + $this->results = empty( $results ) ? self::get_possible_failures() : $results;
189 +
190 + add_action( 'shutdown', array( $this, 'bump_stats' ) );
191 +
192 + Jetpack::init();
193 +
194 + $items_to_log = array( 'plugin', 'theme', 'translation' );
195 + foreach ( $items_to_log as $items ) {
196 + $this->log_items( $items );
197 + }
198 +
199 + Jetpack::log( 'autoupdates', $this->get_log() );
200 + }
201 +
202 + /**
203 + * Get log data.
204 + *
205 + * @return array Data.
206 + */
207 + public function get_log() {
208 + return array(
209 + 'results' => $this->results,
210 + 'failed' => $this->failed,
211 + 'success' => $this->success,
212 + );
213 + }
214 +
215 + /**
216 + * Iterates through expected items ( plugins or themes ) and compares them to actual results.
217 + *
218 + * @param string $items 'plugin' or 'theme'.
219 + */
220 + private function log_items( $items ) {
221 + if ( ! isset( $this->expected[ $items ] ) ) {
222 + return;
223 + }
224 +
225 + $item_results = $this->get_successful_updates( $items );
226 +
227 + if ( is_array( $this->expected[ $items ] ) ) {
228 + foreach ( $this->expected[ $items ] as $item ) {
229 + if ( in_array( $item, $item_results, true ) ) {
230 + $this->success[ $items ][] = $item;
231 + } else {
232 + $this->failed[ $items ][] = $item;
233 + }
234 + }
235 + }
236 + }
237 +
238 + /**
239 + * Bump stats.
240 + */
241 + public function bump_stats() {
242 + $instance = Jetpack::init();
243 + $log = array();
244 + // Bump numbers.
245 +
246 + if ( ! empty( $this->success['theme'] ) ) {
247 + $instance->stat( 'autoupdates/theme-success', is_countable( $this->success['theme'] ) ? count( $this->success['theme'] ) : 0 );
248 + $log['themes_success'] = $this->success['theme'];
249 + }
250 +
251 + if ( ! empty( $this->failed['theme'] ) ) {
252 + $instance->stat( 'autoupdates/theme-fail', is_countable( $this->failed['theme'] ) ? count( $this->failed['theme'] ) : 0 );
253 + $log['themes_failed'] = $this->failed['theme'];
254 + }
255 +
256 + $instance->do_stats( 'server_side' );
257 +
258 + // Send a more detailed log to logstash.
259 + if ( ! empty( $log ) ) {
260 + $xml = new Jetpack_IXR_Client(
261 + array(
262 + 'user_id' => get_current_user_id(),
263 + )
264 + );
265 + $log['blog_id'] = Jetpack_Options::get_option( 'id' );
266 + $xml->query( 'jetpack.debug_autoupdate', $log );
267 + }
268 + }
269 +
270 + /**
271 + * Parses the autoupdate results generated by WP_Automatic_Updater and returns a simple array of successful items.
272 + *
273 + * @param string $type 'plugin' or 'theme'.
274 + * @return array
275 + */
276 + private function get_successful_updates( $type ) {
277 + $successful_updates = array();
278 +
279 + if ( ! isset( $this->results[ $type ] ) ) {
280 + return $successful_updates;
281 + }
282 +
283 + foreach ( $this->results[ $type ] as $result ) {
284 + if ( $result->result ) {
285 + switch ( $type ) {
286 + case 'theme':
287 + $successful_updates[] = $result->item->theme;
288 + break;
289 + case 'translation':
290 + $successful_updates[] = $result->item->type . ':' . $result->item->slug;
291 + break;
292 + }
293 + }
294 + }
295 +
296 + return $successful_updates;
297 + }
298 +
299 + /**
300 + * Get possible failure codes.
301 + *
302 + * @return string[] Failure codes.
303 + */
304 + public static function get_possible_failures() {
305 + $result = array();
306 + // Lets check some reasons why it might not be working as expected.
307 + include_once ABSPATH . '/wp-admin/includes/admin.php';
308 + include_once ABSPATH . '/wp-admin/includes/class-wp-upgrader.php';
309 + $upgrader = new WP_Automatic_Updater();
310 +
311 + if ( $upgrader->is_disabled() ) {
312 + $result[] = 'autoupdates-disabled';
313 + }
314 + if ( ! is_main_site() ) {
315 + $result[] = 'is-not-main-site';
316 + }
317 + if ( ! is_main_network() ) {
318 + $result[] = 'is-not-main-network';
319 + }
320 + if ( $upgrader->is_vcs_checkout( ABSPATH ) ) {
321 + $result[] = 'site-on-vcs';
322 + }
323 + if ( $upgrader->is_vcs_checkout( WP_PLUGIN_DIR ) ) {
324 + $result[] = 'plugin-directory-on-vcs';
325 + }
326 + if ( $upgrader->is_vcs_checkout( WP_CONTENT_DIR ) ) {
327 + $result[] = 'content-directory-on-vcs';
328 + }
329 + $lock = get_option( 'auto_updater.lock' );
330 + if ( $lock > ( time() - HOUR_IN_SECONDS ) ) {
331 + $result[] = 'lock-is-set';
332 + }
333 + $skin = new Automatic_Upgrader_Skin();
334 + include_once ABSPATH . 'wp-admin/includes/file.php';
335 + include_once ABSPATH . 'wp-admin/includes/template.php';
336 + if ( ! $skin->request_filesystem_credentials( false, ABSPATH, false ) ) {
337 + $result[] = 'no-system-write-access';
338 + }
339 + if ( ! $skin->request_filesystem_credentials( false, WP_PLUGIN_DIR, false ) ) {
340 + $result[] = 'no-plugin-directory-write-access';
341 + }
342 + if ( ! $skin->request_filesystem_credentials( false, WP_CONTENT_DIR, false ) ) {
343 + $result[] = 'no-wp-content-directory-write-access';
344 + }
345 +
346 + return $result;
347 + }
348 +
349 + /**
350 + * Get the plugin slug.
351 + *
352 + * @param string $plugin_file Plugin file.
353 + * @return string Slug.
354 + */
355 + public static function get_plugin_slug( $plugin_file ) {
356 + $update_plugins = get_site_transient( 'update_plugins' );
357 + if ( isset( $update_plugins->no_update ) ) {
358 + if ( isset( $update_plugins->no_update[ $plugin_file ]->slug ) ) {
359 + $slug = $update_plugins->no_update[ $plugin_file ]->slug;
360 + }
361 + }
362 + if ( empty( $slug ) && isset( $update_plugins->response ) ) {
363 + if ( isset( $update_plugins->response[ $plugin_file ]->slug ) ) {
364 + $slug = $update_plugins->response[ $plugin_file ]->slug;
365 + }
366 + }
367 +
368 + // Try to infer from the plugin file if not cached.
369 + if ( empty( $slug ) ) {
370 + $slug = dirname( $plugin_file );
371 + if ( '.' === $slug ) {
372 + $slug = preg_replace( '/(.+)\.php$/', '$1', $plugin_file );
373 + }
374 + }
375 + return $slug;
376 + }
377 +}
378 +
379 +Jetpack_Autoupdate::init();