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 ); |
|
|
|
|
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' )); |
|
|
|
|
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 ); |
|
|
|
|
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 ); |
|
|
|
|
311
|
|
|
} |
312
|
|
|
|
313
|
|
|
if ( is_wp_error( $error = validate_plugin( $plugin ) ) ) { |
314
|
|
|
return new WP_Error( 'unknown_plugin', $error->get_error_messages() , 404 ); |
|
|
|
|
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
|
|
|
|
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.