diff --git a/wp-admin/includes/plugin.php b/wp-admin/includes/plugin.php index 977801d92b..de1468352b 100644 --- a/wp-admin/includes/plugin.php +++ b/wp-admin/includes/plugin.php @@ -6,6 +6,221 @@ * @subpackage Administration */ +/** + * Parses the plugin contents to retrieve plugin's metadata. + * + * All plugin headers must be on their own line. Plugin description must not have + * any newlines, otherwise only parts of the description will be displayed. + * The below is formatted for printing. + * + * /* + * Plugin Name: Name of the plugin. + * Plugin URI: The home page of the plugin. + * Description: Plugin description. + * Author: Plugin author's name. + * Author URI: Link to the author's website. + * Version: Plugin version. + * Text Domain: Optional. Unique identifier, should be same as the one used in + * load_plugin_textdomain(). + * Domain Path: Optional. Only useful if the translations are located in a + * folder above the plugin's base path. For example, if .mo files are + * located in the locale folder then Domain Path will be "/locale/" and + * must have the first slash. Defaults to the base folder the plugin is + * located in. + * Network: Optional. Specify "Network: true" to require that a plugin is activated + * across all sites in an installation. This will prevent a plugin from being + * activated on a single site when Multisite is enabled. + * Requires at least: Optional. Specify the minimum required WordPress version. + * Requires PHP: Optional. Specify the minimum required PHP version. + * * / # Remove the space to close comment. + * + * The first 8 KB of the file will be pulled in and if the plugin data is not + * within that first 8 KB, then the plugin author should correct their plugin + * and move the plugin data headers to the top. + * + * The plugin file is assumed to have permissions to allow for scripts to read + * the file. This is not checked however and the file is only opened for + * reading. + * + * @since 1.5.0 + * @since 5.3.0 Added support for `Requires at least` and `Requires PHP` headers. + * @since 5.8.0 Added support for `Update URI` header. + * @since 6.5.0 Added support for `Requires Plugins` header. + * + * @param string $plugin_file Absolute path to the main plugin file. + * @param bool $markup Optional. If the returned data should have HTML markup applied. + * Default true. + * @param bool $translate Optional. If the returned data should be translated. Default true. + * @return array { + * Plugin data. Values will be empty if not supplied by the plugin. + * + * @type string $Name Name of the plugin. Should be unique. + * @type string $PluginURI Plugin URI. + * @type string $Version Plugin version. + * @type string $Description Plugin description. + * @type string $Author Plugin author's name. + * @type string $AuthorURI Plugin author's website address (if set). + * @type string $TextDomain Plugin textdomain. + * @type string $DomainPath Plugin's relative directory path to .mo files. + * @type bool $Network Whether the plugin can only be activated network-wide. + * @type string $RequiresWP Minimum required version of WordPress. + * @type string $RequiresPHP Minimum required version of PHP. + * @type string $UpdateURI ID of the plugin for update purposes, should be a URI. + * @type string $RequiresPlugins Comma separated list of dot org plugin slugs. + * @type string $Title Title of the plugin and link to the plugin's site (if set). + * @type string $AuthorName Plugin author's name. + * } + */ +function get_plugin_data( $plugin_file, $markup = true, $translate = true ) { + + $default_headers = array( + 'Name' => 'Plugin Name', + 'PluginURI' => 'Plugin URI', + 'Version' => 'Version', + 'Description' => 'Description', + 'Author' => 'Author', + 'AuthorURI' => 'Author URI', + 'TextDomain' => 'Text Domain', + 'DomainPath' => 'Domain Path', + 'Network' => 'Network', + 'RequiresWP' => 'Requires at least', + 'RequiresPHP' => 'Requires PHP', + 'UpdateURI' => 'Update URI', + 'RequiresPlugins' => 'Requires Plugins', + // Site Wide Only is deprecated in favor of Network. + '_sitewide' => 'Site Wide Only', + ); + + $plugin_data = get_file_data( $plugin_file, $default_headers, 'plugin' ); + + // Site Wide Only is the old header for Network. + if ( ! $plugin_data['Network'] && $plugin_data['_sitewide'] ) { + /* translators: 1: Site Wide Only: true, 2: Network: true */ + _deprecated_argument( __FUNCTION__, '3.0.0', sprintf( __( 'The %1$s plugin header is deprecated. Use %2$s instead.' ), 'Site Wide Only: true', 'Network: true' ) ); + $plugin_data['Network'] = $plugin_data['_sitewide']; + } + $plugin_data['Network'] = ( 'true' === strtolower( $plugin_data['Network'] ) ); + unset( $plugin_data['_sitewide'] ); + + // If no text domain is defined fall back to the plugin slug. + if ( ! $plugin_data['TextDomain'] ) { + $plugin_slug = dirname( plugin_basename( $plugin_file ) ); + if ( '.' !== $plugin_slug && ! str_contains( $plugin_slug, '/' ) ) { + $plugin_data['TextDomain'] = $plugin_slug; + } + } + + if ( $markup || $translate ) { + $plugin_data = _get_plugin_data_markup_translate( $plugin_file, $plugin_data, $markup, $translate ); + } else { + $plugin_data['Title'] = $plugin_data['Name']; + $plugin_data['AuthorName'] = $plugin_data['Author']; + } + + return $plugin_data; +} + +/** + * Sanitizes plugin data, optionally adds markup, optionally translates. + * + * @since 2.7.0 + * + * @see get_plugin_data() + * + * @access private + * + * @param string $plugin_file Path to the main plugin file. + * @param array $plugin_data An array of plugin data. See get_plugin_data(). + * @param bool $markup Optional. If the returned data should have HTML markup applied. + * Default true. + * @param bool $translate Optional. If the returned data should be translated. Default true. + * @return array Plugin data. Values will be empty if not supplied by the plugin. + * See get_plugin_data() for the list of possible values. + */ +function _get_plugin_data_markup_translate( $plugin_file, $plugin_data, $markup = true, $translate = true ) { + + // Sanitize the plugin filename to a WP_PLUGIN_DIR relative path. + $plugin_file = plugin_basename( $plugin_file ); + + // Translate fields. + if ( $translate ) { + $textdomain = $plugin_data['TextDomain']; + if ( $textdomain ) { + if ( ! is_textdomain_loaded( $textdomain ) ) { + if ( $plugin_data['DomainPath'] ) { + load_plugin_textdomain( $textdomain, false, dirname( $plugin_file ) . $plugin_data['DomainPath'] ); + } else { + load_plugin_textdomain( $textdomain, false, dirname( $plugin_file ) ); + } + } + } elseif ( 'hello.php' === basename( $plugin_file ) ) { + $textdomain = 'default'; + } + if ( $textdomain ) { + foreach ( array( 'Name', 'PluginURI', 'Description', 'Author', 'AuthorURI', 'Version' ) as $field ) { + if ( ! empty( $plugin_data[ $field ] ) ) { + // phpcs:ignore WordPress.WP.I18n.LowLevelTranslationFunction,WordPress.WP.I18n.NonSingularStringLiteralText,WordPress.WP.I18n.NonSingularStringLiteralDomain + $plugin_data[ $field ] = translate( $plugin_data[ $field ], $textdomain ); + } + } + } + } + + // Sanitize fields. + $allowed_tags_in_links = array( + 'abbr' => array( 'title' => true ), + 'acronym' => array( 'title' => true ), + 'code' => true, + 'em' => true, + 'strong' => true, + ); + + $allowed_tags = $allowed_tags_in_links; + $allowed_tags['a'] = array( + 'href' => true, + 'title' => true, + ); + + /* + * Name is marked up inside tags. Don't allow these. + * Author is too, but some plugins have used here (omitting Author URI). + */ + $plugin_data['Name'] = wp_kses( $plugin_data['Name'], $allowed_tags_in_links ); + $plugin_data['Author'] = wp_kses( $plugin_data['Author'], $allowed_tags ); + + $plugin_data['Description'] = wp_kses( $plugin_data['Description'], $allowed_tags ); + $plugin_data['Version'] = wp_kses( $plugin_data['Version'], $allowed_tags ); + + $plugin_data['PluginURI'] = esc_url( $plugin_data['PluginURI'] ); + $plugin_data['AuthorURI'] = esc_url( $plugin_data['AuthorURI'] ); + + $plugin_data['Title'] = $plugin_data['Name']; + $plugin_data['AuthorName'] = $plugin_data['Author']; + + // Apply markup. + if ( $markup ) { + if ( $plugin_data['PluginURI'] && $plugin_data['Name'] ) { + $plugin_data['Title'] = '' . $plugin_data['Name'] . ''; + } + + if ( $plugin_data['AuthorURI'] && $plugin_data['Author'] ) { + $plugin_data['Author'] = '' . $plugin_data['Author'] . ''; + } + + $plugin_data['Description'] = wptexturize( $plugin_data['Description'] ); + + if ( $plugin_data['Author'] ) { + $plugin_data['Description'] .= sprintf( + /* translators: %s: Plugin author. */ + ' ' . __( 'By %s.' ) . '', + $plugin_data['Author'] + ); + } + } + + return $plugin_data; +} + /** * Gets a list of a plugin's files. * @@ -304,6 +519,97 @@ function _get_dropins() { return $dropins; } +/** + * Determines whether a plugin is active. + * + * Only plugins installed in the plugins/ folder can be active. + * + * Plugins in the mu-plugins/ folder can't be "activated," so this function will + * return false for those plugins. + * + * For more information on this and similar theme functions, check out + * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ + * Conditional Tags} article in the Theme Developer Handbook. + * + * @since 2.5.0 + * + * @param string $plugin Path to the plugin file relative to the 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() ), true ) || is_plugin_active_for_network( $plugin ); +} + +/** + * Determines whether the plugin is inactive. + * + * Reverse of is_plugin_active(). Used as a callback. + * + * For more information on this and similar theme functions, check out + * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ + * Conditional Tags} article in the Theme Developer Handbook. + * + * @since 3.1.0 + * + * @see is_plugin_active() + * + * @param string $plugin Path to the plugin file relative to the plugins directory. + * @return bool True if inactive. False if active. + */ +function is_plugin_inactive( $plugin ) { + return ! is_plugin_active( $plugin ); +} + +/** + * Determines whether the plugin is active for the entire network. + * + * Only plugins installed in the plugins/ folder can be active. + * + * Plugins in the mu-plugins/ folder can't be "activated," so this function will + * return false for those plugins. + * + * For more information on this and similar theme functions, check out + * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ + * Conditional Tags} article in the Theme Developer Handbook. + * + * @since 3.0.0 + * + * @param string $plugin Path to the plugin file relative to the 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 backward compatibility. + * + * @since 3.0.0 + * + * @param string $plugin Path to the plugin file relative to the plugins directory. + * @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. * diff --git a/wp-includes/functions.php b/wp-includes/functions.php index 44f908a301..7e46dd53ca 100644 --- a/wp-includes/functions.php +++ b/wp-includes/functions.php @@ -6930,312 +6930,6 @@ function get_file_data( $file, $default_headers, $context = '' ) { return $all_headers; } -/** - * Parses the plugin contents to retrieve plugin's metadata. - * - * All plugin headers must be on their own line. Plugin description must not have - * any newlines, otherwise only parts of the description will be displayed. - * The below is formatted for printing. - * - * /* - * Plugin Name: Name of the plugin. - * Plugin URI: The home page of the plugin. - * Description: Plugin description. - * Author: Plugin author's name. - * Author URI: Link to the author's website. - * Version: Plugin version. - * Text Domain: Optional. Unique identifier, should be same as the one used in - * load_plugin_textdomain(). - * Domain Path: Optional. Only useful if the translations are located in a - * folder above the plugin's base path. For example, if .mo files are - * located in the locale folder then Domain Path will be "/locale/" and - * must have the first slash. Defaults to the base folder the plugin is - * located in. - * Network: Optional. Specify "Network: true" to require that a plugin is activated - * across all sites in an installation. This will prevent a plugin from being - * activated on a single site when Multisite is enabled. - * Requires at least: Optional. Specify the minimum required WordPress version. - * Requires PHP: Optional. Specify the minimum required PHP version. - * * / # Remove the space to close comment. - * - * The first 8 KB of the file will be pulled in and if the plugin data is not - * within that first 8 KB, then the plugin author should correct their plugin - * and move the plugin data headers to the top. - * - * The plugin file is assumed to have permissions to allow for scripts to read - * the file. This is not checked however and the file is only opened for - * reading. - * - * @since 1.5.0 - * @since 5.3.0 Added support for `Requires at least` and `Requires PHP` headers. - * @since 5.8.0 Added support for `Update URI` header. - * @since 6.5.0 Added support for `Requires Plugins` header. - * - * @param string $plugin_file Absolute path to the main plugin file. - * @param bool $markup Optional. If the returned data should have HTML markup applied. - * Default true. - * @param bool $translate Optional. If the returned data should be translated. Default true. - * @return array { - * Plugin data. Values will be empty if not supplied by the plugin. - * - * @type string $Name Name of the plugin. Should be unique. - * @type string $PluginURI Plugin URI. - * @type string $Version Plugin version. - * @type string $Description Plugin description. - * @type string $Author Plugin author's name. - * @type string $AuthorURI Plugin author's website address (if set). - * @type string $TextDomain Plugin textdomain. - * @type string $DomainPath Plugin's relative directory path to .mo files. - * @type bool $Network Whether the plugin can only be activated network-wide. - * @type string $RequiresWP Minimum required version of WordPress. - * @type string $RequiresPHP Minimum required version of PHP. - * @type string $UpdateURI ID of the plugin for update purposes, should be a URI. - * @type string $RequiresPlugins Comma separated list of dot org plugin slugs. - * @type string $Title Title of the plugin and link to the plugin's site (if set). - * @type string $AuthorName Plugin author's name. - * } - */ -function get_plugin_data( $plugin_file, $markup = true, $translate = true ) { - - $default_headers = array( - 'Name' => 'Plugin Name', - 'PluginURI' => 'Plugin URI', - 'Version' => 'Version', - 'Description' => 'Description', - 'Author' => 'Author', - 'AuthorURI' => 'Author URI', - 'TextDomain' => 'Text Domain', - 'DomainPath' => 'Domain Path', - 'Network' => 'Network', - 'RequiresWP' => 'Requires at least', - 'RequiresPHP' => 'Requires PHP', - 'UpdateURI' => 'Update URI', - 'RequiresPlugins' => 'Requires Plugins', - // Site Wide Only is deprecated in favor of Network. - '_sitewide' => 'Site Wide Only', - ); - - $plugin_data = get_file_data( $plugin_file, $default_headers, 'plugin' ); - - // Site Wide Only is the old header for Network. - if ( ! $plugin_data['Network'] && $plugin_data['_sitewide'] ) { - /* translators: 1: Site Wide Only: true, 2: Network: true */ - _deprecated_argument( __FUNCTION__, '3.0.0', sprintf( __( 'The %1$s plugin header is deprecated. Use %2$s instead.' ), 'Site Wide Only: true', 'Network: true' ) ); - $plugin_data['Network'] = $plugin_data['_sitewide']; - } - $plugin_data['Network'] = ( 'true' === strtolower( $plugin_data['Network'] ) ); - unset( $plugin_data['_sitewide'] ); - - // If no text domain is defined fall back to the plugin slug. - if ( ! $plugin_data['TextDomain'] ) { - $plugin_slug = dirname( plugin_basename( $plugin_file ) ); - if ( '.' !== $plugin_slug && ! str_contains( $plugin_slug, '/' ) ) { - $plugin_data['TextDomain'] = $plugin_slug; - } - } - - if ( $markup || $translate ) { - $plugin_data = _get_plugin_data_markup_translate( $plugin_file, $plugin_data, $markup, $translate ); - } else { - $plugin_data['Title'] = $plugin_data['Name']; - $plugin_data['AuthorName'] = $plugin_data['Author']; - } - - return $plugin_data; -} - -/** - * Sanitizes plugin data, optionally adds markup, optionally translates. - * - * @since 2.7.0 - * - * @see get_plugin_data() - * - * @access private - * - * @param string $plugin_file Path to the main plugin file. - * @param array $plugin_data An array of plugin data. See get_plugin_data(). - * @param bool $markup Optional. If the returned data should have HTML markup applied. - * Default true. - * @param bool $translate Optional. If the returned data should be translated. Default true. - * @return array Plugin data. Values will be empty if not supplied by the plugin. - * See get_plugin_data() for the list of possible values. - */ -function _get_plugin_data_markup_translate( $plugin_file, $plugin_data, $markup = true, $translate = true ) { - - // Sanitize the plugin filename to a WP_PLUGIN_DIR relative path. - $plugin_file = plugin_basename( $plugin_file ); - - // Translate fields. - if ( $translate ) { - $textdomain = $plugin_data['TextDomain']; - if ( $textdomain ) { - if ( ! is_textdomain_loaded( $textdomain ) ) { - if ( $plugin_data['DomainPath'] ) { - load_plugin_textdomain( $textdomain, false, dirname( $plugin_file ) . $plugin_data['DomainPath'] ); - } else { - load_plugin_textdomain( $textdomain, false, dirname( $plugin_file ) ); - } - } - } elseif ( 'hello.php' === basename( $plugin_file ) ) { - $textdomain = 'default'; - } - if ( $textdomain ) { - foreach ( array( 'Name', 'PluginURI', 'Description', 'Author', 'AuthorURI', 'Version' ) as $field ) { - if ( ! empty( $plugin_data[ $field ] ) ) { - // phpcs:ignore WordPress.WP.I18n.LowLevelTranslationFunction,WordPress.WP.I18n.NonSingularStringLiteralText,WordPress.WP.I18n.NonSingularStringLiteralDomain - $plugin_data[ $field ] = translate( $plugin_data[ $field ], $textdomain ); - } - } - } - } - - // Sanitize fields. - $allowed_tags_in_links = array( - 'abbr' => array( 'title' => true ), - 'acronym' => array( 'title' => true ), - 'code' => true, - 'em' => true, - 'strong' => true, - ); - - $allowed_tags = $allowed_tags_in_links; - $allowed_tags['a'] = array( - 'href' => true, - 'title' => true, - ); - - /* - * Name is marked up inside tags. Don't allow these. - * Author is too, but some plugins have used here (omitting Author URI). - */ - $plugin_data['Name'] = wp_kses( $plugin_data['Name'], $allowed_tags_in_links ); - $plugin_data['Author'] = wp_kses( $plugin_data['Author'], $allowed_tags ); - - $plugin_data['Description'] = wp_kses( $plugin_data['Description'], $allowed_tags ); - $plugin_data['Version'] = wp_kses( $plugin_data['Version'], $allowed_tags ); - - $plugin_data['PluginURI'] = esc_url( $plugin_data['PluginURI'] ); - $plugin_data['AuthorURI'] = esc_url( $plugin_data['AuthorURI'] ); - - $plugin_data['Title'] = $plugin_data['Name']; - $plugin_data['AuthorName'] = $plugin_data['Author']; - - // Apply markup. - if ( $markup ) { - if ( $plugin_data['PluginURI'] && $plugin_data['Name'] ) { - $plugin_data['Title'] = '' . $plugin_data['Name'] . ''; - } - - if ( $plugin_data['AuthorURI'] && $plugin_data['Author'] ) { - $plugin_data['Author'] = '' . $plugin_data['Author'] . ''; - } - - $plugin_data['Description'] = wptexturize( $plugin_data['Description'] ); - - if ( $plugin_data['Author'] ) { - $plugin_data['Description'] .= sprintf( - /* translators: %s: Plugin author. */ - ' ' . __( 'By %s.' ) . '', - $plugin_data['Author'] - ); - } - } - - return $plugin_data; -} - -/** - * Determines whether a plugin is active. - * - * Only plugins installed in the plugins/ folder can be active. - * - * Plugins in the mu-plugins/ folder can't be "activated," so this function will - * return false for those plugins. - * - * For more information on this and similar theme functions, check out - * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ - * Conditional Tags} article in the Theme Developer Handbook. - * - * @since 2.5.0 - * - * @param string $plugin Path to the plugin file relative to the 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() ), true ) || is_plugin_active_for_network( $plugin ); -} - -/** - * Determines whether the plugin is inactive. - * - * Reverse of is_plugin_active(). Used as a callback. - * - * For more information on this and similar theme functions, check out - * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ - * Conditional Tags} article in the Theme Developer Handbook. - * - * @since 3.1.0 - * - * @see is_plugin_active() - * - * @param string $plugin Path to the plugin file relative to the plugins directory. - * @return bool True if inactive. False if active. - */ -function is_plugin_inactive( $plugin ) { - return ! is_plugin_active( $plugin ); -} - -/** - * Determines whether the plugin is active for the entire network. - * - * Only plugins installed in the plugins/ folder can be active. - * - * Plugins in the mu-plugins/ folder can't be "activated," so this function will - * return false for those plugins. - * - * For more information on this and similar theme functions, check out - * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ - * Conditional Tags} article in the Theme Developer Handbook. - * - * @since 3.0.0 - * - * @param string $plugin Path to the plugin file relative to the 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 backward compatibility. - * - * @since 3.0.0 - * - * @param string $plugin Path to the plugin file relative to the plugins directory. - * @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; -} - /** * Returns true. * diff --git a/wp-includes/version.php b/wp-includes/version.php index 3cf57610f9..2ba321ec58 100644 --- a/wp-includes/version.php +++ b/wp-includes/version.php @@ -16,7 +16,7 @@ * * @global string $wp_version */ -$wp_version = '6.8-alpha-59486'; +$wp_version = '6.8-alpha-59488'; /** * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema. diff --git a/wp-settings.php b/wp-settings.php index 44c04a3a7b..635f6de248 100644 --- a/wp-settings.php +++ b/wp-settings.php @@ -518,6 +518,9 @@ if ( ! is_multisite() && wp_is_fatal_error_handler_enabled() ) { wp_recovery_mode()->initialize(); } +// To make get_plugin_data() available in a way that's compatible with plugins also loading this file, see #62244. +require_once ABSPATH . 'wp-admin/includes/plugin.php'; + // Load active plugins. foreach ( wp_get_active_and_valid_plugins() as $plugin ) { wp_register_plugin_realpath( $plugin );