Completed
Push — fix/idc-same-heal ( 5f98ee...a3787f )
by
unknown
08:15
created

Jetpack_JSON_API_Plugins_Endpoint::get_plugins()   A

Complexity

Conditions 5
Paths 12

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
nc 12
nop 0
dl 0
loc 20
rs 9.2888
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 ( ! empty ( $this->log[ $plugin_file ] ) ) {
156
			$plugin['log'] = $this->log[ $plugin_file ];
157
		}
158
		return $plugin;
159
	}
160
161
	protected function format_plugin_v1_2( $plugin_file, $plugin_data ) {
162
		$plugin = array();
163
		$plugin['slug']            = Jetpack_Autoupdate::get_plugin_slug( $plugin_file );
164
		$plugin['active']          = Jetpack::is_plugin_active( $plugin_file );
165
		$plugin['name']            = preg_replace("/(.+)\.php$/", "$1", $plugin_file );
166
		$plugin['display_name']	   = $plugin_data['Name'];
167
		$plugin['plugin_url']      = $plugin_data['PluginURI'];
168
		$plugin['version']         = $plugin_data['Version'];
169
		$plugin['description']     = $plugin_data['Description'];
170
		$plugin['author']          = $plugin_data['Author'];
171
		$plugin['author_url']      = $plugin_data['AuthorURI'];
172
		$plugin['network']         = $plugin_data['Network'];
173
		$plugin['update']          = $this->get_plugin_updates( $plugin_file );
174
		$action_link = $this->get_plugin_action_links( $plugin_file );
175
		if ( ! empty( $action_link ) ) {
176
			$plugin['action_links'] = $action_link;
177
		}
178
179
		$autoupdate = $this->plugin_has_autoupdates_enabled( $plugin_file );
180
		$plugin['autoupdate']      = $autoupdate;
181
182
		$autoupdate_translation = $this->plugin_has_translations_autoupdates_enabled( $plugin_file );
183
		$plugin['autoupdate_translation'] = $autoupdate || $autoupdate_translation || Jetpack_Options::get_option( 'autoupdate_translations', false );
184
		$plugin['uninstallable']   = is_uninstallable_plugin( $plugin_file );
185
186
		if ( ! empty ( $this->log[ $plugin_file ] ) ) {
187
			$plugin['log'] = $this->log[ $plugin_file ];
188
		}
189
190
		return $plugin;
191
	}
192
193
	protected function plugin_has_autoupdates_enabled( $plugin_file ) {
194
		return (bool) in_array( $plugin_file, (array) get_site_option( 'auto_update_plugins', array() ), true );
195
	}
196
197
	protected function plugin_has_translations_autoupdates_enabled( $plugin_file ) {
198
		return (bool) in_array( $plugin_file, Jetpack_Options::get_option( 'autoupdate_plugins_translations', array() ) );
199
	}
200
201
202
	protected function get_file_mod_capabilities() {
203
		$reasons_can_not_autoupdate = array();
204
		$reasons_can_not_modify_files = array();
205
206
		$has_file_system_write_access = Functions::file_system_write_access();
207
		if ( ! $has_file_system_write_access ) {
208
			$reasons_can_not_modify_files['has_no_file_system_write_access'] =  __( 'The file permissions on this host prevent editing files.', 'jetpack' );
209
		}
210
211
		$disallow_file_mods = Constants::get_constant('DISALLOW_FILE_MODS' );
212
		if ( $disallow_file_mods ) {
213
			$reasons_can_not_modify_files['disallow_file_mods'] =  __( 'File modifications are explicitly disabled by a site administrator.', 'jetpack' );
214
		}
215
216
		$automatic_updater_disabled = Constants::get_constant( 'AUTOMATIC_UPDATER_DISABLED' );
217
		if ( $automatic_updater_disabled ) {
218
			$reasons_can_not_autoupdate['automatic_updater_disabled'] = __( 'Any autoupdates are explicitly disabled by a site administrator.', 'jetpack' );
219
		}
220
221
		if ( is_multisite() ) {
222
			// is it the main network ? is really is multi network
223
			if ( Jetpack::is_multi_network() ) {
224
				$reasons_can_not_modify_files['is_multi_network'] =  __( 'Multi network install are not supported.', 'jetpack' );
225
			}
226
			// Is the site the main site here.
227
			if ( ! is_main_site() ) {
228
				$reasons_can_not_modify_files['is_sub_site'] =  __( 'The site is not the main network site', 'jetpack' );
229
			}
230
		}
231
232
		$file_mod_capabilities = array(
233
			'modify_files' => (bool) empty( $reasons_can_not_modify_files ), // install, remove, update
234
			'autoupdate_files' => (bool) empty( $reasons_can_not_modify_files ) && empty( $reasons_can_not_autoupdate ), // enable autoupdates
235
		);
236
237
		if ( ! empty( $reasons_can_not_modify_files ) ) {
238
			$file_mod_capabilities['reasons_modify_files_unavailable'] = $reasons_can_not_modify_files;
239
		}
240
241
		if ( ! $file_mod_capabilities['autoupdate_files'] ) {
242
			$file_mod_capabilities['reasons_autoupdate_unavailable'] = array_merge( $reasons_can_not_autoupdate, $reasons_can_not_modify_files );
243
		}
244
		return $file_mod_capabilities;
245
	}
246
247
	protected function get_plugins() {
248
		$plugins = array();
249
		/** This filter is documented in wp-admin/includes/class-wp-plugins-list-table.php */
250
		$installed_plugins = apply_filters( 'all_plugins', get_plugins() );
251
		foreach( $this->plugins as $plugin ) {
252
			if ( ! isset( $installed_plugins[ $plugin ] ) )
253
				continue;
254
			$plugins[] = $this->format_plugin( $plugin, $installed_plugins[ $plugin ] );
255
		}
256
		$args = $this->query_args();
257
258
		if ( isset( $args['offset'] ) ) {
259
			$plugins = array_slice( $plugins, (int) $args['offset'] );
260
		}
261
		if ( isset( $args['limit'] ) ) {
262
			$plugins = array_slice( $plugins, 0, (int) $args['limit'] );
263
		}
264
265
		return $plugins;
266
	}
267
268
	protected function validate_network_wide() {
269
		$args = $this->input();
270
271
		if ( isset( $args['network_wide'] ) && $args['network_wide'] ) {
272
			$this->network_wide = true;
273
		}
274
275
		if ( $this->network_wide && ! current_user_can( 'manage_network_plugins' ) ) {
276
			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...
277
		}
278
279
		return true;
280
	}
281
282
283 View Code Duplication
	protected function validate_plugin( $plugin ) {
284
		if ( ! isset( $plugin) || empty( $plugin ) ) {
285
			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...
286
		}
287
288
		if ( is_wp_error( $error = validate_plugin( $plugin ) ) ) {
289
			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...
290
		}
291
292
		return true;
293
	}
294
295
	protected function get_plugin_updates( $plugin_file ) {
296
		$plugin_updates = get_plugin_updates();
297
		if ( isset( $plugin_updates[ $plugin_file ] ) ) {
298
			$update = $plugin_updates[ $plugin_file ]->update;
299
			$cleaned_update = array();
300
			foreach( (array) $update as $update_key => $update_value ) {
301
				switch ( $update_key ) {
302
					case 'id':
303
					case 'slug':
304
					case 'plugin':
305
					case 'new_version':
306
					case 'tested':
307
						$cleaned_update[ $update_key ] = wp_kses( $update_value, array() );
308
						break;
309
					case 'url':
310
					case 'package':
311
						$cleaned_update[ $update_key ] = esc_url( $update_value );
312
						break;
313
				}
314
			}
315
			return (object) $cleaned_update;
316
		}
317
		return null;
318
	}
319
320
	protected function get_plugin_action_links( $plugin_file ) {
321
		return Functions::get_plugins_action_links( $plugin_file );
322
	}
323
}
324