__( 'Adds ActivityPub support to your blog. Be followed on Mastodon, follow people on Mastodon with the Friends plugin.', 'friends' ), 'enable-mastodon-apps' => true, ); $plugins = array_merge( $additional_plugins, self::get_friends_plugins() ); ksort( $plugins ); require_once ABSPATH . 'wp-admin/includes/plugin-install.php'; wp_enqueue_script( 'plugin-install' ); add_thickbox(); wp_enqueue_script( 'updates' ); foreach ( array_keys( (array) $plugins ) as $plugin_slug ) { $button_classes = 'install button button-primary'; $button_text = __( 'Install Now', 'friends' ); $api = plugins_api( 'plugin_information', array( 'slug' => sanitize_file_name( $plugin_slug ), 'fields' => array( 'short_description' => true, 'sections' => false, 'requires' => false, 'downloaded' => true, 'last_updated' => false, 'added' => false, 'tags' => false, 'compatibility' => false, 'homepage' => false, 'donate_link' => false, 'icons' => true, 'banners' => true, ), ) ); if ( isset( $additional_plugins[ $plugin_slug ] ) ) { $api->more_info = admin_url( 'plugin-install.php?tab=plugin-information&plugin=' . $plugin_slug . '&TB_iframe=true' ); if ( is_string( $additional_plugins[ $plugin_slug ] ) ) { $api->comment = $additional_plugins[ $plugin_slug ]; } } if ( ! is_wp_error( $api ) ) { $main_plugin_file = self::get_plugin_file( $plugin_slug ); if ( $main_plugin_file && self::check_file_extension( $main_plugin_file ) ) { if ( is_plugin_active( $main_plugin_file ) ) { $button_classes = 'installed button disabled'; $button_text = __( 'Active' ); // phpcs:ignore WordPress.WP.I18n.MissingArgDomain } else { $button_classes = 'activate button button-primary'; $button_text = __( 'Activate' ); // phpcs:ignore WordPress.WP.I18n.MissingArgDomain } } self::render_template( $api, $button_text, $button_classes ); } } } /** * Gets the available plugins. * * @return object The plugin information. */ public static function get_friends_plugins() { $cache_key = 'friends_plugins_info_v1'; $offline = apply_filters( 'friends_offline_mode', false ); $data = get_transient( $cache_key ); if ( $offline ) { // We'll want to use the shipped file. $data = false; } if ( false === $data || apply_filters( 'friends_deactivate_plugin_cache', false ) ) { $remote = false; if ( ! $offline ) { $remote = wp_remote_get( 'https://raw.githubusercontent.com/akirk/friends/main/plugins.json', array( 'timeout' => 10, 'headers' => array( 'Accept' => 'application/json', ), ) ); if ( is_wp_error( $remote ) || 200 !== wp_remote_retrieve_response_code( $remote ) || is_null( json_decode( wp_remote_retrieve_body( $remote ) ) ) ) { $remote = false; } } if ( false === $remote ) { $remote = array( 'status_code' => 200, 'headers' => array( 'content-type' => 'application/json', ), 'body' => file_get_contents( FRIENDS_PLUGIN_DIR . '/plugins.json' ), ); } $data = array(); foreach ( json_decode( wp_remote_retrieve_body( $remote ) ) as $slug => $plugin_data ) { if ( 'activitypub' === $slug ) { continue; } $plugin_data->sections = (array) $plugin_data->sections; $data[ $slug ] = $plugin_data; } if ( ! $offline ) { set_transient( $cache_key, $data, 12 * HOUR_IN_SECONDS ); } } if ( ! $data ) { return array(); } return $data; } /** * Override the plugin info. * * @param false|object|array $res The resource. * @param string $action The action. * @param object $args The arguments. * * @return bool|stdClass ( description_of_the_return_value ) */ public static function override_plugin_info( $res, $action, $args ) { if ( 'plugin_information' !== $action ) { return $res; } $our_plugins = self::get_friends_plugins(); if ( ! isset( $our_plugins[ $args->slug ] ) ) { return false; } return $our_plugins[ $args->slug ]; } /** * Override the plugin update function for our plugins. * * @param object|false $transient The transient. * * @return object The modified transient. */ public static function override_plugin_push_update( $transient ) { if ( empty( $transient->checked ) ) { return $transient; } foreach ( self::get_friends_plugins() as $data ) { 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' ), '<' ) ) ) { $res = new \stdClass(); $res->slug = $data->slug; $res->plugin = $data->slug . '/' . $data->slug . '.php'; $res->new_version = $data->version; if ( isset( $data->tested ) ) { $res->tested = $data->tested; } $res->package = $data->download_link; $transient->response[ $res->plugin ] = $res; } } return $transient; } /** * Render display template for each plugin. * * @param object $api Results from plugins_api. * @param string $button_text Text for the button. * @param string $button_classes Classnames for the button. * * @since 1.0 */ public static function render_template( $api, $button_text, $button_classes ) { $args = array( 'api' => $api, 'button_classes' => $button_classes, 'button_text' => $button_text, 'deactivate_button_class' => '', ); $args['install_url'] = add_query_arg( array( 'tab' => 'install-plugin', 'plugin' => $api->slug, '_wpnonce' => wp_create_nonce( 'install-plugin_' . $api->slug ), ), admin_url( 'update.php' ) ); if ( preg_match( '/\b(install|activate)\b/', $button_classes ) ) { $args['deactivate_button_class'] = 'hidden'; } Friends::template_loader()->get_template_part( 'admin/plugin-info', null, $args ); } /** * An Ajax method for installing plugin. */ public static function ajax_plugin_installer() { if ( ! current_user_can( 'install_plugins' ) ) { wp_die( esc_html( __( 'Sorry, you are not allowed to install plugins on this site.', 'friends' ) ) ); } $nonce = $_POST['nonce']; // phpcs:ignore $plugin = $_POST['plugin']; // phpcs:ignore // Check our nonce, if they don't match then bounce! if ( ! wp_verify_nonce( $nonce, 'friends_installer_nonce' ) ) { wp_die( esc_html( __( 'Error - unable to verify nonce, please try again.', 'friends' ) ) ); } // Include required libs for installation. require_once ABSPATH . 'wp-admin/includes/plugin-install.php'; require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; require_once ABSPATH . 'wp-admin/includes/class-wp-ajax-upgrader-skin.php'; require_once ABSPATH . 'wp-admin/includes/class-plugin-upgrader.php'; $api = plugins_api( 'plugin_information', array( 'slug' => $plugin, 'fields' => array( 'short_description' => false, 'sections' => false, 'requires' => false, 'rating' => false, 'ratings' => false, 'downloaded' => false, 'last_updated' => false, 'added' => false, 'tags' => false, 'compatibility' => false, 'homepage' => false, 'donate_link' => false, ), ) ); $skin = new \WP_Ajax_Upgrader_Skin(); $upgrader = new \Plugin_Upgrader( $skin ); $upgrader->install( $api->download_link ); if ( $api->name ) { $status = 'success'; $msg = $api->name . ' successfully installed.'; } else { $status = 'failed'; $msg = 'There was an error installing ' . $api->name . '.'; } $json = array( 'status' => $status, 'msg' => $msg, ); wp_send_json( $json ); } /** * Activate plugin via Ajax. */ public static function ajax_plugin_activation() { if ( ! current_user_can( 'install_plugins' ) ) { wp_die( esc_html( __( 'Sorry, you are not allowed to activate plugins on this site.', 'friends' ) ) ); } $nonce = $_POST['nonce']; // phpcs:ignore $plugin = $_POST['plugin']; // phpcs:ignore // Check our nonce, if they don't match then bounce! if ( ! wp_verify_nonce( $nonce, 'friends_installer_nonce' ) ) { die( esc_html( __( 'Error - unable to verify nonce, please try again.', 'friends' ) ) ); } require_once ABSPATH . 'wp-admin/includes/plugin-install.php'; require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; require_once ABSPATH . 'wp-admin/includes/class-plugin-upgrader.php'; $api = plugins_api( 'plugin_information', array( 'slug' => $plugin, 'fields' => array( 'short_description' => false, 'sections' => false, 'requires' => false, 'rating' => false, 'ratings' => false, 'downloaded' => false, 'last_updated' => false, 'added' => false, 'tags' => false, 'compatibility' => false, 'homepage' => false, 'donate_link' => false, ), ) ); if ( $api->name ) { $main_plugin_file = self::get_plugin_file( $plugin ); $status = 'success'; if ( $main_plugin_file ) { activate_plugin( $main_plugin_file ); $msg = $api->name . ' successfully activated.'; } } else { $status = 'failed'; $msg = 'There was an error activating ' . $api->name . '.'; } $json = array( 'status' => $status, 'msg' => $msg, ); wp_send_json( $json ); } /** * Deativate plugin via Ajax. */ public static function ajax_plugin_deactivation() { if ( ! current_user_can( 'install_plugins' ) ) { wp_die( esc_html( __( 'Sorry, you are not allowed to activate plugins on this site.', 'friends' ) ) ); } $nonce = $_POST['nonce']; // phpcs:ignore $plugin = $_POST['plugin']; // phpcs:ignore // Check our nonce, if they don't match then bounce! if ( ! wp_verify_nonce( $nonce, 'friends_installer_nonce' ) ) { die( esc_html( __( 'Error - unable to verify nonce, please try again.', 'friends' ) ) ); } require_once ABSPATH . 'wp-admin/includes/plugin.php'; deactivate_plugins( $plugin . '/' . $plugin . '.php' ); $msg = $plugin . ' successfully deactivated.'; $json = array( 'status' => 'success', 'msg' => $msg, ); wp_send_json( $json ); } /** * Fix wrong slug through Github-added version to ZIP. * * @param bool $response Installation response. * @param array $hook_extra Extra arguments passed to hooked filters. * @param array $result Installation result data. * * @return bool $response Installation response. */ public static function after_install( $response, $hook_extra, $result ) { if ( empty( $result['destination_name'] ) ) { return $response; } // Is it a versioned destination name? if ( ! preg_match( '#-[0-9]+(?:\.[0-9]+)+$#', $result['destination_name'], $m ) ) { return $response; } // This contains the leading slash. $dashed_version = $m[0]; // Strip off the version to get the real slug. $slug = substr( $result['destination_name'], 0, - strlen( $dashed_version ) ); $our_plugins = self::get_friends_plugins(); if ( ! isset( $our_plugins[ $slug ] ) ) { return $response; } // Is this really the plugin ZIP? if ( ! in_array( $slug . '.php', $result['source_files'] ) ) { return $response; } $install_directory = str_replace( $result['destination_name'], $slug, $result['destination'] ); global $wp_filesystem; $wp_filesystem->move( $result['destination'], $install_directory ); $result['destination'] = $install_directory; return $result; } /** * A method to get the main plugin file. * * @param string $plugin_slug The plugin slug. * * @return $plugin_file * * @since 1.0 */ public static function get_plugin_file( $plugin_slug ) { require_once ABSPATH . '/wp-admin/includes/plugin.php'; $plugins = get_plugins(); foreach ( $plugins as $plugin_file => $plugin_info ) { $slug = dirname( plugin_basename( $plugin_file ) ); if ( $slug ) { if ( $slug === $plugin_slug ) { return $plugin_file; } } } return null; } /** * A helper to check file extension * * @param string $filename The filename. * * @return boolean * * @since 1.0 */ public static function check_file_extension( $filename ) { if ( substr( strrchr( $filename, '.' ), 1 ) === 'php' ) { return true; } else { // ./wp-content/plugins return false; } } /** * Enqueue admin scripts and scripts localization * * @since 1.0 */ public static function enqueue_scripts() { wp_enqueue_script( 'friends-plugin-installer', plugins_url( 'plugin-installer.js', FRIENDS_PLUGIN_FILE ), array( 'jquery' ), FRIENDS_VERSION, true ); wp_localize_script( 'friends-plugin-installer', 'friends_plugin_installer_localize', array( 'ajax_url' => admin_url( 'admin-ajax.php' ), 'admin_nonce' => wp_create_nonce( 'friends_installer_nonce' ), 'installing' => __( 'Installing' ), // phpcs:ignore WordPress.WP.I18n.MissingArgDomain 'install_btn' => __( 'Install Now', 'friends' ), 'activate_btn' => __( 'Activate' ), // phpcs:ignore WordPress.WP.I18n.MissingArgDomain 'deactivate_btn' => __( 'Deactivate' ), // phpcs:ignore WordPress.WP.I18n.MissingArgDomain 'installed_btn' => _x( 'Active', 'plugin', 'friends' ), ) ); } }