home *** CD-ROM | disk | FTP | other *** search
/ HTML Examples / WP.iso / wordpress / wp-admin / includes / plugin.php < prev    next >
Encoding:
PHP Script  |  2017-11-26  |  65.2 KB  |  1,899 lines

  1. <?php
  2. /**
  3.  * WordPress Plugin Administration API
  4.  *
  5.  * @package WordPress
  6.  * @subpackage Administration
  7.  */
  8.  
  9. /**
  10.  * Parses the plugin contents to retrieve plugin's metadata.
  11.  *
  12.  * The metadata of the plugin's data searches for the following in the plugin's
  13.  * header. All plugin data must be on its own line. For plugin description, it
  14.  * must not have any newlines or only parts of the description will be displayed
  15.  * and the same goes for the plugin data. The below is formatted for printing.
  16.  *
  17.  *     /*
  18.  *     Plugin Name: Name of Plugin
  19.  *     Plugin URI: Link to plugin information
  20.  *     Description: Plugin Description
  21.  *     Author: Plugin author's name
  22.  *     Author URI: Link to the author's web site
  23.  *     Version: Must be set in the plugin for WordPress 2.3+
  24.  *     Text Domain: Optional. Unique identifier, should be same as the one used in
  25.  *            load_plugin_textdomain()
  26.  *     Domain Path: Optional. Only useful if the translations are located in a
  27.  *            folder above the plugin's base path. For example, if .mo files are
  28.  *            located in the locale folder then Domain Path will be "/locale/" and
  29.  *            must have the first slash. Defaults to the base folder the plugin is
  30.  *            located in.
  31.  *     Network: Optional. Specify "Network: true" to require that a plugin is activated
  32.  *            across all sites in an installation. This will prevent a plugin from being
  33.  *            activated on a single site when Multisite is enabled.
  34.  *      * / # Remove the space to close comment
  35.  *
  36.  * Some users have issues with opening large files and manipulating the contents
  37.  * for want is usually the first 1kiB or 2kiB. This function stops pulling in
  38.  * the plugin contents when it has all of the required plugin data.
  39.  *
  40.  * The first 8kiB of the file will be pulled in and if the plugin data is not
  41.  * within that first 8kiB, then the plugin author should correct their plugin
  42.  * and move the plugin data headers to the top.
  43.  *
  44.  * The plugin file is assumed to have permissions to allow for scripts to read
  45.  * the file. This is not checked however and the file is only opened for
  46.  * reading.
  47.  *
  48.  * @since 1.5.0
  49.  *
  50.  * @param string $plugin_file Path to the main plugin file.
  51.  * @param bool   $markup      Optional. If the returned data should have HTML markup applied.
  52.  *                            Default true.
  53.  * @param bool   $translate   Optional. If the returned data should be translated. Default true.
  54.  * @return array {
  55.  *     Plugin data. Values will be empty if not supplied by the plugin.
  56.  *
  57.  *     @type string $Name        Name of the plugin. Should be unique.
  58.  *     @type string $Title       Title of the plugin and link to the plugin's site (if set).
  59.  *     @type string $Description Plugin description.
  60.  *     @type string $Author      Author's name.
  61.  *     @type string $AuthorURI   Author's website address (if set).
  62.  *     @type string $Version     Plugin version.
  63.  *     @type string $TextDomain  Plugin textdomain.
  64.  *     @type string $DomainPath  Plugins relative directory path to .mo files.
  65.  *     @type bool   $Network     Whether the plugin can only be activated network-wide.
  66.  * }
  67.  */
  68. function get_plugin_data( $plugin_file, $markup = true, $translate = true ) {
  69.  
  70.     $default_headers = array(
  71.         'Name' => 'Plugin Name',
  72.         'PluginURI' => 'Plugin URI',
  73.         'Version' => 'Version',
  74.         'Description' => 'Description',
  75.         'Author' => 'Author',
  76.         'AuthorURI' => 'Author URI',
  77.         'TextDomain' => 'Text Domain',
  78.         'DomainPath' => 'Domain Path',
  79.         'Network' => 'Network',
  80.         // Site Wide Only is deprecated in favor of Network.
  81.         '_sitewide' => 'Site Wide Only',
  82.     );
  83.  
  84.     $plugin_data = get_file_data( $plugin_file, $default_headers, 'plugin' );
  85.  
  86.     // Site Wide Only is the old header for Network
  87.     if ( ! $plugin_data['Network'] && $plugin_data['_sitewide'] ) {
  88.         /* translators: 1: Site Wide Only: true, 2: Network: true */
  89.         _deprecated_argument( __FUNCTION__, '3.0.0', sprintf( __( 'The %1$s plugin header is deprecated. Use %2$s instead.' ), '<code>Site Wide Only: true</code>', '<code>Network: true</code>' ) );
  90.         $plugin_data['Network'] = $plugin_data['_sitewide'];
  91.     }
  92.     $plugin_data['Network'] = ( 'true' == strtolower( $plugin_data['Network'] ) );
  93.     unset( $plugin_data['_sitewide'] );
  94.  
  95.     // If no text domain is defined fall back to the plugin slug.
  96.     if ( ! $plugin_data['TextDomain'] ) {
  97.         $plugin_slug = dirname( plugin_basename( $plugin_file ) );
  98.         if ( '.' !== $plugin_slug && false === strpos( $plugin_slug, '/' ) ) {
  99.             $plugin_data['TextDomain'] = $plugin_slug;
  100.         }
  101.     }
  102.  
  103.     if ( $markup || $translate ) {
  104.         $plugin_data = _get_plugin_data_markup_translate( $plugin_file, $plugin_data, $markup, $translate );
  105.     } else {
  106.         $plugin_data['Title']      = $plugin_data['Name'];
  107.         $plugin_data['AuthorName'] = $plugin_data['Author'];
  108.     }
  109.  
  110.     return $plugin_data;
  111. }
  112.  
  113. /**
  114.  * Sanitizes plugin data, optionally adds markup, optionally translates.
  115.  *
  116.  * @since 2.7.0
  117.  * @access private
  118.  * @see get_plugin_data()
  119.  */
  120. function _get_plugin_data_markup_translate( $plugin_file, $plugin_data, $markup = true, $translate = true ) {
  121.  
  122.     // Sanitize the plugin filename to a WP_PLUGIN_DIR relative path
  123.     $plugin_file = plugin_basename( $plugin_file );
  124.  
  125.     // Translate fields
  126.     if ( $translate ) {
  127.         if ( $textdomain = $plugin_data['TextDomain'] ) {
  128.             if ( ! is_textdomain_loaded( $textdomain ) ) {
  129.                 if ( $plugin_data['DomainPath'] ) {
  130.                     load_plugin_textdomain( $textdomain, false, dirname( $plugin_file ) . $plugin_data['DomainPath'] );
  131.                 } else {
  132.                     load_plugin_textdomain( $textdomain, false, dirname( $plugin_file ) );
  133.                 }
  134.             }
  135.         } elseif ( 'hello.php' == basename( $plugin_file ) ) {
  136.             $textdomain = 'default';
  137.         }
  138.         if ( $textdomain ) {
  139.             foreach ( array( 'Name', 'PluginURI', 'Description', 'Author', 'AuthorURI', 'Version' ) as $field )
  140.                 $plugin_data[ $field ] = translate( $plugin_data[ $field ], $textdomain );
  141.         }
  142.     }
  143.  
  144.     // Sanitize fields
  145.     $allowed_tags = $allowed_tags_in_links = array(
  146.         'abbr'    => array( 'title' => true ),
  147.         'acronym' => array( 'title' => true ),
  148.         'code'    => true,
  149.         'em'      => true,
  150.         'strong'  => true,
  151.     );
  152.     $allowed_tags['a'] = array( 'href' => true, 'title' => true );
  153.  
  154.     // Name is marked up inside <a> tags. Don't allow these.
  155.     // Author is too, but some plugins have used <a> here (omitting Author URI).
  156.     $plugin_data['Name']        = wp_kses( $plugin_data['Name'],        $allowed_tags_in_links );
  157.     $plugin_data['Author']      = wp_kses( $plugin_data['Author'],      $allowed_tags );
  158.  
  159.     $plugin_data['Description'] = wp_kses( $plugin_data['Description'], $allowed_tags );
  160.     $plugin_data['Version']     = wp_kses( $plugin_data['Version'],     $allowed_tags );
  161.  
  162.     $plugin_data['PluginURI']   = esc_url( $plugin_data['PluginURI'] );
  163.     $plugin_data['AuthorURI']   = esc_url( $plugin_data['AuthorURI'] );
  164.  
  165.     $plugin_data['Title']      = $plugin_data['Name'];
  166.     $plugin_data['AuthorName'] = $plugin_data['Author'];
  167.  
  168.     // Apply markup
  169.     if ( $markup ) {
  170.         if ( $plugin_data['PluginURI'] && $plugin_data['Name'] )
  171.             $plugin_data['Title'] = '<a href="' . $plugin_data['PluginURI'] . '">' . $plugin_data['Name'] . '</a>';
  172.  
  173.         if ( $plugin_data['AuthorURI'] && $plugin_data['Author'] )
  174.             $plugin_data['Author'] = '<a href="' . $plugin_data['AuthorURI'] . '">' . $plugin_data['Author'] . '</a>';
  175.  
  176.         $plugin_data['Description'] = wptexturize( $plugin_data['Description'] );
  177.  
  178.         if ( $plugin_data['Author'] )
  179.             $plugin_data['Description'] .= ' <cite>' . sprintf( __('By %s.'), $plugin_data['Author'] ) . '</cite>';
  180.     }
  181.  
  182.     return $plugin_data;
  183. }
  184.  
  185. /**
  186.  * Get a list of a plugin's files.
  187.  *
  188.  * @since 2.8.0
  189.  *
  190.  * @param string $plugin Path to the main plugin file from plugins directory.
  191.  * @return array List of files relative to the plugin root.
  192.  */
  193. function get_plugin_files( $plugin ) {
  194.     $plugin_file = WP_PLUGIN_DIR . '/' . $plugin;
  195.     $dir = dirname( $plugin_file );
  196.  
  197.     $plugin_files = array( plugin_basename( $plugin_file ) );
  198.  
  199.     if ( is_dir( $dir ) && WP_PLUGIN_DIR !== $dir ) {
  200.  
  201.         /**
  202.          * Filters the array of excluded directories and files while scanning the folder.
  203.          *
  204.          * @since 4.9.0
  205.          *
  206.          * @param array $exclusions Array of excluded directories and files.
  207.          */
  208.         $exclusions = (array) apply_filters( 'plugin_files_exclusions', array( 'CVS', 'node_modules', 'vendor', 'bower_components' ) );
  209.  
  210.         $list_files = list_files( $dir, 100, $exclusions );
  211.         $list_files = array_map( 'plugin_basename', $list_files );
  212.  
  213.         $plugin_files = array_merge( $plugin_files, $list_files );
  214.         $plugin_files = array_values( array_unique( $plugin_files ) );
  215.     }
  216.  
  217.     return $plugin_files;
  218. }
  219.  
  220. /**
  221.  * Check the plugins directory and retrieve all plugin files with plugin data.
  222.  *
  223.  * WordPress only supports plugin files in the base plugins directory
  224.  * (wp-content/plugins) and in one directory above the plugins directory
  225.  * (wp-content/plugins/my-plugin). The file it looks for has the plugin data
  226.  * and must be found in those two locations. It is recommended to keep your
  227.  * plugin files in their own directories.
  228.  *
  229.  * The file with the plugin data is the file that will be included and therefore
  230.  * needs to have the main execution for the plugin. This does not mean
  231.  * everything must be contained in the file and it is recommended that the file
  232.  * be split for maintainability. Keep everything in one file for extreme
  233.  * optimization purposes.
  234.  *
  235.  * @since 1.5.0
  236.  *
  237.  * @param string $plugin_folder Optional. Relative path to single plugin folder.
  238.  * @return array Key is the plugin file path and the value is an array of the plugin data.
  239.  */
  240. function get_plugins($plugin_folder = '') {
  241.  
  242.     if ( ! $cache_plugins = wp_cache_get('plugins', 'plugins') )
  243.         $cache_plugins = array();
  244.  
  245.     if ( isset($cache_plugins[ $plugin_folder ]) )
  246.         return $cache_plugins[ $plugin_folder ];
  247.  
  248.     $wp_plugins = array ();
  249.     $plugin_root = WP_PLUGIN_DIR;
  250.     if ( !empty($plugin_folder) )
  251.         $plugin_root .= $plugin_folder;
  252.  
  253.     // Files in wp-content/plugins directory
  254.     $plugins_dir = @ opendir( $plugin_root);
  255.     $plugin_files = array();
  256.     if ( $plugins_dir ) {
  257.         while (($file = readdir( $plugins_dir ) ) !== false ) {
  258.             if ( substr($file, 0, 1) == '.' )
  259.                 continue;
  260.             if ( is_dir( $plugin_root.'/'.$file ) ) {
  261.                 $plugins_subdir = @ opendir( $plugin_root.'/'.$file );
  262.                 if ( $plugins_subdir ) {
  263.                     while (($subfile = readdir( $plugins_subdir ) ) !== false ) {
  264.                         if ( substr($subfile, 0, 1) == '.' )
  265.                             continue;
  266.                         if ( substr($subfile, -4) == '.php' )
  267.                             $plugin_files[] = "$file/$subfile";
  268.                     }
  269.                     closedir( $plugins_subdir );
  270.                 }
  271.             } else {
  272.                 if ( substr($file, -4) == '.php' )
  273.                     $plugin_files[] = $file;
  274.             }
  275.         }
  276.         closedir( $plugins_dir );
  277.     }
  278.  
  279.     if ( empty($plugin_files) )
  280.         return $wp_plugins;
  281.  
  282.     foreach ( $plugin_files as $plugin_file ) {
  283.         if ( !is_readable( "$plugin_root/$plugin_file" ) )
  284.             continue;
  285.  
  286.         $plugin_data = get_plugin_data( "$plugin_root/$plugin_file", false, false ); //Do not apply markup/translate as it'll be cached.
  287.  
  288.         if ( empty ( $plugin_data['Name'] ) )
  289.             continue;
  290.  
  291.         $wp_plugins[plugin_basename( $plugin_file )] = $plugin_data;
  292.     }
  293.  
  294.     uasort( $wp_plugins, '_sort_uname_callback' );
  295.  
  296.     $cache_plugins[ $plugin_folder ] = $wp_plugins;
  297.     wp_cache_set('plugins', $cache_plugins, 'plugins');
  298.  
  299.     return $wp_plugins;
  300. }
  301.  
  302. /**
  303.  * Check the mu-plugins directory and retrieve all mu-plugin files with any plugin data.
  304.  *
  305.  * WordPress only includes mu-plugin files in the base mu-plugins directory (wp-content/mu-plugins).
  306.  *
  307.  * @since 3.0.0
  308.  * @return array Key is the mu-plugin file path and the value is an array of the mu-plugin data.
  309.  */
  310. function get_mu_plugins() {
  311.     $wp_plugins = array();
  312.     // Files in wp-content/mu-plugins directory
  313.     $plugin_files = array();
  314.  
  315.     if ( ! is_dir( WPMU_PLUGIN_DIR ) )
  316.         return $wp_plugins;
  317.     if ( $plugins_dir = @ opendir( WPMU_PLUGIN_DIR ) ) {
  318.         while ( ( $file = readdir( $plugins_dir ) ) !== false ) {
  319.             if ( substr( $file, -4 ) == '.php' )
  320.                 $plugin_files[] = $file;
  321.         }
  322.     } else {
  323.         return $wp_plugins;
  324.     }
  325.  
  326.     @closedir( $plugins_dir );
  327.  
  328.     if ( empty($plugin_files) )
  329.         return $wp_plugins;
  330.  
  331.     foreach ( $plugin_files as $plugin_file ) {
  332.         if ( !is_readable( WPMU_PLUGIN_DIR . "/$plugin_file" ) )
  333.             continue;
  334.  
  335.         $plugin_data = get_plugin_data( WPMU_PLUGIN_DIR . "/$plugin_file", false, false ); //Do not apply markup/translate as it'll be cached.
  336.  
  337.         if ( empty ( $plugin_data['Name'] ) )
  338.             $plugin_data['Name'] = $plugin_file;
  339.  
  340.         $wp_plugins[ $plugin_file ] = $plugin_data;
  341.     }
  342.  
  343.     if ( isset( $wp_plugins['index.php'] ) && filesize( WPMU_PLUGIN_DIR . '/index.php') <= 30 ) // silence is golden
  344.         unset( $wp_plugins['index.php'] );
  345.  
  346.     uasort( $wp_plugins, '_sort_uname_callback' );
  347.  
  348.     return $wp_plugins;
  349. }
  350.  
  351. /**
  352.  * Callback to sort array by a 'Name' key.
  353.  *
  354.  * @since 3.1.0
  355.  * @access private
  356.  */
  357. function _sort_uname_callback( $a, $b ) {
  358.     return strnatcasecmp( $a['Name'], $b['Name'] );
  359. }
  360.  
  361. /**
  362.  * Check the wp-content directory and retrieve all drop-ins with any plugin data.
  363.  *
  364.  * @since 3.0.0
  365.  * @return array Key is the file path and the value is an array of the plugin data.
  366.  */
  367. function get_dropins() {
  368.     $dropins = array();
  369.     $plugin_files = array();
  370.  
  371.     $_dropins = _get_dropins();
  372.  
  373.     // These exist in the wp-content directory
  374.     if ( $plugins_dir = @ opendir( WP_CONTENT_DIR ) ) {
  375.         while ( ( $file = readdir( $plugins_dir ) ) !== false ) {
  376.             if ( isset( $_dropins[ $file ] ) )
  377.                 $plugin_files[] = $file;
  378.         }
  379.     } else {
  380.         return $dropins;
  381.     }
  382.  
  383.     @closedir( $plugins_dir );
  384.  
  385.     if ( empty($plugin_files) )
  386.         return $dropins;
  387.  
  388.     foreach ( $plugin_files as $plugin_file ) {
  389.         if ( !is_readable( WP_CONTENT_DIR . "/$plugin_file" ) )
  390.             continue;
  391.         $plugin_data = get_plugin_data( WP_CONTENT_DIR . "/$plugin_file", false, false ); //Do not apply markup/translate as it'll be cached.
  392.         if ( empty( $plugin_data['Name'] ) )
  393.             $plugin_data['Name'] = $plugin_file;
  394.         $dropins[ $plugin_file ] = $plugin_data;
  395.     }
  396.  
  397.     uksort( $dropins, 'strnatcasecmp' );
  398.  
  399.     return $dropins;
  400. }
  401.  
  402. /**
  403.  * Returns drop-ins that WordPress uses.
  404.  *
  405.  * Includes Multisite drop-ins only when is_multisite()
  406.  *
  407.  * @since 3.0.0
  408.  * @return array Key is file name. The value is an array, with the first value the
  409.  *    purpose of the drop-in and the second value the name of the constant that must be
  410.  *    true for the drop-in to be used, or true if no constant is required.
  411.  */
  412. function _get_dropins() {
  413.     $dropins = array(
  414.         'advanced-cache.php' => array( __( 'Advanced caching plugin.'       ), 'WP_CACHE' ), // WP_CACHE
  415.         'db.php'             => array( __( 'Custom database class.'         ), true ), // auto on load
  416.         'db-error.php'       => array( __( 'Custom database error message.' ), true ), // auto on error
  417.         'install.php'        => array( __( 'Custom installation script.'    ), true ), // auto on installation
  418.         'maintenance.php'    => array( __( 'Custom maintenance message.'    ), true ), // auto on maintenance
  419.         'object-cache.php'   => array( __( 'External object cache.'         ), true ), // auto on load
  420.     );
  421.  
  422.     if ( is_multisite() ) {
  423.         $dropins['sunrise.php'       ] = array( __( 'Executed before Multisite is loaded.' ), 'SUNRISE' ); // SUNRISE
  424.         $dropins['blog-deleted.php'  ] = array( __( 'Custom site deleted message.'   ), true ); // auto on deleted blog
  425.         $dropins['blog-inactive.php' ] = array( __( 'Custom site inactive message.'  ), true ); // auto on inactive blog
  426.         $dropins['blog-suspended.php'] = array( __( 'Custom site suspended message.' ), true ); // auto on archived or spammed blog
  427.     }
  428.  
  429.     return $dropins;
  430. }
  431.  
  432. /**
  433.  * Check whether a plugin is active.
  434.  *
  435.  * Only plugins installed in the plugins/ folder can be active.
  436.  *
  437.  * Plugins in the mu-plugins/ folder can't be "activated," so this function will
  438.  * return false for those plugins.
  439.  *
  440.  * @since 2.5.0
  441.  *
  442.  * @param string $plugin Path to the main plugin file from plugins directory.
  443.  * @return bool True, if in the active plugins list. False, not in the list.
  444.  */
  445. function is_plugin_active( $plugin ) {
  446.     return in_array( $plugin, (array) get_option( 'active_plugins', array() ) ) || is_plugin_active_for_network( $plugin );
  447. }
  448.  
  449. /**
  450.  * Check whether the plugin is inactive.
  451.  *
  452.  * Reverse of is_plugin_active(). Used as a callback.
  453.  *
  454.  * @since 3.1.0
  455.  * @see is_plugin_active()
  456.  *
  457.  * @param string $plugin Path to the main plugin file from plugins directory.
  458.  * @return bool True if inactive. False if active.
  459.  */
  460. function is_plugin_inactive( $plugin ) {
  461.     return ! is_plugin_active( $plugin );
  462. }
  463.  
  464. /**
  465.  * Check whether the plugin is active for the entire network.
  466.  *
  467.  * Only plugins installed in the plugins/ folder can be active.
  468.  *
  469.  * Plugins in the mu-plugins/ folder can't be "activated," so this function will
  470.  * return false for those plugins.
  471.  *
  472.  * @since 3.0.0
  473.  *
  474.  * @param string $plugin Path to the main plugin file from plugins directory.
  475.  * @return bool True, if active for the network, otherwise false.
  476.  */
  477. function is_plugin_active_for_network( $plugin ) {
  478.     if ( !is_multisite() )
  479.         return false;
  480.  
  481.     $plugins = get_site_option( 'active_sitewide_plugins');
  482.     if ( isset($plugins[$plugin]) )
  483.         return true;
  484.  
  485.     return false;
  486. }
  487.  
  488. /**
  489.  * Checks for "Network: true" in the plugin header to see if this should
  490.  * be activated only as a network wide plugin. The plugin would also work
  491.  * when Multisite is not enabled.
  492.  *
  493.  * Checks for "Site Wide Only: true" for backward compatibility.
  494.  *
  495.  * @since 3.0.0
  496.  *
  497.  * @param string $plugin Path to the main plugin file from plugins directory.
  498.  * @return bool True if plugin is network only, false otherwise.
  499.  */
  500. function is_network_only_plugin( $plugin ) {
  501.     $plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
  502.     if ( $plugin_data )
  503.         return $plugin_data['Network'];
  504.     return false;
  505. }
  506.  
  507. /**
  508.  * Attempts activation of plugin in a "sandbox" and redirects on success.
  509.  *
  510.  * A plugin that is already activated will not attempt to be activated again.
  511.  *
  512.  * The way it works is by setting the redirection to the error before trying to
  513.  * include the plugin file. If the plugin fails, then the redirection will not
  514.  * be overwritten with the success message. Also, the options will not be
  515.  * updated and the activation hook will not be called on plugin error.
  516.  *
  517.  * It should be noted that in no way the below code will actually prevent errors
  518.  * within the file. The code should not be used elsewhere to replicate the
  519.  * "sandbox", which uses redirection to work.
  520.  * {@source 13 1}
  521.  *
  522.  * If any errors are found or text is outputted, then it will be captured to
  523.  * ensure that the success redirection will update the error redirection.
  524.  *
  525.  * @since 2.5.0
  526.  *
  527.  * @param string $plugin       Path to the main plugin file from plugins directory.
  528.  * @param string $redirect     Optional. URL to redirect to.
  529.  * @param bool   $network_wide Optional. Whether to enable the plugin for all sites in the network
  530.  *                             or just the current site. Multisite only. Default false.
  531.  * @param bool   $silent       Optional. Whether to prevent calling activation hooks. Default false.
  532.  * @return WP_Error|null WP_Error on invalid file or null on success.
  533.  */
  534. function activate_plugin( $plugin, $redirect = '', $network_wide = false, $silent = false ) {
  535.     $plugin = plugin_basename( trim( $plugin ) );
  536.  
  537.     if ( is_multisite() && ( $network_wide || is_network_only_plugin($plugin) ) ) {
  538.         $network_wide = true;
  539.         $current = get_site_option( 'active_sitewide_plugins', array() );
  540.         $_GET['networkwide'] = 1; // Back compat for plugins looking for this value.
  541.     } else {
  542.         $current = get_option( 'active_plugins', array() );
  543.     }
  544.  
  545.     $valid = validate_plugin($plugin);
  546.     if ( is_wp_error($valid) )
  547.         return $valid;
  548.  
  549.     if ( ( $network_wide && ! isset( $current[ $plugin ] ) ) || ( ! $network_wide && ! in_array( $plugin, $current ) ) ) {
  550.         if ( !empty($redirect) )
  551.             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
  552.         ob_start();
  553.         wp_register_plugin_realpath( WP_PLUGIN_DIR . '/' . $plugin );
  554.         $_wp_plugin_file = $plugin;
  555.         include_once( WP_PLUGIN_DIR . '/' . $plugin );
  556.         $plugin = $_wp_plugin_file; // Avoid stomping of the $plugin variable in a plugin.
  557.  
  558.         if ( ! $silent ) {
  559.             /**
  560.              * Fires before a plugin is activated.
  561.              *
  562.              * If a plugin is silently activated (such as during an update),
  563.              * this hook does not fire.
  564.              *
  565.              * @since 2.9.0
  566.              *
  567.              * @param string $plugin       Path to the main plugin file from plugins directory.
  568.              * @param bool   $network_wide Whether to enable the plugin for all sites in the network
  569.              *                             or just the current site. Multisite only. Default is false.
  570.              */
  571.             do_action( 'activate_plugin', $plugin, $network_wide );
  572.  
  573.             /**
  574.              * Fires as a specific plugin is being activated.
  575.              *
  576.              * This hook is the "activation" hook used internally by register_activation_hook().
  577.              * The dynamic portion of the hook name, `$plugin`, refers to the plugin basename.
  578.              *
  579.              * If a plugin is silently activated (such as during an update), this hook does not fire.
  580.              *
  581.              * @since 2.0.0
  582.              *
  583.              * @param bool $network_wide Whether to enable the plugin for all sites in the network
  584.              *                           or just the current site. Multisite only. Default is false.
  585.              */
  586.             do_action( "activate_{$plugin}", $network_wide );
  587.         }
  588.  
  589.         if ( $network_wide ) {
  590.             $current = get_site_option( 'active_sitewide_plugins', array() );
  591.             $current[$plugin] = time();
  592.             update_site_option( 'active_sitewide_plugins', $current );
  593.         } else {
  594.             $current = get_option( 'active_plugins', array() );
  595.             $current[] = $plugin;
  596.             sort($current);
  597.             update_option('active_plugins', $current);
  598.         }
  599.  
  600.         if ( ! $silent ) {
  601.             /**
  602.              * Fires after a plugin has been activated.
  603.              *
  604.              * If a plugin is silently activated (such as during an update),
  605.              * this hook does not fire.
  606.              *
  607.              * @since 2.9.0
  608.              *
  609.              * @param string $plugin       Path to the main plugin file from plugins directory.
  610.              * @param bool   $network_wide Whether to enable the plugin for all sites in the network
  611.              *                             or just the current site. Multisite only. Default is false.
  612.              */
  613.             do_action( 'activated_plugin', $plugin, $network_wide );
  614.         }
  615.  
  616.         if ( ob_get_length() > 0 ) {
  617.             $output = ob_get_clean();
  618.             return new WP_Error('unexpected_output', __('The plugin generated unexpected output.'), $output);
  619.         }
  620.         ob_end_clean();
  621.     }
  622.  
  623.     return null;
  624. }
  625.  
  626. /**
  627.  * Deactivate a single plugin or multiple plugins.
  628.  *
  629.  * The deactivation hook is disabled by the plugin upgrader by using the $silent
  630.  * parameter.
  631.  *
  632.  * @since 2.5.0
  633.  *
  634.  * @param string|array $plugins Single plugin or list of plugins to deactivate.
  635.  * @param bool $silent Prevent calling deactivation hooks. Default is false.
  636.  * @param mixed $network_wide Whether to deactivate the plugin for all sites in the network.
  637.  *     A value of null (the default) will deactivate plugins for both the site and the network.
  638.  */
  639. function deactivate_plugins( $plugins, $silent = false, $network_wide = null ) {
  640.     if ( is_multisite() )
  641.         $network_current = get_site_option( 'active_sitewide_plugins', array() );
  642.     $current = get_option( 'active_plugins', array() );
  643.     $do_blog = $do_network = false;
  644.  
  645.     foreach ( (array) $plugins as $plugin ) {
  646.         $plugin = plugin_basename( trim( $plugin ) );
  647.         if ( ! is_plugin_active($plugin) )
  648.             continue;
  649.  
  650.         $network_deactivating = false !== $network_wide && is_plugin_active_for_network( $plugin );
  651.  
  652.         if ( ! $silent ) {
  653.             /**
  654.              * Fires before a plugin is deactivated.
  655.              *
  656.              * If a plugin is silently deactivated (such as during an update),
  657.              * this hook does not fire.
  658.              *
  659.              * @since 2.9.0
  660.              *
  661.              * @param string $plugin               Path to the main plugin file from plugins directory.
  662.              * @param bool   $network_deactivating Whether the plugin is deactivated for all sites in the network
  663.              *                                     or just the current site. Multisite only. Default is false.
  664.              */
  665.             do_action( 'deactivate_plugin', $plugin, $network_deactivating );
  666.         }
  667.  
  668.         if ( false !== $network_wide ) {
  669.             if ( is_plugin_active_for_network( $plugin ) ) {
  670.                 $do_network = true;
  671.                 unset( $network_current[ $plugin ] );
  672.             } elseif ( $network_wide ) {
  673.                 continue;
  674.             }
  675.         }
  676.  
  677.         if ( true !== $network_wide ) {
  678.             $key = array_search( $plugin, $current );
  679.             if ( false !== $key ) {
  680.                 $do_blog = true;
  681.                 unset( $current[ $key ] );
  682.             }
  683.         }
  684.  
  685.         if ( ! $silent ) {
  686.             /**
  687.              * Fires as a specific plugin is being deactivated.
  688.              *
  689.              * This hook is the "deactivation" hook used internally by register_deactivation_hook().
  690.              * The dynamic portion of the hook name, `$plugin`, refers to the plugin basename.
  691.              *
  692.              * If a plugin is silently deactivated (such as during an update), this hook does not fire.
  693.              *
  694.              * @since 2.0.0
  695.              *
  696.              * @param bool $network_deactivating Whether the plugin is deactivated for all sites in the network
  697.              *                                   or just the current site. Multisite only. Default is false.
  698.              */
  699.             do_action( "deactivate_{$plugin}", $network_deactivating );
  700.  
  701.             /**
  702.              * Fires after a plugin is deactivated.
  703.              *
  704.              * If a plugin is silently deactivated (such as during an update),
  705.              * this hook does not fire.
  706.              *
  707.              * @since 2.9.0
  708.              *
  709.              * @param string $plugin               Path to the main plugin file from plugins directory.
  710.              * @param bool   $network_deactivating Whether the plugin is deactivated for all sites in the network.
  711.              *                                     or just the current site. Multisite only. Default false.
  712.              */
  713.             do_action( 'deactivated_plugin', $plugin, $network_deactivating );
  714.         }
  715.     }
  716.  
  717.     if ( $do_blog )
  718.         update_option('active_plugins', $current);
  719.     if ( $do_network )
  720.         update_site_option( 'active_sitewide_plugins', $network_current );
  721. }
  722.  
  723. /**
  724.  * Activate multiple plugins.
  725.  *
  726.  * When WP_Error is returned, it does not mean that one of the plugins had
  727.  * errors. It means that one or more of the plugins file path was invalid.
  728.  *
  729.  * The execution will be halted as soon as one of the plugins has an error.
  730.  *
  731.  * @since 2.6.0
  732.  *
  733.  * @param string|array $plugins Single plugin or list of plugins to activate.
  734.  * @param string $redirect Redirect to page after successful activation.
  735.  * @param bool $network_wide Whether to enable the plugin for all sites in the network.
  736.  * @param bool $silent Prevent calling activation hooks. Default is false.
  737.  * @return bool|WP_Error True when finished or WP_Error if there were errors during a plugin activation.
  738.  */
  739. function activate_plugins( $plugins, $redirect = '', $network_wide = false, $silent = false ) {
  740.     if ( !is_array($plugins) )
  741.         $plugins = array($plugins);
  742.  
  743.     $errors = array();
  744.     foreach ( $plugins as $plugin ) {
  745.         if ( !empty($redirect) )
  746.             $redirect = add_query_arg('plugin', $plugin, $redirect);
  747.         $result = activate_plugin($plugin, $redirect, $network_wide, $silent);
  748.         if ( is_wp_error($result) )
  749.             $errors[$plugin] = $result;
  750.     }
  751.  
  752.     if ( !empty($errors) )
  753.         return new WP_Error('plugins_invalid', __('One of the plugins is invalid.'), $errors);
  754.  
  755.     return true;
  756. }
  757.  
  758. /**
  759.  * Remove directory and files of a plugin for a list of plugins.
  760.  *
  761.  * @since 2.6.0
  762.  *
  763.  * @global WP_Filesystem_Base $wp_filesystem
  764.  *
  765.  * @param array  $plugins    List of plugins to delete.
  766.  * @param string $deprecated Deprecated.
  767.  * @return bool|null|WP_Error True on success, false is $plugins is empty, WP_Error on failure.
  768.  *                            Null if filesystem credentials are required to proceed.
  769.  */
  770. function delete_plugins( $plugins, $deprecated = '' ) {
  771.     global $wp_filesystem;
  772.  
  773.     if ( empty($plugins) )
  774.         return false;
  775.  
  776.     $checked = array();
  777.     foreach ( $plugins as $plugin )
  778.         $checked[] = 'checked[]=' . $plugin;
  779.  
  780.     $url = wp_nonce_url('plugins.php?action=delete-selected&verify-delete=1&' . implode('&', $checked), 'bulk-plugins');
  781.  
  782.     ob_start();
  783.     $credentials = request_filesystem_credentials( $url );
  784.     $data = ob_get_clean();
  785.  
  786.     if ( false === $credentials ) {
  787.         if ( ! empty($data) ){
  788.             include_once( ABSPATH . 'wp-admin/admin-header.php');
  789.             echo $data;
  790.             include( ABSPATH . 'wp-admin/admin-footer.php');
  791.             exit;
  792.         }
  793.         return;
  794.     }
  795.  
  796.     if ( ! WP_Filesystem( $credentials ) ) {
  797.         ob_start();
  798.         request_filesystem_credentials( $url, '', true ); // Failed to connect, Error and request again.
  799.         $data = ob_get_clean();
  800.  
  801.         if ( ! empty($data) ){
  802.             include_once( ABSPATH . 'wp-admin/admin-header.php');
  803.             echo $data;
  804.             include( ABSPATH . 'wp-admin/admin-footer.php');
  805.             exit;
  806.         }
  807.         return;
  808.     }
  809.  
  810.     if ( ! is_object($wp_filesystem) )
  811.         return new WP_Error('fs_unavailable', __('Could not access filesystem.'));
  812.  
  813.     if ( is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code() )
  814.         return new WP_Error('fs_error', __('Filesystem error.'), $wp_filesystem->errors);
  815.  
  816.     // Get the base plugin folder.
  817.     $plugins_dir = $wp_filesystem->wp_plugins_dir();
  818.     if ( empty( $plugins_dir ) ) {
  819.         return new WP_Error( 'fs_no_plugins_dir', __( 'Unable to locate WordPress plugin directory.' ) );
  820.     }
  821.  
  822.     $plugins_dir = trailingslashit( $plugins_dir );
  823.  
  824.     $plugin_translations = wp_get_installed_translations( 'plugins' );
  825.  
  826.     $errors = array();
  827.  
  828.     foreach ( $plugins as $plugin_file ) {
  829.         // Run Uninstall hook.
  830.         if ( is_uninstallable_plugin( $plugin_file ) ) {
  831.             uninstall_plugin($plugin_file);
  832.         }
  833.  
  834.         /**
  835.          * Fires immediately before a plugin deletion attempt.
  836.          *
  837.          * @since 4.4.0
  838.          *
  839.          * @param string $plugin_file Plugin file name.
  840.          */
  841.         do_action( 'delete_plugin', $plugin_file );
  842.  
  843.         $this_plugin_dir = trailingslashit( dirname( $plugins_dir . $plugin_file ) );
  844.  
  845.         // If plugin is in its own directory, recursively delete the directory.
  846.         if ( strpos( $plugin_file, '/' ) && $this_plugin_dir != $plugins_dir ) { //base check on if plugin includes directory separator AND that it's not the root plugin folder
  847.             $deleted = $wp_filesystem->delete( $this_plugin_dir, true );
  848.         } else {
  849.             $deleted = $wp_filesystem->delete( $plugins_dir . $plugin_file );
  850.         }
  851.  
  852.         /**
  853.          * Fires immediately after a plugin deletion attempt.
  854.          *
  855.          * @since 4.4.0
  856.          *
  857.          * @param string $plugin_file Plugin file name.
  858.          * @param bool   $deleted     Whether the plugin deletion was successful.
  859.          */
  860.         do_action( 'deleted_plugin', $plugin_file, $deleted );
  861.  
  862.         if ( ! $deleted ) {
  863.             $errors[] = $plugin_file;
  864.             continue;
  865.         }
  866.  
  867.         // Remove language files, silently.
  868.         $plugin_slug = dirname( $plugin_file );
  869.         if ( '.' !== $plugin_slug && ! empty( $plugin_translations[ $plugin_slug ] ) ) {
  870.             $translations = $plugin_translations[ $plugin_slug ];
  871.  
  872.             foreach ( $translations as $translation => $data ) {
  873.                 $wp_filesystem->delete( WP_LANG_DIR . '/plugins/' . $plugin_slug . '-' . $translation . '.po' );
  874.                 $wp_filesystem->delete( WP_LANG_DIR . '/plugins/' . $plugin_slug . '-' . $translation . '.mo' );
  875.             }
  876.         }
  877.     }
  878.  
  879.     // Remove deleted plugins from the plugin updates list.
  880.     if ( $current = get_site_transient('update_plugins') ) {
  881.         // Don't remove the plugins that weren't deleted.
  882.         $deleted = array_diff( $plugins, $errors );
  883.  
  884.         foreach ( $deleted as $plugin_file ) {
  885.             unset( $current->response[ $plugin_file ] );
  886.         }
  887.  
  888.         set_site_transient( 'update_plugins', $current );
  889.     }
  890.  
  891.     if ( ! empty( $errors ) ) {
  892.         if ( 1 === count( $errors ) ) {
  893.             /* translators: %s: plugin filename */
  894.             $message = __( 'Could not fully remove the plugin %s.' );
  895.         } else {
  896.             /* translators: %s: comma-separated list of plugin filenames */
  897.             $message = __( 'Could not fully remove the plugins %s.' );
  898.         }
  899.  
  900.         return new WP_Error( 'could_not_remove_plugin', sprintf( $message, implode( ', ', $errors ) ) );
  901.     }
  902.  
  903.     return true;
  904. }
  905.  
  906. /**
  907.  * Validate active plugins
  908.  *
  909.  * Validate all active plugins, deactivates invalid and
  910.  * returns an array of deactivated ones.
  911.  *
  912.  * @since 2.5.0
  913.  * @return array invalid plugins, plugin as key, error as value
  914.  */
  915. function validate_active_plugins() {
  916.     $plugins = get_option( 'active_plugins', array() );
  917.     // Validate vartype: array.
  918.     if ( ! is_array( $plugins ) ) {
  919.         update_option( 'active_plugins', array() );
  920.         $plugins = array();
  921.     }
  922.  
  923.     if ( is_multisite() && current_user_can( 'manage_network_plugins' ) ) {
  924.         $network_plugins = (array) get_site_option( 'active_sitewide_plugins', array() );
  925.         $plugins = array_merge( $plugins, array_keys( $network_plugins ) );
  926.     }
  927.  
  928.     if ( empty( $plugins ) )
  929.         return array();
  930.  
  931.     $invalid = array();
  932.  
  933.     // Invalid plugins get deactivated.
  934.     foreach ( $plugins as $plugin ) {
  935.         $result = validate_plugin( $plugin );
  936.         if ( is_wp_error( $result ) ) {
  937.             $invalid[$plugin] = $result;
  938.             deactivate_plugins( $plugin, true );
  939.         }
  940.     }
  941.     return $invalid;
  942. }
  943.  
  944. /**
  945.  * Validate the plugin path.
  946.  *
  947.  * Checks that the main plugin file exists and is a valid plugin. See validate_file().
  948.  *
  949.  * @since 2.5.0
  950.  *
  951.  * @param string $plugin Path to the main plugin file from plugins directory.
  952.  * @return WP_Error|int 0 on success, WP_Error on failure.
  953.  */
  954. function validate_plugin($plugin) {
  955.     if ( validate_file($plugin) )
  956.         return new WP_Error('plugin_invalid', __('Invalid plugin path.'));
  957.     if ( ! file_exists(WP_PLUGIN_DIR . '/' . $plugin) )
  958.         return new WP_Error('plugin_not_found', __('Plugin file does not exist.'));
  959.  
  960.     $installed_plugins = get_plugins();
  961.     if ( ! isset($installed_plugins[$plugin]) )
  962.         return new WP_Error('no_plugin_header', __('The plugin does not have a valid header.'));
  963.     return 0;
  964. }
  965.  
  966. /**
  967.  * Whether the plugin can be uninstalled.
  968.  *
  969.  * @since 2.7.0
  970.  *
  971.  * @param string $plugin Path to the main plugin file from plugins directory.
  972.  * @return bool Whether plugin can be uninstalled.
  973.  */
  974. function is_uninstallable_plugin($plugin) {
  975.     $file = plugin_basename($plugin);
  976.  
  977.     $uninstallable_plugins = (array) get_option('uninstall_plugins');
  978.     if ( isset( $uninstallable_plugins[$file] ) || file_exists( WP_PLUGIN_DIR . '/' . dirname($file) . '/uninstall.php' ) )
  979.         return true;
  980.  
  981.     return false;
  982. }
  983.  
  984. /**
  985.  * Uninstall a single plugin.
  986.  *
  987.  * Calls the uninstall hook, if it is available.
  988.  *
  989.  * @since 2.7.0
  990.  *
  991.  * @param string $plugin Path to the main plugin file from plugins directory.
  992.  * @return true True if a plugin's uninstall.php file has been found and included.
  993.  */
  994. function uninstall_plugin($plugin) {
  995.     $file = plugin_basename($plugin);
  996.  
  997.     $uninstallable_plugins = (array) get_option('uninstall_plugins');
  998.  
  999.     /**
  1000.      * Fires in uninstall_plugin() immediately before the plugin is uninstalled.
  1001.      *
  1002.      * @since 4.5.0
  1003.      *
  1004.      * @param string $plugin                Path to the main plugin file from plugins directory.
  1005.      * @param array  $uninstallable_plugins Uninstallable plugins.
  1006.      */
  1007.     do_action( 'pre_uninstall_plugin', $plugin, $uninstallable_plugins );
  1008.  
  1009.     if ( file_exists( WP_PLUGIN_DIR . '/' . dirname($file) . '/uninstall.php' ) ) {
  1010.         if ( isset( $uninstallable_plugins[$file] ) ) {
  1011.             unset($uninstallable_plugins[$file]);
  1012.             update_option('uninstall_plugins', $uninstallable_plugins);
  1013.         }
  1014.         unset($uninstallable_plugins);
  1015.  
  1016.         define('WP_UNINSTALL_PLUGIN', $file);
  1017.         wp_register_plugin_realpath( WP_PLUGIN_DIR . '/' . $file );
  1018.         include( WP_PLUGIN_DIR . '/' . dirname($file) . '/uninstall.php' );
  1019.  
  1020.         return true;
  1021.     }
  1022.  
  1023.     if ( isset( $uninstallable_plugins[$file] ) ) {
  1024.         $callable = $uninstallable_plugins[$file];
  1025.         unset($uninstallable_plugins[$file]);
  1026.         update_option('uninstall_plugins', $uninstallable_plugins);
  1027.         unset($uninstallable_plugins);
  1028.  
  1029.         wp_register_plugin_realpath( WP_PLUGIN_DIR . '/' . $file );
  1030.         include( WP_PLUGIN_DIR . '/' . $file );
  1031.  
  1032.         add_action( "uninstall_{$file}", $callable );
  1033.  
  1034.         /**
  1035.          * Fires in uninstall_plugin() once the plugin has been uninstalled.
  1036.          *
  1037.          * The action concatenates the 'uninstall_' prefix with the basename of the
  1038.          * plugin passed to uninstall_plugin() to create a dynamically-named action.
  1039.          *
  1040.          * @since 2.7.0
  1041.          */
  1042.         do_action( "uninstall_{$file}" );
  1043.     }
  1044. }
  1045.  
  1046. //
  1047. // Menu
  1048. //
  1049.  
  1050. /**
  1051.  * Add a top-level menu page.
  1052.  *
  1053.  * This function takes a capability which will be used to determine whether
  1054.  * or not a page is included in the menu.
  1055.  *
  1056.  * The function which is hooked in to handle the output of the page must check
  1057.  * that the user has the required capability as well.
  1058.  *
  1059.  * @global array $menu
  1060.  * @global array $admin_page_hooks
  1061.  * @global array $_registered_pages
  1062.  * @global array $_parent_pages
  1063.  *
  1064.  * @param string   $page_title The text to be displayed in the title tags of the page when the menu is selected.
  1065.  * @param string   $menu_title The text to be used for the menu.
  1066.  * @param string   $capability The capability required for this menu to be displayed to the user.
  1067.  * @param string   $menu_slug  The slug name to refer to this menu by. Should be unique for this menu page and only
  1068.  *                             include lowercase alphanumeric, dashes, and underscores characters to be compatible
  1069.  *                             with sanitize_key().
  1070.  * @param callable $function   The function to be called to output the content for this page.
  1071.  * @param string   $icon_url   The URL to the icon to be used for this menu.
  1072.  *                             * Pass a base64-encoded SVG using a data URI, which will be colored to match
  1073.  *                               the color scheme. This should begin with 'data:image/svg+xml;base64,'.
  1074.  *                             * Pass the name of a Dashicons helper class to use a font icon,
  1075.  *                               e.g. 'dashicons-chart-pie'.
  1076.  *                             * Pass 'none' to leave div.wp-menu-image empty so an icon can be added via CSS.
  1077.  * @param int      $position   The position in the menu order this one should appear.
  1078.  * @return string The resulting page's hook_suffix.
  1079.  */
  1080. function add_menu_page( $page_title, $menu_title, $capability, $menu_slug, $function = '', $icon_url = '', $position = null ) {
  1081.     global $menu, $admin_page_hooks, $_registered_pages, $_parent_pages;
  1082.  
  1083.     $menu_slug = plugin_basename( $menu_slug );
  1084.  
  1085.     $admin_page_hooks[$menu_slug] = sanitize_title( $menu_title );
  1086.  
  1087.     $hookname = get_plugin_page_hookname( $menu_slug, '' );
  1088.  
  1089.     if ( !empty( $function ) && !empty( $hookname ) && current_user_can( $capability ) )
  1090.         add_action( $hookname, $function );
  1091.  
  1092.     if ( empty($icon_url) ) {
  1093.         $icon_url = 'dashicons-admin-generic';
  1094.         $icon_class = 'menu-icon-generic ';
  1095.     } else {
  1096.         $icon_url = set_url_scheme( $icon_url );
  1097.         $icon_class = '';
  1098.     }
  1099.  
  1100.     $new_menu = array( $menu_title, $capability, $menu_slug, $page_title, 'menu-top ' . $icon_class . $hookname, $hookname, $icon_url );
  1101.  
  1102.     if ( null === $position ) {
  1103.         $menu[] = $new_menu;
  1104.     } elseif ( isset( $menu[ "$position" ] ) ) {
  1105.          $position = $position + substr( base_convert( md5( $menu_slug . $menu_title ), 16, 10 ) , -5 ) * 0.00001;
  1106.         $menu[ "$position" ] = $new_menu;
  1107.     } else {
  1108.         $menu[ $position ] = $new_menu;
  1109.     }
  1110.  
  1111.     $_registered_pages[$hookname] = true;
  1112.  
  1113.     // No parent as top level
  1114.     $_parent_pages[$menu_slug] = false;
  1115.  
  1116.     return $hookname;
  1117. }
  1118.  
  1119. /**
  1120.  * Add a submenu page.
  1121.  *
  1122.  * This function takes a capability which will be used to determine whether
  1123.  * or not a page is included in the menu.
  1124.  *
  1125.  * The function which is hooked in to handle the output of the page must check
  1126.  * that the user has the required capability as well.
  1127.  *
  1128.  * @global array $submenu
  1129.  * @global array $menu
  1130.  * @global array $_wp_real_parent_file
  1131.  * @global bool  $_wp_submenu_nopriv
  1132.  * @global array $_registered_pages
  1133.  * @global array $_parent_pages
  1134.  *
  1135.  * @param string   $parent_slug The slug name for the parent menu (or the file name of a standard
  1136.  *                              WordPress admin page).
  1137.  * @param string   $page_title  The text to be displayed in the title tags of the page when the menu
  1138.  *                              is selected.
  1139.  * @param string   $menu_title  The text to be used for the menu.
  1140.  * @param string   $capability  The capability required for this menu to be displayed to the user.
  1141.  * @param string   $menu_slug   The slug name to refer to this menu by. Should be unique for this menu
  1142.  *                              and only include lowercase alphanumeric, dashes, and underscores characters
  1143.  *                              to be compatible with sanitize_key().
  1144.  * @param callable $function    The function to be called to output the content for this page.
  1145.  * @return false|string The resulting page's hook_suffix, or false if the user does not have the capability required.
  1146.  */
  1147. function add_submenu_page( $parent_slug, $page_title, $menu_title, $capability, $menu_slug, $function = '' ) {
  1148.     global $submenu, $menu, $_wp_real_parent_file, $_wp_submenu_nopriv,
  1149.         $_registered_pages, $_parent_pages;
  1150.  
  1151.     $menu_slug = plugin_basename( $menu_slug );
  1152.     $parent_slug = plugin_basename( $parent_slug);
  1153.  
  1154.     if ( isset( $_wp_real_parent_file[$parent_slug] ) )
  1155.         $parent_slug = $_wp_real_parent_file[$parent_slug];
  1156.  
  1157.     if ( !current_user_can( $capability ) ) {
  1158.         $_wp_submenu_nopriv[$parent_slug][$menu_slug] = true;
  1159.         return false;
  1160.     }
  1161.  
  1162.     /*
  1163.      * If the parent doesn't already have a submenu, add a link to the parent
  1164.      * as the first item in the submenu. If the submenu file is the same as the
  1165.      * parent file someone is trying to link back to the parent manually. In
  1166.      * this case, don't automatically add a link back to avoid duplication.
  1167.      */
  1168.     if (!isset( $submenu[$parent_slug] ) && $menu_slug != $parent_slug ) {
  1169.         foreach ( (array)$menu as $parent_menu ) {
  1170.             if ( $parent_menu[2] == $parent_slug && current_user_can( $parent_menu[1] ) )
  1171.                 $submenu[$parent_slug][] = array_slice( $parent_menu, 0, 4 );
  1172.         }
  1173.     }
  1174.  
  1175.     $submenu[$parent_slug][] = array ( $menu_title, $capability, $menu_slug, $page_title );
  1176.  
  1177.     $hookname = get_plugin_page_hookname( $menu_slug, $parent_slug);
  1178.     if (!empty ( $function ) && !empty ( $hookname ))
  1179.         add_action( $hookname, $function );
  1180.  
  1181.     $_registered_pages[$hookname] = true;
  1182.  
  1183.     /*
  1184.      * Backward-compatibility for plugins using add_management page.
  1185.      * See wp-admin/admin.php for redirect from edit.php to tools.php
  1186.      */
  1187.     if ( 'tools.php' == $parent_slug )
  1188.         $_registered_pages[get_plugin_page_hookname( $menu_slug, 'edit.php')] = true;
  1189.  
  1190.     // No parent as top level.
  1191.     $_parent_pages[$menu_slug] = $parent_slug;
  1192.  
  1193.     return $hookname;
  1194. }
  1195.  
  1196. /**
  1197.  * Add submenu page to the Tools main menu.
  1198.  *
  1199.  * This function takes a capability which will be used to determine whether
  1200.  * or not a page is included in the menu.
  1201.  *
  1202.  * The function which is hooked in to handle the output of the page must check
  1203.  * that the user has the required capability as well.
  1204.  *
  1205.  * @param string   $page_title The text to be displayed in the title tags of the page when the menu is selected.
  1206.  * @param string   $menu_title The text to be used for the menu.
  1207.  * @param string   $capability The capability required for this menu to be displayed to the user.
  1208.  * @param string   $menu_slug  The slug name to refer to this menu by (should be unique for this menu).
  1209.  * @param callable $function   The function to be called to output the content for this page.
  1210.  * @return false|string The resulting page's hook_suffix, or false if the user does not have the capability required.
  1211.  */
  1212. function add_management_page( $page_title, $menu_title, $capability, $menu_slug, $function = '' ) {
  1213.     return add_submenu_page( 'tools.php', $page_title, $menu_title, $capability, $menu_slug, $function );
  1214. }
  1215.  
  1216. /**
  1217.  * Add submenu page to the Settings main menu.
  1218.  *
  1219.  * This function takes a capability which will be used to determine whether
  1220.  * or not a page is included in the menu.
  1221.  *
  1222.  * The function which is hooked in to handle the output of the page must check
  1223.  * that the user has the required capability as well.
  1224.  *
  1225.  * @param string   $page_title The text to be displayed in the title tags of the page when the menu is selected.
  1226.  * @param string   $menu_title The text to be used for the menu.
  1227.  * @param string   $capability The capability required for this menu to be displayed to the user.
  1228.  * @param string   $menu_slug  The slug name to refer to this menu by (should be unique for this menu).
  1229.  * @param callable $function   The function to be called to output the content for this page.
  1230.  * @return false|string The resulting page's hook_suffix, or false if the user does not have the capability required.
  1231.  */
  1232. function add_options_page( $page_title, $menu_title, $capability, $menu_slug, $function = '' ) {
  1233.     return add_submenu_page( 'options-general.php', $page_title, $menu_title, $capability, $menu_slug, $function );
  1234. }
  1235.  
  1236. /**
  1237.  * Add submenu page to the Appearance main menu.
  1238.  *
  1239.  * This function takes a capability which will be used to determine whether
  1240.  * or not a page is included in the menu.
  1241.  *
  1242.  * The function which is hooked in to handle the output of the page must check
  1243.  * that the user has the required capability as well.
  1244.  *
  1245.  * @param string   $page_title The text to be displayed in the title tags of the page when the menu is selected.
  1246.  * @param string   $menu_title The text to be used for the menu.
  1247.  * @param string   $capability The capability required for this menu to be displayed to the user.
  1248.  * @param string   $menu_slug  The slug name to refer to this menu by (should be unique for this menu).
  1249.  * @param callable $function   The function to be called to output the content for this page.
  1250.  * @return false|string The resulting page's hook_suffix, or false if the user does not have the capability required.
  1251.  */
  1252. function add_theme_page( $page_title, $menu_title, $capability, $menu_slug, $function = '' ) {
  1253.     return add_submenu_page( 'themes.php', $page_title, $menu_title, $capability, $menu_slug, $function );
  1254. }
  1255.  
  1256. /**
  1257.  * Add submenu page to the Plugins main menu.
  1258.  *
  1259.  * This function takes a capability which will be used to determine whether
  1260.  * or not a page is included in the menu.
  1261.  *
  1262.  * The function which is hooked in to handle the output of the page must check
  1263.  * that the user has the required capability as well.
  1264.  *
  1265.  * @param string   $page_title The text to be displayed in the title tags of the page when the menu is selected.
  1266.  * @param string   $menu_title The text to be used for the menu.
  1267.  * @param string   $capability The capability required for this menu to be displayed to the user.
  1268.  * @param string   $menu_slug  The slug name to refer to this menu by (should be unique for this menu).
  1269.  * @param callable $function   The function to be called to output the content for this page.
  1270.  * @return false|string The resulting page's hook_suffix, or false if the user does not have the capability required.
  1271.  */
  1272. function add_plugins_page( $page_title, $menu_title, $capability, $menu_slug, $function = '' ) {
  1273.     return add_submenu_page( 'plugins.php', $page_title, $menu_title, $capability, $menu_slug, $function );
  1274. }
  1275.  
  1276. /**
  1277.  * Add submenu page to the Users/Profile main menu.
  1278.  *
  1279.  * This function takes a capability which will be used to determine whether
  1280.  * or not a page is included in the menu.
  1281.  *
  1282.  * The function which is hooked in to handle the output of the page must check
  1283.  * that the user has the required capability as well.
  1284.  *
  1285.  * @param string   $page_title The text to be displayed in the title tags of the page when the menu is selected.
  1286.  * @param string   $menu_title The text to be used for the menu.
  1287.  * @param string   $capability The capability required for this menu to be displayed to the user.
  1288.  * @param string   $menu_slug  The slug name to refer to this menu by (should be unique for this menu).
  1289.  * @param callable $function   The function to be called to output the content for this page.
  1290.  * @return false|string The resulting page's hook_suffix, or false if the user does not have the capability required.
  1291.  */
  1292. function add_users_page( $page_title, $menu_title, $capability, $menu_slug, $function = '' ) {
  1293.     if ( current_user_can('edit_users') )
  1294.         $parent = 'users.php';
  1295.     else
  1296.         $parent = 'profile.php';
  1297.     return add_submenu_page( $parent, $page_title, $menu_title, $capability, $menu_slug, $function );
  1298. }
  1299. /**
  1300.  * Add submenu page to the Dashboard main menu.
  1301.  *
  1302.  * This function takes a capability which will be used to determine whether
  1303.  * or not a page is included in the menu.
  1304.  *
  1305.  * The function which is hooked in to handle the output of the page must check
  1306.  * that the user has the required capability as well.
  1307.  *
  1308.  * @param string   $page_title The text to be displayed in the title tags of the page when the menu is selected.
  1309.  * @param string   $menu_title The text to be used for the menu.
  1310.  * @param string   $capability The capability required for this menu to be displayed to the user.
  1311.  * @param string   $menu_slug  The slug name to refer to this menu by (should be unique for this menu).
  1312.  * @param callable $function   The function to be called to output the content for this page.
  1313.  * @return false|string The resulting page's hook_suffix, or false if the user does not have the capability required.
  1314.  */
  1315. function add_dashboard_page( $page_title, $menu_title, $capability, $menu_slug, $function = '' ) {
  1316.     return add_submenu_page( 'index.php', $page_title, $menu_title, $capability, $menu_slug, $function );
  1317. }
  1318.  
  1319. /**
  1320.  * Add submenu page to the Posts main menu.
  1321.  *
  1322.  * This function takes a capability which will be used to determine whether
  1323.  * or not a page is included in the menu.
  1324.  *
  1325.  * The function which is hooked in to handle the output of the page must check
  1326.  * that the user has the required capability as well.
  1327.  *
  1328.  * @param string   $page_title The text to be displayed in the title tags of the page when the menu is selected.
  1329.  * @param string   $menu_title The text to be used for the menu.
  1330.  * @param string   $capability The capability required for this menu to be displayed to the user.
  1331.  * @param string   $menu_slug  The slug name to refer to this menu by (should be unique for this menu).
  1332.  * @param callable $function   The function to be called to output the content for this page.
  1333.  * @return false|string The resulting page's hook_suffix, or false if the user does not have the capability required.
  1334.  */
  1335. function add_posts_page( $page_title, $menu_title, $capability, $menu_slug, $function = '' ) {
  1336.     return add_submenu_page( 'edit.php', $page_title, $menu_title, $capability, $menu_slug, $function );
  1337. }
  1338.  
  1339. /**
  1340.  * Add submenu page to the Media main menu.
  1341.  *
  1342.  * This function takes a capability which will be used to determine whether
  1343.  * or not a page is included in the menu.
  1344.  *
  1345.  * The function which is hooked in to handle the output of the page must check
  1346.  * that the user has the required capability as well.
  1347.  *
  1348.  * @param string   $page_title The text to be displayed in the title tags of the page when the menu is selected.
  1349.  * @param string   $menu_title The text to be used for the menu.
  1350.  * @param string   $capability The capability required for this menu to be displayed to the user.
  1351.  * @param string   $menu_slug  The slug name to refer to this menu by (should be unique for this menu).
  1352.  * @param callable $function   The function to be called to output the content for this page.
  1353.  * @return false|string The resulting page's hook_suffix, or false if the user does not have the capability required.
  1354.  */
  1355. function add_media_page( $page_title, $menu_title, $capability, $menu_slug, $function = '' ) {
  1356.     return add_submenu_page( 'upload.php', $page_title, $menu_title, $capability, $menu_slug, $function );
  1357. }
  1358.  
  1359. /**
  1360.  * Add submenu page to the Links main menu.
  1361.  *
  1362.  * This function takes a capability which will be used to determine whether
  1363.  * or not a page is included in the menu.
  1364.  *
  1365.  * The function which is hooked in to handle the output of the page must check
  1366.  * that the user has the required capability as well.
  1367.  *
  1368.  * @param string   $page_title The text to be displayed in the title tags of the page when the menu is selected.
  1369.  * @param string   $menu_title The text to be used for the menu.
  1370.  * @param string   $capability The capability required for this menu to be displayed to the user.
  1371.  * @param string   $menu_slug  The slug name to refer to this menu by (should be unique for this menu).
  1372.  * @param callable $function   The function to be called to output the content for this page.
  1373.  * @return false|string The resulting page's hook_suffix, or false if the user does not have the capability required.
  1374.  */
  1375. function add_links_page( $page_title, $menu_title, $capability, $menu_slug, $function = '' ) {
  1376.     return add_submenu_page( 'link-manager.php', $page_title, $menu_title, $capability, $menu_slug, $function );
  1377. }
  1378.  
  1379. /**
  1380.  * Add submenu page to the Pages main menu.
  1381.  *
  1382.  * This function takes a capability which will be used to determine whether
  1383.  * or not a page is included in the menu.
  1384.  *
  1385.  * The function which is hooked in to handle the output of the page must check
  1386.  * that the user has the required capability as well.
  1387.  *
  1388.  * @param string   $page_title The text to be displayed in the title tags of the page when the menu is selected.
  1389.  * @param string   $menu_title The text to be used for the menu.
  1390.  * @param string   $capability The capability required for this menu to be displayed to the user.
  1391.  * @param string   $menu_slug  The slug name to refer to this menu by (should be unique for this menu).
  1392.  * @param callable $function   The function to be called to output the content for this page.
  1393.  * @return false|string The resulting page's hook_suffix, or false if the user does not have the capability required.
  1394.  */
  1395. function add_pages_page( $page_title, $menu_title, $capability, $menu_slug, $function = '' ) {
  1396.     return add_submenu_page( 'edit.php?post_type=page', $page_title, $menu_title, $capability, $menu_slug, $function );
  1397. }
  1398.  
  1399. /**
  1400.  * Add submenu page to the Comments main menu.
  1401.  *
  1402.  * This function takes a capability which will be used to determine whether
  1403.  * or not a page is included in the menu.
  1404.  *
  1405.  * The function which is hooked in to handle the output of the page must check
  1406.  * that the user has the required capability as well.
  1407.  *
  1408.  * @param string   $page_title The text to be displayed in the title tags of the page when the menu is selected.
  1409.  * @param string   $menu_title The text to be used for the menu.
  1410.  * @param string   $capability The capability required for this menu to be displayed to the user.
  1411.  * @param string   $menu_slug  The slug name to refer to this menu by (should be unique for this menu).
  1412.  * @param callable $function   The function to be called to output the content for this page.
  1413.  * @return false|string The resulting page's hook_suffix, or false if the user does not have the capability required.
  1414.  */
  1415. function add_comments_page( $page_title, $menu_title, $capability, $menu_slug, $function = '' ) {
  1416.     return add_submenu_page( 'edit-comments.php', $page_title, $menu_title, $capability, $menu_slug, $function );
  1417. }
  1418.  
  1419. /**
  1420.  * Remove a top-level admin menu.
  1421.  *
  1422.  * @since 3.1.0
  1423.  *
  1424.  * @global array $menu
  1425.  *
  1426.  * @param string $menu_slug The slug of the menu.
  1427.  * @return array|bool The removed menu on success, false if not found.
  1428.  */
  1429. function remove_menu_page( $menu_slug ) {
  1430.     global $menu;
  1431.  
  1432.     foreach ( $menu as $i => $item ) {
  1433.         if ( $menu_slug == $item[2] ) {
  1434.             unset( $menu[$i] );
  1435.             return $item;
  1436.         }
  1437.     }
  1438.  
  1439.     return false;
  1440. }
  1441.  
  1442. /**
  1443.  * Remove an admin submenu.
  1444.  *
  1445.  * @since 3.1.0
  1446.  *
  1447.  * @global array $submenu
  1448.  *
  1449.  * @param string $menu_slug    The slug for the parent menu.
  1450.  * @param string $submenu_slug The slug of the submenu.
  1451.  * @return array|bool The removed submenu on success, false if not found.
  1452.  */
  1453. function remove_submenu_page( $menu_slug, $submenu_slug ) {
  1454.     global $submenu;
  1455.  
  1456.     if ( !isset( $submenu[$menu_slug] ) )
  1457.         return false;
  1458.  
  1459.     foreach ( $submenu[$menu_slug] as $i => $item ) {
  1460.         if ( $submenu_slug == $item[2] ) {
  1461.             unset( $submenu[$menu_slug][$i] );
  1462.             return $item;
  1463.         }
  1464.     }
  1465.  
  1466.     return false;
  1467. }
  1468.  
  1469. /**
  1470.  * Get the url to access a particular menu page based on the slug it was registered with.
  1471.  *
  1472.  * If the slug hasn't been registered properly no url will be returned
  1473.  *
  1474.  * @since 3.0.0
  1475.  *
  1476.  * @global array $_parent_pages
  1477.  *
  1478.  * @param string $menu_slug The slug name to refer to this menu by (should be unique for this menu)
  1479.  * @param bool $echo Whether or not to echo the url - default is true
  1480.  * @return string the url
  1481.  */
  1482. function menu_page_url($menu_slug, $echo = true) {
  1483.     global $_parent_pages;
  1484.  
  1485.     if ( isset( $_parent_pages[$menu_slug] ) ) {
  1486.         $parent_slug = $_parent_pages[$menu_slug];
  1487.         if ( $parent_slug && ! isset( $_parent_pages[$parent_slug] ) ) {
  1488.             $url = admin_url( add_query_arg( 'page', $menu_slug, $parent_slug ) );
  1489.         } else {
  1490.             $url = admin_url( 'admin.php?page=' . $menu_slug );
  1491.         }
  1492.     } else {
  1493.         $url = '';
  1494.     }
  1495.  
  1496.     $url = esc_url($url);
  1497.  
  1498.     if ( $echo )
  1499.         echo $url;
  1500.  
  1501.     return $url;
  1502. }
  1503.  
  1504. //
  1505. // Pluggable Menu Support -- Private
  1506. //
  1507. /**
  1508.  *
  1509.  * @global string $parent_file
  1510.  * @global array $menu
  1511.  * @global array $submenu
  1512.  * @global string $pagenow
  1513.  * @global string $typenow
  1514.  * @global string $plugin_page
  1515.  * @global array $_wp_real_parent_file
  1516.  * @global array $_wp_menu_nopriv
  1517.  * @global array $_wp_submenu_nopriv
  1518.  */
  1519. function get_admin_page_parent( $parent = '' ) {
  1520.     global $parent_file, $menu, $submenu, $pagenow, $typenow,
  1521.         $plugin_page, $_wp_real_parent_file, $_wp_menu_nopriv, $_wp_submenu_nopriv;
  1522.  
  1523.     if ( !empty ( $parent ) && 'admin.php' != $parent ) {
  1524.         if ( isset( $_wp_real_parent_file[$parent] ) )
  1525.             $parent = $_wp_real_parent_file[$parent];
  1526.         return $parent;
  1527.     }
  1528.  
  1529.     if ( $pagenow == 'admin.php' && isset( $plugin_page ) ) {
  1530.         foreach ( (array)$menu as $parent_menu ) {
  1531.             if ( $parent_menu[2] == $plugin_page ) {
  1532.                 $parent_file = $plugin_page;
  1533.                 if ( isset( $_wp_real_parent_file[$parent_file] ) )
  1534.                     $parent_file = $_wp_real_parent_file[$parent_file];
  1535.                 return $parent_file;
  1536.             }
  1537.         }
  1538.         if ( isset( $_wp_menu_nopriv[$plugin_page] ) ) {
  1539.             $parent_file = $plugin_page;
  1540.             if ( isset( $_wp_real_parent_file[$parent_file] ) )
  1541.                     $parent_file = $_wp_real_parent_file[$parent_file];
  1542.             return $parent_file;
  1543.         }
  1544.     }
  1545.  
  1546.     if ( isset( $plugin_page ) && isset( $_wp_submenu_nopriv[$pagenow][$plugin_page] ) ) {
  1547.         $parent_file = $pagenow;
  1548.         if ( isset( $_wp_real_parent_file[$parent_file] ) )
  1549.             $parent_file = $_wp_real_parent_file[$parent_file];
  1550.         return $parent_file;
  1551.     }
  1552.  
  1553.     foreach (array_keys( (array)$submenu ) as $parent) {
  1554.         foreach ( $submenu[$parent] as $submenu_array ) {
  1555.             if ( isset( $_wp_real_parent_file[$parent] ) )
  1556.                 $parent = $_wp_real_parent_file[$parent];
  1557.             if ( !empty($typenow) && ($submenu_array[2] == "$pagenow?post_type=$typenow") ) {
  1558.                 $parent_file = $parent;
  1559.                 return $parent;
  1560.             } elseif ( $submenu_array[2] == $pagenow && empty($typenow) && ( empty($parent_file) || false === strpos($parent_file, '?') ) ) {
  1561.                 $parent_file = $parent;
  1562.                 return $parent;
  1563.             } elseif ( isset( $plugin_page ) && ($plugin_page == $submenu_array[2] ) ) {
  1564.                 $parent_file = $parent;
  1565.                 return $parent;
  1566.             }
  1567.         }
  1568.     }
  1569.  
  1570.     if ( empty($parent_file) )
  1571.         $parent_file = '';
  1572.     return '';
  1573. }
  1574.  
  1575. /**
  1576.  *
  1577.  * @global string $title
  1578.  * @global array $menu
  1579.  * @global array $submenu
  1580.  * @global string $pagenow
  1581.  * @global string $plugin_page
  1582.  * @global string $typenow
  1583.  */
  1584. function get_admin_page_title() {
  1585.     global $title, $menu, $submenu, $pagenow, $plugin_page, $typenow;
  1586.  
  1587.     if ( ! empty ( $title ) )
  1588.         return $title;
  1589.  
  1590.     $hook = get_plugin_page_hook( $plugin_page, $pagenow );
  1591.  
  1592.     $parent = $parent1 = get_admin_page_parent();
  1593.  
  1594.     if ( empty ( $parent) ) {
  1595.         foreach ( (array)$menu as $menu_array ) {
  1596.             if ( isset( $menu_array[3] ) ) {
  1597.                 if ( $menu_array[2] == $pagenow ) {
  1598.                     $title = $menu_array[3];
  1599.                     return $menu_array[3];
  1600.                 } elseif ( isset( $plugin_page ) && ($plugin_page == $menu_array[2] ) && ($hook == $menu_array[3] ) ) {
  1601.                     $title = $menu_array[3];
  1602.                     return $menu_array[3];
  1603.                 }
  1604.             } else {
  1605.                 $title = $menu_array[0];
  1606.                 return $title;
  1607.             }
  1608.         }
  1609.     } else {
  1610.         foreach ( array_keys( $submenu ) as $parent ) {
  1611.             foreach ( $submenu[$parent] as $submenu_array ) {
  1612.                 if ( isset( $plugin_page ) &&
  1613.                     ( $plugin_page == $submenu_array[2] ) &&
  1614.                     (
  1615.                         ( $parent == $pagenow ) ||
  1616.                         ( $parent == $plugin_page ) ||
  1617.                         ( $plugin_page == $hook ) ||
  1618.                         ( $pagenow == 'admin.php' && $parent1 != $submenu_array[2] ) ||
  1619.                         ( !empty($typenow) && $parent == $pagenow . '?post_type=' . $typenow)
  1620.                     )
  1621.                     ) {
  1622.                         $title = $submenu_array[3];
  1623.                         return $submenu_array[3];
  1624.                     }
  1625.  
  1626.                 if ( $submenu_array[2] != $pagenow || isset( $_GET['page'] ) ) // not the current page
  1627.                     continue;
  1628.  
  1629.                 if ( isset( $submenu_array[3] ) ) {
  1630.                     $title = $submenu_array[3];
  1631.                     return $submenu_array[3];
  1632.                 } else {
  1633.                     $title = $submenu_array[0];
  1634.                     return $title;
  1635.                 }
  1636.             }
  1637.         }
  1638.         if ( empty ( $title ) ) {
  1639.             foreach ( $menu as $menu_array ) {
  1640.                 if ( isset( $plugin_page ) &&
  1641.                     ( $plugin_page == $menu_array[2] ) &&
  1642.                     ( $pagenow == 'admin.php' ) &&
  1643.                     ( $parent1 == $menu_array[2] ) )
  1644.                     {
  1645.                         $title = $menu_array[3];
  1646.                         return $menu_array[3];
  1647.                     }
  1648.             }
  1649.         }
  1650.     }
  1651.  
  1652.     return $title;
  1653. }
  1654.  
  1655. /**
  1656.  * @since 2.3.0
  1657.  *
  1658.  * @param string $plugin_page
  1659.  * @param string $parent_page
  1660.  * @return string|null
  1661.  */
  1662. function get_plugin_page_hook( $plugin_page, $parent_page ) {
  1663.     $hook = get_plugin_page_hookname( $plugin_page, $parent_page );
  1664.     if ( has_action($hook) )
  1665.         return $hook;
  1666.     else
  1667.         return null;
  1668. }
  1669.  
  1670. /**
  1671.  *
  1672.  * @global array $admin_page_hooks
  1673.  * @param string $plugin_page
  1674.  * @param string $parent_page
  1675.  */
  1676. function get_plugin_page_hookname( $plugin_page, $parent_page ) {
  1677.     global $admin_page_hooks;
  1678.  
  1679.     $parent = get_admin_page_parent( $parent_page );
  1680.  
  1681.     $page_type = 'admin';
  1682.     if ( empty ( $parent_page ) || 'admin.php' == $parent_page || isset( $admin_page_hooks[$plugin_page] ) ) {
  1683.         if ( isset( $admin_page_hooks[$plugin_page] ) ) {
  1684.             $page_type = 'toplevel';
  1685.         } elseif ( isset( $admin_page_hooks[$parent] )) {
  1686.             $page_type = $admin_page_hooks[$parent];
  1687.         }
  1688.     } elseif ( isset( $admin_page_hooks[$parent] ) ) {
  1689.         $page_type = $admin_page_hooks[$parent];
  1690.     }
  1691.  
  1692.     $plugin_name = preg_replace( '!\.php!', '', $plugin_page );
  1693.  
  1694.     return $page_type . '_page_' . $plugin_name;
  1695. }
  1696.  
  1697. /**
  1698.  *
  1699.  * @global string $pagenow
  1700.  * @global array $menu
  1701.  * @global array $submenu
  1702.  * @global array $_wp_menu_nopriv
  1703.  * @global array $_wp_submenu_nopriv
  1704.  * @global string $plugin_page
  1705.  * @global array $_registered_pages
  1706.  */
  1707. function user_can_access_admin_page() {
  1708.     global $pagenow, $menu, $submenu, $_wp_menu_nopriv, $_wp_submenu_nopriv,
  1709.         $plugin_page, $_registered_pages;
  1710.  
  1711.     $parent = get_admin_page_parent();
  1712.  
  1713.     if ( !isset( $plugin_page ) && isset( $_wp_submenu_nopriv[$parent][$pagenow] ) )
  1714.         return false;
  1715.  
  1716.     if ( isset( $plugin_page ) ) {
  1717.         if ( isset( $_wp_submenu_nopriv[$parent][$plugin_page] ) )
  1718.             return false;
  1719.  
  1720.         $hookname = get_plugin_page_hookname($plugin_page, $parent);
  1721.  
  1722.         if ( !isset($_registered_pages[$hookname]) )
  1723.             return false;
  1724.     }
  1725.  
  1726.     if ( empty( $parent) ) {
  1727.         if ( isset( $_wp_menu_nopriv[$pagenow] ) )
  1728.             return false;
  1729.         if ( isset( $_wp_submenu_nopriv[$pagenow][$pagenow] ) )
  1730.             return false;
  1731.         if ( isset( $plugin_page ) && isset( $_wp_submenu_nopriv[$pagenow][$plugin_page] ) )
  1732.             return false;
  1733.         if ( isset( $plugin_page ) && isset( $_wp_menu_nopriv[$plugin_page] ) )
  1734.             return false;
  1735.         foreach (array_keys( $_wp_submenu_nopriv ) as $key ) {
  1736.             if ( isset( $_wp_submenu_nopriv[$key][$pagenow] ) )
  1737.                 return false;
  1738.             if ( isset( $plugin_page ) && isset( $_wp_submenu_nopriv[$key][$plugin_page] ) )
  1739.             return false;
  1740.         }
  1741.         return true;
  1742.     }
  1743.  
  1744.     if ( isset( $plugin_page ) && ( $plugin_page == $parent ) && isset( $_wp_menu_nopriv[$plugin_page] ) )
  1745.         return false;
  1746.  
  1747.     if ( isset( $submenu[$parent] ) ) {
  1748.         foreach ( $submenu[$parent] as $submenu_array ) {
  1749.             if ( isset( $plugin_page ) && ( $submenu_array[2] == $plugin_page ) ) {
  1750.                 if ( current_user_can( $submenu_array[1] ))
  1751.                     return true;
  1752.                 else
  1753.                     return false;
  1754.             } elseif ( $submenu_array[2] == $pagenow ) {
  1755.                 if ( current_user_can( $submenu_array[1] ))
  1756.                     return true;
  1757.                 else
  1758.                     return false;
  1759.             }
  1760.         }
  1761.     }
  1762.  
  1763.     foreach ( $menu as $menu_array ) {
  1764.         if ( $menu_array[2] == $parent) {
  1765.             if ( current_user_can( $menu_array[1] ))
  1766.                 return true;
  1767.             else
  1768.                 return false;
  1769.         }
  1770.     }
  1771.  
  1772.     return true;
  1773. }
  1774.  
  1775. /* Whitelist functions */
  1776.  
  1777. /**
  1778.  * Refreshes the value of the options whitelist available via the 'whitelist_options' hook.
  1779.  *
  1780.  * See the {@see 'whitelist_options'} filter.
  1781.  *
  1782.  * @since 2.7.0
  1783.  *
  1784.  * @global array $new_whitelist_options
  1785.  *
  1786.  * @param array $options
  1787.  * @return array
  1788.  */
  1789. function option_update_filter( $options ) {
  1790.     global $new_whitelist_options;
  1791.  
  1792.     if ( is_array( $new_whitelist_options ) )
  1793.         $options = add_option_whitelist( $new_whitelist_options, $options );
  1794.  
  1795.     return $options;
  1796. }
  1797.  
  1798. /**
  1799.  * Adds an array of options to the options whitelist.
  1800.  *
  1801.  * @since 2.7.0
  1802.  *
  1803.  * @global array $whitelist_options
  1804.  *
  1805.  * @param array        $new_options
  1806.  * @param string|array $options
  1807.  * @return array
  1808.  */
  1809. function add_option_whitelist( $new_options, $options = '' ) {
  1810.     if ( $options == '' )
  1811.         global $whitelist_options;
  1812.     else
  1813.         $whitelist_options = $options;
  1814.  
  1815.     foreach ( $new_options as $page => $keys ) {
  1816.         foreach ( $keys as $key ) {
  1817.             if ( !isset($whitelist_options[ $page ]) || !is_array($whitelist_options[ $page ]) ) {
  1818.                 $whitelist_options[ $page ] = array();
  1819.                 $whitelist_options[ $page ][] = $key;
  1820.             } else {
  1821.                 $pos = array_search( $key, $whitelist_options[ $page ] );
  1822.                 if ( $pos === false )
  1823.                     $whitelist_options[ $page ][] = $key;
  1824.             }
  1825.         }
  1826.     }
  1827.  
  1828.     return $whitelist_options;
  1829. }
  1830.  
  1831. /**
  1832.  * Removes a list of options from the options whitelist.
  1833.  *
  1834.  * @since 2.7.0
  1835.  *
  1836.  * @global array $whitelist_options
  1837.  *
  1838.  * @param array        $del_options
  1839.  * @param string|array $options
  1840.  * @return array
  1841.  */
  1842. function remove_option_whitelist( $del_options, $options = '' ) {
  1843.     if ( $options == '' )
  1844.         global $whitelist_options;
  1845.     else
  1846.         $whitelist_options = $options;
  1847.  
  1848.     foreach ( $del_options as $page => $keys ) {
  1849.         foreach ( $keys as $key ) {
  1850.             if ( isset($whitelist_options[ $page ]) && is_array($whitelist_options[ $page ]) ) {
  1851.                 $pos = array_search( $key, $whitelist_options[ $page ] );
  1852.                 if ( $pos !== false )
  1853.                     unset( $whitelist_options[ $page ][ $pos ] );
  1854.             }
  1855.         }
  1856.     }
  1857.  
  1858.     return $whitelist_options;
  1859. }
  1860.  
  1861. /**
  1862.  * Output nonce, action, and option_page fields for a settings page.
  1863.  *
  1864.  * @since 2.7.0
  1865.  *
  1866.  * @param string $option_group A settings group name. This should match the group name used in register_setting().
  1867.  */
  1868. function settings_fields($option_group) {
  1869.     echo "<input type='hidden' name='option_page' value='" . esc_attr($option_group) . "' />";
  1870.     echo '<input type="hidden" name="action" value="update" />';
  1871.     wp_nonce_field("$option_group-options");
  1872. }
  1873.  
  1874. /**
  1875.  * Clears the Plugins cache used by get_plugins() and by default, the Plugin Update cache.
  1876.  *
  1877.  * @since 3.7.0
  1878.  *
  1879.  * @param bool $clear_update_cache Whether to clear the Plugin updates cache
  1880.  */
  1881. function wp_clean_plugins_cache( $clear_update_cache = true ) {
  1882.     if ( $clear_update_cache )
  1883.         delete_site_transient( 'update_plugins' );
  1884.     wp_cache_delete( 'plugins', 'plugins' );
  1885. }
  1886.  
  1887. /**
  1888.  * Load a given plugin attempt to generate errors.
  1889.  *
  1890.  * @since 3.0.0
  1891.  * @since 4.4.0 Function was moved into the `wp-admin/includes/plugin.php` file.
  1892.  *
  1893.  * @param string $plugin Plugin file to load.
  1894.  */
  1895. function plugin_sandbox_scrape( $plugin ) {
  1896.     wp_register_plugin_realpath( WP_PLUGIN_DIR . '/' . $plugin );
  1897.     include( WP_PLUGIN_DIR . '/' . $plugin );
  1898. }
  1899.