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