+/**
+ * Callback to sort array by a 'Name' key.
+ *
+ * @since 3.1.0
+ * @access private
+ */
+function _sort_uname_callback( $a, $b ) {
+ return strnatcasecmp( $a['Name'], $b['Name'] );
+}
+
+/**
+ * Check the wp-content directory and retrieve all drop-ins with any plugin data.
+ *
+ * @since 3.0.0
+ * @return array Key is the file path and the value is an array of the plugin data.
+ */
+function get_dropins() {
+ $dropins = array();
+ $plugin_files = array();
+
+ $_dropins = _get_dropins();
+
+ // These exist in the wp-content directory
+ if ( $plugins_dir = @ opendir( WP_CONTENT_DIR ) ) {
+ while ( ( $file = readdir( $plugins_dir ) ) !== false ) {
+ if ( isset( $_dropins[ $file ] ) )
+ $plugin_files[] = $file;
+ }
+ } else {
+ return $dropins;
+ }
+
+ @closedir( $plugins_dir );
+
+ if ( empty($plugin_files) )
+ return $dropins;
+
+ foreach ( $plugin_files as $plugin_file ) {
+ if ( !is_readable( WP_CONTENT_DIR . "/$plugin_file" ) )
+ continue;
+ $plugin_data = get_plugin_data( WP_CONTENT_DIR . "/$plugin_file", false, false ); //Do not apply markup/translate as it'll be cached.
+ if ( empty( $plugin_data['Name'] ) )
+ $plugin_data['Name'] = $plugin_file;
+ $dropins[ $plugin_file ] = $plugin_data;
+ }
+
+ uksort( $dropins, 'strnatcasecmp' );
+
+ return $dropins;
+}
+
+/**
+ * Returns drop-ins that WordPress uses.
+ *
+ * Includes Multisite drop-ins only when is_multisite()
+ *
+ * @since 3.0.0
+ * @return array Key is file name. The value is an array, with the first value the
+ * purpose of the drop-in and the second value the name of the constant that must be
+ * true for the drop-in to be used, or true if no constant is required.
+ */
+function _get_dropins() {
+ $dropins = array(
+ 'advanced-cache.php' => array( __( 'Advanced caching plugin.' ), 'WP_CACHE' ), // WP_CACHE
+ 'db.php' => array( __( 'Custom database class.' ), true ), // auto on load
+ 'db-error.php' => array( __( 'Custom database error message.' ), true ), // auto on error
+ 'install.php' => array( __( 'Custom install script.' ), true ), // auto on install
+ 'maintenance.php' => array( __( 'Custom maintenance message.' ), true ), // auto on maintenance
+ 'object-cache.php' => array( __( 'External object cache.' ), true ), // auto on load
+ );
+
+ if ( is_multisite() ) {
+ $dropins['sunrise.php' ] = array( __( 'Executed before Multisite is loaded.' ), 'SUNRISE' ); // SUNRISE
+ $dropins['blog-deleted.php' ] = array( __( 'Custom site deleted message.' ), true ); // auto on deleted blog
+ $dropins['blog-inactive.php' ] = array( __( 'Custom site inactive message.' ), true ); // auto on inactive blog
+ $dropins['blog-suspended.php'] = array( __( 'Custom site suspended message.' ), true ); // auto on archived or spammed blog
+ }
+
+ return $dropins;
+}
+
+/**
+ * Check whether the plugin is active by checking the active_plugins list.
+ *
+ * @since 2.5.0
+ *
+ * @param string $plugin Base plugin path from plugins directory.
+ * @return bool True, if in the active plugins list. False, not in the list.
+ */
+function is_plugin_active( $plugin ) {
+ return in_array( $plugin, (array) get_option( 'active_plugins', array() ) ) || is_plugin_active_for_network( $plugin );
+}
+
+/**
+ * Check whether the plugin is inactive.
+ *
+ * Reverse of is_plugin_active(). Used as a callback.
+ *
+ * @since 3.1.0
+ * @see is_plugin_active()
+ *
+ * @param string $plugin Base plugin path from plugins directory.
+ * @return bool True if inactive. False if active.
+ */
+function is_plugin_inactive( $plugin ) {
+ return ! is_plugin_active( $plugin );
+}
+
+/**
+ * Check whether the plugin is active for the entire network.
+ *
+ * @since 3.0.0
+ *
+ * @param string $plugin Base plugin path from plugins directory.
+ * @return bool True, if active for the network, otherwise false.
+ */
+function is_plugin_active_for_network( $plugin ) {
+ if ( !is_multisite() )
+ return false;
+
+ $plugins = get_site_option( 'active_sitewide_plugins');
+ if ( isset($plugins[$plugin]) )
+ return true;
+
+ return false;
+}
+
+/**
+ * Checks for "Network: true" in the plugin header to see if this should
+ * be activated only as a network wide plugin. The plugin would also work
+ * when Multisite is not enabled.
+ *
+ * Checks for "Site Wide Only: true" for backwards compatibility.
+ *
+ * @since 3.0.0
+ *
+ * @param string $plugin Plugin to check
+ * @return bool True if plugin is network only, false otherwise.
+ */
+function is_network_only_plugin( $plugin ) {
+ $plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
+ if ( $plugin_data )
+ return $plugin_data['Network'];
+ return false;
+}
+
+/**
+ * Attempts activation of plugin in a "sandbox" and redirects on success.
+ *
+ * A plugin that is already activated will not attempt to be activated again.
+ *
+ * The way it works is by setting the redirection to the error before trying to
+ * include the plugin file. If the plugin fails, then the redirection will not
+ * be overwritten with the success message. Also, the options will not be
+ * updated and the activation hook will not be called on plugin error.
+ *
+ * It should be noted that in no way the below code will actually prevent errors
+ * within the file. The code should not be used elsewhere to replicate the
+ * "sandbox", which uses redirection to work.
+ * {@source 13 1}
+ *
+ * If any errors are found or text is outputted, then it will be captured to
+ * ensure that the success redirection will update the error redirection.
+ *
+ * @since 2.5.0
+ *
+ * @param string $plugin Plugin path to main plugin file with plugin data.
+ * @param string $redirect Optional. URL to redirect to.
+ * @param bool $network_wide Whether to enable the plugin for all sites in the
+ * network or just the current site. Multisite only. Default is false.
+ * @param bool $silent Prevent calling activation hooks. Optional, default is false.
+ * @return WP_Error|null WP_Error on invalid file or null on success.
+ */
+function activate_plugin( $plugin, $redirect = '', $network_wide = false, $silent = false ) {
+ $plugin = plugin_basename( trim( $plugin ) );
+
+ if ( is_multisite() && ( $network_wide || is_network_only_plugin($plugin) ) ) {
+ $network_wide = true;
+ $current = get_site_option( 'active_sitewide_plugins', array() );
+ } else {
+ $current = get_option( 'active_plugins', array() );
+ }
+
+ $valid = validate_plugin($plugin);
+ if ( is_wp_error($valid) )
+ return $valid;
+
+ if ( !in_array($plugin, $current) ) {
+ if ( !empty($redirect) )
+ wp_redirect(add_query_arg('_error_nonce', wp_create_nonce('plugin-activation-error_' . $plugin), $redirect)); // we'll override this later if the plugin can be included without fatal error
+ ob_start();
+ include_once(WP_PLUGIN_DIR . '/' . $plugin);
+
+ if ( ! $silent ) {
+ do_action( 'activate_plugin', $plugin, $network_wide );
+ do_action( 'activate_' . $plugin, $network_wide );
+ }
+
+ if ( $network_wide ) {
+ $current[$plugin] = time();
+ update_site_option( 'active_sitewide_plugins', $current );
+ } else {
+ $current[] = $plugin;
+ sort($current);
+ update_option('active_plugins', $current);
+ }
+
+ if ( ! $silent ) {
+ do_action( 'activated_plugin', $plugin, $network_wide );
+ }
+
+ if ( ob_get_length() > 0 ) {
+ $output = ob_get_clean();
+ return new WP_Error('unexpected_output', __('The plugin generated unexpected output.'), $output);
+ }
+ ob_end_clean();
+ }
+
+ return null;
+}
+
+/**
+ * Deactivate a single plugin or multiple plugins.
+ *
+ * The deactivation hook is disabled by the plugin upgrader by using the $silent
+ * parameter.
+ *
+ * @since 2.5.0
+ *
+ * @param string|array $plugins Single plugin or list of plugins to deactivate.
+ * @param bool $silent Prevent calling deactivation hooks. Default is false.
+ */
+function deactivate_plugins( $plugins, $silent = false ) {
+ if ( is_multisite() )
+ $network_current = get_site_option( 'active_sitewide_plugins', array() );
+ $current = get_option( 'active_plugins', array() );
+ $do_blog = $do_network = false;
+
+ foreach ( (array) $plugins as $plugin ) {
+ $plugin = plugin_basename( trim( $plugin ) );
+ if ( ! is_plugin_active($plugin) )
+ continue;
+
+ $network_wide = is_plugin_active_for_network( $plugin );
+
+ if ( ! $silent )
+ do_action( 'deactivate_plugin', $plugin, $network_wide );
+
+ if ( $network_wide ) {
+ $do_network = true;
+ unset( $network_current[ $plugin ] );
+ } else {
+ $key = array_search( $plugin, $current );
+ if ( false !== $key ) {
+ $do_blog = true;
+ array_splice( $current, $key, 1 );
+ }
+ }
+
+ if ( ! $silent ) {
+ do_action( 'deactivate_' . $plugin, $network_wide );
+ do_action( 'deactivated_plugin', $plugin, $network_wide );
+ }
+ }
+
+ if ( $do_blog )
+ update_option('active_plugins', $current);
+ if ( $do_network )
+ update_site_option( 'active_sitewide_plugins', $network_current );
+}
+
+/**
+ * Activate multiple plugins.
+ *
+ * When WP_Error is returned, it does not mean that one of the plugins had
+ * errors. It means that one or more of the plugins file path was invalid.
+ *
+ * The execution will be halted as soon as one of the plugins has an error.
+ *
+ * @since 2.6.0
+ *
+ * @param string|array $plugins
+ * @param string $redirect Redirect to page after successful activation.
+ * @param bool $network_wide Whether to enable the plugin for all sites in the network.
+ * @param bool $silent Prevent calling activation hooks. Default is false.
+ * @return bool|WP_Error True when finished or WP_Error if there were errors during a plugin activation.
+ */
+function activate_plugins( $plugins, $redirect = '', $network_wide = false, $silent = false ) {
+ if ( !is_array($plugins) )
+ $plugins = array($plugins);
+
+ $errors = array();
+ foreach ( $plugins as $plugin ) {
+ if ( !empty($redirect) )
+ $redirect = add_query_arg('plugin', $plugin, $redirect);
+ $result = activate_plugin($plugin, $redirect, $network_wide, $silent);
+ if ( is_wp_error($result) )
+ $errors[$plugin] = $result;
+ }
+
+ if ( !empty($errors) )
+ return new WP_Error('plugins_invalid', __('One of the plugins is invalid.'), $errors);
+
+ return true;
+}
+
+/**
+ * Remove directory and files of a plugin for a single or list of plugin(s).
+ *
+ * If the plugins parameter list is empty, false will be returned. True when
+ * completed.
+ *
+ * @since 2.6.0
+ *
+ * @param array $plugins List of plugin
+ * @param string $redirect Redirect to page when complete.
+ * @return mixed
+ */
+function delete_plugins($plugins, $redirect = '' ) {
+ global $wp_filesystem;
+
+ if ( empty($plugins) )
+ return false;
+
+ $checked = array();
+ foreach( $plugins as $plugin )
+ $checked[] = 'checked[]=' . $plugin;
+
+ ob_start();
+ $url = wp_nonce_url('plugins.php?action=delete-selected&verify-delete=1&' . implode('&', $checked), 'bulk-plugins');
+ if ( false === ($credentials = request_filesystem_credentials($url)) ) {
+ $data = ob_get_contents();
+ ob_end_clean();
+ if ( ! empty($data) ){
+ include_once( ABSPATH . 'wp-admin/admin-header.php');
+ echo $data;
+ include( ABSPATH . 'wp-admin/admin-footer.php');
+ exit;
+ }
+ return;
+ }
+
+ if ( ! WP_Filesystem($credentials) ) {
+ request_filesystem_credentials($url, '', true); //Failed to connect, Error and request again
+ $data = ob_get_contents();
+ ob_end_clean();
+ if ( ! empty($data) ){
+ include_once( ABSPATH . 'wp-admin/admin-header.php');
+ echo $data;
+ include( ABSPATH . 'wp-admin/admin-footer.php');
+ exit;
+ }
+ return;
+ }
+
+ if ( ! is_object($wp_filesystem) )
+ return new WP_Error('fs_unavailable', __('Could not access filesystem.'));
+
+ if ( is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code() )
+ return new WP_Error('fs_error', __('Filesystem error.'), $wp_filesystem->errors);
+
+ //Get the base plugin folder
+ $plugins_dir = $wp_filesystem->wp_plugins_dir();
+ if ( empty($plugins_dir) )
+ return new WP_Error('fs_no_plugins_dir', __('Unable to locate WordPress Plugin directory.'));
+
+ $plugins_dir = trailingslashit( $plugins_dir );
+
+ $errors = array();
+
+ foreach( $plugins as $plugin_file ) {
+ // Run Uninstall hook
+ if ( is_uninstallable_plugin( $plugin_file ) )
+ uninstall_plugin($plugin_file);
+
+ $this_plugin_dir = trailingslashit( dirname($plugins_dir . $plugin_file) );
+ // If plugin is in its own directory, recursively delete the directory.
+ if ( strpos($plugin_file, '/') && $this_plugin_dir != $plugins_dir ) //base check on if plugin includes directory separator AND that its not the root plugin folder
+ $deleted = $wp_filesystem->delete($this_plugin_dir, true);
+ else
+ $deleted = $wp_filesystem->delete($plugins_dir . $plugin_file);
+
+ if ( ! $deleted )
+ $errors[] = $plugin_file;
+ }
+
+ if ( ! empty($errors) )
+ return new WP_Error('could_not_remove_plugin', sprintf(__('Could not fully remove the plugin(s) %s.'), implode(', ', $errors)) );
+
+ // Force refresh of plugin update information
+ if ( $current = get_site_transient('update_plugins') ) {
+ unset( $current->response[ $plugin_file ] );
+ set_site_transient('update_plugins', $current);
+ }
+
+ return true;
+}
+
+/**
+ * Validate active plugins
+ *
+ * Validate all active plugins, deactivates invalid and
+ * returns an array of deactivated ones.
+ *
+ * @since 2.5.0
+ * @return array invalid plugins, plugin as key, error as value
+ */
+function validate_active_plugins() {
+ $plugins = get_option( 'active_plugins', array() );
+ // validate vartype: array
+ if ( ! is_array( $plugins ) ) {
+ update_option( 'active_plugins', array() );
+ $plugins = array();
+ }
+
+ if ( is_multisite() && is_super_admin() ) {
+ $network_plugins = (array) get_site_option( 'active_sitewide_plugins', array() );
+ $plugins = array_merge( $plugins, array_keys( $network_plugins ) );
+ }
+
+ if ( empty( $plugins ) )
+ return;
+
+ $invalid = array();
+
+ // invalid plugins get deactivated
+ foreach ( $plugins as $plugin ) {
+ $result = validate_plugin( $plugin );
+ if ( is_wp_error( $result ) ) {
+ $invalid[$plugin] = $result;
+ deactivate_plugins( $plugin, true );
+ }
+ }
+ return $invalid;
+}
+
+/**
+ * Validate the plugin path.
+ *
+ * Checks that the file exists and {@link validate_file() is valid file}.
+ *
+ * @since 2.5.0
+ *
+ * @param string $plugin Plugin Path
+ * @return WP_Error|int 0 on success, WP_Error on failure.
+ */
+function validate_plugin($plugin) {
+ if ( validate_file($plugin) )
+ return new WP_Error('plugin_invalid', __('Invalid plugin path.'));
+ if ( ! file_exists(WP_PLUGIN_DIR . '/' . $plugin) )
+ return new WP_Error('plugin_not_found', __('Plugin file does not exist.'));
+
+ $installed_plugins = get_plugins();
+ if ( ! isset($installed_plugins[$plugin]) )
+ return new WP_Error('no_plugin_header', __('The plugin does not have a valid header.'));
+ return 0;
+}
+
+/**
+ * Whether the plugin can be uninstalled.
+ *
+ * @since 2.7.0
+ *
+ * @param string $plugin Plugin path to check.
+ * @return bool Whether plugin can be uninstalled.
+ */
+function is_uninstallable_plugin($plugin) {
+ $file = plugin_basename($plugin);
+
+ $uninstallable_plugins = (array) get_option('uninstall_plugins');
+ if ( isset( $uninstallable_plugins[$file] ) || file_exists( WP_PLUGIN_DIR . '/' . dirname($file) . '/uninstall.php' ) )
+ return true;
+
+ return false;
+}
+
+/**
+ * Uninstall a single plugin.
+ *
+ * Calls the uninstall hook, if it is available.
+ *
+ * @since 2.7.0
+ *
+ * @param string $plugin Relative plugin path from Plugin Directory.
+ */
+function uninstall_plugin($plugin) {
+ $file = plugin_basename($plugin);
+
+ $uninstallable_plugins = (array) get_option('uninstall_plugins');
+ if ( file_exists( WP_PLUGIN_DIR . '/' . dirname($file) . '/uninstall.php' ) ) {
+ if ( isset( $uninstallable_plugins[$file] ) ) {
+ unset($uninstallable_plugins[$file]);
+ update_option('uninstall_plugins', $uninstallable_plugins);
+ }
+ unset($uninstallable_plugins);
+
+ define('WP_UNINSTALL_PLUGIN', $file);
+ include WP_PLUGIN_DIR . '/' . dirname($file) . '/uninstall.php';
+
+ return true;
+ }
+
+ if ( isset( $uninstallable_plugins[$file] ) ) {
+ $callable = $uninstallable_plugins[$file];
+ unset($uninstallable_plugins[$file]);
+ update_option('uninstall_plugins', $uninstallable_plugins);
+ unset($uninstallable_plugins);
+
+ include WP_PLUGIN_DIR . '/' . $file;
+
+ add_action( 'uninstall_' . $file, $callable );
+ do_action( 'uninstall_' . $file );
+ }
+}
+