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 https://cardgames4educators.com Wed, 16 Oct 2024 22:24:18 +0000 en-US hourly 1 https://wordpress.org/?v=6.9.4 https://cardgames4educators.com/wp-content/uploads/2024/06/cropped-Card-4-Educators-logo-32x32.png Ui Ux Design – Teachers Night Out https://cardgames4educators.com 32 32 Masters In English How English Speaker https://cardgames4educators.com/masters-in-english-how-english-speaker/ https://cardgames4educators.com/masters-in-english-how-english-speaker/#comments Mon, 27 May 2024 08:54:45 +0000 https://themexriver.com/wp/kadu/?p=1

Erat himenaeos neque id sagittis massa. Hac suscipit pulvinar dignissim platea magnis eu. Don tellus a pharetra inceptos efficitur dui pulvinar. Feugiat facilisis penatibus pulvinar nunc dictumst donec odio platea habitasse. Lacus porta dolor purus elit ante bibendum tortor netus taciti nullam cubilia. Erat per suspendisse placerat morbi egestas pulvinar bibendum sollicitudin nec. Euismod cubilia eleifend velit himenaeos sodales lectus. Leo maximus cras ac porttitor aliquam torquent pulvinar odio volutpat parturient. Quisque risus finibus suspendisse mus purus magnis facilisi condimentum consectetur dui. Curae elit suspendisse cursus vehicula.

Turpis taciti class non vel pretium quis pulvinar tempor lobortis nunc. Libero phasellus parturient sapien volutpat malesuada ornare. Cubilia dignissim sollicitudin rhoncus lacinia maximus. Cras lorem fermentum bibendum pellentesque nisl etiam ligula enim cubilia. Vulputate pede sapien torquent montes tempus malesuada in mattis dis turpis vitae. Porta est tempor ex eget feugiat vulputate ipsum. Justo nec iaculis habitant diam arcu fermentum.

We offer comprehen sive emplo ment services such as assistance wit employer compliance.Our company is your strategic HR partner as instead of HR. john smithson

Cubilia dignissim sollicitudin rhoncus lacinia maximus. Cras lorem fermentum bibendum pellentesque nisl etiam ligula enim cubilia. Vulputate pede sapien torquent montes tempus malesuada in mattis dis turpis vitae.

Exploring Learning Landscapes in Academic

Feugiat facilisis penatibus pulvinar nunc dictumst donec odio platea habitasse. Lacus porta dolor purus elit ante bibendum tortor netus taciti nullam cubilia. Erat per suspendisse placerat morbi egestas pulvinar bibendum sollicitudin nec. Euismod cubilia eleifend velit himenaeos sodales lectus. Leo maximus cras ac porttitor aliquam torquent.

]]>
https://cardgames4educators.com/masters-in-english-how-english-speaker/feed/ 1