43 * reading. |
43 * reading. |
44 * |
44 * |
45 * @since 1.5.0 |
45 * @since 1.5.0 |
46 * @since 5.3.0 Added support for `Requires at least` and `Requires PHP` headers. |
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. |
47 * @since 5.8.0 Added support for `Update URI` header. |
|
48 * @since 6.5.0 Added support for `Requires Plugins` header. |
48 * |
49 * |
49 * @param string $plugin_file Absolute path to the main plugin file. |
50 * @param string $plugin_file Absolute path to the main plugin file. |
50 * @param bool $markup Optional. If the returned data should have HTML markup applied. |
51 * @param bool $markup Optional. If the returned data should have HTML markup applied. |
51 * Default true. |
52 * Default true. |
52 * @param bool $translate Optional. If the returned data should be translated. Default true. |
53 * @param bool $translate Optional. If the returned data should be translated. Default true. |
53 * @return array { |
54 * @return array { |
54 * Plugin data. Values will be empty if not supplied by the plugin. |
55 * Plugin data. Values will be empty if not supplied by the plugin. |
55 * |
56 * |
56 * @type string $Name Name of the plugin. Should be unique. |
57 * @type string $Name Name of the plugin. Should be unique. |
57 * @type string $PluginURI Plugin URI. |
58 * @type string $PluginURI Plugin URI. |
58 * @type string $Version Plugin version. |
59 * @type string $Version Plugin version. |
59 * @type string $Description Plugin description. |
60 * @type string $Description Plugin description. |
60 * @type string $Author Plugin author's name. |
61 * @type string $Author Plugin author's name. |
61 * @type string $AuthorURI Plugin author's website address (if set). |
62 * @type string $AuthorURI Plugin author's website address (if set). |
62 * @type string $TextDomain Plugin textdomain. |
63 * @type string $TextDomain Plugin textdomain. |
63 * @type string $DomainPath Plugin's relative directory path to .mo files. |
64 * @type string $DomainPath Plugin's relative directory path to .mo files. |
64 * @type bool $Network Whether the plugin can only be activated network-wide. |
65 * @type bool $Network Whether the plugin can only be activated network-wide. |
65 * @type string $RequiresWP Minimum required version of WordPress. |
66 * @type string $RequiresWP Minimum required version of WordPress. |
66 * @type string $RequiresPHP Minimum required version of PHP. |
67 * @type string $RequiresPHP Minimum required version of PHP. |
67 * @type string $UpdateURI ID of the plugin for update purposes, should be a URI. |
68 * @type string $UpdateURI ID of the plugin for update purposes, should be a URI. |
68 * @type string $Title Title of the plugin and link to the plugin's site (if set). |
69 * @type string $RequiresPlugins Comma separated list of dot org plugin slugs. |
69 * @type string $AuthorName Plugin author's name. |
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. |
70 * } |
72 * } |
71 */ |
73 */ |
72 function get_plugin_data( $plugin_file, $markup = true, $translate = true ) { |
74 function get_plugin_data( $plugin_file, $markup = true, $translate = true ) { |
73 |
75 |
74 $default_headers = array( |
76 $default_headers = array( |
75 'Name' => 'Plugin Name', |
77 'Name' => 'Plugin Name', |
76 'PluginURI' => 'Plugin URI', |
78 'PluginURI' => 'Plugin URI', |
77 'Version' => 'Version', |
79 'Version' => 'Version', |
78 'Description' => 'Description', |
80 'Description' => 'Description', |
79 'Author' => 'Author', |
81 'Author' => 'Author', |
80 'AuthorURI' => 'Author URI', |
82 'AuthorURI' => 'Author URI', |
81 'TextDomain' => 'Text Domain', |
83 'TextDomain' => 'Text Domain', |
82 'DomainPath' => 'Domain Path', |
84 'DomainPath' => 'Domain Path', |
83 'Network' => 'Network', |
85 'Network' => 'Network', |
84 'RequiresWP' => 'Requires at least', |
86 'RequiresWP' => 'Requires at least', |
85 'RequiresPHP' => 'Requires PHP', |
87 'RequiresPHP' => 'Requires PHP', |
86 'UpdateURI' => 'Update URI', |
88 'UpdateURI' => 'Update URI', |
|
89 'RequiresPlugins' => 'Requires Plugins', |
87 // Site Wide Only is deprecated in favor of Network. |
90 // Site Wide Only is deprecated in favor of Network. |
88 '_sitewide' => 'Site Wide Only', |
91 '_sitewide' => 'Site Wide Only', |
89 ); |
92 ); |
90 |
93 |
91 $plugin_data = get_file_data( $plugin_file, $default_headers, 'plugin' ); |
94 $plugin_data = get_file_data( $plugin_file, $default_headers, 'plugin' ); |
92 |
95 |
93 // Site Wide Only is the old header for Network. |
96 // Site Wide Only is the old header for Network. |
176 $allowed_tags['a'] = array( |
179 $allowed_tags['a'] = array( |
177 'href' => true, |
180 'href' => true, |
178 'title' => true, |
181 'title' => true, |
179 ); |
182 ); |
180 |
183 |
181 // Name is marked up inside <a> tags. Don't allow these. |
184 /* |
182 // Author is too, but some plugins have used <a> here (omitting Author URI). |
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 */ |
183 $plugin_data['Name'] = wp_kses( $plugin_data['Name'], $allowed_tags_in_links ); |
188 $plugin_data['Name'] = wp_kses( $plugin_data['Name'], $allowed_tags_in_links ); |
184 $plugin_data['Author'] = wp_kses( $plugin_data['Author'], $allowed_tags ); |
189 $plugin_data['Author'] = wp_kses( $plugin_data['Author'], $allowed_tags ); |
185 |
190 |
186 $plugin_data['Description'] = wp_kses( $plugin_data['Description'], $allowed_tags ); |
191 $plugin_data['Description'] = wp_kses( $plugin_data['Description'], $allowed_tags ); |
187 $plugin_data['Version'] = wp_kses( $plugin_data['Version'], $allowed_tags ); |
192 $plugin_data['Version'] = wp_kses( $plugin_data['Version'], $allowed_tags ); |
292 $plugins_dir = @opendir( $plugin_root ); |
297 $plugins_dir = @opendir( $plugin_root ); |
293 $plugin_files = array(); |
298 $plugin_files = array(); |
294 |
299 |
295 if ( $plugins_dir ) { |
300 if ( $plugins_dir ) { |
296 while ( ( $file = readdir( $plugins_dir ) ) !== false ) { |
301 while ( ( $file = readdir( $plugins_dir ) ) !== false ) { |
297 if ( '.' === substr( $file, 0, 1 ) ) { |
302 if ( str_starts_with( $file, '.' ) ) { |
298 continue; |
303 continue; |
299 } |
304 } |
300 |
305 |
301 if ( is_dir( $plugin_root . '/' . $file ) ) { |
306 if ( is_dir( $plugin_root . '/' . $file ) ) { |
302 $plugins_subdir = @opendir( $plugin_root . '/' . $file ); |
307 $plugins_subdir = @opendir( $plugin_root . '/' . $file ); |
303 |
308 |
304 if ( $plugins_subdir ) { |
309 if ( $plugins_subdir ) { |
305 while ( ( $subfile = readdir( $plugins_subdir ) ) !== false ) { |
310 while ( ( $subfile = readdir( $plugins_subdir ) ) !== false ) { |
306 if ( '.' === substr( $subfile, 0, 1 ) ) { |
311 if ( str_starts_with( $subfile, '.' ) ) { |
307 continue; |
312 continue; |
308 } |
313 } |
309 |
314 |
310 if ( '.php' === substr( $subfile, -4 ) ) { |
315 if ( str_ends_with( $subfile, '.php' ) ) { |
311 $plugin_files[] = "$file/$subfile"; |
316 $plugin_files[] = "$file/$subfile"; |
312 } |
317 } |
313 } |
318 } |
314 |
319 |
315 closedir( $plugins_subdir ); |
320 closedir( $plugins_subdir ); |
316 } |
321 } |
317 } else { |
322 } elseif ( str_ends_with( $file, '.php' ) ) { |
318 if ( '.php' === substr( $file, -4 ) ) { |
323 $plugin_files[] = $file; |
319 $plugin_files[] = $file; |
|
320 } |
|
321 } |
324 } |
322 } |
325 } |
323 |
326 |
324 closedir( $plugins_dir ); |
327 closedir( $plugins_dir ); |
325 } |
328 } |
474 |
477 |
475 return $dropins; |
478 return $dropins; |
476 } |
479 } |
477 |
480 |
478 /** |
481 /** |
479 * Returns drop-ins that WordPress uses. |
482 * Returns drop-in plugins that WordPress uses. |
480 * |
483 * |
481 * Includes Multisite drop-ins only when is_multisite() |
484 * Includes Multisite drop-ins only when is_multisite() |
482 * |
485 * |
483 * @since 3.0.0 |
486 * @since 3.0.0 |
484 * @return array[] Key is file name. The value is an array, with the first value the |
487 * |
485 * purpose of the drop-in and the second value the name of the constant that must be |
488 * @return array[] { |
486 * true for the drop-in to be used, or true if no constant is required. |
489 * Key is file name. The value is an array of data about the drop-in. |
|
490 * |
|
491 * @type array ...$0 { |
|
492 * Data about the drop-in. |
|
493 * |
|
494 * @type string $0 The purpose of the drop-in. |
|
495 * @type string|true $1 Name of the constant that must be true for the drop-in |
|
496 * to be used, or true if no constant is required. |
|
497 * } |
|
498 * } |
487 */ |
499 */ |
488 function _get_dropins() { |
500 function _get_dropins() { |
489 $dropins = array( |
501 $dropins = array( |
490 'advanced-cache.php' => array( __( 'Advanced caching plugin.' ), 'WP_CACHE' ), // WP_CACHE |
502 'advanced-cache.php' => array( __( 'Advanced caching plugin.' ), 'WP_CACHE' ), // WP_CACHE |
491 'db.php' => array( __( 'Custom database class.' ), true ), // Auto on load. |
503 'db.php' => array( __( 'Custom database class.' ), true ), // Auto on load. |
850 * @param string|string[] $plugins Single plugin or list of plugins to activate. |
862 * @param string|string[] $plugins Single plugin or list of plugins to activate. |
851 * @param string $redirect Redirect to page after successful activation. |
863 * @param string $redirect Redirect to page after successful activation. |
852 * @param bool $network_wide Whether to enable the plugin for all sites in the network. |
864 * @param bool $network_wide Whether to enable the plugin for all sites in the network. |
853 * Default false. |
865 * Default false. |
854 * @param bool $silent Prevent calling activation hooks. Default false. |
866 * @param bool $silent Prevent calling activation hooks. Default false. |
855 * @return bool|WP_Error True when finished or WP_Error if there were errors during a plugin activation. |
867 * @return true|WP_Error True when finished or WP_Error if there were errors during a plugin activation. |
856 */ |
868 */ |
857 function activate_plugins( $plugins, $redirect = '', $network_wide = false, $silent = false ) { |
869 function activate_plugins( $plugins, $redirect = '', $network_wide = false, $silent = false ) { |
858 if ( ! is_array( $plugins ) ) { |
870 if ( ! is_array( $plugins ) ) { |
859 $plugins = array( $plugins ); |
871 $plugins = array( $plugins ); |
860 } |
872 } |
1107 } |
1122 } |
1108 |
1123 |
1109 /** |
1124 /** |
1110 * Validates the plugin requirements for WordPress version and PHP version. |
1125 * Validates the plugin requirements for WordPress version and PHP version. |
1111 * |
1126 * |
1112 * Uses the information from `Requires at least` and `Requires PHP` headers |
1127 * Uses the information from `Requires at least`, `Requires PHP` and `Requires Plugins` headers |
1113 * defined in the plugin's main PHP file. |
1128 * defined in the plugin's main PHP file. |
1114 * |
1129 * |
1115 * @since 5.2.0 |
1130 * @since 5.2.0 |
1116 * @since 5.3.0 Added support for reading the headers from the plugin's |
1131 * @since 5.3.0 Added support for reading the headers from the plugin's |
1117 * main PHP file, with `readme.txt` as a fallback. |
1132 * main PHP file, with `readme.txt` as a fallback. |
1118 * @since 5.8.0 Removed support for using `readme.txt` as a fallback. |
1133 * @since 5.8.0 Removed support for using `readme.txt` as a fallback. |
|
1134 * @since 6.5.0 Added support for the 'Requires Plugins' header. |
1119 * |
1135 * |
1120 * @param string $plugin Path to the plugin file relative to the plugins directory. |
1136 * @param string $plugin Path to the plugin file relative to the plugins directory. |
1121 * @return true|WP_Error True if requirements are met, WP_Error on failure. |
1137 * @return true|WP_Error True if requirements are met, WP_Error on failure. |
1122 */ |
1138 */ |
1123 function validate_plugin_requirements( $plugin ) { |
1139 function validate_plugin_requirements( $plugin ) { |
1124 $plugin_headers = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin ); |
1140 $plugin_headers = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin ); |
1125 |
1141 |
1126 $requirements = array( |
1142 $requirements = array( |
1127 'requires' => ! empty( $plugin_headers['RequiresWP'] ) ? $plugin_headers['RequiresWP'] : '', |
1143 'requires' => ! empty( $plugin_headers['RequiresWP'] ) ? $plugin_headers['RequiresWP'] : '', |
1128 'requires_php' => ! empty( $plugin_headers['RequiresPHP'] ) ? $plugin_headers['RequiresPHP'] : '', |
1144 'requires_php' => ! empty( $plugin_headers['RequiresPHP'] ) ? $plugin_headers['RequiresPHP'] : '', |
|
1145 'requires_plugins' => ! empty( $plugin_headers['RequiresPlugins'] ) ? $plugin_headers['RequiresPlugins'] : '', |
1129 ); |
1146 ); |
1130 |
1147 |
1131 $compatible_wp = is_wp_version_compatible( $requirements['requires'] ); |
1148 $compatible_wp = is_wp_version_compatible( $requirements['requires'] ); |
1132 $compatible_php = is_php_version_compatible( $requirements['requires_php'] ); |
1149 $compatible_php = is_php_version_compatible( $requirements['requires_php'] ); |
1133 |
1150 |
1148 'plugin_wp_php_incompatible', |
1165 'plugin_wp_php_incompatible', |
1149 '<p>' . sprintf( |
1166 '<p>' . sprintf( |
1150 /* translators: 1: Current WordPress version, 2: Current PHP version, 3: Plugin name, 4: Required WordPress version, 5: Required PHP version. */ |
1167 /* translators: 1: Current WordPress version, 2: Current PHP version, 3: Plugin name, 4: Required WordPress version, 5: Required PHP version. */ |
1151 _x( '<strong>Error:</strong> Current versions of WordPress (%1$s) and PHP (%2$s) do not meet minimum requirements for %3$s. The plugin requires WordPress %4$s and PHP %5$s.', 'plugin' ), |
1168 _x( '<strong>Error:</strong> Current versions of WordPress (%1$s) and PHP (%2$s) do not meet minimum requirements for %3$s. The plugin requires WordPress %4$s and PHP %5$s.', 'plugin' ), |
1152 get_bloginfo( 'version' ), |
1169 get_bloginfo( 'version' ), |
1153 phpversion(), |
1170 PHP_VERSION, |
1154 $plugin_headers['Name'], |
1171 $plugin_headers['Name'], |
1155 $requirements['requires'], |
1172 $requirements['requires'], |
1156 $requirements['requires_php'] |
1173 $requirements['requires_php'] |
1157 ) . $php_update_message . '</p>' |
1174 ) . $php_update_message . '</p>' |
1158 ); |
1175 ); |
1160 return new WP_Error( |
1177 return new WP_Error( |
1161 'plugin_php_incompatible', |
1178 'plugin_php_incompatible', |
1162 '<p>' . sprintf( |
1179 '<p>' . sprintf( |
1163 /* translators: 1: Current PHP version, 2: Plugin name, 3: Required PHP version. */ |
1180 /* translators: 1: Current PHP version, 2: Plugin name, 3: Required PHP version. */ |
1164 _x( '<strong>Error:</strong> Current PHP version (%1$s) does not meet minimum requirements for %2$s. The plugin requires PHP %3$s.', 'plugin' ), |
1181 _x( '<strong>Error:</strong> Current PHP version (%1$s) does not meet minimum requirements for %2$s. The plugin requires PHP %3$s.', 'plugin' ), |
1165 phpversion(), |
1182 PHP_VERSION, |
1166 $plugin_headers['Name'], |
1183 $plugin_headers['Name'], |
1167 $requirements['requires_php'] |
1184 $requirements['requires_php'] |
1168 ) . $php_update_message . '</p>' |
1185 ) . $php_update_message . '</p>' |
1169 ); |
1186 ); |
1170 } elseif ( ! $compatible_wp ) { |
1187 } elseif ( ! $compatible_wp ) { |
1175 _x( '<strong>Error:</strong> Current WordPress version (%1$s) does not meet minimum requirements for %2$s. The plugin requires WordPress %3$s.', 'plugin' ), |
1192 _x( '<strong>Error:</strong> Current WordPress version (%1$s) does not meet minimum requirements for %2$s. The plugin requires WordPress %3$s.', 'plugin' ), |
1176 get_bloginfo( 'version' ), |
1193 get_bloginfo( 'version' ), |
1177 $plugin_headers['Name'], |
1194 $plugin_headers['Name'], |
1178 $requirements['requires'] |
1195 $requirements['requires'] |
1179 ) . '</p>' |
1196 ) . '</p>' |
|
1197 ); |
|
1198 } |
|
1199 |
|
1200 WP_Plugin_Dependencies::initialize(); |
|
1201 |
|
1202 if ( WP_Plugin_Dependencies::has_unmet_dependencies( $plugin ) ) { |
|
1203 $dependency_names = WP_Plugin_Dependencies::get_dependency_names( $plugin ); |
|
1204 $unmet_dependencies = array(); |
|
1205 $unmet_dependency_names = array(); |
|
1206 |
|
1207 foreach ( $dependency_names as $dependency => $dependency_name ) { |
|
1208 $dependency_file = WP_Plugin_Dependencies::get_dependency_filepath( $dependency ); |
|
1209 |
|
1210 if ( false === $dependency_file ) { |
|
1211 $unmet_dependencies['not_installed'][ $dependency ] = $dependency_name; |
|
1212 $unmet_dependency_names[] = $dependency_name; |
|
1213 } elseif ( is_plugin_inactive( $dependency_file ) ) { |
|
1214 $unmet_dependencies['inactive'][ $dependency ] = $dependency_name; |
|
1215 $unmet_dependency_names[] = $dependency_name; |
|
1216 } |
|
1217 } |
|
1218 |
|
1219 $error_message = sprintf( |
|
1220 /* translators: 1: Plugin name, 2: Number of plugins, 3: A comma-separated list of plugin names. */ |
|
1221 _n( |
|
1222 '<strong>Error:</strong> %1$s requires %2$d plugin to be installed and activated: %3$s.', |
|
1223 '<strong>Error:</strong> %1$s requires %2$d plugins to be installed and activated: %3$s.', |
|
1224 count( $unmet_dependency_names ) |
|
1225 ), |
|
1226 $plugin_headers['Name'], |
|
1227 count( $unmet_dependency_names ), |
|
1228 implode( wp_get_list_item_separator(), $unmet_dependency_names ) |
|
1229 ); |
|
1230 |
|
1231 if ( is_multisite() ) { |
|
1232 if ( current_user_can( 'manage_network_plugins' ) ) { |
|
1233 $error_message .= ' ' . sprintf( |
|
1234 /* translators: %s: Link to the plugins page. */ |
|
1235 __( '<a href="%s">Manage plugins</a>.' ), |
|
1236 esc_url( network_admin_url( 'plugins.php' ) ) |
|
1237 ); |
|
1238 } else { |
|
1239 $error_message .= ' ' . __( 'Please contact your network administrator.' ); |
|
1240 } |
|
1241 } else { |
|
1242 $error_message .= ' ' . sprintf( |
|
1243 /* translators: %s: Link to the plugins page. */ |
|
1244 __( '<a href="%s">Manage plugins</a>.' ), |
|
1245 esc_url( admin_url( 'plugins.php' ) ) |
|
1246 ); |
|
1247 } |
|
1248 |
|
1249 return new WP_Error( |
|
1250 'plugin_missing_dependencies', |
|
1251 "<p>{$error_message}</p>", |
|
1252 $unmet_dependencies |
1180 ); |
1253 ); |
1181 } |
1254 } |
1182 |
1255 |
1183 return true; |
1256 return true; |
1184 } |
1257 } |
1452 $position = max( $position, 0 ); |
1525 $position = max( $position, 0 ); |
1453 if ( 0 === $position ) { |
1526 if ( 0 === $position ) { |
1454 // For negative or `0` positions, prepend the submenu. |
1527 // For negative or `0` positions, prepend the submenu. |
1455 array_unshift( $submenu[ $parent_slug ], $new_sub_menu ); |
1528 array_unshift( $submenu[ $parent_slug ], $new_sub_menu ); |
1456 } else { |
1529 } else { |
|
1530 $position = absint( $position ); |
1457 // Grab all of the items before the insertion point. |
1531 // Grab all of the items before the insertion point. |
1458 $before_items = array_slice( $submenu[ $parent_slug ], 0, $position, true ); |
1532 $before_items = array_slice( $submenu[ $parent_slug ], 0, $position, true ); |
1459 // Grab all of the items after the insertion point. |
1533 // Grab all of the items after the insertion point. |
1460 $after_items = array_slice( $submenu[ $parent_slug ], $position, null, true ); |
1534 $after_items = array_slice( $submenu[ $parent_slug ], $position, null, true ); |
1461 // Add the new item. |
1535 // Add the new item. |
2564 $GLOBALS['wp_version'], |
2647 $GLOBALS['wp_version'], |
2565 $plugin['version_compatible'] |
2648 $plugin['version_compatible'] |
2566 ); |
2649 ); |
2567 } |
2650 } |
2568 |
2651 |
2569 printf( |
2652 $message = sprintf( |
2570 '<div class="notice notice-warning"><p><strong>%s</strong><br>%s</p><p><a href="%s">%s</a></p></div>', |
2653 '<strong>%s</strong><br>%s</p><p><a href="%s">%s</a>', |
2571 sprintf( |
2654 sprintf( |
2572 /* translators: %s: Name of deactivated plugin. */ |
2655 /* translators: %s: Name of deactivated plugin. */ |
2573 __( '%s plugin deactivated during WordPress upgrade.' ), |
2656 __( '%s plugin deactivated during WordPress upgrade.' ), |
2574 $plugin['plugin_name'] |
2657 $plugin['plugin_name'] |
2575 ), |
2658 ), |
2576 $explanation, |
2659 $explanation, |
2577 esc_url( admin_url( 'plugins.php?plugin_status=inactive' ) ), |
2660 esc_url( admin_url( 'plugins.php?plugin_status=inactive' ) ), |
2578 __( 'Go to the Plugins screen' ) |
2661 __( 'Go to the Plugins screen' ) |
2579 ); |
2662 ); |
|
2663 wp_admin_notice( $message, array( 'type' => 'warning' ) ); |
2580 } |
2664 } |
2581 |
2665 |
2582 // Empty the options. |
2666 // Empty the options. |
2583 update_option( 'wp_force_deactivated_plugins', array() ); |
2667 update_option( 'wp_force_deactivated_plugins', array() ); |
2584 if ( is_multisite() ) { |
2668 if ( is_multisite() ) { |