PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.0
Jetpack – WP Security, Backup, Speed, & Growth v16.0
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 13.7.2 13.8.3 13.9.2 14.0.1 14.1.1 14.2.2 14.3.1 All 501 releases
jetpack / class.jetpack-autoupdate.php
class.jetpack-autoupdate.php
380 lines 9.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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();
380