Completed
Push — renovate/mocha-8.x ( 07030e...e8e64c )
by
unknown
28:17 queued 19:09
created

Jetpack_JSON_API_Plugins_Endpoint::get_plugins()   B

Complexity

Conditions 8
Paths 16

Size

Total Lines 37

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
nc 16
nop 0
dl 0
loc 37
rs 8.0835
c 0
b 0
f 0
1
<?php
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
	protected $plugins = array();
12
13
	protected $network_wide = false;
14
15
	protected $bulk = true;
16
	protected $log;
17
18
	static $_response_format = array(
19
		'id'              => '(safehtml)  The plugin\'s ID',
20
		'slug'            => '(safehtml)  The plugin\'s .org slug',
21
		'active'          => '(boolean) The plugin status.',
22
		'update'          => '(object)  The plugin update info.',
23
		'name'            => '(safehtml)  The name of the plugin.',
24
		'plugin_url'      => '(url)  Link to the plugin\'s web site.',
25
		'version'         => '(safehtml)  The plugin version number.',
26
		'description'     => '(safehtml)  Description of what the plugin does and/or notes from the author',
27
		'author'          => '(safehtml)  The author\'s name',
28
		'author_url'      => '(url)  The authors web site address',
29
		'network'         => '(boolean) Whether the plugin can only be activated network wide.',
30
		'autoupdate'      => '(boolean) Whether the plugin is automatically updated',
31
		'autoupdate_translation' => '(boolean) Whether the plugin is automatically updating translations',
32
		'next_autoupdate' => '(string) Y-m-d H:i:s for next scheduled update event',
33
		'log'             => '(array:safehtml) An array of update log strings.',
34
		'uninstallable'   => '(boolean) Whether the plugin is unistallable.',
35
		'action_links'    => '(array) An array of action links that the plugin uses.',
36
	);
37
38
	static $_response_format_v1_2 = array(
39
		'slug'            => '(safehtml) The plugin\'s .org slug',
40
		'active'          => '(boolean) The plugin status.',
41
		'update'          => '(object) The plugin update info.',
42
		'name'            => '(safehtml) The plugin\'s ID',
43
		'display_name'    => '(safehtml) The name of the plugin.',
44
		'version'         => '(safehtml) The plugin version number.',
45
		'description'     => '(safehtml) Description of what the plugin does and/or notes from the author',
46
		'author'          => '(safehtml) The author\'s name',
47
		'author_url'      => '(url) The authors web site address',
48
		'plugin_url'      => '(url) Link to the plugin\'s web site.',
49
		'network'         => '(boolean) Whether the plugin can only be activated network wide.',
50
		'autoupdate'      => '(boolean) Whether the plugin is automatically updated',
51
		'autoupdate_translation' => '(boolean) Whether the plugin is automatically updating translations',
52
		'uninstallable'   => '(boolean) Whether the plugin is unistallable.',
53
		'action_links'    => '(array) An array of action links that the plugin uses.',
54
		'log'             => '(array:safehtml) An array of update log strings.',
55
	);
56
57
	protected function result() {
58
59
		$plugins = $this->get_plugins();
60
61
		if ( ! $this->bulk && ! empty( $plugins ) ) {
62
			return array_pop( $plugins );
63
		}
64
65
		return array( 'plugins' => $plugins );
66
67
	}
68
69
	protected function validate_input( $plugin ) {
70
71
		if ( is_wp_error( $error = parent::validate_input( $plugin ) ) ) {
72
			return $error;
73
		}
74
75
		if ( is_wp_error( $error = $this->validate_network_wide() ) ) {
76
			return $error;
77
		}
78
79
		$args = $this->input();
80
		// find out what plugin, or plugins we are dealing with
81
		// validate the requested plugins
82
		if ( ! isset( $plugin ) || empty( $plugin ) ) {
83
			if ( ! $args['plugins'] || empty( $args['plugins'] ) ) {
84
				return new WP_Error( 'missing_plugin', __( 'You are required to specify a plugin.', 'jetpack' ), 400 );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'missing_plugin'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
85
			}
86
			if ( is_array( $args['plugins'] ) ) {
87
				$this->plugins = $args['plugins'];
88
			} else {
89
				$this->plugins[] = $args['plugins'];
90
			}
91
		} else {
92
			$this->bulk = false;
93
			$this->plugins[] = urldecode( $plugin );
94
		}
95
96
		if ( is_wp_error( $error = $this->validate_plugins() ) ) {
97
			return $error;
98
		};
99
100
		return true;
101
	}
102
103
	/**
104
	 * Walks through submitted plugins to make sure they are valid
105
	 * @return bool|WP_Error
106
	 */
107
	protected function validate_plugins() {
108 View Code Duplication
		if ( empty( $this->plugins ) || ! is_array( $this->plugins ) ) {
109
			return new WP_Error( 'missing_plugins', __( 'No plugins found.', 'jetpack' ));
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'missing_plugins'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
110
		}
111
		foreach( $this->plugins as $index => $plugin ) {
112
			if ( ! preg_match( "/\.php$/", $plugin ) ) {
113
				$plugin =  $plugin . '.php';
114
				$this->plugins[ $index ] = $plugin;
115
			}
116
			$valid = $this->validate_plugin( urldecode( $plugin ) ) ;
117
			if ( is_wp_error( $valid ) ) {
118
				return $valid;
119
			}
120
		}
121
122
		return true;
123
	}
124
125
	protected function format_plugin( $plugin_file, $plugin_data ) {
126
		if ( version_compare( $this->min_version, '1.2', '>=' ) ) {
127
			return $this->format_plugin_v1_2( $plugin_file, $plugin_data );
128
		}
129
		$plugin = array();
130
		$plugin['id']              = preg_replace("/(.+)\.php$/", "$1", $plugin_file );
131
		$plugin['slug']            = Jetpack_Autoupdate::get_plugin_slug( $plugin_file );
132
		$plugin['active']          = Jetpack::is_plugin_active( $plugin_file );
133
		$plugin['name']            = $plugin_data['Name'];
134
		$plugin['plugin_url']      = $plugin_data['PluginURI'];
135
		$plugin['version']         = $plugin_data['Version'];
136
		$plugin['description']     = $plugin_data['Description'];
137
		$plugin['author']          = $plugin_data['Author'];
138
		$plugin['author_url']      = $plugin_data['AuthorURI'];
139
		$plugin['network']         = $plugin_data['Network'];
140
		$plugin['update']          = $this->get_plugin_updates( $plugin_file );
141
		$plugin['next_autoupdate'] = date( 'Y-m-d H:i:s', wp_next_scheduled( 'wp_maybe_auto_update' ) );
142
		$action_link = $this->get_plugin_action_links( $plugin_file );
143
		if ( ! empty( $action_link ) ) {
144
			$plugin['action_links'] = $action_link;
145
		}
146
147
		$autoupdate           = in_array( $plugin_file, (array) get_site_option( 'auto_update_plugins', array() ), true );
148
		$plugin['autoupdate'] = $autoupdate;
149
150
		$autoupdate_translation = in_array( $plugin_file, Jetpack_Options::get_option( 'autoupdate_plugins_translations', array() ) );
151
		$plugin['autoupdate_translation'] = $autoupdate || $autoupdate_translation || Jetpack_Options::get_option( 'autoupdate_translations', false );
152
153
		$plugin['uninstallable']   = is_uninstallable_plugin( $plugin_file );
154
155
		if ( is_multisite() ) {
156
			$plugin['network_active'] = is_plugin_active_for_network( $plugin_file );
157
		}
158
159
		if ( ! empty ( $this->log[ $plugin_file ] ) ) {
160
			$plugin['log'] = $this->log[ $plugin_file ];
161
		}
162
		return $plugin;
163
	}
164
165
	protected function format_plugin_v1_2( $plugin_file, $plugin_data ) {
166
		$plugin = array();
167
		$plugin['slug']            = Jetpack_Autoupdate::get_plugin_slug( $plugin_file );
168
		$plugin['active']          = Jetpack::is_plugin_active( $plugin_file );
169
		$plugin['name']            = preg_replace("/(.+)\.php$/", "$1", $plugin_file );
170
		$plugin['display_name']	   = $plugin_data['Name'];
171
		$plugin['plugin_url']      = $plugin_data['PluginURI'];
172
		$plugin['version']         = $plugin_data['Version'];
173
		$plugin['description']     = $plugin_data['Description'];
174
		$plugin['author']          = $plugin_data['Author'];
175
		$plugin['author_url']      = $plugin_data['AuthorURI'];
176
		$plugin['network']         = $plugin_data['Network'];
177
		$plugin['update']          = $this->get_plugin_updates( $plugin_file );
178
		$action_link = $this->get_plugin_action_links( $plugin_file );
179
		if ( ! empty( $action_link ) ) {
180
			$plugin['action_links'] = $action_link;
181
		}
182
183
		$autoupdate = $this->plugin_has_autoupdates_enabled( $plugin_file );
184
		$plugin['autoupdate']      = $autoupdate;
185
186
		$autoupdate_translation = $this->plugin_has_translations_autoupdates_enabled( $plugin_file );
187
		$plugin['autoupdate_translation'] = $autoupdate || $autoupdate_translation || Jetpack_Options::get_option( 'autoupdate_translations', false );
188
		$plugin['uninstallable']   = is_uninstallable_plugin( $plugin_file );
189
190
		if ( is_multisite() ) {
191
			$plugin['network_active'] = is_plugin_active_for_network( $plugin_file );
192
		}
193
194
		if ( ! empty ( $this->log[ $plugin_file ] ) ) {
195
			$plugin['log'] = $this->log[ $plugin_file ];
196
		}
197
198
		return $plugin;
199
	}
200
201
	protected function plugin_has_autoupdates_enabled( $plugin_file ) {
202
		return (bool) in_array( $plugin_file, (array) get_site_option( 'auto_update_plugins', array() ), true );
203
	}
204
205
	protected function plugin_has_translations_autoupdates_enabled( $plugin_file ) {
206
		return (bool) in_array( $plugin_file, Jetpack_Options::get_option( 'autoupdate_plugins_translations', array() ) );
207
	}
208
209
210
	protected function get_file_mod_capabilities() {
211
		$reasons_can_not_autoupdate = array();
212
		$reasons_can_not_modify_files = array();
213
214
		$has_file_system_write_access = Functions::file_system_write_access();
215
		if ( ! $has_file_system_write_access ) {
216
			$reasons_can_not_modify_files['has_no_file_system_write_access'] =  __( 'The file permissions on this host prevent editing files.', 'jetpack' );
217
		}
218
219
		$disallow_file_mods = Constants::get_constant('DISALLOW_FILE_MODS' );
220
		if ( $disallow_file_mods ) {
221
			$reasons_can_not_modify_files['disallow_file_mods'] =  __( 'File modifications are explicitly disabled by a site administrator.', 'jetpack' );
222
		}
223
224
		$automatic_updater_disabled = Constants::get_constant( 'AUTOMATIC_UPDATER_DISABLED' );
225
		if ( $automatic_updater_disabled ) {
226
			$reasons_can_not_autoupdate['automatic_updater_disabled'] = __( 'Any autoupdates are explicitly disabled by a site administrator.', 'jetpack' );
227
		}
228
229
		if ( is_multisite() ) {
230
			// is it the main network ? is really is multi network
231
			if ( Jetpack::is_multi_network() ) {
232
				$reasons_can_not_modify_files['is_multi_network'] =  __( 'Multi network install are not supported.', 'jetpack' );
233
			}
234
			// Is the site the main site here.
235
			if ( ! is_main_site() ) {
236
				$reasons_can_not_modify_files['is_sub_site'] =  __( 'The site is not the main network site', 'jetpack' );
237
			}
238
		}
239
240
		$file_mod_capabilities = array(
241
			'modify_files' => (bool) empty( $reasons_can_not_modify_files ), // install, remove, update
242
			'autoupdate_files' => (bool) empty( $reasons_can_not_modify_files ) && empty( $reasons_can_not_autoupdate ), // enable autoupdates
243
		);
244
245
		if ( ! empty( $reasons_can_not_modify_files ) ) {
246
			$file_mod_capabilities['reasons_modify_files_unavailable'] = $reasons_can_not_modify_files;
247
		}
248
249
		if ( ! $file_mod_capabilities['autoupdate_files'] ) {
250
			$file_mod_capabilities['reasons_autoupdate_unavailable'] = array_merge( $reasons_can_not_autoupdate, $reasons_can_not_modify_files );
251
		}
252
		return $file_mod_capabilities;
253
	}
254
255
	protected function get_plugins() {
256
		$plugins = array();
257
		/** This filter is documented in wp-admin/includes/class-wp-plugins-list-table.php */
258
		$installed_plugins = apply_filters( 'all_plugins', get_plugins() );
259
		foreach ( $this->plugins as $plugin ) {
260
			if ( ! isset( $installed_plugins[ $plugin ] ) ) {
261
				continue;
262
			}
263
264
			$formatted_plugin = $this->format_plugin( $plugin, $installed_plugins[ $plugin ] );
265
266
			/*
267
			 * Do not show network-active plugins
268
			 * to folks who do not have the permission to see them.
269
			 */
270
			if (
271
				/** This filter is documented in src/wp-admin/includes/class-wp-plugins-list-table.php */
272
				! apply_filters( 'show_network_active_plugins', current_user_can( 'manage_network_plugins' ) )
273
				&& ! empty( $formatted_plugin['network_active'] )
274
				&& true === $formatted_plugin['network_active']
275
			) {
276
				continue;
277
			}
278
279
			$plugins[] = $formatted_plugin;
280
		}
281
		$args = $this->query_args();
282
283
		if ( isset( $args['offset'] ) ) {
284
			$plugins = array_slice( $plugins, (int) $args['offset'] );
285
		}
286
		if ( isset( $args['limit'] ) ) {
287
			$plugins = array_slice( $plugins, 0, (int) $args['limit'] );
288
		}
289
290
		return $plugins;
291
	}
292
293
	protected function validate_network_wide() {
294
		$args = $this->input();
295
296
		if ( isset( $args['network_wide'] ) && $args['network_wide'] ) {
297
			$this->network_wide = true;
298
		}
299
300
		if ( $this->network_wide && ! current_user_can( 'manage_network_plugins' ) ) {
301
			return new WP_Error( 'unauthorized', __( 'This user is not authorized to manage plugins network wide.', 'jetpack' ), 403 );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'unauthorized'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
302
		}
303
304
		return true;
305
	}
306
307
308 View Code Duplication
	protected function validate_plugin( $plugin ) {
309
		if ( ! isset( $plugin) || empty( $plugin ) ) {
310
			return new WP_Error( 'missing_plugin', __( 'You are required to specify a plugin to activate.', 'jetpack' ), 400 );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'missing_plugin'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
311
		}
312
313
		if ( is_wp_error( $error = validate_plugin( $plugin ) ) ) {
314
			return new WP_Error( 'unknown_plugin', $error->get_error_messages() , 404 );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'unknown_plugin'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
315
		}
316
317
		return true;
318
	}
319
320
	protected function get_plugin_updates( $plugin_file ) {
321
		$plugin_updates = get_plugin_updates();
322
		if ( isset( $plugin_updates[ $plugin_file ] ) ) {
323
			$update = $plugin_updates[ $plugin_file ]->update;
324
			$cleaned_update = array();
325
			foreach( (array) $update as $update_key => $update_value ) {
326
				switch ( $update_key ) {
327
					case 'id':
328
					case 'slug':
329
					case 'plugin':
330
					case 'new_version':
331
					case 'tested':
332
						$cleaned_update[ $update_key ] = wp_kses( $update_value, array() );
333
						break;
334
					case 'url':
335
					case 'package':
336
						$cleaned_update[ $update_key ] = esc_url( $update_value );
337
						break;
338
				}
339
			}
340
			return (object) $cleaned_update;
341
		}
342
		return null;
343
	}
344
345
	protected function get_plugin_action_links( $plugin_file ) {
346
		return Functions::get_plugins_action_links( $plugin_file );
347
	}
348
}
349