PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 12.4.2
Jetpack – WP Security, Backup, Speed, & Growth v12.4.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 / json-endpoints / jetpack / class.jetpack-json-api-plugins-endpoint.php

class.jetpack-json-api-plugins-endpoint.php in Jetpack – WP Security, Backup, Speed, & Growth 12.4.2, at json-endpoints/jetpack/class.jetpack-json-api-plugins-endpoint.php

466 lines 15.5 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 use Automattic\Jetpack\Constants;
4 use Automattic\Jetpack\Sync\Functions;
5
6 /**
7 * Base class for working with plugins.
8 */
9 abstract class Jetpack_JSON_API_Plugins_Endpoint extends Jetpack_JSON_API_Endpoint {
10
11 /**
12 * Plugins.
13 *
14 * @var array
15 */
16 protected $plugins = array();
17
18 /**
19 * If the plugin is network wide.
20 *
21 * @var boolean
22 */
23 protected $network_wide = false;
24
25 /**
26 * If we're working in bulk.
27 *
28 * @var boolean
29 */
30 protected $bulk = true;
31
32 /**
33 * The log.
34 *
35 * @var array
36 */
37 protected $log;
38
39 /**
40 * Response format.
41 *
42 * @var array
43 */
44 public static $_response_format = array( // phpcs:ignore PSR2.Classes.PropertyDeclaration.Underscore
45 'id' => '(safehtml) The plugin\'s ID',
46 'slug' => '(safehtml) The plugin\'s .org slug',
47 'active' => '(boolean) The plugin status.',
48 'update' => '(object) The plugin update info.',
49 'name' => '(safehtml) The name of the plugin.',
50 'plugin_url' => '(url) Link to the plugin\'s web site.',
51 'version' => '(safehtml) The plugin version number.',
52 'description' => '(safehtml) Description of what the plugin does and/or notes from the author',
53 'author' => '(safehtml) The author\'s name',
54 'author_url' => '(url) The authors web site address',
55 'network' => '(boolean) Whether the plugin can only be activated network wide.',
56 'autoupdate' => '(boolean) Whether the plugin is automatically updated',
57 'autoupdate_translation' => '(boolean) Whether the plugin is automatically updating translations',
58 'next_autoupdate' => '(string) Y-m-d H:i:s for next scheduled update event',
59 'log' => '(array:safehtml) An array of update log strings.',
60 'uninstallable' => '(boolean) Whether the plugin is unistallable.',
61 'action_links' => '(array) An array of action links that the plugin uses.',
62 );
63
64 /**
65 * Response format v1_2
66 *
67 * @var array
68 */
69 public static $_response_format_v1_2 = array( // phpcs:ignore PSR2.Classes.PropertyDeclaration.Underscore
70 'slug' => '(safehtml) The plugin\'s .org slug',
71 'active' => '(boolean) The plugin status.',
72 'update' => '(object) The plugin update info.',
73 'name' => '(safehtml) The plugin\'s ID',
74 'display_name' => '(safehtml) The name of the plugin.',
75 'version' => '(safehtml) The plugin version number.',
76 'description' => '(safehtml) Description of what the plugin does and/or notes from the author',
77 'author' => '(safehtml) The author\'s name',
78 'author_url' => '(url) The authors web site address',
79 'plugin_url' => '(url) Link to the plugin\'s web site.',
80 'network' => '(boolean) Whether the plugin can only be activated network wide.',
81 'autoupdate' => '(boolean) Whether the plugin is automatically updated',
82 'autoupdate_translation' => '(boolean) Whether the plugin is automatically updating translations',
83 'uninstallable' => '(boolean) Whether the plugin is unistallable.',
84 'action_links' => '(array) An array of action links that the plugin uses.',
85 'log' => '(array:safehtml) An array of update log strings.',
86 );
87
88 /**
89 * The result.
90 *
91 * @return array
92 */
93 protected function result() {
94
95 $plugins = $this->get_plugins();
96
97 if ( ! $this->bulk && ! empty( $plugins ) ) {
98 return array_pop( $plugins );
99 }
100
101 return array( 'plugins' => $plugins );
102 }
103
104 /**
105 * Validate the input.
106 *
107 * @param string $plugin - the plugin we're validating.
108 *
109 * @return bool|WP_Error
110 */
111 protected function validate_input( $plugin ) {
112
113 $error = parent::validate_input( $plugin );
114 if ( is_wp_error( $error ) ) {
115 return $error;
116 }
117
118 $error = $this->validate_network_wide();
119 if ( is_wp_error( $error ) ) {
120 return $error;
121 }
122
123 $args = $this->input();
124 // find out what plugin, or plugins we are dealing with
125 // validate the requested plugins
126 if ( ! isset( $plugin ) || empty( $plugin ) ) {
127 if ( ! $args['plugins'] || empty( $args['plugins'] ) ) {
128 return new WP_Error( 'missing_plugin', __( 'You are required to specify a plugin.', 'jetpack' ), 400 );
129 }
130 if ( is_array( $args['plugins'] ) ) {
131 $this->plugins = $args['plugins'];
132 } else {
133 $this->plugins[] = $args['plugins'];
134 }
135 } else {
136 $this->bulk = false;
137 $this->plugins[] = urldecode( $plugin );
138 }
139
140 $error = $this->validate_plugins();
141 if ( is_wp_error( $error ) ) {
142 return $error;
143 }
144
145 return true;
146 }
147
148 /**
149 * Walks through submitted plugins to make sure they are valid
150 *
151 * @return bool|WP_Error
152 */
153 protected function validate_plugins() {
154 if ( empty( $this->plugins ) || ! is_array( $this->plugins ) ) {
155 return new WP_Error( 'missing_plugins', __( 'No plugins found.', 'jetpack' ) );
156 }
157 foreach ( $this->plugins as $index => $plugin ) {
158 if ( ! preg_match( '/\.php$/', $plugin ) ) {
159 $plugin = $plugin . '.php';
160 $this->plugins[ $index ] = $plugin;
161 }
162 $valid = $this->validate_plugin( urldecode( $plugin ) );
163 if ( is_wp_error( $valid ) ) {
164 return $valid;
165 }
166 }
167
168 return true;
169 }
170
171 /**
172 * Format the plugin.
173 *
174 * @param string $plugin_file - the plugin file.
175 * @param array $plugin_data - the plugin data.
176 *
177 * @return array
178 */
179 protected function format_plugin( $plugin_file, $plugin_data ) {
180 if ( version_compare( $this->min_version, '1.2', '>=' ) ) {
181 return $this->format_plugin_v1_2( $plugin_file, $plugin_data );
182 }
183 $plugin = array();
184 $plugin['id'] = preg_replace( '/(.+)\.php$/', '$1', $plugin_file );
185 $plugin['slug'] = Jetpack_Autoupdate::get_plugin_slug( $plugin_file );
186 $plugin['active'] = Jetpack::is_plugin_active( $plugin_file );
187 $plugin['name'] = $plugin_data['Name'];
188 $plugin['plugin_url'] = $plugin_data['PluginURI'];
189 $plugin['version'] = $plugin_data['Version'];
190 $plugin['description'] = $plugin_data['Description'];
191 $plugin['author'] = $plugin_data['Author'];
192 $plugin['author_url'] = $plugin_data['AuthorURI'];
193 $plugin['network'] = $plugin_data['Network'];
194 $plugin['update'] = $this->get_plugin_updates( $plugin_file );
195 $plugin['next_autoupdate'] = gmdate( 'Y-m-d H:i:s', wp_next_scheduled( 'wp_maybe_auto_update' ) );
196 $action_link = $this->get_plugin_action_links( $plugin_file );
197 if ( ! empty( $action_link ) ) {
198 $plugin['action_links'] = $action_link;
199 }
200
201 $plugin['plugin'] = $plugin_file;
202 if ( ! class_exists( 'WP_Automatic_Updater' ) ) {
203 require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
204 }
205 $autoupdate = ( new WP_Automatic_Updater() )->should_update( 'plugin', (object) $plugin, WP_PLUGIN_DIR );
206 $plugin['autoupdate'] = $autoupdate;
207
208 $autoupdate_translation = in_array( $plugin_file, Jetpack_Options::get_option( 'autoupdate_plugins_translations', array() ), true );
209 $plugin['autoupdate_translation'] = $autoupdate || $autoupdate_translation || Jetpack_Options::get_option( 'autoupdate_translations', false );
210
211 $plugin['uninstallable'] = is_uninstallable_plugin( $plugin_file );
212
213 if ( is_multisite() ) {
214 $plugin['network_active'] = is_plugin_active_for_network( $plugin_file );
215 }
216
217 if ( ! empty( $this->log[ $plugin_file ] ) ) {
218 $plugin['log'] = $this->log[ $plugin_file ];
219 }
220 return $plugin;
221 }
222
223 /**
224 * Format the plugin for v1_2.
225 *
226 * @param string $plugin_file - the plugin file.
227 * @param array $plugin_data - the plugin data.
228 *
229 * @return array
230 */
231 protected function format_plugin_v1_2( $plugin_file, $plugin_data ) {
232 $plugin = array();
233 $plugin['slug'] = Jetpack_Autoupdate::get_plugin_slug( $plugin_file );
234 $plugin['active'] = Jetpack::is_plugin_active( $plugin_file );
235 $plugin['name'] = preg_replace( '/(.+)\.php$/', '$1', $plugin_file );
236 $plugin['display_name'] = $plugin_data['Name'];
237 $plugin['plugin_url'] = $plugin_data['PluginURI'];
238 $plugin['version'] = $plugin_data['Version'];
239 $plugin['description'] = $plugin_data['Description'];
240 $plugin['author'] = $plugin_data['Author'];
241 $plugin['author_url'] = $plugin_data['AuthorURI'];
242 $plugin['network'] = $plugin_data['Network'];
243 $plugin['update'] = $this->get_plugin_updates( $plugin_file );
244 $action_link = $this->get_plugin_action_links( $plugin_file );
245 if ( ! empty( $action_link ) ) {
246 $plugin['action_links'] = $action_link;
247 }
248
249 $plugin['plugin'] = $plugin_file;
250 if ( ! class_exists( 'WP_Automatic_Updater' ) ) {
251 require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
252 }
253 $autoupdate = ( new WP_Automatic_Updater() )->should_update( 'plugin', (object) $plugin, WP_PLUGIN_DIR );
254 $plugin['autoupdate'] = $autoupdate;
255
256 $autoupdate_translation = $this->plugin_has_translations_autoupdates_enabled( $plugin_file );
257 $plugin['autoupdate_translation'] = $autoupdate || $autoupdate_translation || Jetpack_Options::get_option( 'autoupdate_translations', false );
258 $plugin['uninstallable'] = is_uninstallable_plugin( $plugin_file );
259
260 if ( is_multisite() ) {
261 $plugin['network_active'] = is_plugin_active_for_network( $plugin_file );
262 }
263
264 if ( ! empty( $this->log[ $plugin_file ] ) ) {
265 $plugin['log'] = $this->log[ $plugin_file ];
266 }
267
268 return $plugin;
269 }
270
271 /**
272 * Check if plugin has autoupdates for translations enabled.
273 *
274 * @param string $plugin_file - the plugin file.
275 *
276 * @return bool
277 */
278 protected function plugin_has_translations_autoupdates_enabled( $plugin_file ) {
279 return (bool) in_array( $plugin_file, Jetpack_Options::get_option( 'autoupdate_plugins_translations', array() ), true );
280 }
281
282 /**
283 * Get file mod capabilities.
284 */
285 protected function get_file_mod_capabilities() {
286 $reasons_can_not_autoupdate = array();
287 $reasons_can_not_modify_files = array();
288
289 $has_file_system_write_access = Functions::file_system_write_access();
290 if ( ! $has_file_system_write_access ) {
291 $reasons_can_not_modify_files['has_no_file_system_write_access'] = __( 'The file permissions on this host prevent editing files.', 'jetpack' );
292 }
293
294 $disallow_file_mods = Constants::get_constant( 'DISALLOW_FILE_MODS' );
295 if ( $disallow_file_mods ) {
296 $reasons_can_not_modify_files['disallow_file_mods'] = __( 'File modifications are explicitly disabled by a site administrator.', 'jetpack' );
297 }
298
299 $automatic_updater_disabled = Constants::get_constant( 'AUTOMATIC_UPDATER_DISABLED' );
300 if ( $automatic_updater_disabled ) {
301 $reasons_can_not_autoupdate['automatic_updater_disabled'] = __( 'Any autoupdates are explicitly disabled by a site administrator.', 'jetpack' );
302 }
303
304 if ( is_multisite() ) {
305 // is it the main network ? is really is multi network
306 if ( Jetpack::is_multi_network() ) {
307 $reasons_can_not_modify_files['is_multi_network'] = __( 'Multi network install are not supported.', 'jetpack' );
308 }
309 // Is the site the main site here.
310 if ( ! is_main_site() ) {
311 $reasons_can_not_modify_files['is_sub_site'] = __( 'The site is not the main network site', 'jetpack' );
312 }
313 }
314
315 $file_mod_capabilities = array(
316 'modify_files' => (bool) empty( $reasons_can_not_modify_files ), // install, remove, update
317 'autoupdate_files' => (bool) empty( $reasons_can_not_modify_files ) && empty( $reasons_can_not_autoupdate ), // enable autoupdates
318 );
319
320 if ( ! empty( $reasons_can_not_modify_files ) ) {
321 $file_mod_capabilities['reasons_modify_files_unavailable'] = $reasons_can_not_modify_files;
322 }
323
324 if ( ! $file_mod_capabilities['autoupdate_files'] ) {
325 $file_mod_capabilities['reasons_autoupdate_unavailable'] = array_merge( $reasons_can_not_autoupdate, $reasons_can_not_modify_files );
326 }
327 return $file_mod_capabilities;
328 }
329
330 /**
331 * Get plugins.
332 *
333 * @return array
334 */
335 protected function get_plugins() {
336 $plugins = array();
337 /** This filter is documented in wp-admin/includes/class-wp-plugins-list-table.php */
338 $installed_plugins = apply_filters( 'all_plugins', get_plugins() );
339 foreach ( $this->plugins as $plugin ) {
340 if ( ! isset( $installed_plugins[ $plugin ] ) ) {
341 continue;
342 }
343
344 $formatted_plugin = $this->format_plugin( $plugin, $installed_plugins[ $plugin ] );
345
346 // If this endpoint accepts site based authentication and a blog token is used, skip capabilities check.
347 if ( $this->accepts_site_based_authentication() ) {
348 $plugins[] = $formatted_plugin;
349 continue;
350 }
351
352 /*
353 * Do not show network-active plugins
354 * to folks who do not have the permission to see them.
355 */
356 if (
357 /** This filter is documented in src/wp-admin/includes/class-wp-plugins-list-table.php */
358 ! apply_filters( 'show_network_active_plugins', current_user_can( 'manage_network_plugins' ) )
359 && ! empty( $formatted_plugin['network_active'] )
360 && true === $formatted_plugin['network_active']
361 ) {
362 continue;
363 }
364
365 $plugins[] = $formatted_plugin;
366 }
367 $args = $this->query_args();
368
369 if ( isset( $args['offset'] ) ) {
370 $plugins = array_slice( $plugins, (int) $args['offset'] );
371 }
372 if ( isset( $args['limit'] ) ) {
373 $plugins = array_slice( $plugins, 0, (int) $args['limit'] );
374 }
375
376 return $plugins;
377 }
378
379 /**
380 * Validate network wide.
381 *
382 * @return bool|WP_Error
383 */
384 protected function validate_network_wide() {
385 $args = $this->input();
386
387 if ( isset( $args['network_wide'] ) && $args['network_wide'] ) {
388 $this->network_wide = true;
389 }
390
391 // If this endpoint accepts site based authentication and a blog token is used, skip capabilities check.
392 if ( $this->accepts_site_based_authentication() ) {
393 return true;
394 }
395
396 if ( $this->network_wide && ! current_user_can( 'manage_network_plugins' ) ) {
397 return new WP_Error( 'unauthorized', __( 'This user is not authorized to manage plugins network wide.', 'jetpack' ), 403 );
398 }
399
400 return true;
401 }
402
403 /**
404 * Validate the plugin.
405 *
406 * @param string $plugin - the plugin we're validating.
407 *
408 * @return bool|WP_Error
409 */
410 protected function validate_plugin( $plugin ) {
411 if ( ! isset( $plugin ) || empty( $plugin ) ) {
412 return new WP_Error( 'missing_plugin', __( 'You are required to specify a plugin to activate.', 'jetpack' ), 400 );
413 }
414
415 $error = validate_plugin( $plugin );
416 if ( is_wp_error( $error ) ) {
417 return new WP_Error( 'unknown_plugin', $error->get_error_messages(), 404 );
418 }
419
420 return true;
421 }
422
423 /**
424 * Get plugin updates.
425 *
426 * @param string $plugin_file - the plugin file.
427 *
428 * @return object|null
429 */
430 protected function get_plugin_updates( $plugin_file ) {
431 $plugin_updates = get_plugin_updates();
432 if ( isset( $plugin_updates[ $plugin_file ] ) ) {
433 $update = $plugin_updates[ $plugin_file ]->update;
434 $cleaned_update = array();
435 foreach ( (array) $update as $update_key => $update_value ) {
436 switch ( $update_key ) {
437 case 'id':
438 case 'slug':
439 case 'plugin':
440 case 'new_version':
441 case 'tested':
442 $cleaned_update[ $update_key ] = wp_kses( $update_value, array() );
443 break;
444 case 'url':
445 case 'package':
446 $cleaned_update[ $update_key ] = esc_url( $update_value );
447 break;
448 }
449 }
450 return (object) $cleaned_update;
451 }
452 return null;
453 }
454
455 /**
456 * Get plugin action links.
457 *
458 * @param string $plugin_file - the plugin file.
459 *
460 * @return array
461 */
462 protected function get_plugin_action_links( $plugin_file ) {
463 return Functions::get_plugins_action_links( $plugin_file );
464 }
465 }
466