|
6 | 6 | * @subpackage Administration |
7 | 7 | */ |
8 | 8 |
|
| 9 | +/** |
| 10 | + * Parses the plugin contents to retrieve plugin's metadata. |
| 11 | + * |
| 12 | + * All plugin headers must be on their own line. Plugin description must not have |
| 13 | + * any newlines, otherwise only parts of the description will be displayed. |
| 14 | + * The below is formatted for printing. |
| 15 | + * |
| 16 | + * /* |
| 17 | + * Plugin Name: Name of the plugin. |
| 18 | + * Plugin URI: The home page of the plugin. |
| 19 | + * Description: Plugin description. |
| 20 | + * Author: Plugin author's name. |
| 21 | + * Author URI: Link to the author's website. |
| 22 | + * Version: Plugin version. |
| 23 | + * Text Domain: Optional. Unique identifier, should be same as the one used in |
| 24 | + * load_plugin_textdomain(). |
| 25 | + * Domain Path: Optional. Only useful if the translations are located in a |
| 26 | + * folder above the plugin's base path. For example, if .mo files are |
| 27 | + * located in the locale folder then Domain Path will be "/locale/" and |
| 28 | + * must have the first slash. Defaults to the base folder the plugin is |
| 29 | + * located in. |
| 30 | + * Network: Optional. Specify "Network: true" to require that a plugin is activated |
| 31 | + * across all sites in an installation. This will prevent a plugin from being |
| 32 | + * activated on a single site when Multisite is enabled. |
| 33 | + * Requires at least: Optional. Specify the minimum required WordPress version. |
| 34 | + * Requires PHP: Optional. Specify the minimum required PHP version. |
| 35 | + * * / # Remove the space to close comment. |
| 36 | + * |
| 37 | + * The first 8 KB of the file will be pulled in and if the plugin data is not |
| 38 | + * within that first 8 KB, then the plugin author should correct their plugin |
| 39 | + * and move the plugin data headers to the top. |
| 40 | + * |
| 41 | + * The plugin file is assumed to have permissions to allow for scripts to read |
| 42 | + * the file. This is not checked however and the file is only opened for |
| 43 | + * reading. |
| 44 | + * |
| 45 | + * @since 1.5.0 |
| 46 | + * @since 5.3.0 Added support for `Requires at least` and `Requires PHP` headers. |
| 47 | + * @since 5.8.0 Added support for `Update URI` header. |
| 48 | + * @since 6.5.0 Added support for `Requires Plugins` header. |
| 49 | + * |
| 50 | + * @param string $plugin_file Absolute 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 $PluginURI Plugin URI. |
| 59 | + * @type string $Version Plugin version. |
| 60 | + * @type string $Description Plugin description. |
| 61 | + * @type string $Author Plugin author's name. |
| 62 | + * @type string $AuthorURI Plugin author's website address (if set). |
| 63 | + * @type string $TextDomain Plugin textdomain. |
| 64 | + * @type string $DomainPath Plugin's relative directory path to .mo files. |
| 65 | + * @type bool $Network Whether the plugin can only be activated network-wide. |
| 66 | + * @type string $RequiresWP Minimum required version of WordPress. |
| 67 | + * @type string $RequiresPHP Minimum required version of PHP. |
| 68 | + * @type string $UpdateURI ID of the plugin for update purposes, should be a URI. |
| 69 | + * @type string $RequiresPlugins Comma separated list of dot org plugin slugs. |
| 70 | + * @type string $Title Title of the plugin and link to the plugin's site (if set). |
| 71 | + * @type string $AuthorName Plugin author's name. |
| 72 | + * } |
| 73 | + */ |
| 74 | +function get_plugin_data( $plugin_file, $markup = true, $translate = true ) { |
| 75 | + |
| 76 | + $default_headers = array( |
| 77 | + 'Name' => 'Plugin Name', |
| 78 | + 'PluginURI' => 'Plugin URI', |
| 79 | + 'Version' => 'Version', |
| 80 | + 'Description' => 'Description', |
| 81 | + 'Author' => 'Author', |
| 82 | + 'AuthorURI' => 'Author URI', |
| 83 | + 'TextDomain' => 'Text Domain', |
| 84 | + 'DomainPath' => 'Domain Path', |
| 85 | + 'Network' => 'Network', |
| 86 | + 'RequiresWP' => 'Requires at least', |
| 87 | + 'RequiresPHP' => 'Requires PHP', |
| 88 | + 'UpdateURI' => 'Update URI', |
| 89 | + 'RequiresPlugins' => 'Requires Plugins', |
| 90 | + // Site Wide Only is deprecated in favor of Network. |
| 91 | + '_sitewide' => 'Site Wide Only', |
| 92 | + ); |
| 93 | + |
| 94 | + $plugin_data = get_file_data( $plugin_file, $default_headers, 'plugin' ); |
| 95 | + |
| 96 | + // Site Wide Only is the old header for Network. |
| 97 | + if ( ! $plugin_data['Network'] && $plugin_data['_sitewide'] ) { |
| 98 | + /* translators: 1: Site Wide Only: true, 2: Network: true */ |
| 99 | + _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>' ) ); |
| 100 | + $plugin_data['Network'] = $plugin_data['_sitewide']; |
| 101 | + } |
| 102 | + $plugin_data['Network'] = ( 'true' === strtolower( $plugin_data['Network'] ) ); |
| 103 | + unset( $plugin_data['_sitewide'] ); |
| 104 | + |
| 105 | + // If no text domain is defined fall back to the plugin slug. |
| 106 | + if ( ! $plugin_data['TextDomain'] ) { |
| 107 | + $plugin_slug = dirname( plugin_basename( $plugin_file ) ); |
| 108 | + if ( '.' !== $plugin_slug && ! str_contains( $plugin_slug, '/' ) ) { |
| 109 | + $plugin_data['TextDomain'] = $plugin_slug; |
| 110 | + } |
| 111 | + } |
| 112 | + |
| 113 | + if ( $markup || $translate ) { |
| 114 | + $plugin_data = _get_plugin_data_markup_translate( $plugin_file, $plugin_data, $markup, $translate ); |
| 115 | + } else { |
| 116 | + $plugin_data['Title'] = $plugin_data['Name']; |
| 117 | + $plugin_data['AuthorName'] = $plugin_data['Author']; |
| 118 | + } |
| 119 | + |
| 120 | + return $plugin_data; |
| 121 | +} |
| 122 | + |
| 123 | +/** |
| 124 | + * Sanitizes plugin data, optionally adds markup, optionally translates. |
| 125 | + * |
| 126 | + * @since 2.7.0 |
| 127 | + * |
| 128 | + * @see get_plugin_data() |
| 129 | + * |
| 130 | + * @access private |
| 131 | + * |
| 132 | + * @param string $plugin_file Path to the main plugin file. |
| 133 | + * @param array $plugin_data An array of plugin data. See get_plugin_data(). |
| 134 | + * @param bool $markup Optional. If the returned data should have HTML markup applied. |
| 135 | + * Default true. |
| 136 | + * @param bool $translate Optional. If the returned data should be translated. Default true. |
| 137 | + * @return array Plugin data. Values will be empty if not supplied by the plugin. |
| 138 | + * See get_plugin_data() for the list of possible values. |
| 139 | + */ |
| 140 | +function _get_plugin_data_markup_translate( $plugin_file, $plugin_data, $markup = true, $translate = true ) { |
| 141 | + |
| 142 | + // Sanitize the plugin filename to a WP_PLUGIN_DIR relative path. |
| 143 | + $plugin_file = plugin_basename( $plugin_file ); |
| 144 | + |
| 145 | + // Translate fields. |
| 146 | + if ( $translate ) { |
| 147 | + $textdomain = $plugin_data['TextDomain']; |
| 148 | + if ( $textdomain ) { |
| 149 | + if ( ! is_textdomain_loaded( $textdomain ) ) { |
| 150 | + if ( $plugin_data['DomainPath'] ) { |
| 151 | + load_plugin_textdomain( $textdomain, false, dirname( $plugin_file ) . $plugin_data['DomainPath'] ); |
| 152 | + } else { |
| 153 | + load_plugin_textdomain( $textdomain, false, dirname( $plugin_file ) ); |
| 154 | + } |
| 155 | + } |
| 156 | + } elseif ( 'hello.php' === basename( $plugin_file ) ) { |
| 157 | + $textdomain = 'default'; |
| 158 | + } |
| 159 | + if ( $textdomain ) { |
| 160 | + foreach ( array( 'Name', 'PluginURI', 'Description', 'Author', 'AuthorURI', 'Version' ) as $field ) { |
| 161 | + if ( ! empty( $plugin_data[ $field ] ) ) { |
| 162 | + // phpcs:ignore WordPress.WP.I18n.LowLevelTranslationFunction,WordPress.WP.I18n.NonSingularStringLiteralText,WordPress.WP.I18n.NonSingularStringLiteralDomain |
| 163 | + $plugin_data[ $field ] = translate( $plugin_data[ $field ], $textdomain ); |
| 164 | + } |
| 165 | + } |
| 166 | + } |
| 167 | + } |
| 168 | + |
| 169 | + // Sanitize fields. |
| 170 | + $allowed_tags_in_links = array( |
| 171 | + 'abbr' => array( 'title' => true ), |
| 172 | + 'acronym' => array( 'title' => true ), |
| 173 | + 'code' => true, |
| 174 | + 'em' => true, |
| 175 | + 'strong' => true, |
| 176 | + ); |
| 177 | + |
| 178 | + $allowed_tags = $allowed_tags_in_links; |
| 179 | + $allowed_tags['a'] = array( |
| 180 | + 'href' => true, |
| 181 | + 'title' => true, |
| 182 | + ); |
| 183 | + |
| 184 | + /* |
| 185 | + * Name is marked up inside <a> tags. Don't allow these. |
| 186 | + * Author is too, but some plugins have used <a> here (omitting Author URI). |
| 187 | + */ |
| 188 | + $plugin_data['Name'] = wp_kses( $plugin_data['Name'], $allowed_tags_in_links ); |
| 189 | + $plugin_data['Author'] = wp_kses( $plugin_data['Author'], $allowed_tags ); |
| 190 | + |
| 191 | + $plugin_data['Description'] = wp_kses( $plugin_data['Description'], $allowed_tags ); |
| 192 | + $plugin_data['Version'] = wp_kses( $plugin_data['Version'], $allowed_tags ); |
| 193 | + |
| 194 | + $plugin_data['PluginURI'] = esc_url( $plugin_data['PluginURI'] ); |
| 195 | + $plugin_data['AuthorURI'] = esc_url( $plugin_data['AuthorURI'] ); |
| 196 | + |
| 197 | + $plugin_data['Title'] = $plugin_data['Name']; |
| 198 | + $plugin_data['AuthorName'] = $plugin_data['Author']; |
| 199 | + |
| 200 | + // Apply markup. |
| 201 | + if ( $markup ) { |
| 202 | + if ( $plugin_data['PluginURI'] && $plugin_data['Name'] ) { |
| 203 | + $plugin_data['Title'] = '<a href="' . $plugin_data['PluginURI'] . '">' . $plugin_data['Name'] . '</a>'; |
| 204 | + } |
| 205 | + |
| 206 | + if ( $plugin_data['AuthorURI'] && $plugin_data['Author'] ) { |
| 207 | + $plugin_data['Author'] = '<a href="' . $plugin_data['AuthorURI'] . '">' . $plugin_data['Author'] . '</a>'; |
| 208 | + } |
| 209 | + |
| 210 | + $plugin_data['Description'] = wptexturize( $plugin_data['Description'] ); |
| 211 | + |
| 212 | + if ( $plugin_data['Author'] ) { |
| 213 | + $plugin_data['Description'] .= sprintf( |
| 214 | + /* translators: %s: Plugin author. */ |
| 215 | + ' <cite>' . __( 'By %s.' ) . '</cite>', |
| 216 | + $plugin_data['Author'] |
| 217 | + ); |
| 218 | + } |
| 219 | + } |
| 220 | + |
| 221 | + return $plugin_data; |
| 222 | +} |
| 223 | + |
9 | 224 | /** |
10 | 225 | * Gets a list of a plugin's files. |
11 | 226 | * |
@@ -304,6 +519,97 @@ function _get_dropins() { |
304 | 519 | return $dropins; |
305 | 520 | } |
306 | 521 |
|
| 522 | +/** |
| 523 | + * Determines whether a plugin is active. |
| 524 | + * |
| 525 | + * Only plugins installed in the plugins/ folder can be active. |
| 526 | + * |
| 527 | + * Plugins in the mu-plugins/ folder can't be "activated," so this function will |
| 528 | + * return false for those plugins. |
| 529 | + * |
| 530 | + * For more information on this and similar theme functions, check out |
| 531 | + * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ |
| 532 | + * Conditional Tags} article in the Theme Developer Handbook. |
| 533 | + * |
| 534 | + * @since 2.5.0 |
| 535 | + * |
| 536 | + * @param string $plugin Path to the plugin file relative to the plugins directory. |
| 537 | + * @return bool True, if in the active plugins list. False, not in the list. |
| 538 | + */ |
| 539 | +function is_plugin_active( $plugin ) { |
| 540 | + return in_array( $plugin, (array) get_option( 'active_plugins', array() ), true ) || is_plugin_active_for_network( $plugin ); |
| 541 | +} |
| 542 | + |
| 543 | +/** |
| 544 | + * Determines whether the plugin is inactive. |
| 545 | + * |
| 546 | + * Reverse of is_plugin_active(). Used as a callback. |
| 547 | + * |
| 548 | + * For more information on this and similar theme functions, check out |
| 549 | + * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ |
| 550 | + * Conditional Tags} article in the Theme Developer Handbook. |
| 551 | + * |
| 552 | + * @since 3.1.0 |
| 553 | + * |
| 554 | + * @see is_plugin_active() |
| 555 | + * |
| 556 | + * @param string $plugin Path to the plugin file relative to the plugins directory. |
| 557 | + * @return bool True if inactive. False if active. |
| 558 | + */ |
| 559 | +function is_plugin_inactive( $plugin ) { |
| 560 | + return ! is_plugin_active( $plugin ); |
| 561 | +} |
| 562 | + |
| 563 | +/** |
| 564 | + * Determines whether the plugin is active for the entire network. |
| 565 | + * |
| 566 | + * Only plugins installed in the plugins/ folder can be active. |
| 567 | + * |
| 568 | + * Plugins in the mu-plugins/ folder can't be "activated," so this function will |
| 569 | + * return false for those plugins. |
| 570 | + * |
| 571 | + * For more information on this and similar theme functions, check out |
| 572 | + * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ |
| 573 | + * Conditional Tags} article in the Theme Developer Handbook. |
| 574 | + * |
| 575 | + * @since 3.0.0 |
| 576 | + * |
| 577 | + * @param string $plugin Path to the plugin file relative to the plugins directory. |
| 578 | + * @return bool True if active for the network, otherwise false. |
| 579 | + */ |
| 580 | +function is_plugin_active_for_network( $plugin ) { |
| 581 | + if ( ! is_multisite() ) { |
| 582 | + return false; |
| 583 | + } |
| 584 | + |
| 585 | + $plugins = get_site_option( 'active_sitewide_plugins' ); |
| 586 | + if ( isset( $plugins[ $plugin ] ) ) { |
| 587 | + return true; |
| 588 | + } |
| 589 | + |
| 590 | + return false; |
| 591 | +} |
| 592 | + |
| 593 | +/** |
| 594 | + * Checks for "Network: true" in the plugin header to see if this should |
| 595 | + * be activated only as a network wide plugin. The plugin would also work |
| 596 | + * when Multisite is not enabled. |
| 597 | + * |
| 598 | + * Checks for "Site Wide Only: true" for backward compatibility. |
| 599 | + * |
| 600 | + * @since 3.0.0 |
| 601 | + * |
| 602 | + * @param string $plugin Path to the plugin file relative to the plugins directory. |
| 603 | + * @return bool True if plugin is network only, false otherwise. |
| 604 | + */ |
| 605 | +function is_network_only_plugin( $plugin ) { |
| 606 | + $plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin ); |
| 607 | + if ( $plugin_data ) { |
| 608 | + return $plugin_data['Network']; |
| 609 | + } |
| 610 | + return false; |
| 611 | +} |
| 612 | + |
307 | 613 | /** |
308 | 614 | * Attempts activation of plugin in a "sandbox" and redirects on success. |
309 | 615 | * |
|
0 commit comments