PluginProbe
Friends / 2.9.1
Friends v2.9.1
4.3.2 4.3.1 4.3.0 4.2.2 4.2.1 4.2.0 4.1.0 2.7.4 2.7.5 2.7.6 2.7.7 2.7.8 2.7.9 2.8.0 2.8.1 2.8.2 2.8.3 2.8.4 2.8.5 2.8.6 2.8.7 2.8.8 2.8.9 2.9.0 2.9.1 All 88 releases
friends / includes / class-plugin-installer.php

class-plugin-installer.php in Friends 2.9.1, at includes/class-plugin-installer.php

525 lines 15.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Friends Plugin Installer
4 *
5 * This contains the functions to install plugins for the Friends Plugin.
6 *
7 * @package Friends
8 */
9
10 namespace Friends;
11
12 /**
13 * This is the class to install plugins for the Friends Plugin. Adapted from the github link below.
14 *
15 * @since 1.0
16 *
17 * @package Friends
18 * @author Alex Kirk
19 * @author Darren Cooney
20 * @link https://github.com/dcooney/wordpress-plugin-installer
21 */
22 class Plugin_Installer {
23 /**
24 * Our plugins.
25 *
26 * @var array
27 */
28 private static $plugins = array();
29
30 /**
31 * Register the WordPress hooks
32 */
33 public static function register_hooks() {
34 add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue_scripts' ) );
35 add_action( 'wp_ajax_friends_plugin_installer', array( __CLASS__, 'ajax_plugin_installer' ) );
36 add_action( 'wp_ajax_friends_plugin_activation', array( __CLASS__, 'ajax_plugin_activation' ) );
37 add_action( 'wp_ajax_friends_plugin_deactivation', array( __CLASS__, 'ajax_plugin_deactivation' ) );
38 add_filter( 'plugins_api', array( __CLASS__, 'override_plugin_info' ), 20, 3 );
39 // TODO: Can be removed after other plugins make it into the plugin directory.
40 add_filter( 'site_transient' . '_update_plugins', array( __CLASS__, 'override_plugin_push_update' ) );
41 add_filter( 'upgrader_post_install', array( __CLASS__, 'after_install' ), 10, 3 );
42 }
43
44 /**
45 * Initialize the display of the plugins.
46 *
47 * @since 1.0
48 */
49 public static function init() {
50 $additional_plugins = array(
51 'activitypub' => __( 'Adds ActivityPub support to your blog. Be followed on Mastodon, follow people on Mastodon with the Friends plugin.', 'friends' ),
52 'enable-mastodon-apps' => true,
53 );
54 $plugins = array_merge( $additional_plugins, self::get_friends_plugins() );
55 ksort( $plugins );
56
57 require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
58
59 wp_enqueue_script( 'plugin-install' );
60 add_thickbox();
61 wp_enqueue_script( 'updates' );
62
63 foreach ( array_keys( (array) $plugins ) as $plugin_slug ) {
64
65 $button_classes = 'install button button-primary';
66 $button_text = __( 'Install Now', 'friends' );
67
68 $api = plugins_api(
69 'plugin_information',
70 array(
71 'slug' => sanitize_file_name( $plugin_slug ),
72 'fields' => array(
73 'short_description' => true,
74 'sections' => false,
75 'requires' => false,
76 'downloaded' => true,
77 'last_updated' => false,
78 'added' => false,
79 'tags' => false,
80 'compatibility' => false,
81 'homepage' => false,
82 'donate_link' => false,
83 'icons' => true,
84 'banners' => true,
85 ),
86 )
87 );
88
89 if ( isset( $additional_plugins[ $plugin_slug ] ) ) {
90 $api->more_info = admin_url( 'plugin-install.php?tab=plugin-information&plugin=' . $plugin_slug . '&TB_iframe=true' );
91 if ( is_string( $additional_plugins[ $plugin_slug ] ) ) {
92 $api->comment = $additional_plugins[ $plugin_slug ];
93 }
94 }
95 if ( ! is_wp_error( $api ) ) {
96 $main_plugin_file = self::get_plugin_file( $plugin_slug );
97 if ( $main_plugin_file && self::check_file_extension( $main_plugin_file ) ) {
98 if ( is_plugin_active( $main_plugin_file ) ) {
99 $button_classes = 'installed button disabled';
100 $button_text = __( 'Active' ); // phpcs:ignore WordPress.WP.I18n.MissingArgDomain
101 } else {
102 $button_classes = 'activate button button-primary';
103 $button_text = __( 'Activate' ); // phpcs:ignore WordPress.WP.I18n.MissingArgDomain
104 }
105 }
106 self::render_template( $api, $button_text, $button_classes );
107 }
108 }
109 }
110
111 /**
112 * Gets the available plugins.
113 *
114 * @return object The plugin information.
115 */
116 public static function get_friends_plugins() {
117 $cache_key = 'friends_plugins_info_v1';
118 $offline = apply_filters( 'friends_offline_mode', false );
119
120 $data = get_transient( $cache_key );
121 if ( $offline ) {
122 // We'll want to use the shipped file.
123 $data = false;
124 }
125
126 if ( false === $data || apply_filters( 'friends_deactivate_plugin_cache', false ) ) {
127 $remote = false;
128 if ( ! $offline ) {
129 $remote = wp_remote_get(
130 'https://raw.githubusercontent.com/akirk/friends/main/plugins.json',
131 array(
132 'timeout' => 10,
133 'headers' => array(
134 'Accept' => 'application/json',
135 ),
136 )
137 );
138
139 if (
140 is_wp_error( $remote )
141 || 200 !== wp_remote_retrieve_response_code( $remote )
142 || is_null( json_decode( wp_remote_retrieve_body( $remote ) ) )
143 ) {
144 $remote = false;
145 }
146 }
147
148 if ( false === $remote ) {
149 $remote = array(
150 'status_code' => 200,
151 'headers' => array(
152 'content-type' => 'application/json',
153 ),
154 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
155 'body' => file_get_contents( FRIENDS_PLUGIN_DIR . '/plugins.json' ),
156 );
157 }
158
159 $data = array();
160 foreach ( json_decode( wp_remote_retrieve_body( $remote ) ) as $slug => $plugin_data ) {
161 if ( 'activitypub' === $slug ) {
162 continue;
163 }
164 $plugin_data->sections = (array) $plugin_data->sections;
165 $data[ $slug ] = $plugin_data;
166 }
167
168 if ( ! $offline ) {
169 set_transient( $cache_key, $data, 12 * HOUR_IN_SECONDS );
170 }
171 }
172
173 if ( ! $data ) {
174 return array();
175 }
176
177 return $data;
178 }
179
180 /**
181 * Override the plugin info.
182 *
183 * @param false|object|array $res The resource.
184 * @param string $action The action.
185 * @param object $args The arguments.
186 *
187 * @return bool|stdClass ( description_of_the_return_value )
188 */
189 public static function override_plugin_info( $res, $action, $args ) {
190 if ( 'plugin_information' !== $action ) {
191 return $res;
192 }
193
194 $our_plugins = self::get_friends_plugins();
195 if ( ! isset( $our_plugins[ $args->slug ] ) ) {
196 return false;
197 }
198
199 return $our_plugins[ $args->slug ];
200 }
201
202 /**
203 * Override the plugin update function for our plugins.
204 *
205 * @param object|false $transient The transient.
206 *
207 * @return object The modified transient.
208 */
209 public static function override_plugin_push_update( $transient ) {
210 if ( empty( $transient->checked ) ) {
211 return $transient;
212 }
213
214 foreach ( self::get_friends_plugins() as $data ) {
215 if ( $data && isset( $transient->checked[ $data->slug . '/' . $data->slug . '.php' ] ) && version_compare( $transient->checked[ $data->slug . '/' . $data->slug . '.php' ], $data->version, '<' ) && ( ! isset( $data->requires ) || version_compare( $data->requires, get_bloginfo( 'version' ), '<' ) ) ) {
216 $res = new \stdClass();
217 $res->slug = $data->slug;
218 $res->plugin = $data->slug . '/' . $data->slug . '.php';
219 $res->new_version = $data->version;
220 if ( isset( $data->tested ) ) {
221 $res->tested = $data->tested;
222 }
223 $res->package = $data->download_link;
224 $transient->response[ $res->plugin ] = $res;
225 }
226 }
227
228 return $transient;
229 }
230
231 /**
232 * Render display template for each plugin.
233 *
234 * @param object $api Results from plugins_api.
235 * @param string $button_text Text for the button.
236 * @param string $button_classes Classnames for the button.
237 *
238 * @since 1.0
239 */
240 public static function render_template( $api, $button_text, $button_classes ) {
241 $args = array(
242 'api' => $api,
243 'button_classes' => $button_classes,
244 'button_text' => $button_text,
245 'deactivate_button_class' => '',
246 );
247
248 $args['install_url'] = add_query_arg(
249 array(
250 'tab' => 'install-plugin',
251 'plugin' => $api->slug,
252 '_wpnonce' => wp_create_nonce( 'install-plugin_' . $api->slug ),
253 ),
254 admin_url( 'update.php' )
255 );
256 if ( preg_match( '/\b(install|activate)\b/', $button_classes ) ) {
257 $args['deactivate_button_class'] = 'hidden';
258 }
259
260 Friends::template_loader()->get_template_part( 'admin/plugin-info', null, $args );
261 }
262
263 /**
264 * An Ajax method for installing plugin.
265 */
266 public static function ajax_plugin_installer() {
267
268 if ( ! current_user_can( 'install_plugins' ) ) {
269 wp_die( esc_html( __( 'Sorry, you are not allowed to install plugins on this site.', 'friends' ) ) );
270 }
271
272 $nonce = $_POST['nonce']; // phpcs:ignore
273 $plugin = $_POST['plugin']; // phpcs:ignore
274
275 // Check our nonce, if they don't match then bounce!
276 if ( ! wp_verify_nonce( $nonce, 'friends_installer_nonce' ) ) {
277 wp_die( esc_html( __( 'Error - unable to verify nonce, please try again.', 'friends' ) ) );
278 }
279
280 // Include required libs for installation.
281 require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
282 require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
283 require_once ABSPATH . 'wp-admin/includes/class-wp-ajax-upgrader-skin.php';
284 require_once ABSPATH . 'wp-admin/includes/class-plugin-upgrader.php';
285
286 $api = plugins_api(
287 'plugin_information',
288 array(
289 'slug' => $plugin,
290 'fields' => array(
291 'short_description' => false,
292 'sections' => false,
293 'requires' => false,
294 'rating' => false,
295 'ratings' => false,
296 'downloaded' => false,
297 'last_updated' => false,
298 'added' => false,
299 'tags' => false,
300 'compatibility' => false,
301 'homepage' => false,
302 'donate_link' => false,
303 ),
304 )
305 );
306
307 $skin = new \WP_Ajax_Upgrader_Skin();
308 $upgrader = new \Plugin_Upgrader( $skin );
309 $upgrader->install( $api->download_link );
310
311 if ( $api->name ) {
312 $status = 'success';
313 $msg = $api->name . ' successfully installed.';
314 } else {
315 $status = 'failed';
316 $msg = 'There was an error installing ' . $api->name . '.';
317 }
318
319 $json = array(
320 'status' => $status,
321 'msg' => $msg,
322 );
323
324 wp_send_json( $json );
325 }
326
327 /**
328 * Activate plugin via Ajax.
329 */
330 public static function ajax_plugin_activation() {
331 if ( ! current_user_can( 'install_plugins' ) ) {
332 wp_die( esc_html( __( 'Sorry, you are not allowed to activate plugins on this site.', 'friends' ) ) );
333 }
334
335 $nonce = $_POST['nonce']; // phpcs:ignore
336 $plugin = $_POST['plugin']; // phpcs:ignore
337
338 // Check our nonce, if they don't match then bounce!
339 if ( ! wp_verify_nonce( $nonce, 'friends_installer_nonce' ) ) {
340 die( esc_html( __( 'Error - unable to verify nonce, please try again.', 'friends' ) ) );
341 }
342
343 require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
344 require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
345 require_once ABSPATH . 'wp-admin/includes/class-plugin-upgrader.php';
346
347 $api = plugins_api(
348 'plugin_information',
349 array(
350 'slug' => $plugin,
351 'fields' => array(
352 'short_description' => false,
353 'sections' => false,
354 'requires' => false,
355 'rating' => false,
356 'ratings' => false,
357 'downloaded' => false,
358 'last_updated' => false,
359 'added' => false,
360 'tags' => false,
361 'compatibility' => false,
362 'homepage' => false,
363 'donate_link' => false,
364 ),
365 )
366 );
367
368 if ( $api->name ) {
369 $main_plugin_file = self::get_plugin_file( $plugin );
370 $status = 'success';
371 if ( $main_plugin_file ) {
372 activate_plugin( $main_plugin_file );
373 $msg = $api->name . ' successfully activated.';
374 }
375 } else {
376 $status = 'failed';
377 $msg = 'There was an error activating ' . $api->name . '.';
378 }
379
380 $json = array(
381 'status' => $status,
382 'msg' => $msg,
383 );
384
385 wp_send_json( $json );
386 }
387
388 /**
389 * Deativate plugin via Ajax.
390 */
391 public static function ajax_plugin_deactivation() {
392 if ( ! current_user_can( 'install_plugins' ) ) {
393 wp_die( esc_html( __( 'Sorry, you are not allowed to activate plugins on this site.', 'friends' ) ) );
394 }
395
396 $nonce = $_POST['nonce']; // phpcs:ignore
397 $plugin = $_POST['plugin']; // phpcs:ignore
398
399 // Check our nonce, if they don't match then bounce!
400 if ( ! wp_verify_nonce( $nonce, 'friends_installer_nonce' ) ) {
401 die( esc_html( __( 'Error - unable to verify nonce, please try again.', 'friends' ) ) );
402 }
403
404 require_once ABSPATH . 'wp-admin/includes/plugin.php';
405
406 deactivate_plugins( $plugin . '/' . $plugin . '.php' );
407
408 $msg = $plugin . ' successfully deactivated.';
409
410 $json = array(
411 'status' => 'success',
412 'msg' => $msg,
413 );
414
415 wp_send_json( $json );
416 }
417
418 /**
419 * Fix wrong slug through Github-added version to ZIP.
420 *
421 * @param bool $response Installation response.
422 * @param array $hook_extra Extra arguments passed to hooked filters.
423 * @param array $result Installation result data.
424 *
425 * @return bool $response Installation response.
426 */
427 public static function after_install( $response, $hook_extra, $result ) {
428 if ( empty( $result['destination_name'] ) ) {
429 return $response;
430 }
431
432 // Is it a versioned destination name?
433 if ( ! preg_match( '#-[0-9]+(?:\.[0-9]+)+$#', $result['destination_name'], $m ) ) {
434 return $response;
435 }
436
437 // This contains the leading slash.
438 $dashed_version = $m[0];
439
440 // Strip off the version to get the real slug.
441 $slug = substr( $result['destination_name'], 0, - strlen( $dashed_version ) );
442
443 $our_plugins = self::get_friends_plugins();
444 if ( ! isset( $our_plugins[ $slug ] ) ) {
445 return $response;
446 }
447
448 // Is this really the plugin ZIP?
449 if ( ! in_array( $slug . '.php', $result['source_files'] ) ) {
450 return $response;
451 }
452
453 $install_directory = str_replace( $result['destination_name'], $slug, $result['destination'] );
454
455 global $wp_filesystem;
456 $wp_filesystem->move( $result['destination'], $install_directory );
457 $result['destination'] = $install_directory;
458
459 return $result;
460 }
461 /**
462 * A method to get the main plugin file.
463 *
464 * @param string $plugin_slug The plugin slug.
465 *
466 * @return $plugin_file
467 *
468 * @since 1.0
469 */
470 public static function get_plugin_file( $plugin_slug ) {
471 require_once ABSPATH . '/wp-admin/includes/plugin.php';
472 $plugins = get_plugins();
473
474 foreach ( $plugins as $plugin_file => $plugin_info ) {
475 $slug = dirname( plugin_basename( $plugin_file ) );
476 if ( $slug ) {
477 if ( $slug === $plugin_slug ) {
478 return $plugin_file;
479 }
480 }
481 }
482 return null;
483 }
484
485 /**
486 * A helper to check file extension
487 *
488 * @param string $filename The filename.
489 *
490 * @return boolean
491 *
492 * @since 1.0
493 */
494 public static function check_file_extension( $filename ) {
495 if ( substr( strrchr( $filename, '.' ), 1 ) === 'php' ) {
496 return true;
497 } else {
498 // ./wp-content/plugins
499 return false;
500 }
501 }
502
503 /**
504 * Enqueue admin scripts and scripts localization
505 *
506 * @since 1.0
507 */
508 public static function enqueue_scripts() {
509 wp_enqueue_script( 'friends-plugin-installer', plugins_url( 'plugin-installer.js', FRIENDS_PLUGIN_FILE ), array( 'jquery' ), FRIENDS_VERSION, true );
510 wp_localize_script(
511 'friends-plugin-installer',
512 'friends_plugin_installer_localize',
513 array(
514 'ajax_url' => admin_url( 'admin-ajax.php' ),
515 'admin_nonce' => wp_create_nonce( 'friends_installer_nonce' ),
516 'installing' => __( 'Installing' ), // phpcs:ignore WordPress.WP.I18n.MissingArgDomain
517 'install_btn' => __( 'Install Now', 'friends' ),
518 'activate_btn' => __( 'Activate' ), // phpcs:ignore WordPress.WP.I18n.MissingArgDomain
519 'deactivate_btn' => __( 'Deactivate' ), // phpcs:ignore WordPress.WP.I18n.MissingArgDomain
520 'installed_btn' => _x( 'Active', 'plugin', 'friends' ),
521 )
522 );
523 }
524 }
525