run:R W Run
7.85 KB
2026-03-11 16:18:51
R W Run
3.54 KB
2026-03-11 16:18:51
R W Run
148.33 KB
2026-03-11 16:18:51
R W Run
11.45 KB
2026-03-11 16:18:51
R W Run
3.58 KB
2026-03-11 16:18:51
R W Run
2.53 KB
2026-03-11 16:18:51
R W Run
2.6 KB
2026-03-11 16:18:51
R W Run
6.59 KB
2026-03-11 16:18:51
R W Run
14.83 KB
2026-03-11 16:18:51
R W Run
21.18 KB
2026-03-11 16:18:51
R W Run
48.13 KB
2026-03-11 16:18:51
R W Run
4.07 KB
2026-03-11 16:18:51
R W Run
5.3 KB
2026-03-11 16:18:51
R W Run
8.28 KB
2026-03-11 16:18:51
R W Run
26.73 KB
2026-03-11 16:18:51
R W Run
2.8 KB
2026-03-11 16:18:51
R W Run
15.2 KB
2026-03-11 16:18:51
R W Run
192.08 KB
2026-03-11 16:18:51
R W Run
11.77 KB
2026-03-11 16:18:51
R W Run
3.2 KB
2026-03-11 16:18:51
R W Run
22.89 KB
2026-03-11 16:18:51
R W Run
12.77 KB
2026-03-11 16:18:51
R W Run
4.08 KB
2026-03-11 16:18:51
R W Run
26.27 KB
2026-03-11 16:18:51
R W Run
4.97 KB
2026-03-11 16:18:51
R W Run
5.57 KB
2026-03-11 16:18:51
R W Run
13.93 KB
2026-03-11 16:18:51
R W Run
4.09 KB
2026-03-11 16:18:51
R W Run
6.79 KB
2026-03-11 16:18:51
R W Run
60.45 KB
2026-03-11 16:18:51
R W Run
32.4 KB
2026-03-11 16:18:51
R W Run
18.24 KB
2026-03-11 16:18:51
R W Run
66.01 KB
2026-03-11 16:18:51
R W Run
23.84 KB
2026-03-11 16:18:51
R W Run
17.72 KB
2026-03-11 16:18:51
R W Run
22.71 KB
2026-03-11 16:18:51
R W Run
18.05 KB
2026-03-11 16:18:51
R W Run
22.76 KB
2026-03-11 16:18:51
R W Run
7.34 KB
2026-03-11 16:18:51
R W Run
4.51 KB
2026-03-11 16:18:51
R W Run
9.02 KB
2026-03-11 16:18:51
R W Run
1.46 KB
2026-03-11 16:18:51
R W Run
51.76 KB
2026-03-11 16:18:51
R W Run
25.29 KB
2026-03-11 16:18:51
R W Run
21.61 KB
2026-03-11 16:18:51
R W Run
27.77 KB
2026-03-11 16:18:51
R W Run
15.35 KB
2026-03-11 16:18:51
R W Run
24.54 KB
2026-03-11 16:18:51
R W Run
56.44 KB
2026-03-11 16:18:51
R W Run
1.42 KB
2026-03-11 16:18:51
R W Run
63.66 KB
2026-03-11 16:18:51
R W Run
31.9 KB
2026-03-11 16:18:51
R W Run
14.44 KB
2026-03-11 16:18:51
R W Run
36.47 KB
2026-03-11 16:18:51
R W Run
14 KB
2026-03-11 16:18:51
R W Run
121.89 KB
2026-03-11 16:18:51
R W Run
6.26 KB
2026-03-11 16:18:51
R W Run
20.73 KB
2026-03-11 16:18:51
R W Run
15.23 KB
2026-03-11 16:18:51
R W Run
10.14 KB
2026-03-11 16:18:51
R W Run
6.94 KB
2026-03-11 16:18:51
R W Run
1.44 KB
2026-03-11 16:18:51
R W Run
46.85 KB
2026-03-11 16:18:51
R W Run
18.61 KB
2026-03-11 16:18:51
R W Run
6.08 KB
2026-03-11 16:18:51
R W Run
20.06 KB
2026-03-11 16:18:51
R W Run
5.73 KB
2026-03-11 16:18:51
R W Run
68.18 KB
2026-03-11 16:18:51
R W Run
40.8 KB
2026-03-11 16:18:51
R W Run
1.44 KB
2026-03-11 16:18:51
R W Run
25.26 KB
2026-03-11 16:18:51
R W Run
95.94 KB
2026-03-11 16:18:51
R W Run
43.12 KB
2026-03-11 16:18:51
R W Run
41.73 KB
2026-03-11 16:18:51
R W Run
6.46 KB
2026-03-11 16:18:51
R W Run
3.71 KB
2026-03-11 16:18:51
R W Run
116.31 KB
2026-03-11 16:18:51
R W Run
9.39 KB
2026-03-11 16:18:51
R W Run
64.34 KB
2026-03-11 16:18:51
R W Run
44.73 KB
2026-03-11 16:18:51
R W Run
1.27 KB
2026-03-11 16:18:51
R W Run
3.68 KB
2026-03-11 16:18:51
R W Run
33.53 KB
2026-03-11 16:18:51
R W Run
48.84 KB
2026-03-11 16:18:51
R W Run
26.35 KB
2026-03-11 16:18:51
R W Run
1.12 KB
2026-03-11 16:18:51
R W Run
4.19 KB
2026-03-11 16:18:51
R W Run
38.19 KB
2026-03-11 16:18:51
R W Run
91.33 KB
2026-03-11 16:18:51
R W Run
80.39 KB
2026-03-11 16:18:51
R W Run
32.67 KB
2026-03-11 16:18:51
R W Run
16.18 KB
2026-03-11 16:18:51
R W Run
44.46 KB
2026-03-11 16:18:51
R W Run
6.23 KB
2026-03-11 16:18:51
R W Run
8.23 KB
2026-03-11 16:18:51
R W Run
96.96 KB
2026-03-11 16:18:51
R W Run
6.83 KB
2026-03-11 16:18:51
R W Run
46.62 KB
2026-03-11 16:18:51
R W Run
10.82 KB
2026-03-11 16:18:51
R W Run
68.86 KB
2026-03-11 16:18:51
R W Run
33.63 KB
2026-03-11 16:18:51
R W Run
113.3 KB
2026-03-11 16:18:51
R W Run
22.98 KB
2026-03-11 16:18:51
R W Run
10.66 KB
2026-03-11 16:18:51
R W Run
error_log
📄plugin.php
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 * 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 */
74function 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 */
140function _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
224/**
225 * Gets a list of a plugin's files.
226 *
227 * @since 2.8.0
228 *
229 * @param string $plugin Path to the plugin file relative to the plugins directory.
230 * @return string[] Array of file names relative to the plugin root.
231 */
232function get_plugin_files( $plugin ) {
233 $plugin_file = WP_PLUGIN_DIR . '/' . $plugin;
234 $dir = dirname( $plugin_file );
235
236 $plugin_files = array( plugin_basename( $plugin_file ) );
237
238 if ( is_dir( $dir ) && WP_PLUGIN_DIR !== $dir ) {
239
240 /**
241 * Filters the array of excluded directories and files while scanning the folder.
242 *
243 * @since 4.9.0
244 *
245 * @param string[] $exclusions Array of excluded directories and files.
246 */
247 $exclusions = (array) apply_filters( 'plugin_files_exclusions', array( 'CVS', 'node_modules', 'vendor', 'bower_components' ) );
248
249 $list_files = list_files( $dir, 100, $exclusions );
250 $list_files = array_map( 'plugin_basename', $list_files );
251
252 $plugin_files = array_merge( $plugin_files, $list_files );
253 $plugin_files = array_values( array_unique( $plugin_files ) );
254 }
255
256 return $plugin_files;
257}
258
259/**
260 * Checks the plugins directory and retrieve all plugin files with plugin data.
261 *
262 * WordPress only supports plugin files in the base plugins directory
263 * (wp-content/plugins) and in one directory above the plugins directory
264 * (wp-content/plugins/my-plugin). The file it looks for has the plugin data
265 * and must be found in those two locations. It is recommended to keep your
266 * plugin files in their own directories.
267 *
268 * The file with the plugin data is the file that will be included and therefore
269 * needs to have the main execution for the plugin. This does not mean
270 * everything must be contained in the file and it is recommended that the file
271 * be split for maintainability. Keep everything in one file for extreme
272 * optimization purposes.
273 *
274 * @since 1.5.0
275 *
276 * @param string $plugin_folder Optional. Relative path to single plugin folder.
277 * @return array[] Array of arrays of plugin data, keyed by plugin file name. See get_plugin_data().
278 */
279function get_plugins( $plugin_folder = '' ) {
280
281 $cache_plugins = wp_cache_get( 'plugins', 'plugins' );
282 if ( ! $cache_plugins ) {
283 $cache_plugins = array();
284 }
285
286 if ( isset( $cache_plugins[ $plugin_folder ] ) ) {
287 return $cache_plugins[ $plugin_folder ];
288 }
289
290 $wp_plugins = array();
291 $plugin_root = WP_PLUGIN_DIR;
292 if ( ! empty( $plugin_folder ) ) {
293 $plugin_root .= $plugin_folder;
294 }
295
296 // Files in wp-content/plugins directory.
297 $plugins_dir = @opendir( $plugin_root );
298 $plugin_files = array();
299
300 if ( $plugins_dir ) {
301 while ( ( $file = readdir( $plugins_dir ) ) !== false ) {
302 if ( str_starts_with( $file, '.' ) ) {
303 continue;
304 }
305
306 if ( is_dir( $plugin_root . '/' . $file ) ) {
307 $plugins_subdir = @opendir( $plugin_root . '/' . $file );
308
309 if ( $plugins_subdir ) {
310 while ( ( $subfile = readdir( $plugins_subdir ) ) !== false ) {
311 if ( str_starts_with( $subfile, '.' ) ) {
312 continue;
313 }
314
315 if ( str_ends_with( $subfile, '.php' ) ) {
316 $plugin_files[] = "$file/$subfile";
317 }
318 }
319
320 closedir( $plugins_subdir );
321 }
322 } elseif ( str_ends_with( $file, '.php' ) ) {
323 $plugin_files[] = $file;
324 }
325 }
326
327 closedir( $plugins_dir );
328 }
329
330 if ( empty( $plugin_files ) ) {
331 return $wp_plugins;
332 }
333
334 foreach ( $plugin_files as $plugin_file ) {
335 if ( ! is_readable( "$plugin_root/$plugin_file" ) ) {
336 continue;
337 }
338
339 // Do not apply markup/translate as it will be cached.
340 $plugin_data = get_plugin_data( "$plugin_root/$plugin_file", false, false );
341
342 if ( empty( $plugin_data['Name'] ) ) {
343 continue;
344 }
345
346 $wp_plugins[ plugin_basename( $plugin_file ) ] = $plugin_data;
347 }
348
349 uasort( $wp_plugins, '_sort_uname_callback' );
350
351 $cache_plugins[ $plugin_folder ] = $wp_plugins;
352 wp_cache_set( 'plugins', $cache_plugins, 'plugins' );
353
354 return $wp_plugins;
355}
356
357/**
358 * Checks the mu-plugins directory and retrieve all mu-plugin files with any plugin data.
359 *
360 * WordPress only includes mu-plugin files in the base mu-plugins directory (wp-content/mu-plugins).
361 *
362 * @since 3.0.0
363 * @return array[] Array of arrays of mu-plugin data, keyed by plugin file name. See get_plugin_data().
364 */
365function get_mu_plugins() {
366 $wp_plugins = array();
367 $plugin_files = array();
368
369 if ( ! is_dir( WPMU_PLUGIN_DIR ) ) {
370 return $wp_plugins;
371 }
372
373 // Files in wp-content/mu-plugins directory.
374 $plugins_dir = @opendir( WPMU_PLUGIN_DIR );
375 if ( $plugins_dir ) {
376 while ( ( $file = readdir( $plugins_dir ) ) !== false ) {
377 if ( str_ends_with( $file, '.php' ) ) {
378 $plugin_files[] = $file;
379 }
380 }
381 } else {
382 return $wp_plugins;
383 }
384
385 closedir( $plugins_dir );
386
387 if ( empty( $plugin_files ) ) {
388 return $wp_plugins;
389 }
390
391 foreach ( $plugin_files as $plugin_file ) {
392 if ( ! is_readable( WPMU_PLUGIN_DIR . "/$plugin_file" ) ) {
393 continue;
394 }
395
396 // Do not apply markup/translate as it will be cached.
397 $plugin_data = get_plugin_data( WPMU_PLUGIN_DIR . "/$plugin_file", false, false );
398
399 if ( empty( $plugin_data['Name'] ) ) {
400 $plugin_data['Name'] = $plugin_file;
401 }
402
403 $wp_plugins[ $plugin_file ] = $plugin_data;
404 }
405
406 if ( isset( $wp_plugins['index.php'] ) && filesize( WPMU_PLUGIN_DIR . '/index.php' ) <= 30 ) {
407 // Silence is golden.
408 unset( $wp_plugins['index.php'] );
409 }
410
411 uasort( $wp_plugins, '_sort_uname_callback' );
412
413 return $wp_plugins;
414}
415
416/**
417 * Declares a callback to sort array by a 'Name' key.
418 *
419 * @since 3.1.0
420 *
421 * @access private
422 *
423 * @param array $a array with 'Name' key.
424 * @param array $b array with 'Name' key.
425 * @return int Return 0 or 1 based on two string comparison.
426 */
427function _sort_uname_callback( $a, $b ) {
428 return strnatcasecmp( $a['Name'], $b['Name'] );
429}
430
431/**
432 * Checks the wp-content directory and retrieve all drop-ins with any plugin data.
433 *
434 * @since 3.0.0
435 * @return array[] Array of arrays of dropin plugin data, keyed by plugin file name. See get_plugin_data().
436 */
437function get_dropins() {
438 $dropins = array();
439 $plugin_files = array();
440
441 $_dropins = _get_dropins();
442
443 // Files in wp-content directory.
444 $plugins_dir = @opendir( WP_CONTENT_DIR );
445 if ( $plugins_dir ) {
446 while ( ( $file = readdir( $plugins_dir ) ) !== false ) {
447 if ( isset( $_dropins[ $file ] ) ) {
448 $plugin_files[] = $file;
449 }
450 }
451 } else {
452 return $dropins;
453 }
454
455 closedir( $plugins_dir );
456
457 if ( empty( $plugin_files ) ) {
458 return $dropins;
459 }
460
461 foreach ( $plugin_files as $plugin_file ) {
462 if ( ! is_readable( WP_CONTENT_DIR . "/$plugin_file" ) ) {
463 continue;
464 }
465
466 // Do not apply markup/translate as it will be cached.
467 $plugin_data = get_plugin_data( WP_CONTENT_DIR . "/$plugin_file", false, false );
468
469 if ( empty( $plugin_data['Name'] ) ) {
470 $plugin_data['Name'] = $plugin_file;
471 }
472
473 $dropins[ $plugin_file ] = $plugin_data;
474 }
475
476 uksort( $dropins, 'strnatcasecmp' );
477
478 return $dropins;
479}
480
481/**
482 * Returns drop-in plugins that WordPress uses.
483 *
484 * Includes Multisite drop-ins only when is_multisite()
485 *
486 * @since 3.0.0
487 *
488 * @return array[] {
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 * }
499 */
500function _get_dropins() {
501 $dropins = array(
502 'advanced-cache.php' => array( __( 'Advanced caching plugin.' ), 'WP_CACHE' ), // WP_CACHE
503 'db.php' => array( __( 'Custom database class.' ), true ), // Auto on load.
504 'db-error.php' => array( __( 'Custom database error message.' ), true ), // Auto on error.
505 'install.php' => array( __( 'Custom installation script.' ), true ), // Auto on installation.
506 'maintenance.php' => array( __( 'Custom maintenance message.' ), true ), // Auto on maintenance.
507 'object-cache.php' => array( __( 'External object cache.' ), true ), // Auto on load.
508 'php-error.php' => array( __( 'Custom PHP error message.' ), true ), // Auto on error.
509 'fatal-error-handler.php' => array( __( 'Custom PHP fatal error handler.' ), true ), // Auto on error.
510 );
511
512 if ( is_multisite() ) {
513 $dropins['sunrise.php'] = array( __( 'Executed before Multisite is loaded.' ), 'SUNRISE' ); // SUNRISE
514 $dropins['blog-deleted.php'] = array( __( 'Custom site deleted message.' ), true ); // Auto on deleted blog.
515 $dropins['blog-inactive.php'] = array( __( 'Custom site inactive message.' ), true ); // Auto on inactive blog.
516 $dropins['blog-suspended.php'] = array( __( 'Custom site suspended message.' ), true ); // Auto on archived or spammed blog.
517 }
518
519 return $dropins;
520}
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 */
539function 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 */
559function 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 */
580function 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 */
605function 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
613/**
614 * Attempts activation of plugin in a "sandbox" and redirects on success.
615 *
616 * A plugin that is already activated will not attempt to be activated again.
617 *
618 * The way it works is by setting the redirection to the error before trying to
619 * include the plugin file. If the plugin fails, then the redirection will not
620 * be overwritten with the success message. Also, the options will not be
621 * updated and the activation hook will not be called on plugin error.
622 *
623 * It should be noted that in no way the below code will actually prevent errors
624 * within the file. The code should not be used elsewhere to replicate the
625 * "sandbox", which uses redirection to work.
626 * {@source 13 1}
627 *
628 * If any errors are found or text is outputted, then it will be captured to
629 * ensure that the success redirection will update the error redirection.
630 *
631 * @since 2.5.0
632 * @since 5.2.0 Test for WordPress version and PHP version compatibility.
633 *
634 * @param string $plugin Path to the plugin file relative to the plugins directory.
635 * @param string $redirect Optional. URL to redirect to.
636 * @param bool $network_wide Optional. Whether to enable the plugin for all sites in the network
637 * or just the current site. Multisite only. Default false.
638 * @param bool $silent Optional. Whether to prevent calling activation hooks. Default false.
639 * @return null|WP_Error Null on success, WP_Error on invalid file.
640 */
641function activate_plugin( $plugin, $redirect = '', $network_wide = false, $silent = false ) {
642 $plugin = plugin_basename( trim( $plugin ) );
643
644 if ( is_multisite() && ( $network_wide || is_network_only_plugin( $plugin ) ) ) {
645 $network_wide = true;
646 $current = get_site_option( 'active_sitewide_plugins', array() );
647 $_GET['networkwide'] = 1; // Back compat for plugins looking for this value.
648 } else {
649 $current = get_option( 'active_plugins', array() );
650 }
651
652 $valid = validate_plugin( $plugin );
653 if ( is_wp_error( $valid ) ) {
654 return $valid;
655 }
656
657 $requirements = validate_plugin_requirements( $plugin );
658 if ( is_wp_error( $requirements ) ) {
659 return $requirements;
660 }
661
662 if ( $network_wide && ! isset( $current[ $plugin ] )
663 || ! $network_wide && ! in_array( $plugin, $current, true )
664 ) {
665 if ( ! empty( $redirect ) ) {
666 // We'll override this later if the plugin can be included without fatal error.
667 wp_redirect( add_query_arg( '_error_nonce', wp_create_nonce( 'plugin-activation-error_' . $plugin ), $redirect ) );
668 }
669
670 ob_start();
671
672 // Load the plugin to test whether it throws any errors.
673 plugin_sandbox_scrape( $plugin );
674
675 if ( ! $silent ) {
676 /**
677 * Fires before a plugin is activated.
678 *
679 * If a plugin is silently activated (such as during an update),
680 * this hook does not fire.
681 *
682 * @since 2.9.0
683 *
684 * @param string $plugin Path to the plugin file relative to the plugins directory.
685 * @param bool $network_wide Whether to enable the plugin for all sites in the network
686 * or just the current site. Multisite only. Default false.
687 */
688 do_action( 'activate_plugin', $plugin, $network_wide );
689
690 /**
691 * Fires as a specific plugin is being activated.
692 *
693 * This hook is the "activation" hook used internally by register_activation_hook().
694 * The dynamic portion of the hook name, `$plugin`, refers to the plugin basename.
695 *
696 * If a plugin is silently activated (such as during an update), this hook does not fire.
697 *
698 * @since 2.0.0
699 *
700 * @param bool $network_wide Whether to enable the plugin for all sites in the network
701 * or just the current site. Multisite only. Default false.
702 */
703 do_action( "activate_{$plugin}", $network_wide );
704 }
705
706 if ( $network_wide ) {
707 $current = get_site_option( 'active_sitewide_plugins', array() );
708 $current[ $plugin ] = time();
709 update_site_option( 'active_sitewide_plugins', $current );
710 } else {
711 $current = get_option( 'active_plugins', array() );
712 $current[] = $plugin;
713 sort( $current );
714 update_option( 'active_plugins', $current );
715 }
716
717 if ( ! $silent ) {
718 /**
719 * Fires after a plugin has been activated.
720 *
721 * If a plugin is silently activated (such as during an update),
722 * this hook does not fire.
723 *
724 * @since 2.9.0
725 *
726 * @param string $plugin Path to the plugin file relative to the plugins directory.
727 * @param bool $network_wide Whether to enable the plugin for all sites in the network
728 * or just the current site. Multisite only. Default false.
729 */
730 do_action( 'activated_plugin', $plugin, $network_wide );
731 }
732
733 if ( ob_get_length() > 0 ) {
734 $output = ob_get_clean();
735 return new WP_Error( 'unexpected_output', __( 'The plugin generated unexpected output.' ), $output );
736 }
737
738 ob_end_clean();
739 }
740
741 return null;
742}
743
744/**
745 * Deactivates a single plugin or multiple plugins.
746 *
747 * The deactivation hook is disabled by the plugin upgrader by using the $silent
748 * parameter.
749 *
750 * @since 2.5.0
751 *
752 * @param string|string[] $plugins Single plugin or list of plugins to deactivate.
753 * @param bool $silent Prevent calling deactivation hooks. Default false.
754 * @param bool|null $network_wide Whether to deactivate the plugin for all sites in the network.
755 * A value of null will deactivate plugins for both the network
756 * and the current site. Multisite only. Default null.
757 */
758function deactivate_plugins( $plugins, $silent = false, $network_wide = null ) {
759 if ( is_multisite() ) {
760 $network_current = get_site_option( 'active_sitewide_plugins', array() );
761 }
762 $current = get_option( 'active_plugins', array() );
763 $do_blog = false;
764 $do_network = false;
765
766 foreach ( (array) $plugins as $plugin ) {
767 $plugin = plugin_basename( trim( $plugin ) );
768 if ( ! is_plugin_active( $plugin ) ) {
769 continue;
770 }
771
772 $network_deactivating = ( false !== $network_wide ) && is_plugin_active_for_network( $plugin );
773
774 if ( ! $silent ) {
775 /**
776 * Fires before a plugin is deactivated.
777 *
778 * If a plugin is silently deactivated (such as during an update),
779 * this hook does not fire.
780 *
781 * @since 2.9.0
782 *
783 * @param string $plugin Path to the plugin file relative to the plugins directory.
784 * @param bool $network_deactivating Whether the plugin is deactivated for all sites in the network
785 * or just the current site. Multisite only. Default false.
786 */
787 do_action( 'deactivate_plugin', $plugin, $network_deactivating );
788 }
789
790 if ( false !== $network_wide ) {
791 if ( is_plugin_active_for_network( $plugin ) ) {
792 $do_network = true;
793 unset( $network_current[ $plugin ] );
794 } elseif ( $network_wide ) {
795 continue;
796 }
797 }
798
799 if ( true !== $network_wide ) {
800 $key = array_search( $plugin, $current, true );
801 if ( false !== $key ) {
802 $do_blog = true;
803 unset( $current[ $key ] );
804 }
805 }
806
807 if ( $do_blog && wp_is_recovery_mode() ) {
808 list( $extension ) = explode( '/', $plugin );
809 wp_paused_plugins()->delete( $extension );
810 }
811
812 if ( ! $silent ) {
813 /**
814 * Fires as a specific plugin is being deactivated.
815 *
816 * This hook is the "deactivation" hook used internally by register_deactivation_hook().
817 * The dynamic portion of the hook name, `$plugin`, refers to the plugin basename.
818 *
819 * If a plugin is silently deactivated (such as during an update), this hook does not fire.
820 *
821 * @since 2.0.0
822 *
823 * @param bool $network_deactivating Whether the plugin is deactivated for all sites in the network
824 * or just the current site. Multisite only. Default false.
825 */
826 do_action( "deactivate_{$plugin}", $network_deactivating );
827
828 /**
829 * Fires after a plugin is deactivated.
830 *
831 * If a plugin is silently deactivated (such as during an update),
832 * this hook does not fire.
833 *
834 * @since 2.9.0
835 *
836 * @param string $plugin Path to the plugin file relative to the plugins directory.
837 * @param bool $network_deactivating Whether the plugin is deactivated for all sites in the network
838 * or just the current site. Multisite only. Default false.
839 */
840 do_action( 'deactivated_plugin', $plugin, $network_deactivating );
841 }
842 }
843
844 if ( $do_blog ) {
845 update_option( 'active_plugins', $current );
846 }
847 if ( $do_network ) {
848 update_site_option( 'active_sitewide_plugins', $network_current );
849 }
850}
851
852/**
853 * Activates multiple plugins.
854 *
855 * When WP_Error is returned, it does not mean that one of the plugins had
856 * errors. It means that one or more of the plugin file paths were invalid.
857 *
858 * The execution will be halted as soon as one of the plugins has an error.
859 *
860 * @since 2.6.0
861 *
862 * @param string|string[] $plugins Single plugin or list of plugins to activate.
863 * @param string $redirect Redirect to page after successful activation.
864 * @param bool $network_wide Whether to enable the plugin for all sites in the network.
865 * Default false.
866 * @param bool $silent Prevent calling activation hooks. Default false.
867 * @return true|WP_Error True when finished or WP_Error if there were errors during a plugin activation.
868 */
869function activate_plugins( $plugins, $redirect = '', $network_wide = false, $silent = false ) {
870 if ( ! is_array( $plugins ) ) {
871 $plugins = array( $plugins );
872 }
873
874 $errors = array();
875 foreach ( $plugins as $plugin ) {
876 if ( ! empty( $redirect ) ) {
877 $redirect = add_query_arg( 'plugin', $plugin, $redirect );
878 }
879 $result = activate_plugin( $plugin, $redirect, $network_wide, $silent );
880 if ( is_wp_error( $result ) ) {
881 $errors[ $plugin ] = $result;
882 }
883 }
884
885 if ( ! empty( $errors ) ) {
886 return new WP_Error( 'plugins_invalid', __( 'One of the plugins is invalid.' ), $errors );
887 }
888
889 return true;
890}
891
892/**
893 * Removes directory and files of a plugin for a list of plugins.
894 *
895 * @since 2.6.0
896 *
897 * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
898 *
899 * @param string[] $plugins List of plugin paths to delete, relative to the plugins directory.
900 * @param string $deprecated Not used.
901 * @return bool|null|WP_Error True on success, false if `$plugins` is empty, `WP_Error` on failure.
902 * `null` if filesystem credentials are required to proceed.
903 */
904function delete_plugins( $plugins, $deprecated = '' ) {
905 global $wp_filesystem;
906
907 if ( empty( $plugins ) ) {
908 return false;
909 }
910
911 $checked = array();
912 foreach ( $plugins as $plugin ) {
913 $checked[] = 'checked[]=' . $plugin;
914 }
915
916 $url = wp_nonce_url( 'plugins.php?action=delete-selected&verify-delete=1&' . implode( '&', $checked ), 'bulk-plugins' );
917
918 ob_start();
919 $credentials = request_filesystem_credentials( $url );
920 $data = ob_get_clean();
921
922 if ( false === $credentials ) {
923 if ( ! empty( $data ) ) {
924 require_once ABSPATH . 'wp-admin/admin-header.php';
925 echo $data;
926 require_once ABSPATH . 'wp-admin/admin-footer.php';
927 exit;
928 }
929 return;
930 }
931
932 if ( ! WP_Filesystem( $credentials ) ) {
933 ob_start();
934 // Failed to connect. Error and request again.
935 request_filesystem_credentials( $url, '', true );
936 $data = ob_get_clean();
937
938 if ( ! empty( $data ) ) {
939 require_once ABSPATH . 'wp-admin/admin-header.php';
940 echo $data;
941 require_once ABSPATH . 'wp-admin/admin-footer.php';
942 exit;
943 }
944 return;
945 }
946
947 if ( ! is_object( $wp_filesystem ) ) {
948 return new WP_Error( 'fs_unavailable', __( 'Could not access filesystem.' ) );
949 }
950
951 if ( is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
952 return new WP_Error( 'fs_error', __( 'Filesystem error.' ), $wp_filesystem->errors );
953 }
954
955 // Get the base plugin folder.
956 $plugins_dir = $wp_filesystem->wp_plugins_dir();
957 if ( empty( $plugins_dir ) ) {
958 return new WP_Error( 'fs_no_plugins_dir', __( 'Unable to locate WordPress plugin directory.' ) );
959 }
960
961 $plugins_dir = trailingslashit( $plugins_dir );
962
963 $plugin_translations = wp_get_installed_translations( 'plugins' );
964
965 $errors = array();
966
967 foreach ( $plugins as $plugin_file ) {
968 // Run Uninstall hook.
969 if ( is_uninstallable_plugin( $plugin_file ) ) {
970 uninstall_plugin( $plugin_file );
971 }
972
973 /**
974 * Fires immediately before a plugin deletion attempt.
975 *
976 * @since 4.4.0
977 *
978 * @param string $plugin_file Path to the plugin file relative to the plugins directory.
979 */
980 do_action( 'delete_plugin', $plugin_file );
981
982 $this_plugin_dir = trailingslashit( dirname( $plugins_dir . $plugin_file ) );
983
984 /*
985 * If plugin is in its own directory, recursively delete the directory.
986 * Base check on if plugin includes directory separator AND that it's not the root plugin folder.
987 */
988 if ( strpos( $plugin_file, '/' ) && $this_plugin_dir !== $plugins_dir ) {
989 $deleted = $wp_filesystem->delete( $this_plugin_dir, true );
990 } else {
991 $deleted = $wp_filesystem->delete( $plugins_dir . $plugin_file );
992 }
993
994 /**
995 * Fires immediately after a plugin deletion attempt.
996 *
997 * @since 4.4.0
998 *
999 * @param string $plugin_file Path to the plugin file relative to the plugins directory.
1000 * @param bool $deleted Whether the plugin deletion was successful.
1001 */
1002 do_action( 'deleted_plugin', $plugin_file, $deleted );
1003
1004 if ( ! $deleted ) {
1005 $errors[] = $plugin_file;
1006 continue;
1007 }
1008
1009 $plugin_slug = dirname( $plugin_file );
1010
1011 if ( 'hello.php' === $plugin_file ) {
1012 $plugin_slug = 'hello-dolly';
1013 }
1014
1015 // Remove language files, silently.
1016 if ( '.' !== $plugin_slug && ! empty( $plugin_translations[ $plugin_slug ] ) ) {
1017 $translations = $plugin_translations[ $plugin_slug ];
1018
1019 foreach ( $translations as $translation => $data ) {
1020 $wp_filesystem->delete( WP_LANG_DIR . '/plugins/' . $plugin_slug . '-' . $translation . '.po' );
1021 $wp_filesystem->delete( WP_LANG_DIR . '/plugins/' . $plugin_slug . '-' . $translation . '.mo' );
1022 $wp_filesystem->delete( WP_LANG_DIR . '/plugins/' . $plugin_slug . '-' . $translation . '.l10n.php' );
1023
1024 $json_translation_files = glob( WP_LANG_DIR . '/plugins/' . $plugin_slug . '-' . $translation . '-*.json' );
1025 if ( $json_translation_files ) {
1026 array_map( array( $wp_filesystem, 'delete' ), $json_translation_files );
1027 }
1028 }
1029 }
1030 }
1031
1032 // Remove deleted plugins from the plugin updates list.
1033 $current = get_site_transient( 'update_plugins' );
1034 if ( $current ) {
1035 // Don't remove the plugins that weren't deleted.
1036 $deleted = array_diff( $plugins, $errors );
1037
1038 foreach ( $deleted as $plugin_file ) {
1039 unset( $current->response[ $plugin_file ] );
1040 }
1041
1042 set_site_transient( 'update_plugins', $current );
1043 }
1044
1045 if ( ! empty( $errors ) ) {
1046 if ( 1 === count( $errors ) ) {
1047 /* translators: %s: Plugin filename. */
1048 $message = __( 'Could not fully remove the plugin %s.' );
1049 } else {
1050 /* translators: %s: Comma-separated list of plugin filenames. */
1051 $message = __( 'Could not fully remove the plugins %s.' );
1052 }
1053
1054 return new WP_Error( 'could_not_remove_plugin', sprintf( $message, implode( ', ', $errors ) ) );
1055 }
1056
1057 return true;
1058}
1059
1060/**
1061 * Validates active plugins.
1062 *
1063 * Validate all active plugins, deactivates invalid and
1064 * returns an array of deactivated ones.
1065 *
1066 * @since 2.5.0
1067 * @return WP_Error[] Array of plugin errors keyed by plugin file name.
1068 */
1069function validate_active_plugins() {
1070 $plugins = get_option( 'active_plugins', array() );
1071 // Validate vartype: array.
1072 if ( ! is_array( $plugins ) ) {
1073 update_option( 'active_plugins', array() );
1074 $plugins = array();
1075 }
1076
1077 if ( is_multisite() && current_user_can( 'manage_network_plugins' ) ) {
1078 $network_plugins = (array) get_site_option( 'active_sitewide_plugins', array() );
1079 $plugins = array_merge( $plugins, array_keys( $network_plugins ) );
1080 }
1081
1082 if ( empty( $plugins ) ) {
1083 return array();
1084 }
1085
1086 $invalid = array();
1087
1088 // Invalid plugins get deactivated.
1089 foreach ( $plugins as $plugin ) {
1090 $result = validate_plugin( $plugin );
1091 if ( is_wp_error( $result ) ) {
1092 $invalid[ $plugin ] = $result;
1093 deactivate_plugins( $plugin, true );
1094 }
1095 }
1096 return $invalid;
1097}
1098
1099/**
1100 * Validates the plugin path.
1101 *
1102 * Checks that the main plugin file exists and is a valid plugin. See validate_file().
1103 *
1104 * @since 2.5.0
1105 *
1106 * @param string $plugin Path to the plugin file relative to the plugins directory.
1107 * @return int|WP_Error 0 on success, WP_Error on failure.
1108 */
1109function validate_plugin( $plugin ) {
1110 if ( validate_file( $plugin ) ) {
1111 return new WP_Error( 'plugin_invalid', __( 'Invalid plugin path.' ) );
1112 }
1113 if ( ! file_exists( WP_PLUGIN_DIR . '/' . $plugin ) ) {
1114 return new WP_Error( 'plugin_not_found', __( 'Plugin file does not exist.' ) );
1115 }
1116
1117 $installed_plugins = get_plugins();
1118 if ( ! isset( $installed_plugins[ $plugin ] ) ) {
1119 return new WP_Error( 'no_plugin_header', __( 'The plugin does not have a valid header.' ) );
1120 }
1121 return 0;
1122}
1123
1124/**
1125 * Validates the plugin requirements for WordPress version and PHP version.
1126 *
1127 * Uses the information from `Requires at least`, `Requires PHP` and `Requires Plugins` headers
1128 * defined in the plugin's main PHP file.
1129 *
1130 * @since 5.2.0
1131 * @since 5.3.0 Added support for reading the headers from the plugin's
1132 * main PHP file, with `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.
1135 *
1136 * @param string $plugin Path to the plugin file relative to the plugins directory.
1137 * @return true|WP_Error True if requirements are met, WP_Error on failure.
1138 */
1139function validate_plugin_requirements( $plugin ) {
1140 $plugin_headers = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
1141
1142 $requirements = array(
1143 'requires' => ! empty( $plugin_headers['RequiresWP'] ) ? $plugin_headers['RequiresWP'] : '',
1144 'requires_php' => ! empty( $plugin_headers['RequiresPHP'] ) ? $plugin_headers['RequiresPHP'] : '',
1145 'requires_plugins' => ! empty( $plugin_headers['RequiresPlugins'] ) ? $plugin_headers['RequiresPlugins'] : '',
1146 );
1147
1148 $compatible_wp = is_wp_version_compatible( $requirements['requires'] );
1149 $compatible_php = is_php_version_compatible( $requirements['requires_php'] );
1150
1151 $php_update_message = '</p><p>' . sprintf(
1152 /* translators: %s: URL to Update PHP page. */
1153 __( '<a href="%s">Learn more about updating PHP</a>.' ),
1154 esc_url( wp_get_update_php_url() )
1155 );
1156
1157 $annotation = wp_get_update_php_annotation();
1158
1159 if ( $annotation ) {
1160 $php_update_message .= '</p><p><em>' . $annotation . '</em>';
1161 }
1162
1163 if ( ! $compatible_wp && ! $compatible_php ) {
1164 return new WP_Error(
1165 'plugin_wp_php_incompatible',
1166 '<p>' . sprintf(
1167 /* translators: 1: Current WordPress version, 2: Current PHP version, 3: Plugin name, 4: Required WordPress version, 5: Required PHP version. */
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' ),
1169 get_bloginfo( 'version' ),
1170 PHP_VERSION,
1171 $plugin_headers['Name'],
1172 $requirements['requires'],
1173 $requirements['requires_php']
1174 ) . $php_update_message . '</p>'
1175 );
1176 } elseif ( ! $compatible_php ) {
1177 return new WP_Error(
1178 'plugin_php_incompatible',
1179 '<p>' . sprintf(
1180 /* translators: 1: Current PHP version, 2: Plugin name, 3: Required PHP version. */
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' ),
1182 PHP_VERSION,
1183 $plugin_headers['Name'],
1184 $requirements['requires_php']
1185 ) . $php_update_message . '</p>'
1186 );
1187 } elseif ( ! $compatible_wp ) {
1188 return new WP_Error(
1189 'plugin_wp_incompatible',
1190 '<p>' . sprintf(
1191 /* translators: 1: Current WordPress version, 2: Plugin name, 3: Required WordPress version. */
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' ),
1193 get_bloginfo( 'version' ),
1194 $plugin_headers['Name'],
1195 $requirements['requires']
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
1253 );
1254 }
1255
1256 /**
1257 * Filters the plugin requirement validation response.
1258 *
1259 * If a plugin fails due to a Core-provided validation (incompatible WP, PHP versions), this
1260 * filter will not fire. A WP_Error response will already be returned.
1261 *
1262 * This filter is intended to add additional validation steps by site administrators.
1263 *
1264 * @since 6.9.0
1265 *
1266 * @param bool|WP_Error $met_requirements True if the plugin meets requirements, WP_Error if not.
1267 * @param string $plugin Path to the plugin file relative to the plugins directory.
1268 */
1269 return apply_filters( 'validate_plugin_requirements', true, $plugin );
1270}
1271
1272/**
1273 * Determines whether the plugin can be uninstalled.
1274 *
1275 * @since 2.7.0
1276 *
1277 * @param string $plugin Path to the plugin file relative to the plugins directory.
1278 * @return bool Whether plugin can be uninstalled.
1279 */
1280function is_uninstallable_plugin( $plugin ) {
1281 $file = plugin_basename( $plugin );
1282
1283 $uninstallable_plugins = (array) get_option( 'uninstall_plugins' );
1284 if ( isset( $uninstallable_plugins[ $file ] ) || file_exists( WP_PLUGIN_DIR . '/' . dirname( $file ) . '/uninstall.php' ) ) {
1285 return true;
1286 }
1287
1288 return false;
1289}
1290
1291/**
1292 * Uninstalls a single plugin.
1293 *
1294 * Calls the uninstall hook, if it is available.
1295 *
1296 * @since 2.7.0
1297 *
1298 * @param string $plugin Path to the plugin file relative to the plugins directory.
1299 * @return true|void True if a plugin's uninstall.php file has been found and included.
1300 * Void otherwise.
1301 */
1302function uninstall_plugin( $plugin ) {
1303 $file = plugin_basename( $plugin );
1304
1305 $uninstallable_plugins = (array) get_option( 'uninstall_plugins' );
1306
1307 /**
1308 * Fires in uninstall_plugin() immediately before the plugin is uninstalled.
1309 *
1310 * @since 4.5.0
1311 *
1312 * @param string $plugin Path to the plugin file relative to the plugins directory.
1313 * @param array $uninstallable_plugins Uninstallable plugins.
1314 */
1315 do_action( 'pre_uninstall_plugin', $plugin, $uninstallable_plugins );
1316
1317 if ( file_exists( WP_PLUGIN_DIR . '/' . dirname( $file ) . '/uninstall.php' ) ) {
1318 if ( isset( $uninstallable_plugins[ $file ] ) ) {
1319 unset( $uninstallable_plugins[ $file ] );
1320 update_option( 'uninstall_plugins', $uninstallable_plugins );
1321 }
1322 unset( $uninstallable_plugins );
1323
1324 define( 'WP_UNINSTALL_PLUGIN', $file );
1325
1326 wp_register_plugin_realpath( WP_PLUGIN_DIR . '/' . $file );
1327 include_once WP_PLUGIN_DIR . '/' . dirname( $file ) . '/uninstall.php';
1328
1329 return true;
1330 }
1331
1332 if ( isset( $uninstallable_plugins[ $file ] ) ) {
1333 $callable = $uninstallable_plugins[ $file ];
1334 unset( $uninstallable_plugins[ $file ] );
1335 update_option( 'uninstall_plugins', $uninstallable_plugins );
1336 unset( $uninstallable_plugins );
1337
1338 wp_register_plugin_realpath( WP_PLUGIN_DIR . '/' . $file );
1339 include_once WP_PLUGIN_DIR . '/' . $file;
1340
1341 add_action( "uninstall_{$file}", $callable );
1342
1343 /**
1344 * Fires in uninstall_plugin() once the plugin has been uninstalled.
1345 *
1346 * The action concatenates the 'uninstall_' prefix with the basename of the
1347 * plugin passed to uninstall_plugin() to create a dynamically-named action.
1348 *
1349 * @since 2.7.0
1350 */
1351 do_action( "uninstall_{$file}" );
1352 }
1353}
1354
1355//
1356// Menu.
1357//
1358
1359/**
1360 * Adds a top-level menu page.
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 * @since 1.5.0
1369 *
1370 * @global array $menu
1371 * @global array $admin_page_hooks
1372 * @global array $_registered_pages
1373 * @global array $_parent_pages
1374 *
1375 * @param string $page_title The text to be displayed in the title tags of the page when the menu is selected.
1376 * @param string $menu_title The text to be used for the menu.
1377 * @param string $capability The capability required for this menu to be displayed to the user.
1378 * @param string $menu_slug The slug name to refer to this menu by. Should be unique for this menu page and only
1379 * include lowercase alphanumeric, dashes, and underscores characters to be compatible
1380 * with sanitize_key().
1381 * @param callable $callback Optional. The function to be called to output the content for this page.
1382 * @param string $icon_url Optional. The URL to the icon to be used for this menu.
1383 * * Pass a base64-encoded SVG using a data URI, which will be colored to match
1384 * the color scheme. This should begin with 'data:image/svg+xml;base64,'.
1385 * * Pass the name of a Dashicons helper class to use a font icon,
1386 * e.g. 'dashicons-chart-pie'.
1387 * * Pass 'none' to leave div.wp-menu-image empty so an icon can be added via CSS.
1388 * @param int|float $position Optional. The position in the menu order this item should appear.
1389 * @return string The resulting page's hook_suffix.
1390 */
1391function add_menu_page( $page_title, $menu_title, $capability, $menu_slug, $callback = '', $icon_url = '', $position = null ) {
1392 global $menu, $admin_page_hooks, $_registered_pages, $_parent_pages;
1393
1394 $menu_slug = plugin_basename( $menu_slug );
1395
1396 $admin_page_hooks[ $menu_slug ] = sanitize_title( $menu_title );
1397
1398 $hookname = get_plugin_page_hookname( $menu_slug, '' );
1399
1400 if ( ! empty( $callback ) && ! empty( $hookname ) && current_user_can( $capability ) ) {
1401 add_action( $hookname, $callback );
1402 }
1403
1404 if ( empty( $icon_url ) ) {
1405 $icon_url = 'dashicons-admin-generic';
1406 $icon_class = 'menu-icon-generic ';
1407 } else {
1408 $icon_url = set_url_scheme( $icon_url );
1409 $icon_class = '';
1410 }
1411
1412 $new_menu = array( $menu_title, $capability, $menu_slug, $page_title, 'menu-top ' . $icon_class . $hookname, $hookname, $icon_url );
1413
1414 if ( null !== $position && ! is_numeric( $position ) ) {
1415 _doing_it_wrong(
1416 __FUNCTION__,
1417 sprintf(
1418 /* translators: %s: add_menu_page() */
1419 __( 'The seventh parameter passed to %s should be numeric representing menu position.' ),
1420 '<code>add_menu_page()</code>'
1421 ),
1422 '6.0.0'
1423 );
1424 $position = null;
1425 }
1426
1427 if ( null === $position || ! is_numeric( $position ) ) {
1428 $menu[] = $new_menu;
1429 } elseif ( isset( $menu[ (string) $position ] ) ) {
1430 $collision_avoider = base_convert( substr( md5( $menu_slug . $menu_title ), -4 ), 16, 10 ) * 0.00001;
1431 $position = (string) ( $position + $collision_avoider );
1432 $menu[ $position ] = $new_menu;
1433 } else {
1434 /*
1435 * Cast menu position to a string.
1436 *
1437 * This allows for floats to be passed as the position. PHP will normally cast a float to an
1438 * integer value, this ensures the float retains its mantissa (positive fractional part).
1439 *
1440 * A string containing an integer value, eg "10", is treated as a numeric index.
1441 */
1442 $position = (string) $position;
1443 $menu[ $position ] = $new_menu;
1444 }
1445
1446 $_registered_pages[ $hookname ] = true;
1447
1448 // No parent as top level.
1449 $_parent_pages[ $menu_slug ] = false;
1450
1451 return $hookname;
1452}
1453
1454/**
1455 * Adds a submenu page.
1456 *
1457 * This function takes a capability which will be used to determine whether
1458 * or not a page is included in the menu.
1459 *
1460 * The function which is hooked in to handle the output of the page must check
1461 * that the user has the required capability as well.
1462 *
1463 * @since 1.5.0
1464 * @since 5.3.0 Added the `$position` parameter.
1465 *
1466 * @global array $submenu
1467 * @global array $menu
1468 * @global array $_wp_real_parent_file
1469 * @global bool $_wp_submenu_nopriv
1470 * @global array $_registered_pages
1471 * @global array $_parent_pages
1472 *
1473 * @param string $parent_slug The slug name for the parent menu (or the file name of a standard
1474 * WordPress admin page).
1475 * @param string $page_title The text to be displayed in the title tags of the page when the menu
1476 * is selected.
1477 * @param string $menu_title The text to be used for the menu.
1478 * @param string $capability The capability required for this menu to be displayed to the user.
1479 * @param string $menu_slug The slug name to refer to this menu by. Should be unique for this menu
1480 * and only include lowercase alphanumeric, dashes, and underscores characters
1481 * to be compatible with sanitize_key().
1482 * @param callable $callback Optional. The function to be called to output the content for this page.
1483 * @param int|float $position Optional. The position in the menu order this item should appear.
1484 * @return string|false The resulting page's hook_suffix, or false if the user does not have the capability required.
1485 */
1486function add_submenu_page( $parent_slug, $page_title, $menu_title, $capability, $menu_slug, $callback = '', $position = null ) {
1487 global $submenu, $menu, $_wp_real_parent_file, $_wp_submenu_nopriv,
1488 $_registered_pages, $_parent_pages;
1489
1490 $menu_slug = plugin_basename( $menu_slug );
1491 $parent_slug = plugin_basename( $parent_slug );
1492
1493 if ( isset( $_wp_real_parent_file[ $parent_slug ] ) ) {
1494 $parent_slug = $_wp_real_parent_file[ $parent_slug ];
1495 }
1496
1497 if ( ! current_user_can( $capability ) ) {
1498 $_wp_submenu_nopriv[ $parent_slug ][ $menu_slug ] = true;
1499 return false;
1500 }
1501
1502 /*
1503 * If the parent doesn't already have a submenu, add a link to the parent
1504 * as the first item in the submenu. If the submenu file is the same as the
1505 * parent file someone is trying to link back to the parent manually. In
1506 * this case, don't automatically add a link back to avoid duplication.
1507 */
1508 if ( ! isset( $submenu[ $parent_slug ] ) && $menu_slug !== $parent_slug ) {
1509 foreach ( (array) $menu as $parent_menu ) {
1510 if ( $parent_menu[2] === $parent_slug && current_user_can( $parent_menu[1] ) ) {
1511 $submenu[ $parent_slug ][] = array_slice( $parent_menu, 0, 4 );
1512 }
1513 }
1514 }
1515
1516 $new_sub_menu = array( $menu_title, $capability, $menu_slug, $page_title );
1517
1518 if ( null !== $position && ! is_numeric( $position ) ) {
1519 _doing_it_wrong(
1520 __FUNCTION__,
1521 sprintf(
1522 /* translators: %s: add_submenu_page() */
1523 __( 'The seventh parameter passed to %s should be numeric representing menu position.' ),
1524 '<code>add_submenu_page()</code>'
1525 ),
1526 '5.3.0'
1527 );
1528 $position = null;
1529 }
1530
1531 if (
1532 null === $position ||
1533 ( ! isset( $submenu[ $parent_slug ] ) || $position >= count( $submenu[ $parent_slug ] ) )
1534 ) {
1535 $submenu[ $parent_slug ][] = $new_sub_menu;
1536 } else {
1537 // Test for a negative position.
1538 $position = max( $position, 0 );
1539 if ( 0 === $position ) {
1540 // For negative or `0` positions, prepend the submenu.
1541 array_unshift( $submenu[ $parent_slug ], $new_sub_menu );
1542 } else {
1543 $position = absint( $position );
1544 // Grab all of the items before the insertion point.
1545 $before_items = array_slice( $submenu[ $parent_slug ], 0, $position, true );
1546 // Grab all of the items after the insertion point.
1547 $after_items = array_slice( $submenu[ $parent_slug ], $position, null, true );
1548 // Add the new item.
1549 $before_items[] = $new_sub_menu;
1550 // Merge the items.
1551 $submenu[ $parent_slug ] = array_merge( $before_items, $after_items );
1552 }
1553 }
1554
1555 // Sort the parent array.
1556 ksort( $submenu[ $parent_slug ] );
1557
1558 $hookname = get_plugin_page_hookname( $menu_slug, $parent_slug );
1559 if ( ! empty( $callback ) && ! empty( $hookname ) ) {
1560 add_action( $hookname, $callback );
1561 }
1562
1563 $_registered_pages[ $hookname ] = true;
1564
1565 /*
1566 * Backward-compatibility for plugins using add_management_page().
1567 * See wp-admin/admin.php for redirect from edit.php to tools.php.
1568 */
1569 if ( 'tools.php' === $parent_slug ) {
1570 $_registered_pages[ get_plugin_page_hookname( $menu_slug, 'edit.php' ) ] = true;
1571 }
1572
1573 // No parent as top level.
1574 $_parent_pages[ $menu_slug ] = $parent_slug;
1575
1576 return $hookname;
1577}
1578
1579/**
1580 * Adds a submenu page to the Tools main menu.
1581 *
1582 * This function takes a capability which will be used to determine whether
1583 * or not a page is included in the menu.
1584 *
1585 * The function which is hooked in to handle the output of the page must check
1586 * that the user has the required capability as well.
1587 *
1588 * @since 1.5.0
1589 * @since 5.3.0 Added the `$position` parameter.
1590 *
1591 * @param string $page_title The text to be displayed in the title tags of the page when the menu is selected.
1592 * @param string $menu_title The text to be used for the menu.
1593 * @param string $capability The capability required for this menu to be displayed to the user.
1594 * @param string $menu_slug The slug name to refer to this menu by (should be unique for this menu).
1595 * @param callable $callback Optional. The function to be called to output the content for this page.
1596 * @param int $position Optional. The position in the menu order this item should appear.
1597 * @return string|false The resulting page's hook_suffix, or false if the user does not have the capability required.
1598 */
1599function add_management_page( $page_title, $menu_title, $capability, $menu_slug, $callback = '', $position = null ) {
1600 return add_submenu_page( 'tools.php', $page_title, $menu_title, $capability, $menu_slug, $callback, $position );
1601}
1602
1603/**
1604 * Adds a submenu page to the Settings main menu.
1605 *
1606 * This function takes a capability which will be used to determine whether
1607 * or not a page is included in the menu.
1608 *
1609 * The function which is hooked in to handle the output of the page must check
1610 * that the user has the required capability as well.
1611 *
1612 * @since 1.5.0
1613 * @since 5.3.0 Added the `$position` parameter.
1614 *
1615 * @param string $page_title The text to be displayed in the title tags of the page when the menu is selected.
1616 * @param string $menu_title The text to be used for the menu.
1617 * @param string $capability The capability required for this menu to be displayed to the user.
1618 * @param string $menu_slug The slug name to refer to this menu by (should be unique for this menu).
1619 * @param callable $callback Optional. The function to be called to output the content for this page.
1620 * @param int $position Optional. The position in the menu order this item should appear.
1621 * @return string|false The resulting page's hook_suffix, or false if the user does not have the capability required.
1622 */
1623function add_options_page( $page_title, $menu_title, $capability, $menu_slug, $callback = '', $position = null ) {
1624 return add_submenu_page( 'options-general.php', $page_title, $menu_title, $capability, $menu_slug, $callback, $position );
1625}
1626
1627/**
1628 * Adds a submenu page to the Appearance main menu.
1629 *
1630 * This function takes a capability which will be used to determine whether
1631 * or not a page is included in the menu.
1632 *
1633 * The function which is hooked in to handle the output of the page must check
1634 * that the user has the required capability as well.
1635 *
1636 * @since 2.0.0
1637 * @since 5.3.0 Added the `$position` parameter.
1638 *
1639 * @param string $page_title The text to be displayed in the title tags of the page when the menu is selected.
1640 * @param string $menu_title The text to be used for the menu.
1641 * @param string $capability The capability required for this menu to be displayed to the user.
1642 * @param string $menu_slug The slug name to refer to this menu by (should be unique for this menu).
1643 * @param callable $callback Optional. The function to be called to output the content for this page.
1644 * @param int $position Optional. The position in the menu order this item should appear.
1645 * @return string|false The resulting page's hook_suffix, or false if the user does not have the capability required.
1646 */
1647function add_theme_page( $page_title, $menu_title, $capability, $menu_slug, $callback = '', $position = null ) {
1648 return add_submenu_page( 'themes.php', $page_title, $menu_title, $capability, $menu_slug, $callback, $position );
1649}
1650
1651/**
1652 * Adds a submenu page to the Plugins main menu.
1653 *
1654 * This function takes a capability which will be used to determine whether
1655 * or not a page is included in the menu.
1656 *
1657 * The function which is hooked in to handle the output of the page must check
1658 * that the user has the required capability as well.
1659 *
1660 * @since 3.0.0
1661 * @since 5.3.0 Added the `$position` parameter.
1662 *
1663 * @param string $page_title The text to be displayed in the title tags of the page when the menu is selected.
1664 * @param string $menu_title The text to be used for the menu.
1665 * @param string $capability The capability required for this menu to be displayed to the user.
1666 * @param string $menu_slug The slug name to refer to this menu by (should be unique for this menu).
1667 * @param callable $callback Optional. The function to be called to output the content for this page.
1668 * @param int $position Optional. The position in the menu order this item should appear.
1669 * @return string|false The resulting page's hook_suffix, or false if the user does not have the capability required.
1670 */
1671function add_plugins_page( $page_title, $menu_title, $capability, $menu_slug, $callback = '', $position = null ) {
1672 return add_submenu_page( 'plugins.php', $page_title, $menu_title, $capability, $menu_slug, $callback, $position );
1673}
1674
1675/**
1676 * Adds a submenu page to the Users/Profile main menu.
1677 *
1678 * This function takes a capability which will be used to determine whether
1679 * or not a page is included in the menu.
1680 *
1681 * The function which is hooked in to handle the output of the page must check
1682 * that the user has the required capability as well.
1683 *
1684 * @since 2.1.3
1685 * @since 5.3.0 Added the `$position` parameter.
1686 *
1687 * @param string $page_title The text to be displayed in the title tags of the page when the menu is selected.
1688 * @param string $menu_title The text to be used for the menu.
1689 * @param string $capability The capability required for this menu to be displayed to the user.
1690 * @param string $menu_slug The slug name to refer to this menu by (should be unique for this menu).
1691 * @param callable $callback Optional. The function to be called to output the content for this page.
1692 * @param int $position Optional. The position in the menu order this item should appear.
1693 * @return string|false The resulting page's hook_suffix, or false if the user does not have the capability required.
1694 */
1695function add_users_page( $page_title, $menu_title, $capability, $menu_slug, $callback = '', $position = null ) {
1696 if ( current_user_can( 'edit_users' ) ) {
1697 $parent = 'users.php';
1698 } else {
1699 $parent = 'profile.php';
1700 }
1701 return add_submenu_page( $parent, $page_title, $menu_title, $capability, $menu_slug, $callback, $position );
1702}
1703
1704/**
1705 * Adds a submenu page to the Dashboard main menu.
1706 *
1707 * This function takes a capability which will be used to determine whether
1708 * or not a page is included in the menu.
1709 *
1710 * The function which is hooked in to handle the output of the page must check
1711 * that the user has the required capability as well.
1712 *
1713 * @since 2.7.0
1714 * @since 5.3.0 Added the `$position` parameter.
1715 *
1716 * @param string $page_title The text to be displayed in the title tags of the page when the menu is selected.
1717 * @param string $menu_title The text to be used for the menu.
1718 * @param string $capability The capability required for this menu to be displayed to the user.
1719 * @param string $menu_slug The slug name to refer to this menu by (should be unique for this menu).
1720 * @param callable $callback Optional. The function to be called to output the content for this page.
1721 * @param int $position Optional. The position in the menu order this item should appear.
1722 * @return string|false The resulting page's hook_suffix, or false if the user does not have the capability required.
1723 */
1724function add_dashboard_page( $page_title, $menu_title, $capability, $menu_slug, $callback = '', $position = null ) {
1725 return add_submenu_page( 'index.php', $page_title, $menu_title, $capability, $menu_slug, $callback, $position );
1726}
1727
1728/**
1729 * Adds a submenu page to the Posts main menu.
1730 *
1731 * This function takes a capability which will be used to determine whether
1732 * or not a page is included in the menu.
1733 *
1734 * The function which is hooked in to handle the output of the page must check
1735 * that the user has the required capability as well.
1736 *
1737 * @since 2.7.0
1738 * @since 5.3.0 Added the `$position` parameter.
1739 *
1740 * @param string $page_title The text to be displayed in the title tags of the page when the menu is selected.
1741 * @param string $menu_title The text to be used for the menu.
1742 * @param string $capability The capability required for this menu to be displayed to the user.
1743 * @param string $menu_slug The slug name to refer to this menu by (should be unique for this menu).
1744 * @param callable $callback Optional. The function to be called to output the content for this page.
1745 * @param int $position Optional. The position in the menu order this item should appear.
1746 * @return string|false The resulting page's hook_suffix, or false if the user does not have the capability required.
1747 */
1748function add_posts_page( $page_title, $menu_title, $capability, $menu_slug, $callback = '', $position = null ) {
1749 return add_submenu_page( 'edit.php', $page_title, $menu_title, $capability, $menu_slug, $callback, $position );
1750}
1751
1752/**
1753 * Adds a submenu page to the Media main menu.
1754 *
1755 * This function takes a capability which will be used to determine whether
1756 * or not a page is included in the menu.
1757 *
1758 * The function which is hooked in to handle the output of the page must check
1759 * that the user has the required capability as well.
1760 *
1761 * @since 2.7.0
1762 * @since 5.3.0 Added the `$position` parameter.
1763 *
1764 * @param string $page_title The text to be displayed in the title tags of the page when the menu is selected.
1765 * @param string $menu_title The text to be used for the menu.
1766 * @param string $capability The capability required for this menu to be displayed to the user.
1767 * @param string $menu_slug The slug name to refer to this menu by (should be unique for this menu).
1768 * @param callable $callback Optional. The function to be called to output the content for this page.
1769 * @param int $position Optional. The position in the menu order this item should appear.
1770 * @return string|false The resulting page's hook_suffix, or false if the user does not have the capability required.
1771 */
1772function add_media_page( $page_title, $menu_title, $capability, $menu_slug, $callback = '', $position = null ) {
1773 return add_submenu_page( 'upload.php', $page_title, $menu_title, $capability, $menu_slug, $callback, $position );
1774}
1775
1776/**
1777 * Adds a submenu page to the Links main menu.
1778 *
1779 * This function takes a capability which will be used to determine whether
1780 * or not a page is included in the menu.
1781 *
1782 * The function which is hooked in to handle the output of the page must check
1783 * that the user has the required capability as well.
1784 *
1785 * @since 2.7.0
1786 * @since 5.3.0 Added the `$position` parameter.
1787 *
1788 * @param string $page_title The text to be displayed in the title tags of the page when the menu is selected.
1789 * @param string $menu_title The text to be used for the menu.
1790 * @param string $capability The capability required for this menu to be displayed to the user.
1791 * @param string $menu_slug The slug name to refer to this menu by (should be unique for this menu).
1792 * @param callable $callback Optional. The function to be called to output the content for this page.
1793 * @param int $position Optional. The position in the menu order this item should appear.
1794 * @return string|false The resulting page's hook_suffix, or false if the user does not have the capability required.
1795 */
1796function add_links_page( $page_title, $menu_title, $capability, $menu_slug, $callback = '', $position = null ) {
1797 return add_submenu_page( 'link-manager.php', $page_title, $menu_title, $capability, $menu_slug, $callback, $position );
1798}
1799
1800/**
1801 * Adds a submenu page to the Pages main menu.
1802 *
1803 * This function takes a capability which will be used to determine whether
1804 * or not a page is included in the menu.
1805 *
1806 * The function which is hooked in to handle the output of the page must check
1807 * that the user has the required capability as well.
1808 *
1809 * @since 2.7.0
1810 * @since 5.3.0 Added the `$position` parameter.
1811 *
1812 * @param string $page_title The text to be displayed in the title tags of the page when the menu is selected.
1813 * @param string $menu_title The text to be used for the menu.
1814 * @param string $capability The capability required for this menu to be displayed to the user.
1815 * @param string $menu_slug The slug name to refer to this menu by (should be unique for this menu).
1816 * @param callable $callback Optional. The function to be called to output the content for this page.
1817 * @param int $position Optional. The position in the menu order this item should appear.
1818 * @return string|false The resulting page's hook_suffix, or false if the user does not have the capability required.
1819 */
1820function add_pages_page( $page_title, $menu_title, $capability, $menu_slug, $callback = '', $position = null ) {
1821 return add_submenu_page( 'edit.php?post_type=page', $page_title, $menu_title, $capability, $menu_slug, $callback, $position );
1822}
1823
1824/**
1825 * Adds a submenu page to the Comments main menu.
1826 *
1827 * This function takes a capability which will be used to determine whether
1828 * or not a page is included in the menu.
1829 *
1830 * The function which is hooked in to handle the output of the page must check
1831 * that the user has the required capability as well.
1832 *
1833 * @since 2.7.0
1834 * @since 5.3.0 Added the `$position` parameter.
1835 *
1836 * @param string $page_title The text to be displayed in the title tags of the page when the menu is selected.
1837 * @param string $menu_title The text to be used for the menu.
1838 * @param string $capability The capability required for this menu to be displayed to the user.
1839 * @param string $menu_slug The slug name to refer to this menu by (should be unique for this menu).
1840 * @param callable $callback Optional. The function to be called to output the content for this page.
1841 * @param int $position Optional. The position in the menu order this item should appear.
1842 * @return string|false The resulting page's hook_suffix, or false if the user does not have the capability required.
1843 */
1844function add_comments_page( $page_title, $menu_title, $capability, $menu_slug, $callback = '', $position = null ) {
1845 return add_submenu_page( 'edit-comments.php', $page_title, $menu_title, $capability, $menu_slug, $callback, $position );
1846}
1847
1848/**
1849 * Removes a top-level admin menu.
1850 *
1851 * Example usage:
1852 *
1853 * - `remove_menu_page( 'tools.php' )`
1854 * - `remove_menu_page( 'plugin_menu_slug' )`
1855 *
1856 * @since 3.1.0
1857 *
1858 * @global array $menu
1859 *
1860 * @param string $menu_slug The slug of the menu.
1861 * @return array|false The removed menu on success, false if not found.
1862 */
1863function remove_menu_page( $menu_slug ) {
1864 global $menu;
1865
1866 foreach ( $menu as $i => $item ) {
1867 if ( $menu_slug === $item[2] ) {
1868 unset( $menu[ $i ] );
1869 return $item;
1870 }
1871 }
1872
1873 return false;
1874}
1875
1876/**
1877 * Removes an admin submenu.
1878 *
1879 * Example usage:
1880 *
1881 * - `remove_submenu_page( 'themes.php', 'nav-menus.php' )`
1882 * - `remove_submenu_page( 'tools.php', 'plugin_submenu_slug' )`
1883 * - `remove_submenu_page( 'plugin_menu_slug', 'plugin_submenu_slug' )`
1884 *
1885 * @since 3.1.0
1886 *
1887 * @global array $submenu
1888 *
1889 * @param string $menu_slug The slug for the parent menu.
1890 * @param string $submenu_slug The slug of the submenu.
1891 * @return array|false The removed submenu on success, false if not found.
1892 */
1893function remove_submenu_page( $menu_slug, $submenu_slug ) {
1894 global $submenu;
1895
1896 if ( ! isset( $submenu[ $menu_slug ] ) ) {
1897 return false;
1898 }
1899
1900 foreach ( $submenu[ $menu_slug ] as $i => $item ) {
1901 if ( $submenu_slug === $item[2] ) {
1902 unset( $submenu[ $menu_slug ][ $i ] );
1903 return $item;
1904 }
1905 }
1906
1907 return false;
1908}
1909
1910/**
1911 * Gets the URL to access a particular menu page based on the slug it was registered with.
1912 *
1913 * If the slug hasn't been registered properly, no URL will be returned.
1914 *
1915 * @since 3.0.0
1916 *
1917 * @global array $_parent_pages
1918 *
1919 * @param string $menu_slug The slug name to refer to this menu by (should be unique for this menu).
1920 * @param bool $display Optional. Whether or not to display the URL. Default true.
1921 * @return string The menu page URL.
1922 */
1923function menu_page_url( $menu_slug, $display = true ) {
1924 global $_parent_pages;
1925
1926 if ( isset( $_parent_pages[ $menu_slug ] ) ) {
1927 $parent_slug = $_parent_pages[ $menu_slug ];
1928
1929 if ( $parent_slug && ! isset( $_parent_pages[ $parent_slug ] ) ) {
1930 $url = admin_url( add_query_arg( 'page', $menu_slug, $parent_slug ) );
1931 } else {
1932 $url = admin_url( 'admin.php?page=' . $menu_slug );
1933 }
1934 } else {
1935 $url = '';
1936 }
1937
1938 $url = esc_url( $url );
1939
1940 if ( $display ) {
1941 echo $url;
1942 }
1943
1944 return $url;
1945}
1946
1947//
1948// Pluggable Menu Support -- Private.
1949//
1950/**
1951 * Gets the parent file of the current admin page.
1952 *
1953 * @since 1.5.0
1954 *
1955 * @global string $parent_file
1956 * @global array $menu
1957 * @global array $submenu
1958 * @global string $pagenow The filename of the current screen.
1959 * @global string $typenow The post type of the current screen.
1960 * @global string $plugin_page
1961 * @global array $_wp_real_parent_file
1962 * @global array $_wp_menu_nopriv
1963 * @global array $_wp_submenu_nopriv
1964 *
1965 * @param string $parent_page Optional. The slug name for the parent menu (or the file name
1966 * of a standard WordPress admin page). Default empty string.
1967 * @return string The parent file of the current admin page.
1968 */
1969function get_admin_page_parent( $parent_page = '' ) {
1970 global $parent_file, $menu, $submenu, $pagenow, $typenow,
1971 $plugin_page, $_wp_real_parent_file, $_wp_menu_nopriv, $_wp_submenu_nopriv;
1972
1973 if ( ! empty( $parent_page ) && 'admin.php' !== $parent_page ) {
1974 if ( isset( $_wp_real_parent_file[ $parent_page ] ) ) {
1975 $parent_page = $_wp_real_parent_file[ $parent_page ];
1976 }
1977
1978 return $parent_page;
1979 }
1980
1981 if ( 'admin.php' === $pagenow && isset( $plugin_page ) ) {
1982 foreach ( (array) $menu as $parent_menu ) {
1983 if ( $parent_menu[2] === $plugin_page ) {
1984 $parent_file = $plugin_page;
1985
1986 if ( isset( $_wp_real_parent_file[ $parent_file ] ) ) {
1987 $parent_file = $_wp_real_parent_file[ $parent_file ];
1988 }
1989
1990 return $parent_file;
1991 }
1992 }
1993 if ( isset( $_wp_menu_nopriv[ $plugin_page ] ) ) {
1994 $parent_file = $plugin_page;
1995
1996 if ( isset( $_wp_real_parent_file[ $parent_file ] ) ) {
1997 $parent_file = $_wp_real_parent_file[ $parent_file ];
1998 }
1999
2000 return $parent_file;
2001 }
2002 }
2003
2004 if ( isset( $plugin_page ) && isset( $_wp_submenu_nopriv[ $pagenow ][ $plugin_page ] ) ) {
2005 $parent_file = $pagenow;
2006
2007 if ( isset( $_wp_real_parent_file[ $parent_file ] ) ) {
2008 $parent_file = $_wp_real_parent_file[ $parent_file ];
2009 }
2010
2011 return $parent_file;
2012 }
2013
2014 foreach ( array_keys( (array) $submenu ) as $parent_page ) {
2015 foreach ( $submenu[ $parent_page ] as $submenu_array ) {
2016 if ( isset( $_wp_real_parent_file[ $parent_page ] ) ) {
2017 $parent_page = $_wp_real_parent_file[ $parent_page ];
2018 }
2019
2020 if ( ! empty( $typenow ) && "$pagenow?post_type=$typenow" === $submenu_array[2] ) {
2021 $parent_file = $parent_page;
2022 return $parent_page;
2023 } elseif ( empty( $typenow ) && $pagenow === $submenu_array[2]
2024 && ( empty( $parent_file ) || ! str_contains( $parent_file, '?' ) )
2025 ) {
2026 $parent_file = $parent_page;
2027 return $parent_page;
2028 } elseif ( isset( $plugin_page ) && $plugin_page === $submenu_array[2] ) {
2029 $parent_file = $parent_page;
2030 return $parent_page;
2031 }
2032 }
2033 }
2034
2035 if ( empty( $parent_file ) ) {
2036 $parent_file = '';
2037 }
2038 return '';
2039}
2040
2041/**
2042 * Gets the title of the current admin page.
2043 *
2044 * @since 1.5.0
2045 *
2046 * @global string $title The title of the current screen.
2047 * @global array $menu
2048 * @global array $submenu
2049 * @global string $pagenow The filename of the current screen.
2050 * @global string $typenow The post type of the current screen.
2051 * @global string $plugin_page
2052 *
2053 * @return string The title of the current admin page.
2054 */
2055function get_admin_page_title() {
2056 global $title, $menu, $submenu, $pagenow, $typenow, $plugin_page;
2057
2058 if ( ! empty( $title ) ) {
2059 return $title;
2060 }
2061
2062 $hook = get_plugin_page_hook( $plugin_page, $pagenow );
2063
2064 $parent = get_admin_page_parent();
2065 $parent1 = $parent;
2066
2067 if ( empty( $parent ) ) {
2068 foreach ( (array) $menu as $menu_array ) {
2069 if ( isset( $menu_array[3] ) ) {
2070 if ( $menu_array[2] === $pagenow ) {
2071 $title = $menu_array[3];
2072 return $menu_array[3];
2073 } elseif ( isset( $plugin_page ) && $plugin_page === $menu_array[2] && $hook === $menu_array[5] ) {
2074 $title = $menu_array[3];
2075 return $menu_array[3];
2076 }
2077 } else {
2078 $title = $menu_array[0];
2079 return $title;
2080 }
2081 }
2082 } else {
2083 foreach ( array_keys( $submenu ) as $parent ) {
2084 foreach ( $submenu[ $parent ] as $submenu_array ) {
2085 if ( isset( $plugin_page )
2086 && $plugin_page === $submenu_array[2]
2087 && ( $pagenow === $parent
2088 || $plugin_page === $parent
2089 || $plugin_page === $hook
2090 || 'admin.php' === $pagenow && $parent1 !== $submenu_array[2]
2091 || ! empty( $typenow ) && "$pagenow?post_type=$typenow" === $parent )
2092 ) {
2093 $title = $submenu_array[3];
2094 return $submenu_array[3];
2095 }
2096
2097 if ( $submenu_array[2] !== $pagenow || isset( $_GET['page'] ) ) { // Not the current page.
2098 continue;
2099 }
2100
2101 if ( isset( $submenu_array[3] ) ) {
2102 $title = $submenu_array[3];
2103 return $submenu_array[3];
2104 } else {
2105 $title = $submenu_array[0];
2106 return $title;
2107 }
2108 }
2109 }
2110 if ( empty( $title ) ) {
2111 foreach ( $menu as $menu_array ) {
2112 if ( isset( $plugin_page )
2113 && $plugin_page === $menu_array[2]
2114 && 'admin.php' === $pagenow
2115 && $parent1 === $menu_array[2]
2116 ) {
2117 $title = $menu_array[3];
2118 return $menu_array[3];
2119 }
2120 }
2121 }
2122 }
2123
2124 return $title;
2125}
2126
2127/**
2128 * Gets the hook attached to the administrative page of a plugin.
2129 *
2130 * @since 1.5.0
2131 *
2132 * @param string $plugin_page The slug name of the plugin page.
2133 * @param string $parent_page The slug name for the parent menu (or the file name of a standard
2134 * WordPress admin page).
2135 * @return string|null Hook attached to the plugin page, null otherwise.
2136 */
2137function get_plugin_page_hook( $plugin_page, $parent_page ) {
2138 $hook = get_plugin_page_hookname( $plugin_page, $parent_page );
2139 if ( has_action( $hook ) ) {
2140 return $hook;
2141 } else {
2142 return null;
2143 }
2144}
2145
2146/**
2147 * Gets the hook name for the administrative page of a plugin.
2148 *
2149 * @since 1.5.0
2150 *
2151 * @global array $admin_page_hooks
2152 *
2153 * @param string $plugin_page The slug name of the plugin page.
2154 * @param string $parent_page The slug name for the parent menu (or the file name of a standard
2155 * WordPress admin page).
2156 * @return string Hook name for the plugin page.
2157 */
2158function get_plugin_page_hookname( $plugin_page, $parent_page ) {
2159 global $admin_page_hooks;
2160
2161 $parent = get_admin_page_parent( $parent_page );
2162
2163 $page_type = 'admin';
2164 if ( empty( $parent_page ) || 'admin.php' === $parent_page || isset( $admin_page_hooks[ $plugin_page ] ) ) {
2165 if ( isset( $admin_page_hooks[ $plugin_page ] ) ) {
2166 $page_type = 'toplevel';
2167 } elseif ( isset( $admin_page_hooks[ $parent ] ) ) {
2168 $page_type = $admin_page_hooks[ $parent ];
2169 }
2170 } elseif ( isset( $admin_page_hooks[ $parent ] ) ) {
2171 $page_type = $admin_page_hooks[ $parent ];
2172 }
2173
2174 $plugin_name = preg_replace( '!\.php!', '', $plugin_page );
2175
2176 return $page_type . '_page_' . $plugin_name;
2177}
2178
2179/**
2180 * Determines whether the current user can access the current admin page.
2181 *
2182 * @since 1.5.0
2183 *
2184 * @global string $pagenow The filename of the current screen.
2185 * @global array $menu
2186 * @global array $submenu
2187 * @global array $_wp_menu_nopriv
2188 * @global array $_wp_submenu_nopriv
2189 * @global string $plugin_page
2190 * @global array $_registered_pages
2191 *
2192 * @return bool True if the current user can access the admin page, false otherwise.
2193 */
2194function user_can_access_admin_page() {
2195 global $pagenow, $menu, $submenu, $_wp_menu_nopriv, $_wp_submenu_nopriv,
2196 $plugin_page, $_registered_pages;
2197
2198 $parent = get_admin_page_parent();
2199
2200 if ( ! isset( $plugin_page ) && isset( $_wp_submenu_nopriv[ $parent ][ $pagenow ] ) ) {
2201 return false;
2202 }
2203
2204 if ( isset( $plugin_page ) ) {
2205 if ( isset( $_wp_submenu_nopriv[ $parent ][ $plugin_page ] ) ) {
2206 return false;
2207 }
2208
2209 $hookname = get_plugin_page_hookname( $plugin_page, $parent );
2210
2211 if ( ! isset( $_registered_pages[ $hookname ] ) ) {
2212 return false;
2213 }
2214 }
2215
2216 if ( empty( $parent ) ) {
2217 if ( isset( $_wp_menu_nopriv[ $pagenow ] ) ) {
2218 return false;
2219 }
2220 if ( isset( $_wp_submenu_nopriv[ $pagenow ][ $pagenow ] ) ) {
2221 return false;
2222 }
2223 if ( isset( $plugin_page ) && isset( $_wp_submenu_nopriv[ $pagenow ][ $plugin_page ] ) ) {
2224 return false;
2225 }
2226 if ( isset( $plugin_page ) && isset( $_wp_menu_nopriv[ $plugin_page ] ) ) {
2227 return false;
2228 }
2229
2230 foreach ( array_keys( $_wp_submenu_nopriv ) as $key ) {
2231 if ( isset( $_wp_submenu_nopriv[ $key ][ $pagenow ] ) ) {
2232 return false;
2233 }
2234 if ( isset( $plugin_page ) && isset( $_wp_submenu_nopriv[ $key ][ $plugin_page ] ) ) {
2235 return false;
2236 }
2237 }
2238
2239 return true;
2240 }
2241
2242 if ( isset( $plugin_page ) && $plugin_page === $parent && isset( $_wp_menu_nopriv[ $plugin_page ] ) ) {
2243 return false;
2244 }
2245
2246 if ( isset( $submenu[ $parent ] ) ) {
2247 foreach ( $submenu[ $parent ] as $submenu_array ) {
2248 if ( isset( $plugin_page ) && $submenu_array[2] === $plugin_page ) {
2249 return current_user_can( $submenu_array[1] );
2250 } elseif ( $submenu_array[2] === $pagenow ) {
2251 return current_user_can( $submenu_array[1] );
2252 }
2253 }
2254 }
2255
2256 foreach ( $menu as $menu_array ) {
2257 if ( $menu_array[2] === $parent ) {
2258 return current_user_can( $menu_array[1] );
2259 }
2260 }
2261
2262 return true;
2263}
2264
2265/* Allowed list functions */
2266
2267/**
2268 * Refreshes the value of the allowed options list available via the 'allowed_options' hook.
2269 *
2270 * See the {@see 'allowed_options'} filter.
2271 *
2272 * @since 2.7.0
2273 * @since 5.5.0 `$new_whitelist_options` was renamed to `$new_allowed_options`.
2274 * Please consider writing more inclusive code.
2275 *
2276 * @global array $new_allowed_options
2277 *
2278 * @param array $options
2279 * @return array
2280 */
2281function option_update_filter( $options ) {
2282 global $new_allowed_options;
2283
2284 if ( is_array( $new_allowed_options ) ) {
2285 $options = add_allowed_options( $new_allowed_options, $options );
2286 }
2287
2288 return $options;
2289}
2290
2291/**
2292 * Adds an array of options to the list of allowed options.
2293 *
2294 * @since 5.5.0
2295 *
2296 * @global array $allowed_options
2297 *
2298 * @param array $new_options
2299 * @param string|array $options
2300 * @return array
2301 */
2302function add_allowed_options( $new_options, $options = '' ) {
2303 if ( '' === $options ) {
2304 global $allowed_options;
2305 } else {
2306 $allowed_options = $options;
2307 }
2308
2309 foreach ( $new_options as $page => $keys ) {
2310 foreach ( $keys as $key ) {
2311 if ( ! isset( $allowed_options[ $page ] ) || ! is_array( $allowed_options[ $page ] ) ) {
2312 $allowed_options[ $page ] = array();
2313 $allowed_options[ $page ][] = $key;
2314 } else {
2315 $pos = array_search( $key, $allowed_options[ $page ], true );
2316 if ( false === $pos ) {
2317 $allowed_options[ $page ][] = $key;
2318 }
2319 }
2320 }
2321 }
2322
2323 return $allowed_options;
2324}
2325
2326/**
2327 * Removes a list of options from the allowed options list.
2328 *
2329 * @since 5.5.0
2330 *
2331 * @global array $allowed_options
2332 *
2333 * @param array $del_options
2334 * @param string|array $options
2335 * @return array
2336 */
2337function remove_allowed_options( $del_options, $options = '' ) {
2338 if ( '' === $options ) {
2339 global $allowed_options;
2340 } else {
2341 $allowed_options = $options;
2342 }
2343
2344 foreach ( $del_options as $page => $keys ) {
2345 foreach ( $keys as $key ) {
2346 if ( isset( $allowed_options[ $page ] ) && is_array( $allowed_options[ $page ] ) ) {
2347 $pos = array_search( $key, $allowed_options[ $page ], true );
2348 if ( false !== $pos ) {
2349 unset( $allowed_options[ $page ][ $pos ] );
2350 }
2351 }
2352 }
2353 }
2354
2355 return $allowed_options;
2356}
2357
2358/**
2359 * Outputs nonce, action, and option_page fields for a settings page.
2360 *
2361 * @since 2.7.0
2362 *
2363 * @param string $option_group A settings group name. This should match the group name
2364 * used in register_setting().
2365 */
2366function settings_fields( $option_group ) {
2367 echo "<input type='hidden' name='option_page' value='" . esc_attr( $option_group ) . "' />";
2368 echo '<input type="hidden" name="action" value="update" />';
2369 wp_nonce_field( "$option_group-options" );
2370}
2371
2372/**
2373 * Clears the plugins cache used by get_plugins() and by default, the plugin updates cache.
2374 *
2375 * @since 3.7.0
2376 *
2377 * @param bool $clear_update_cache Whether to clear the plugin updates cache. Default true.
2378 */
2379function wp_clean_plugins_cache( $clear_update_cache = true ) {
2380 if ( $clear_update_cache ) {
2381 delete_site_transient( 'update_plugins' );
2382 }
2383 wp_cache_delete( 'plugins', 'plugins' );
2384}
2385
2386/**
2387 * Loads a given plugin attempt to generate errors.
2388 *
2389 * @since 3.0.0
2390 * @since 4.4.0 Function was moved into the `wp-admin/includes/plugin.php` file.
2391 *
2392 * @param string $plugin Path to the plugin file relative to the plugins directory.
2393 */
2394function plugin_sandbox_scrape( $plugin ) {
2395 if ( ! defined( 'WP_SANDBOX_SCRAPING' ) ) {
2396 define( 'WP_SANDBOX_SCRAPING', true );
2397 }
2398
2399 wp_register_plugin_realpath( WP_PLUGIN_DIR . '/' . $plugin );
2400 include_once WP_PLUGIN_DIR . '/' . $plugin;
2401}
2402
2403/**
2404 * Declares a helper function for adding content to the Privacy Policy Guide.
2405 *
2406 * Plugins and themes should suggest text for inclusion in the site's privacy policy.
2407 * The suggested text should contain information about any functionality that affects user privacy,
2408 * and will be shown on the Privacy Policy Guide screen.
2409 *
2410 * A plugin or theme can use this function multiple times as long as it will help to better present
2411 * the suggested policy content. For example modular plugins such as WooCommerse or Jetpack
2412 * can add or remove suggested content depending on the modules/extensions that are enabled.
2413 * For more information see the Plugin Handbook:
2414 * https://developer.wordpress.org/plugins/privacy/suggesting-text-for-the-site-privacy-policy/.
2415 *
2416 * The HTML contents of the `$policy_text` supports use of a specialized `.privacy-policy-tutorial`
2417 * CSS class which can be used to provide supplemental information. Any content contained within
2418 * HTML elements that have the `.privacy-policy-tutorial` CSS class applied will be omitted
2419 * from the clipboard when the section content is copied.
2420 *
2421 * Intended for use with the `'admin_init'` action.
2422 *
2423 * @since 4.9.6
2424 *
2425 * @param string $plugin_name The name of the plugin or theme that is suggesting content
2426 * for the site's privacy policy.
2427 * @param string $policy_text The suggested content for inclusion in the policy.
2428 */
2429function wp_add_privacy_policy_content( $plugin_name, $policy_text ) {
2430 if ( ! is_admin() ) {
2431 _doing_it_wrong(
2432 __FUNCTION__,
2433 sprintf(
2434 /* translators: %s: admin_init */
2435 __( 'The suggested privacy policy content should be added only in wp-admin by using the %s (or later) action.' ),
2436 '<code>admin_init</code>'
2437 ),
2438 '4.9.7'
2439 );
2440 return;
2441 } elseif ( ! doing_action( 'admin_init' ) && ! did_action( 'admin_init' ) ) {
2442 _doing_it_wrong(
2443 __FUNCTION__,
2444 sprintf(
2445 /* translators: %s: admin_init */
2446 __( 'The suggested privacy policy content should be added by using the %s (or later) action. Please see the inline documentation.' ),
2447 '<code>admin_init</code>'
2448 ),
2449 '4.9.7'
2450 );
2451 return;
2452 }
2453
2454 if ( ! class_exists( 'WP_Privacy_Policy_Content' ) ) {
2455 require_once ABSPATH . 'wp-admin/includes/class-wp-privacy-policy-content.php';
2456 }
2457
2458 WP_Privacy_Policy_Content::add( $plugin_name, $policy_text );
2459}
2460
2461/**
2462 * Determines whether a plugin is technically active but was paused while
2463 * loading.
2464 *
2465 * For more information on this and similar theme functions, check out
2466 * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
2467 * Conditional Tags} article in the Theme Developer Handbook.
2468 *
2469 * @since 5.2.0
2470 *
2471 * @global WP_Paused_Extensions_Storage $_paused_plugins
2472 *
2473 * @param string $plugin Path to the plugin file relative to the plugins directory.
2474 * @return bool True, if in the list of paused plugins. False, if not in the list.
2475 */
2476function is_plugin_paused( $plugin ) {
2477 if ( ! isset( $GLOBALS['_paused_plugins'] ) ) {
2478 return false;
2479 }
2480
2481 if ( ! is_plugin_active( $plugin ) ) {
2482 return false;
2483 }
2484
2485 list( $plugin ) = explode( '/', $plugin );
2486
2487 return array_key_exists( $plugin, $GLOBALS['_paused_plugins'] );
2488}
2489
2490/**
2491 * Gets the error that was recorded for a paused plugin.
2492 *
2493 * @since 5.2.0
2494 *
2495 * @global WP_Paused_Extensions_Storage $_paused_plugins
2496 *
2497 * @param string $plugin Path to the plugin file relative to the plugins directory.
2498 * @return array|false Array of error information as returned by `error_get_last()`,
2499 * or false if none was recorded.
2500 */
2501function wp_get_plugin_error( $plugin ) {
2502 if ( ! isset( $GLOBALS['_paused_plugins'] ) ) {
2503 return false;
2504 }
2505
2506 list( $plugin ) = explode( '/', $plugin );
2507
2508 if ( ! array_key_exists( $plugin, $GLOBALS['_paused_plugins'] ) ) {
2509 return false;
2510 }
2511
2512 return $GLOBALS['_paused_plugins'][ $plugin ];
2513}
2514
2515/**
2516 * Tries to resume a single plugin.
2517 *
2518 * If a redirect was provided, we first ensure the plugin does not throw fatal
2519 * errors anymore.
2520 *
2521 * The way it works is by setting the redirection to the error before trying to
2522 * include the plugin file. If the plugin fails, then the redirection will not
2523 * be overwritten with the success message and the plugin will not be resumed.
2524 *
2525 * @since 5.2.0
2526 *
2527 * @param string $plugin Single plugin to resume.
2528 * @param string $redirect Optional. URL to redirect to. Default empty string.
2529 * @return true|WP_Error True on success, false if `$plugin` was not paused,
2530 * `WP_Error` on failure.
2531 */
2532function resume_plugin( $plugin, $redirect = '' ) {
2533 /*
2534 * We'll override this later if the plugin could be resumed without
2535 * creating a fatal error.
2536 */
2537 if ( ! empty( $redirect ) ) {
2538 wp_redirect(
2539 add_query_arg(
2540 '_error_nonce',
2541 wp_create_nonce( 'plugin-resume-error_' . $plugin ),
2542 $redirect
2543 )
2544 );
2545
2546 // Load the plugin to test whether it throws a fatal error.
2547 ob_start();
2548 plugin_sandbox_scrape( $plugin );
2549 ob_clean();
2550 }
2551
2552 list( $extension ) = explode( '/', $plugin );
2553
2554 $result = wp_paused_plugins()->delete( $extension );
2555
2556 if ( ! $result ) {
2557 return new WP_Error(
2558 'could_not_resume_plugin',
2559 __( 'Could not resume the plugin.' )
2560 );
2561 }
2562
2563 return true;
2564}
2565
2566/**
2567 * Renders an admin notice in case some plugins have been paused due to errors.
2568 *
2569 * @since 5.2.0
2570 *
2571 * @global string $pagenow The filename of the current screen.
2572 * @global WP_Paused_Extensions_Storage $_paused_plugins
2573 */
2574function paused_plugins_notice() {
2575 if ( 'plugins.php' === $GLOBALS['pagenow'] ) {
2576 return;
2577 }
2578
2579 if ( ! current_user_can( 'resume_plugins' ) ) {
2580 return;
2581 }
2582
2583 if ( ! isset( $GLOBALS['_paused_plugins'] ) || empty( $GLOBALS['_paused_plugins'] ) ) {
2584 return;
2585 }
2586
2587 $message = sprintf(
2588 '<strong>%s</strong><br>%s</p><p><a href="%s">%s</a>',
2589 __( 'One or more plugins failed to load properly.' ),
2590 __( 'You can find more details and make changes on the Plugins screen.' ),
2591 esc_url( admin_url( 'plugins.php?plugin_status=paused' ) ),
2592 __( 'Go to the Plugins screen' )
2593 );
2594 wp_admin_notice(
2595 $message,
2596 array( 'type' => 'error' )
2597 );
2598}
2599
2600/**
2601 * Renders an admin notice when a plugin was deactivated during an update.
2602 *
2603 * Displays an admin notice in case a plugin has been deactivated during an
2604 * upgrade due to incompatibility with the current version of WordPress.
2605 *
2606 * @since 5.8.0
2607 * @access private
2608 *
2609 * @global string $pagenow The filename of the current screen.
2610 * @global string $wp_version The WordPress version string.
2611 */
2612function deactivated_plugins_notice() {
2613 if ( 'plugins.php' === $GLOBALS['pagenow'] ) {
2614 return;
2615 }
2616
2617 if ( ! current_user_can( 'activate_plugins' ) ) {
2618 return;
2619 }
2620
2621 $blog_deactivated_plugins = get_option( 'wp_force_deactivated_plugins' );
2622 $site_deactivated_plugins = array();
2623
2624 if ( false === $blog_deactivated_plugins ) {
2625 // Option not in database, add an empty array to avoid extra DB queries on subsequent loads.
2626 update_option( 'wp_force_deactivated_plugins', array(), false );
2627 }
2628
2629 if ( is_multisite() ) {
2630 $site_deactivated_plugins = get_site_option( 'wp_force_deactivated_plugins' );
2631 if ( false === $site_deactivated_plugins ) {
2632 // Option not in database, add an empty array to avoid extra DB queries on subsequent loads.
2633 update_site_option( 'wp_force_deactivated_plugins', array() );
2634 }
2635 }
2636
2637 if ( empty( $blog_deactivated_plugins ) && empty( $site_deactivated_plugins ) ) {
2638 // No deactivated plugins.
2639 return;
2640 }
2641
2642 $deactivated_plugins = array_merge( $blog_deactivated_plugins, $site_deactivated_plugins );
2643
2644 foreach ( $deactivated_plugins as $plugin ) {
2645 if ( ! empty( $plugin['version_compatible'] ) && ! empty( $plugin['version_deactivated'] ) ) {
2646 $explanation = sprintf(
2647 /* translators: 1: Name of deactivated plugin, 2: Plugin version deactivated, 3: Current WP version, 4: Compatible plugin version. */
2648 __( '%1$s %2$s was deactivated due to incompatibility with WordPress %3$s, please upgrade to %1$s %4$s or later.' ),
2649 $plugin['plugin_name'],
2650 $plugin['version_deactivated'],
2651 $GLOBALS['wp_version'],
2652 $plugin['version_compatible']
2653 );
2654 } else {
2655 $explanation = sprintf(
2656 /* translators: 1: Name of deactivated plugin, 2: Plugin version deactivated, 3: Current WP version. */
2657 __( '%1$s %2$s was deactivated due to incompatibility with WordPress %3$s.' ),
2658 $plugin['plugin_name'],
2659 ! empty( $plugin['version_deactivated'] ) ? $plugin['version_deactivated'] : '',
2660 $GLOBALS['wp_version'],
2661 $plugin['version_compatible']
2662 );
2663 }
2664
2665 $message = sprintf(
2666 '<strong>%s</strong><br>%s</p><p><a href="%s">%s</a>',
2667 sprintf(
2668 /* translators: %s: Name of deactivated plugin. */
2669 __( '%s plugin deactivated during WordPress upgrade.' ),
2670 $plugin['plugin_name']
2671 ),
2672 $explanation,
2673 esc_url( admin_url( 'plugins.php?plugin_status=inactive' ) ),
2674 __( 'Go to the Plugins screen' )
2675 );
2676 wp_admin_notice( $message, array( 'type' => 'warning' ) );
2677 }
2678
2679 // Empty the options.
2680 update_option( 'wp_force_deactivated_plugins', array(), false );
2681 if ( is_multisite() ) {
2682 update_site_option( 'wp_force_deactivated_plugins', array() );
2683 }
2684}
2685
Ui Ux Design – Teachers Night Out

Get in Touch

© 2024 Teachers Night Out. All Rights Reserved.