at path:ROOT / wp-admin / includes / file.php
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
📄file.php
1<?php
2/**
3 * Filesystem API: Top-level functionality
4 *
5 * Functions for reading, writing, modifying, and deleting files on the file system.
6 * Includes functionality for theme-specific files as well as operations for uploading,
7 * archiving, and rendering output when necessary.
8 *
9 * @package WordPress
10 * @subpackage Filesystem
11 * @since 2.3.0
12 */
13
14/** The descriptions for theme files. */
15$wp_file_descriptions = array(
16 'functions.php' => __( 'Theme Functions' ),
17 'header.php' => __( 'Theme Header' ),
18 'footer.php' => __( 'Theme Footer' ),
19 'sidebar.php' => __( 'Sidebar' ),
20 'comments.php' => __( 'Comments' ),
21 'searchform.php' => __( 'Search Form' ),
22 '404.php' => __( '404 Template' ),
23 'link.php' => __( 'Links Template' ),
24 'theme.json' => __( 'Theme Styles & Block Settings' ),
25 // Archives.
26 'index.php' => __( 'Main Index Template' ),
27 'archive.php' => __( 'Archives' ),
28 'author.php' => __( 'Author Template' ),
29 'taxonomy.php' => __( 'Taxonomy Template' ),
30 'category.php' => __( 'Category Template' ),
31 'tag.php' => __( 'Tag Template' ),
32 'home.php' => __( 'Posts Page' ),
33 'search.php' => __( 'Search Results' ),
34 'date.php' => __( 'Date Template' ),
35 // Content.
36 'singular.php' => __( 'Singular Template' ),
37 'single.php' => __( 'Single Post' ),
38 'page.php' => __( 'Single Page' ),
39 'front-page.php' => __( 'Homepage' ),
40 'privacy-policy.php' => __( 'Privacy Policy Page' ),
41 // Attachments.
42 'attachment.php' => __( 'Attachment Template' ),
43 'image.php' => __( 'Image Attachment Template' ),
44 'video.php' => __( 'Video Attachment Template' ),
45 'audio.php' => __( 'Audio Attachment Template' ),
46 'application.php' => __( 'Application Attachment Template' ),
47 // Embeds.
48 'embed.php' => __( 'Embed Template' ),
49 'embed-404.php' => __( 'Embed 404 Template' ),
50 'embed-content.php' => __( 'Embed Content Template' ),
51 'header-embed.php' => __( 'Embed Header Template' ),
52 'footer-embed.php' => __( 'Embed Footer Template' ),
53 // Stylesheets.
54 'style.css' => __( 'Stylesheet' ),
55 'editor-style.css' => __( 'Visual Editor Stylesheet' ),
56 'editor-style-rtl.css' => __( 'Visual Editor RTL Stylesheet' ),
57 'rtl.css' => __( 'RTL Stylesheet' ),
58 // Other.
59 'my-hacks.php' => __( 'my-hacks.php (legacy hacks support)' ),
60 '.htaccess' => __( '.htaccess (for rewrite rules )' ),
61 // Deprecated files.
62 'wp-layout.css' => __( 'Stylesheet' ),
63 'wp-comments.php' => __( 'Comments Template' ),
64 'wp-comments-popup.php' => __( 'Popup Comments Template' ),
65 'comments-popup.php' => __( 'Popup Comments' ),
66);
67
68/**
69 * Gets the description for standard WordPress theme files.
70 *
71 * @since 1.5.0
72 *
73 * @global array $wp_file_descriptions Theme file descriptions.
74 * @global array $allowed_files List of allowed files.
75 *
76 * @param string $file Filesystem path or filename.
77 * @return string Description of file from $wp_file_descriptions or basename of $file if description doesn't exist.
78 * Appends 'Page Template' to basename of $file if the file is a page template.
79 */
80function get_file_description( $file ) {
81 global $wp_file_descriptions, $allowed_files;
82
83 $dirname = pathinfo( $file, PATHINFO_DIRNAME );
84 $file_path = $allowed_files[ $file ];
85
86 if ( isset( $wp_file_descriptions[ basename( $file ) ] ) && '.' === $dirname ) {
87 return $wp_file_descriptions[ basename( $file ) ];
88 } elseif ( file_exists( $file_path ) && is_file( $file_path ) ) {
89 $template_data = implode( '', file( $file_path ) );
90
91 if ( preg_match( '|Template Name:(.*)$|mi', $template_data, $name ) ) {
92 /* translators: %s: Template name. */
93 return sprintf( __( '%s Page Template' ), _cleanup_header_comment( $name[1] ) );
94 }
95 }
96
97 return trim( basename( $file ) );
98}
99
100/**
101 * Gets the absolute filesystem path to the root of the WordPress installation.
102 *
103 * @since 1.5.0
104 *
105 * @return string Full filesystem path to the root of the WordPress installation.
106 */
107function get_home_path() {
108 $home = set_url_scheme( get_option( 'home' ), 'http' );
109 $siteurl = set_url_scheme( get_option( 'siteurl' ), 'http' );
110
111 if ( ! empty( $home ) && 0 !== strcasecmp( $home, $siteurl ) ) {
112 $wp_path_rel_to_home = str_ireplace( $home, '', $siteurl ); /* $siteurl - $home */
113 $pos = strripos( str_replace( '\\', '/', $_SERVER['SCRIPT_FILENAME'] ), trailingslashit( $wp_path_rel_to_home ) );
114 $home_path = substr( $_SERVER['SCRIPT_FILENAME'], 0, $pos );
115 $home_path = trailingslashit( $home_path );
116 } else {
117 $home_path = ABSPATH;
118 }
119
120 return str_replace( '\\', '/', $home_path );
121}
122
123/**
124 * Returns a listing of all files in the specified folder and all subdirectories up to 100 levels deep.
125 *
126 * The depth of the recursiveness can be controlled by the $levels param.
127 *
128 * @since 2.6.0
129 * @since 4.9.0 Added the `$exclusions` parameter.
130 * @since 6.3.0 Added the `$include_hidden` parameter.
131 *
132 * @param string $folder Optional. Full path to folder. Default empty.
133 * @param int $levels Optional. Levels of folders to follow, Default 100 (PHP Loop limit).
134 * @param string[] $exclusions Optional. List of folders and files to skip.
135 * @param bool $include_hidden Optional. Whether to include details of hidden ("." prefixed) files.
136 * Default false.
137 * @return string[]|false Array of files on success, false on failure.
138 */
139function list_files( $folder = '', $levels = 100, $exclusions = array(), $include_hidden = false ) {
140 if ( empty( $folder ) ) {
141 return false;
142 }
143
144 $folder = trailingslashit( $folder );
145
146 if ( ! $levels ) {
147 return false;
148 }
149
150 $files = array();
151
152 $dir = @opendir( $folder );
153
154 if ( $dir ) {
155 while ( ( $file = readdir( $dir ) ) !== false ) {
156 // Skip current and parent folder links.
157 if ( in_array( $file, array( '.', '..' ), true ) ) {
158 continue;
159 }
160
161 // Skip hidden and excluded files.
162 if ( ( ! $include_hidden && '.' === $file[0] ) || in_array( $file, $exclusions, true ) ) {
163 continue;
164 }
165
166 if ( is_dir( $folder . $file ) ) {
167 $files2 = list_files( $folder . $file, $levels - 1, array(), $include_hidden );
168 if ( $files2 ) {
169 $files = array_merge( $files, $files2 );
170 } else {
171 $files[] = $folder . $file . '/';
172 }
173 } else {
174 $files[] = $folder . $file;
175 }
176 }
177
178 closedir( $dir );
179 }
180
181 return $files;
182}
183
184/**
185 * Gets the list of file extensions that are editable in plugins.
186 *
187 * @since 4.9.0
188 *
189 * @param string $plugin Path to the plugin file relative to the plugins directory.
190 * @return string[] Array of editable file extensions.
191 */
192function wp_get_plugin_file_editable_extensions( $plugin ) {
193
194 $default_types = array(
195 'bash',
196 'conf',
197 'css',
198 'diff',
199 'htm',
200 'html',
201 'http',
202 'inc',
203 'include',
204 'js',
205 'json',
206 'jsx',
207 'less',
208 'md',
209 'patch',
210 'php',
211 'php3',
212 'php4',
213 'php5',
214 'php7',
215 'phps',
216 'phtml',
217 'sass',
218 'scss',
219 'sh',
220 'sql',
221 'svg',
222 'text',
223 'txt',
224 'xml',
225 'yaml',
226 'yml',
227 );
228
229 /**
230 * Filters the list of file types allowed for editing in the plugin file editor.
231 *
232 * @since 2.8.0
233 * @since 4.9.0 Added the `$plugin` parameter.
234 *
235 * @param string[] $default_types An array of editable plugin file extensions.
236 * @param string $plugin Path to the plugin file relative to the plugins directory.
237 */
238 $file_types = (array) apply_filters( 'editable_extensions', $default_types, $plugin );
239
240 return $file_types;
241}
242
243/**
244 * Gets the list of file extensions that are editable for a given theme.
245 *
246 * @since 4.9.0
247 *
248 * @param WP_Theme $theme Theme object.
249 * @return string[] Array of editable file extensions.
250 */
251function wp_get_theme_file_editable_extensions( $theme ) {
252
253 $default_types = array(
254 'bash',
255 'conf',
256 'css',
257 'diff',
258 'htm',
259 'html',
260 'http',
261 'inc',
262 'include',
263 'js',
264 'json',
265 'jsx',
266 'less',
267 'md',
268 'patch',
269 'php',
270 'php3',
271 'php4',
272 'php5',
273 'php7',
274 'phps',
275 'phtml',
276 'sass',
277 'scss',
278 'sh',
279 'sql',
280 'svg',
281 'text',
282 'txt',
283 'xml',
284 'yaml',
285 'yml',
286 );
287
288 /**
289 * Filters the list of file types allowed for editing in the theme file editor.
290 *
291 * @since 4.4.0
292 *
293 * @param string[] $default_types An array of editable theme file extensions.
294 * @param WP_Theme $theme The active theme object.
295 */
296 $file_types = apply_filters( 'wp_theme_editor_filetypes', $default_types, $theme );
297
298 // Ensure that default types are still there.
299 return array_unique( array_merge( $file_types, $default_types ) );
300}
301
302/**
303 * Prints file editor templates (for plugins and themes).
304 *
305 * @since 4.9.0
306 */
307function wp_print_file_editor_templates() {
308 ?>
309 <script type="text/html" id="tmpl-wp-file-editor-notice">
310 <div class="notice inline notice-{{ data.type || 'info' }} {{ data.alt ? 'notice-alt' : '' }} {{ data.dismissible ? 'is-dismissible' : '' }} {{ data.classes || '' }}">
311 <# if ( 'php_error' === data.code ) { #>
312 <p>
313 <?php
314 printf(
315 /* translators: 1: Line number, 2: File path. */
316 __( 'Your PHP code changes were not applied due to an error on line %1$s of file %2$s. Please fix and try saving again.' ),
317 '{{ data.line }}',
318 '{{ data.file }}'
319 );
320 ?>
321 </p>
322 <pre>{{ data.message }}</pre>
323 <# } else if ( 'file_not_writable' === data.code ) { #>
324 <p>
325 <?php
326 printf(
327 /* translators: %s: Documentation URL. */
328 __( 'You need to make this file writable before you can save your changes. See <a href="%s">Changing File Permissions</a> for more information.' ),
329 __( 'https://developer.wordpress.org/advanced-administration/server/file-permissions/' )
330 );
331 ?>
332 </p>
333 <# } else { #>
334 <p>{{ data.message || data.code }}</p>
335
336 <# if ( 'lint_errors' === data.code ) { #>
337 <p>
338 <# var elementId = 'el-' + String( Math.random() ); #>
339 <input id="{{ elementId }}" type="checkbox">
340 <label for="{{ elementId }}"><?php _e( 'Update anyway, even though it might break your site?' ); ?></label>
341 </p>
342 <# } #>
343 <# } #>
344 <# if ( data.dismissible ) { #>
345 <button type="button" class="notice-dismiss"><span class="screen-reader-text">
346 <?php
347 /* translators: Hidden accessibility text. */
348 _e( 'Dismiss' );
349 ?>
350 </span></button>
351 <# } #>
352 </div>
353 </script>
354 <?php
355}
356
357/**
358 * Attempts to edit a file for a theme or plugin.
359 *
360 * When editing a PHP file, loopback requests will be made to the admin and the homepage
361 * to attempt to see if there is a fatal error introduced. If so, the PHP change will be
362 * reverted.
363 *
364 * @since 4.9.0
365 *
366 * @param string[] $args {
367 * Args. Note that all of the arg values are already unslashed. They are, however,
368 * coming straight from `$_POST` and are not validated or sanitized in any way.
369 *
370 * @type string $file Relative path to file.
371 * @type string $plugin Path to the plugin file relative to the plugins directory.
372 * @type string $theme Theme being edited.
373 * @type string $newcontent New content for the file.
374 * @type string $nonce Nonce.
375 * }
376 * @return true|WP_Error True on success or `WP_Error` on failure.
377 */
378function wp_edit_theme_plugin_file( $args ) {
379 if ( empty( $args['file'] ) ) {
380 return new WP_Error( 'missing_file' );
381 }
382
383 if ( 0 !== validate_file( $args['file'] ) ) {
384 return new WP_Error( 'bad_file' );
385 }
386
387 if ( ! isset( $args['newcontent'] ) ) {
388 return new WP_Error( 'missing_content' );
389 }
390
391 if ( ! isset( $args['nonce'] ) ) {
392 return new WP_Error( 'missing_nonce' );
393 }
394
395 $file = $args['file'];
396 $content = $args['newcontent'];
397
398 $plugin = null;
399 $theme = null;
400 $real_file = null;
401
402 if ( ! empty( $args['plugin'] ) ) {
403 $plugin = $args['plugin'];
404
405 if ( ! current_user_can( 'edit_plugins' ) ) {
406 return new WP_Error( 'unauthorized', __( 'Sorry, you are not allowed to edit plugins for this site.' ) );
407 }
408
409 if ( ! wp_verify_nonce( $args['nonce'], 'edit-plugin_' . $file ) ) {
410 return new WP_Error( 'nonce_failure' );
411 }
412
413 if ( ! array_key_exists( $plugin, get_plugins() ) ) {
414 return new WP_Error( 'invalid_plugin' );
415 }
416
417 if ( 0 !== validate_file( $file, get_plugin_files( $plugin ) ) ) {
418 return new WP_Error( 'bad_plugin_file_path', __( 'Sorry, that file cannot be edited.' ) );
419 }
420
421 $editable_extensions = wp_get_plugin_file_editable_extensions( $plugin );
422
423 $real_file = WP_PLUGIN_DIR . '/' . $file;
424
425 $is_active = in_array(
426 $plugin,
427 (array) get_option( 'active_plugins', array() ),
428 true
429 );
430
431 } elseif ( ! empty( $args['theme'] ) ) {
432 $stylesheet = $args['theme'];
433
434 if ( 0 !== validate_file( $stylesheet ) ) {
435 return new WP_Error( 'bad_theme_path' );
436 }
437
438 if ( ! current_user_can( 'edit_themes' ) ) {
439 return new WP_Error( 'unauthorized', __( 'Sorry, you are not allowed to edit templates for this site.' ) );
440 }
441
442 $theme = wp_get_theme( $stylesheet );
443 if ( ! $theme->exists() ) {
444 return new WP_Error( 'non_existent_theme', __( 'The requested theme does not exist.' ) );
445 }
446
447 if ( ! wp_verify_nonce( $args['nonce'], 'edit-theme_' . $stylesheet . '_' . $file ) ) {
448 return new WP_Error( 'nonce_failure' );
449 }
450
451 if ( $theme->errors() && 'theme_no_stylesheet' === $theme->errors()->get_error_code() ) {
452 return new WP_Error(
453 'theme_no_stylesheet',
454 __( 'The requested theme does not exist.' ) . ' ' . $theme->errors()->get_error_message()
455 );
456 }
457
458 $editable_extensions = wp_get_theme_file_editable_extensions( $theme );
459
460 $allowed_files = array();
461 foreach ( $editable_extensions as $type ) {
462 switch ( $type ) {
463 case 'php':
464 $allowed_files = array_merge( $allowed_files, $theme->get_files( 'php', -1 ) );
465 break;
466 case 'css':
467 $style_files = $theme->get_files( 'css', -1 );
468 $allowed_files['style.css'] = $style_files['style.css'];
469 $allowed_files = array_merge( $allowed_files, $style_files );
470 break;
471 default:
472 $allowed_files = array_merge( $allowed_files, $theme->get_files( $type, -1 ) );
473 break;
474 }
475 }
476
477 // Compare based on relative paths.
478 if ( 0 !== validate_file( $file, array_keys( $allowed_files ) ) ) {
479 return new WP_Error( 'disallowed_theme_file', __( 'Sorry, that file cannot be edited.' ) );
480 }
481
482 $real_file = $theme->get_stylesheet_directory() . '/' . $file;
483
484 $is_active = ( get_stylesheet() === $stylesheet || get_template() === $stylesheet );
485
486 } else {
487 return new WP_Error( 'missing_theme_or_plugin' );
488 }
489
490 // Ensure file is real.
491 if ( ! is_file( $real_file ) ) {
492 return new WP_Error( 'file_does_not_exist', __( 'File does not exist! Please double check the name and try again.' ) );
493 }
494
495 // Ensure file extension is allowed.
496 $extension = null;
497 if ( preg_match( '/\.([^.]+)$/', $real_file, $matches ) ) {
498 $extension = strtolower( $matches[1] );
499 if ( ! in_array( $extension, $editable_extensions, true ) ) {
500 return new WP_Error( 'illegal_file_type', __( 'Files of this type are not editable.' ) );
501 }
502 }
503
504 $previous_content = file_get_contents( $real_file );
505
506 if ( ! is_writable( $real_file ) ) {
507 return new WP_Error( 'file_not_writable' );
508 }
509
510 $f = fopen( $real_file, 'w+' );
511
512 if ( false === $f ) {
513 return new WP_Error( 'file_not_writable' );
514 }
515
516 $written = fwrite( $f, $content );
517 fclose( $f );
518
519 if ( false === $written ) {
520 return new WP_Error( 'unable_to_write', __( 'Unable to write to file.' ) );
521 }
522
523 wp_opcache_invalidate( $real_file, true );
524
525 if ( $is_active && 'php' === $extension ) {
526
527 $scrape_key = md5( rand() );
528 $transient = 'scrape_key_' . $scrape_key;
529 $scrape_nonce = (string) rand();
530 // It shouldn't take more than 60 seconds to make the two loopback requests.
531 set_transient( $transient, $scrape_nonce, 60 );
532
533 $cookies = wp_unslash( $_COOKIE );
534 $scrape_params = array(
535 'wp_scrape_key' => $scrape_key,
536 'wp_scrape_nonce' => $scrape_nonce,
537 );
538 $headers = array(
539 'Cache-Control' => 'no-cache',
540 );
541
542 /** This filter is documented in wp-includes/class-wp-http-streams.php */
543 $sslverify = apply_filters( 'https_local_ssl_verify', false );
544
545 // Include Basic auth in loopback requests.
546 if ( isset( $_SERVER['PHP_AUTH_USER'] ) && isset( $_SERVER['PHP_AUTH_PW'] ) ) {
547 $headers['Authorization'] = 'Basic ' . base64_encode( wp_unslash( $_SERVER['PHP_AUTH_USER'] ) . ':' . wp_unslash( $_SERVER['PHP_AUTH_PW'] ) );
548 }
549
550 // Make sure PHP process doesn't die before loopback requests complete.
551 if ( function_exists( 'set_time_limit' ) ) {
552 set_time_limit( 5 * MINUTE_IN_SECONDS );
553 }
554
555 // Time to wait for loopback requests to finish.
556 $timeout = 100; // 100 seconds.
557
558 $needle_start = "###### wp_scraping_result_start:$scrape_key ######";
559 $needle_end = "###### wp_scraping_result_end:$scrape_key ######";
560
561 // Attempt loopback request to editor to see if user just whitescreened themselves.
562 if ( $plugin ) {
563 $url = add_query_arg( compact( 'plugin', 'file' ), admin_url( 'plugin-editor.php' ) );
564 } elseif ( isset( $stylesheet ) ) {
565 $url = add_query_arg(
566 array(
567 'theme' => $stylesheet,
568 'file' => $file,
569 ),
570 admin_url( 'theme-editor.php' )
571 );
572 } else {
573 $url = admin_url();
574 }
575
576 if ( function_exists( 'session_status' ) && PHP_SESSION_ACTIVE === session_status() ) {
577 /*
578 * Close any active session to prevent HTTP requests from timing out
579 * when attempting to connect back to the site.
580 */
581 session_write_close();
582 }
583
584 $url = add_query_arg( $scrape_params, $url );
585 $r = wp_remote_get( $url, compact( 'cookies', 'headers', 'timeout', 'sslverify' ) );
586 $body = wp_remote_retrieve_body( $r );
587 $scrape_result_position = strpos( $body, $needle_start );
588
589 $loopback_request_failure = array(
590 'code' => 'loopback_request_failed',
591 'message' => __( 'Unable to communicate back with site to check for fatal errors, so the PHP change was reverted. You will need to upload your PHP file change by some other means, such as by using SFTP.' ),
592 );
593 $json_parse_failure = array(
594 'code' => 'json_parse_error',
595 );
596
597 $result = null;
598
599 if ( false === $scrape_result_position ) {
600 $result = $loopback_request_failure;
601 } else {
602 $error_output = substr( $body, $scrape_result_position + strlen( $needle_start ) );
603 $error_output = substr( $error_output, 0, strpos( $error_output, $needle_end ) );
604 $result = json_decode( trim( $error_output ), true );
605 if ( empty( $result ) ) {
606 $result = $json_parse_failure;
607 }
608 }
609
610 // Try making request to homepage as well to see if visitors have been whitescreened.
611 if ( true === $result ) {
612 $url = home_url( '/' );
613 $url = add_query_arg( $scrape_params, $url );
614 $r = wp_remote_get( $url, compact( 'cookies', 'headers', 'timeout', 'sslverify' ) );
615 $body = wp_remote_retrieve_body( $r );
616 $scrape_result_position = strpos( $body, $needle_start );
617
618 if ( false === $scrape_result_position ) {
619 $result = $loopback_request_failure;
620 } else {
621 $error_output = substr( $body, $scrape_result_position + strlen( $needle_start ) );
622 $error_output = substr( $error_output, 0, strpos( $error_output, $needle_end ) );
623 $result = json_decode( trim( $error_output ), true );
624 if ( empty( $result ) ) {
625 $result = $json_parse_failure;
626 }
627 }
628 }
629
630 delete_transient( $transient );
631
632 if ( true !== $result ) {
633 // Roll-back file change.
634 file_put_contents( $real_file, $previous_content );
635 wp_opcache_invalidate( $real_file, true );
636
637 if ( ! isset( $result['message'] ) ) {
638 $message = __( 'An error occurred. Please try again later.' );
639 } else {
640 $message = $result['message'];
641 unset( $result['message'] );
642 }
643
644 return new WP_Error( 'php_error', $message, $result );
645 }
646 }
647
648 if ( $theme instanceof WP_Theme ) {
649 $theme->cache_delete();
650 }
651
652 return true;
653}
654
655
656/**
657 * Returns a filename of a temporary unique file.
658 *
659 * Please note that the calling function must delete or move the file.
660 *
661 * The filename is based off the passed parameter or defaults to the current unix timestamp,
662 * while the directory can either be passed as well, or by leaving it blank, default to a writable
663 * temporary directory.
664 *
665 * @since 2.6.0
666 *
667 * @param string $filename Optional. Filename to base the Unique file off. Default empty.
668 * @param string $dir Optional. Directory to store the file in. Default empty.
669 * @return string A writable filename.
670 */
671function wp_tempnam( $filename = '', $dir = '' ) {
672 if ( empty( $dir ) ) {
673 $dir = get_temp_dir();
674 }
675
676 if ( empty( $filename ) || in_array( $filename, array( '.', '/', '\\' ), true ) ) {
677 $filename = uniqid();
678 }
679
680 // Use the basename of the given file without the extension as the name for the temporary directory.
681 $temp_filename = basename( $filename );
682 $temp_filename = preg_replace( '|\.[^.]*$|', '', $temp_filename );
683
684 // If the folder is falsey, use its parent directory name instead.
685 if ( ! $temp_filename ) {
686 return wp_tempnam( dirname( $filename ), $dir );
687 }
688
689 // Suffix some random data to avoid filename conflicts.
690 $temp_filename .= '-' . wp_generate_password( 6, false );
691 $temp_filename .= '.tmp';
692 $temp_filename = wp_unique_filename( $dir, $temp_filename );
693
694 /*
695 * Filesystems typically have a limit of 255 characters for a filename.
696 *
697 * If the generated unique filename exceeds this, truncate the initial
698 * filename and try again.
699 *
700 * As it's possible that the truncated filename may exist, producing a
701 * suffix of "-1" or "-10" which could exceed the limit again, truncate
702 * it to 252 instead.
703 */
704 $characters_over_limit = strlen( $temp_filename ) - 252;
705 if ( $characters_over_limit > 0 ) {
706 $filename = substr( $filename, 0, -$characters_over_limit );
707 return wp_tempnam( $filename, $dir );
708 }
709
710 $temp_filename = $dir . $temp_filename;
711
712 $fp = @fopen( $temp_filename, 'x' );
713
714 if ( ! $fp && is_writable( $dir ) && file_exists( $temp_filename ) ) {
715 return wp_tempnam( $filename, $dir );
716 }
717
718 if ( $fp ) {
719 fclose( $fp );
720 }
721
722 return $temp_filename;
723}
724
725/**
726 * Makes sure that the file that was requested to be edited is allowed to be edited.
727 *
728 * Function will die if you are not allowed to edit the file.
729 *
730 * @since 1.5.0
731 *
732 * @param string $file File the user is attempting to edit.
733 * @param string[] $allowed_files Optional. Array of allowed files to edit.
734 * `$file` must match an entry exactly.
735 * @return string|void Returns the file name on success, dies on failure.
736 */
737function validate_file_to_edit( $file, $allowed_files = array() ) {
738 $code = validate_file( $file, $allowed_files );
739
740 if ( ! $code ) {
741 return $file;
742 }
743
744 switch ( $code ) {
745 case 1:
746 wp_die( __( 'Sorry, that file cannot be edited.' ) );
747
748 // case 2 :
749 // wp_die( __('Sorry, cannot call files with their real path.' ));
750
751 case 3:
752 wp_die( __( 'Sorry, that file cannot be edited.' ) );
753 }
754}
755
756/**
757 * Handles PHP uploads in WordPress.
758 *
759 * Sanitizes file names, checks extensions for mime type, and moves the file
760 * to the appropriate directory within the uploads directory.
761 *
762 * @access private
763 * @since 4.0.0
764 *
765 * @see wp_handle_upload_error
766 *
767 * @param array $file {
768 * Reference to a single element from `$_FILES`. Call the function once for each uploaded file.
769 *
770 * @type string $name The original name of the file on the client machine.
771 * @type string $type The mime type of the file, if the browser provided this information.
772 * @type string $tmp_name The temporary filename of the file in which the uploaded file was stored on the server.
773 * @type int $size The size, in bytes, of the uploaded file.
774 * @type int $error The error code associated with this file upload.
775 * }
776 * @param array|false $overrides {
777 * An array of override parameters for this file, or boolean false if none are provided.
778 *
779 * @type callable $upload_error_handler Function to call when there is an error during the upload process.
780 * See {@see wp_handle_upload_error()}.
781 * @type callable $unique_filename_callback Function to call when determining a unique file name for the file.
782 * See {@see wp_unique_filename()}.
783 * @type string[] $upload_error_strings The strings that describe the error indicated in
784 * `$_FILES[{form field}]['error']`.
785 * @type bool $test_form Whether to test that the `$_POST['action']` parameter is as expected.
786 * @type bool $test_size Whether to test that the file size is greater than zero bytes.
787 * @type bool $test_type Whether to test that the mime type of the file is as expected.
788 * @type string[] $mimes Array of allowed mime types keyed by their file extension regex.
789 * }
790 * @param string $time Time formatted in 'yyyy/mm'.
791 * @param string $action Expected value for `$_POST['action']`.
792 * @return array {
793 * On success, returns an associative array of file attributes.
794 * On failure, returns `$overrides['upload_error_handler']( &$file, $message )`
795 * or `array( 'error' => $message )`.
796 *
797 * @type string $file Filename of the newly-uploaded file.
798 * @type string $url URL of the newly-uploaded file.
799 * @type string $type Mime type of the newly-uploaded file.
800 * }
801 */
802function _wp_handle_upload( &$file, $overrides, $time, $action ) {
803 // The default error handler.
804 if ( ! function_exists( 'wp_handle_upload_error' ) ) {
805 function wp_handle_upload_error( &$file, $message ) {
806 return array( 'error' => $message );
807 }
808 }
809
810 /**
811 * Filters the data for a file before it is uploaded to WordPress.
812 *
813 * The dynamic portion of the hook name, `$action`, refers to the post action.
814 *
815 * Possible hook names include:
816 *
817 * - `wp_handle_sideload_prefilter`
818 * - `wp_handle_upload_prefilter`
819 *
820 * @since 2.9.0 as 'wp_handle_upload_prefilter'.
821 * @since 4.0.0 Converted to a dynamic hook with `$action`.
822 *
823 * @param array $file {
824 * Reference to a single element from `$_FILES`.
825 *
826 * @type string $name The original name of the file on the client machine.
827 * @type string $type The mime type of the file, if the browser provided this information.
828 * @type string $tmp_name The temporary filename of the file in which the uploaded file was stored on the server.
829 * @type int $size The size, in bytes, of the uploaded file.
830 * @type int $error The error code associated with this file upload.
831 * }
832 */
833 $file = apply_filters( "{$action}_prefilter", $file );
834
835 /**
836 * Filters the override parameters for a file before it is uploaded to WordPress.
837 *
838 * The dynamic portion of the hook name, `$action`, refers to the post action.
839 *
840 * Possible hook names include:
841 *
842 * - `wp_handle_sideload_overrides`
843 * - `wp_handle_upload_overrides`
844 *
845 * @since 5.7.0
846 *
847 * @param array|false $overrides An array of override parameters for this file. Boolean false if none are
848 * provided. See {@see _wp_handle_upload()}.
849 * @param array $file {
850 * Reference to a single element from `$_FILES`.
851 *
852 * @type string $name The original name of the file on the client machine.
853 * @type string $type The mime type of the file, if the browser provided this information.
854 * @type string $tmp_name The temporary filename of the file in which the uploaded file was stored on the server.
855 * @type int $size The size, in bytes, of the uploaded file.
856 * @type int $error The error code associated with this file upload.
857 * }
858 */
859 $overrides = apply_filters( "{$action}_overrides", $overrides, $file );
860
861 // You may define your own function and pass the name in $overrides['upload_error_handler'].
862 $upload_error_handler = 'wp_handle_upload_error';
863 if ( isset( $overrides['upload_error_handler'] ) ) {
864 $upload_error_handler = $overrides['upload_error_handler'];
865 }
866
867 // You may have had one or more 'wp_handle_upload_prefilter' functions error out the file. Handle that gracefully.
868 if ( isset( $file['error'] ) && ! is_numeric( $file['error'] ) && $file['error'] ) {
869 return call_user_func_array( $upload_error_handler, array( &$file, $file['error'] ) );
870 }
871
872 // Install user overrides. Did we mention that this voids your warranty?
873
874 // You may define your own function and pass the name in $overrides['unique_filename_callback'].
875 $unique_filename_callback = null;
876 if ( isset( $overrides['unique_filename_callback'] ) ) {
877 $unique_filename_callback = $overrides['unique_filename_callback'];
878 }
879
880 /*
881 * This may not have originally been intended to be overridable,
882 * but historically has been.
883 */
884 if ( isset( $overrides['upload_error_strings'] ) ) {
885 $upload_error_strings = $overrides['upload_error_strings'];
886 } else {
887 // Courtesy of php.net, the strings that describe the error indicated in $_FILES[{form field}]['error'].
888 $upload_error_strings = array(
889 false,
890 sprintf(
891 /* translators: 1: upload_max_filesize, 2: php.ini */
892 __( 'The uploaded file exceeds the %1$s directive in %2$s.' ),
893 'upload_max_filesize',
894 'php.ini'
895 ),
896 sprintf(
897 /* translators: %s: MAX_FILE_SIZE */
898 __( 'The uploaded file exceeds the %s directive that was specified in the HTML form.' ),
899 'MAX_FILE_SIZE'
900 ),
901 __( 'The uploaded file was only partially uploaded.' ),
902 __( 'No file was uploaded.' ),
903 '',
904 __( 'Missing a temporary folder.' ),
905 __( 'Failed to write file to disk.' ),
906 __( 'File upload stopped by extension.' ),
907 );
908 }
909
910 // All tests are on by default. Most can be turned off by $overrides[{test_name}] = false;
911 $test_form = isset( $overrides['test_form'] ) ? $overrides['test_form'] : true;
912 $test_size = isset( $overrides['test_size'] ) ? $overrides['test_size'] : true;
913
914 // If you override this, you must provide $ext and $type!!
915 $test_type = isset( $overrides['test_type'] ) ? $overrides['test_type'] : true;
916 $mimes = isset( $overrides['mimes'] ) ? $overrides['mimes'] : null;
917
918 // A correct form post will pass this test.
919 if ( $test_form && ( ! isset( $_POST['action'] ) || $_POST['action'] !== $action ) ) {
920 return call_user_func_array( $upload_error_handler, array( &$file, __( 'Invalid form submission.' ) ) );
921 }
922
923 // A successful upload will pass this test. It makes no sense to override this one.
924 if ( isset( $file['error'] ) && $file['error'] > 0 ) {
925 return call_user_func_array( $upload_error_handler, array( &$file, $upload_error_strings[ $file['error'] ] ) );
926 }
927
928 // A properly uploaded file will pass this test. There should be no reason to override this one.
929 $test_uploaded_file = 'wp_handle_upload' === $action ? is_uploaded_file( $file['tmp_name'] ) : @is_readable( $file['tmp_name'] );
930 if ( ! $test_uploaded_file ) {
931 return call_user_func_array( $upload_error_handler, array( &$file, __( 'Specified file failed upload test.' ) ) );
932 }
933
934 $test_file_size = 'wp_handle_upload' === $action ? $file['size'] : filesize( $file['tmp_name'] );
935 // A non-empty file will pass this test.
936 if ( $test_size && ! ( $test_file_size > 0 ) ) {
937 if ( is_multisite() ) {
938 $error_msg = __( 'File is empty. Please upload something more substantial.' );
939 } else {
940 $error_msg = sprintf(
941 /* translators: 1: php.ini, 2: post_max_size, 3: upload_max_filesize */
942 __( 'File is empty. Please upload something more substantial. This error could also be caused by uploads being disabled in your %1$s file or by %2$s being defined as smaller than %3$s in %1$s.' ),
943 'php.ini',
944 'post_max_size',
945 'upload_max_filesize'
946 );
947 }
948
949 return call_user_func_array( $upload_error_handler, array( &$file, $error_msg ) );
950 }
951
952 // A correct MIME type will pass this test. Override $mimes or use the upload_mimes filter.
953 if ( $test_type ) {
954 $wp_filetype = wp_check_filetype_and_ext( $file['tmp_name'], $file['name'], $mimes );
955 $ext = empty( $wp_filetype['ext'] ) ? '' : $wp_filetype['ext'];
956 $type = empty( $wp_filetype['type'] ) ? '' : $wp_filetype['type'];
957 $proper_filename = empty( $wp_filetype['proper_filename'] ) ? '' : $wp_filetype['proper_filename'];
958
959 // Check to see if wp_check_filetype_and_ext() determined the filename was incorrect.
960 if ( $proper_filename ) {
961 $file['name'] = $proper_filename;
962 }
963
964 if ( ( ! $type || ! $ext ) && ! current_user_can( 'unfiltered_upload' ) ) {
965 return call_user_func_array( $upload_error_handler, array( &$file, __( 'Sorry, you are not allowed to upload this file type.' ) ) );
966 }
967
968 if ( ! $type ) {
969 $type = $file['type'];
970 }
971 } else {
972 $type = '';
973 }
974
975 /*
976 * A writable uploads dir will pass this test. Again, there's no point
977 * overriding this one.
978 */
979 $uploads = wp_upload_dir( $time );
980 if ( ! ( $uploads && false === $uploads['error'] ) ) {
981 return call_user_func_array( $upload_error_handler, array( &$file, $uploads['error'] ) );
982 }
983
984 $filename = wp_unique_filename( $uploads['path'], $file['name'], $unique_filename_callback );
985
986 // Move the file to the uploads dir.
987 $new_file = $uploads['path'] . "/$filename";
988
989 /**
990 * Filters whether to short-circuit moving the uploaded file after passing all checks.
991 *
992 * If a non-null value is returned from the filter, moving the file and any related
993 * error reporting will be completely skipped.
994 *
995 * @since 4.9.0
996 *
997 * @param mixed $move_new_file If null (default) move the file after the upload.
998 * @param array $file {
999 * Reference to a single element from `$_FILES`.
1000 *
1001 * @type string $name The original name of the file on the client machine.
1002 * @type string $type The mime type of the file, if the browser provided this information.
1003 * @type string $tmp_name The temporary filename of the file in which the uploaded file was stored on the server.
1004 * @type int $size The size, in bytes, of the uploaded file.
1005 * @type int $error The error code associated with this file upload.
1006 * }
1007 * @param string $new_file Filename of the newly-uploaded file.
1008 * @param string $type Mime type of the newly-uploaded file.
1009 */
1010 $move_new_file = apply_filters( 'pre_move_uploaded_file', null, $file, $new_file, $type );
1011
1012 if ( null === $move_new_file ) {
1013 if ( 'wp_handle_upload' === $action ) {
1014 $move_new_file = @move_uploaded_file( $file['tmp_name'], $new_file );
1015 } else {
1016 // Use copy and unlink because rename breaks streams.
1017 // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
1018 $move_new_file = @copy( $file['tmp_name'], $new_file );
1019 unlink( $file['tmp_name'] );
1020 }
1021
1022 if ( false === $move_new_file ) {
1023 if ( str_starts_with( $uploads['basedir'], ABSPATH ) ) {
1024 $error_path = str_replace( ABSPATH, '', $uploads['basedir'] ) . $uploads['subdir'];
1025 } else {
1026 $error_path = basename( $uploads['basedir'] ) . $uploads['subdir'];
1027 }
1028
1029 return $upload_error_handler(
1030 $file,
1031 sprintf(
1032 /* translators: %s: Destination file path. */
1033 __( 'The uploaded file could not be moved to %s.' ),
1034 $error_path
1035 )
1036 );
1037 }
1038 }
1039
1040 // Set correct file permissions.
1041 $stat = stat( dirname( $new_file ) );
1042 $perms = $stat['mode'] & 0000666;
1043 chmod( $new_file, $perms );
1044
1045 // Compute the URL.
1046 $url = $uploads['url'] . "/$filename";
1047
1048 if ( is_multisite() ) {
1049 clean_dirsize_cache( $new_file );
1050 }
1051
1052 /**
1053 * Filters the data array for the uploaded file.
1054 *
1055 * @since 2.1.0
1056 *
1057 * @param array $upload {
1058 * Array of upload data.
1059 *
1060 * @type string $file Filename of the newly-uploaded file.
1061 * @type string $url URL of the newly-uploaded file.
1062 * @type string $type Mime type of the newly-uploaded file.
1063 * }
1064 * @param string $context The type of upload action. Values include 'upload' or 'sideload'.
1065 */
1066 return apply_filters(
1067 'wp_handle_upload',
1068 array(
1069 'file' => $new_file,
1070 'url' => $url,
1071 'type' => $type,
1072 ),
1073 'wp_handle_sideload' === $action ? 'sideload' : 'upload'
1074 );
1075}
1076
1077/**
1078 * Wrapper for _wp_handle_upload().
1079 *
1080 * Passes the {@see 'wp_handle_upload'} action.
1081 *
1082 * @since 2.0.0
1083 *
1084 * @see _wp_handle_upload()
1085 *
1086 * @param array $file Reference to a single element of `$_FILES`.
1087 * Call the function once for each uploaded file.
1088 * See _wp_handle_upload() for accepted values.
1089 * @param array|false $overrides Optional. An associative array of names => values
1090 * to override default variables. Default false.
1091 * See _wp_handle_upload() for accepted values.
1092 * @param string|null $time Optional. Time formatted in 'yyyy/mm'. Default null.
1093 * @return array See _wp_handle_upload() for return value.
1094 */
1095function wp_handle_upload( &$file, $overrides = false, $time = null ) {
1096 /*
1097 * $_POST['action'] must be set and its value must equal $overrides['action']
1098 * or this:
1099 */
1100 $action = 'wp_handle_upload';
1101 if ( isset( $overrides['action'] ) ) {
1102 $action = $overrides['action'];
1103 }
1104
1105 return _wp_handle_upload( $file, $overrides, $time, $action );
1106}
1107
1108/**
1109 * Wrapper for _wp_handle_upload().
1110 *
1111 * Passes the {@see 'wp_handle_sideload'} action.
1112 *
1113 * @since 2.6.0
1114 *
1115 * @see _wp_handle_upload()
1116 *
1117 * @param array $file Reference to a single element of `$_FILES`.
1118 * Call the function once for each uploaded file.
1119 * See _wp_handle_upload() for accepted values.
1120 * @param array|false $overrides Optional. An associative array of names => values
1121 * to override default variables. Default false.
1122 * See _wp_handle_upload() for accepted values.
1123 * @param string|null $time Optional. Time formatted in 'yyyy/mm'. Default null.
1124 * @return array See _wp_handle_upload() for return value.
1125 */
1126function wp_handle_sideload( &$file, $overrides = false, $time = null ) {
1127 /*
1128 * $_POST['action'] must be set and its value must equal $overrides['action']
1129 * or this:
1130 */
1131 $action = 'wp_handle_sideload';
1132 if ( isset( $overrides['action'] ) ) {
1133 $action = $overrides['action'];
1134 }
1135
1136 return _wp_handle_upload( $file, $overrides, $time, $action );
1137}
1138
1139/**
1140 * Downloads a URL to a local temporary file using the WordPress HTTP API.
1141 *
1142 * Please note that the calling function must delete or move the file.
1143 *
1144 * @since 2.5.0
1145 * @since 5.2.0 Signature Verification with SoftFail was added.
1146 * @since 5.9.0 Support for Content-Disposition filename was added.
1147 *
1148 * @param string $url The URL of the file to download.
1149 * @param int $timeout The timeout for the request to download the file.
1150 * Default 300 seconds.
1151 * @param bool $signature_verification Whether to perform Signature Verification.
1152 * Default false.
1153 * @return string|WP_Error Filename on success, WP_Error on failure.
1154 */
1155function download_url( $url, $timeout = 300, $signature_verification = false ) {
1156 // WARNING: The file is not automatically deleted, the script must delete or move the file.
1157 if ( ! $url ) {
1158 return new WP_Error( 'http_no_url', __( 'No URL Provided.' ) );
1159 }
1160
1161 $url_path = parse_url( $url, PHP_URL_PATH );
1162 $url_filename = '';
1163 if ( is_string( $url_path ) && '' !== $url_path ) {
1164 $url_filename = basename( $url_path );
1165 }
1166
1167 $tmpfname = wp_tempnam( $url_filename );
1168 if ( ! $tmpfname ) {
1169 return new WP_Error( 'http_no_file', __( 'Could not create temporary file.' ) );
1170 }
1171
1172 $response = wp_safe_remote_get(
1173 $url,
1174 array(
1175 'timeout' => $timeout,
1176 'stream' => true,
1177 'filename' => $tmpfname,
1178 )
1179 );
1180
1181 if ( is_wp_error( $response ) ) {
1182 unlink( $tmpfname );
1183 return $response;
1184 }
1185
1186 $response_code = wp_remote_retrieve_response_code( $response );
1187
1188 if ( 200 !== $response_code ) {
1189 $data = array(
1190 'code' => $response_code,
1191 );
1192
1193 // Retrieve a sample of the response body for debugging purposes.
1194 $tmpf = fopen( $tmpfname, 'rb' );
1195
1196 if ( $tmpf ) {
1197 /**
1198 * Filters the maximum error response body size in `download_url()`.
1199 *
1200 * @since 5.1.0
1201 *
1202 * @see download_url()
1203 *
1204 * @param int $size The maximum error response body size. Default 1 KB.
1205 */
1206 $response_size = apply_filters( 'download_url_error_max_body_size', KB_IN_BYTES );
1207
1208 $data['body'] = fread( $tmpf, $response_size );
1209 fclose( $tmpf );
1210 }
1211
1212 unlink( $tmpfname );
1213
1214 return new WP_Error( 'http_404', trim( wp_remote_retrieve_response_message( $response ) ), $data );
1215 }
1216
1217 $content_disposition = wp_remote_retrieve_header( $response, 'Content-Disposition' );
1218
1219 if ( $content_disposition ) {
1220 $content_disposition = strtolower( $content_disposition );
1221
1222 if ( str_starts_with( $content_disposition, 'attachment; filename=' ) ) {
1223 $tmpfname_disposition = sanitize_file_name( substr( $content_disposition, 21 ) );
1224 } else {
1225 $tmpfname_disposition = '';
1226 }
1227
1228 // Potential file name must be valid string.
1229 if ( $tmpfname_disposition && is_string( $tmpfname_disposition )
1230 && ( 0 === validate_file( $tmpfname_disposition ) )
1231 ) {
1232 $tmpfname_disposition = dirname( $tmpfname ) . '/' . $tmpfname_disposition;
1233
1234 if ( rename( $tmpfname, $tmpfname_disposition ) ) {
1235 $tmpfname = $tmpfname_disposition;
1236 }
1237
1238 if ( ( $tmpfname !== $tmpfname_disposition ) && file_exists( $tmpfname_disposition ) ) {
1239 unlink( $tmpfname_disposition );
1240 }
1241 }
1242 }
1243
1244 $mime_type = wp_remote_retrieve_header( $response, 'content-type' );
1245 if ( $mime_type && 'tmp' === pathinfo( $tmpfname, PATHINFO_EXTENSION ) ) {
1246 $valid_mime_types = array_flip( get_allowed_mime_types() );
1247 if ( ! empty( $valid_mime_types[ $mime_type ] ) ) {
1248 $extensions = explode( '|', $valid_mime_types[ $mime_type ] );
1249 $new_image_name = substr( $tmpfname, 0, -4 ) . ".{$extensions[0]}";
1250 if ( 0 === validate_file( $new_image_name ) ) {
1251 if ( rename( $tmpfname, $new_image_name ) ) {
1252 $tmpfname = $new_image_name;
1253 }
1254
1255 if ( ( $tmpfname !== $new_image_name ) && file_exists( $new_image_name ) ) {
1256 unlink( $new_image_name );
1257 }
1258 }
1259 }
1260 }
1261
1262 $content_md5 = wp_remote_retrieve_header( $response, 'Content-MD5' );
1263
1264 if ( $content_md5 ) {
1265 $md5_check = verify_file_md5( $tmpfname, $content_md5 );
1266
1267 if ( is_wp_error( $md5_check ) ) {
1268 unlink( $tmpfname );
1269 return $md5_check;
1270 }
1271 }
1272
1273 // If the caller expects signature verification to occur, check to see if this URL supports it.
1274 if ( $signature_verification ) {
1275 /**
1276 * Filters the list of hosts which should have Signature Verification attempted on.
1277 *
1278 * @since 5.2.0
1279 *
1280 * @param string[] $hostnames List of hostnames.
1281 */
1282 $signed_hostnames = apply_filters( 'wp_signature_hosts', array( 'wordpress.org', 'downloads.wordpress.org', 's.w.org' ) );
1283
1284 $signature_verification = in_array( parse_url( $url, PHP_URL_HOST ), $signed_hostnames, true );
1285 }
1286
1287 // Perform signature validation if supported.
1288 if ( $signature_verification ) {
1289 $signature = wp_remote_retrieve_header( $response, 'X-Content-Signature' );
1290
1291 if ( ! $signature ) {
1292 /*
1293 * Retrieve signatures from a file if the header wasn't included.
1294 * WordPress.org stores signatures at $package_url.sig.
1295 */
1296
1297 $signature_url = false;
1298
1299 if ( is_string( $url_path ) && ( str_ends_with( $url_path, '.zip' ) || str_ends_with( $url_path, '.tar.gz' ) ) ) {
1300 $signature_url = str_replace( $url_path, $url_path . '.sig', $url );
1301 }
1302
1303 /**
1304 * Filters the URL where the signature for a file is located.
1305 *
1306 * @since 5.2.0
1307 *
1308 * @param false|string $signature_url The URL where signatures can be found for a file, or false if none are known.
1309 * @param string $url The URL being verified.
1310 */
1311 $signature_url = apply_filters( 'wp_signature_url', $signature_url, $url );
1312
1313 if ( $signature_url ) {
1314 $signature_request = wp_safe_remote_get(
1315 $signature_url,
1316 array(
1317 'limit_response_size' => 10 * KB_IN_BYTES, // 10KB should be large enough for quite a few signatures.
1318 )
1319 );
1320
1321 if ( ! is_wp_error( $signature_request ) && 200 === wp_remote_retrieve_response_code( $signature_request ) ) {
1322 $signature = explode( "\n", wp_remote_retrieve_body( $signature_request ) );
1323 }
1324 }
1325 }
1326
1327 // Perform the checks.
1328 $signature_verification = verify_file_signature( $tmpfname, $signature, $url_filename );
1329 }
1330
1331 if ( is_wp_error( $signature_verification ) ) {
1332 if (
1333 /**
1334 * Filters whether Signature Verification failures should be allowed to soft fail.
1335 *
1336 * WARNING: This may be removed from a future release.
1337 *
1338 * @since 5.2.0
1339 *
1340 * @param bool $signature_softfail If a softfail is allowed.
1341 * @param string $url The url being accessed.
1342 */
1343 apply_filters( 'wp_signature_softfail', true, $url )
1344 ) {
1345 $signature_verification->add_data( $tmpfname, 'softfail-filename' );
1346 } else {
1347 // Hard-fail.
1348 unlink( $tmpfname );
1349 }
1350
1351 return $signature_verification;
1352 }
1353
1354 return $tmpfname;
1355}
1356
1357/**
1358 * Calculates and compares the MD5 of a file to its expected value.
1359 *
1360 * @since 3.7.0
1361 *
1362 * @param string $filename The filename to check the MD5 of.
1363 * @param string $expected_md5 The expected MD5 of the file, either a base64-encoded raw md5,
1364 * or a hex-encoded md5.
1365 * @return bool|WP_Error True on success, false when the MD5 format is unknown/unexpected,
1366 * WP_Error on failure.
1367 */
1368function verify_file_md5( $filename, $expected_md5 ) {
1369 if ( 32 === strlen( $expected_md5 ) ) {
1370 $expected_raw_md5 = pack( 'H*', $expected_md5 );
1371 } elseif ( 24 === strlen( $expected_md5 ) ) {
1372 $expected_raw_md5 = base64_decode( $expected_md5 );
1373 } else {
1374 return false; // Unknown format.
1375 }
1376
1377 $file_md5 = md5_file( $filename, true );
1378
1379 if ( $file_md5 === $expected_raw_md5 ) {
1380 return true;
1381 }
1382
1383 return new WP_Error(
1384 'md5_mismatch',
1385 sprintf(
1386 /* translators: 1: File checksum, 2: Expected checksum value. */
1387 __( 'The checksum of the file (%1$s) does not match the expected checksum value (%2$s).' ),
1388 bin2hex( $file_md5 ),
1389 bin2hex( $expected_raw_md5 )
1390 )
1391 );
1392}
1393
1394/**
1395 * Verifies the contents of a file against its ED25519 signature.
1396 *
1397 * @since 5.2.0
1398 *
1399 * @param string $filename The file to validate.
1400 * @param string|array $signatures A Signature provided for the file.
1401 * @param string|false $filename_for_errors Optional. A friendly filename for errors.
1402 * @return bool|WP_Error True on success, false if verification not attempted,
1403 * or WP_Error describing an error condition.
1404 */
1405function verify_file_signature( $filename, $signatures, $filename_for_errors = false ) {
1406 if ( ! $filename_for_errors ) {
1407 $filename_for_errors = wp_basename( $filename );
1408 }
1409
1410 // Check we can process signatures.
1411 if ( ! function_exists( 'sodium_crypto_sign_verify_detached' ) || ! in_array( 'sha384', array_map( 'strtolower', hash_algos() ), true ) ) {
1412 return new WP_Error(
1413 'signature_verification_unsupported',
1414 sprintf(
1415 /* translators: %s: The filename of the package. */
1416 __( 'The authenticity of %s could not be verified as signature verification is unavailable on this system.' ),
1417 '<span class="code">' . esc_html( $filename_for_errors ) . '</span>'
1418 ),
1419 ( ! function_exists( 'sodium_crypto_sign_verify_detached' ) ? 'sodium_crypto_sign_verify_detached' : 'sha384' )
1420 );
1421 }
1422
1423 // Verify runtime speed of Sodium_Compat is acceptable.
1424 if ( ! extension_loaded( 'sodium' ) && ! ParagonIE_Sodium_Compat::polyfill_is_fast() ) {
1425 $sodium_compat_is_fast = false;
1426
1427 // Allow for an old version of Sodium_Compat being loaded before the bundled WordPress one.
1428 if ( method_exists( 'ParagonIE_Sodium_Compat', 'runtime_speed_test' ) ) {
1429 /*
1430 * Run `ParagonIE_Sodium_Compat::runtime_speed_test()` in optimized integer mode,
1431 * as that's what WordPress utilizes during signing verifications.
1432 */
1433 // phpcs:disable WordPress.NamingConventions.ValidVariableName
1434 $old_fastMult = ParagonIE_Sodium_Compat::$fastMult;
1435 ParagonIE_Sodium_Compat::$fastMult = true;
1436 $sodium_compat_is_fast = ParagonIE_Sodium_Compat::runtime_speed_test( 100, 10 );
1437 ParagonIE_Sodium_Compat::$fastMult = $old_fastMult;
1438 // phpcs:enable
1439 }
1440
1441 /*
1442 * This cannot be performed in a reasonable amount of time.
1443 * https://github.com/paragonie/sodium_compat#help-sodium_compat-is-slow-how-can-i-make-it-fast
1444 */
1445 if ( ! $sodium_compat_is_fast ) {
1446 return new WP_Error(
1447 'signature_verification_unsupported',
1448 sprintf(
1449 /* translators: %s: The filename of the package. */
1450 __( 'The authenticity of %s could not be verified as signature verification is unavailable on this system.' ),
1451 '<span class="code">' . esc_html( $filename_for_errors ) . '</span>'
1452 ),
1453 array(
1454 'php' => PHP_VERSION,
1455 'sodium' => defined( 'SODIUM_LIBRARY_VERSION' ) ? SODIUM_LIBRARY_VERSION : ( defined( 'ParagonIE_Sodium_Compat::VERSION_STRING' ) ? ParagonIE_Sodium_Compat::VERSION_STRING : false ),
1456 'polyfill_is_fast' => false,
1457 'max_execution_time' => ini_get( 'max_execution_time' ),
1458 )
1459 );
1460 }
1461 }
1462
1463 if ( ! $signatures ) {
1464 return new WP_Error(
1465 'signature_verification_no_signature',
1466 sprintf(
1467 /* translators: %s: The filename of the package. */
1468 __( 'The authenticity of %s could not be verified as no signature was found.' ),
1469 '<span class="code">' . esc_html( $filename_for_errors ) . '</span>'
1470 ),
1471 array(
1472 'filename' => $filename_for_errors,
1473 )
1474 );
1475 }
1476
1477 $trusted_keys = wp_trusted_keys();
1478 $file_hash = hash_file( 'sha384', $filename, true );
1479
1480 mbstring_binary_safe_encoding();
1481
1482 $skipped_key = 0;
1483 $skipped_signature = 0;
1484
1485 foreach ( (array) $signatures as $signature ) {
1486 $signature_raw = base64_decode( $signature );
1487
1488 // Ensure only valid-length signatures are considered.
1489 if ( SODIUM_CRYPTO_SIGN_BYTES !== strlen( $signature_raw ) ) {
1490 ++$skipped_signature;
1491 continue;
1492 }
1493
1494 foreach ( (array) $trusted_keys as $key ) {
1495 $key_raw = base64_decode( $key );
1496
1497 // Only pass valid public keys through.
1498 if ( SODIUM_CRYPTO_SIGN_PUBLICKEYBYTES !== strlen( $key_raw ) ) {
1499 ++$skipped_key;
1500 continue;
1501 }
1502
1503 if ( sodium_crypto_sign_verify_detached( $signature_raw, $file_hash, $key_raw ) ) {
1504 reset_mbstring_encoding();
1505 return true;
1506 }
1507 }
1508 }
1509
1510 reset_mbstring_encoding();
1511
1512 return new WP_Error(
1513 'signature_verification_failed',
1514 sprintf(
1515 /* translators: %s: The filename of the package. */
1516 __( 'The authenticity of %s could not be verified.' ),
1517 '<span class="code">' . esc_html( $filename_for_errors ) . '</span>'
1518 ),
1519 // Error data helpful for debugging:
1520 array(
1521 'filename' => $filename_for_errors,
1522 'keys' => $trusted_keys,
1523 'signatures' => $signatures,
1524 'hash' => bin2hex( $file_hash ),
1525 'skipped_key' => $skipped_key,
1526 'skipped_sig' => $skipped_signature,
1527 'php' => PHP_VERSION,
1528 'sodium' => defined( 'SODIUM_LIBRARY_VERSION' ) ? SODIUM_LIBRARY_VERSION : ( defined( 'ParagonIE_Sodium_Compat::VERSION_STRING' ) ? ParagonIE_Sodium_Compat::VERSION_STRING : false ),
1529 )
1530 );
1531}
1532
1533/**
1534 * Retrieves the list of signing keys trusted by WordPress.
1535 *
1536 * @since 5.2.0
1537 *
1538 * @return string[] Array of base64-encoded signing keys.
1539 */
1540function wp_trusted_keys() {
1541 $trusted_keys = array();
1542
1543 if ( time() < 1617235200 ) {
1544 // WordPress.org Key #1 - This key is only valid before April 1st, 2021.
1545 $trusted_keys[] = 'fRPyrxb/MvVLbdsYi+OOEv4xc+Eqpsj+kkAS6gNOkI0=';
1546 }
1547
1548 // TODO: Add key #2 with longer expiration.
1549
1550 /**
1551 * Filters the valid signing keys used to verify the contents of files.
1552 *
1553 * @since 5.2.0
1554 *
1555 * @param string[] $trusted_keys The trusted keys that may sign packages.
1556 */
1557 return apply_filters( 'wp_trusted_keys', $trusted_keys );
1558}
1559
1560/**
1561 * Determines whether the given file is a valid ZIP file.
1562 *
1563 * This function does not test to ensure that a file exists. Non-existent files
1564 * are not valid ZIPs, so those will also return false.
1565 *
1566 * @since 6.4.4
1567 *
1568 * @param string $file Full path to the ZIP file.
1569 * @return bool Whether the file is a valid ZIP file.
1570 */
1571function wp_zip_file_is_valid( $file ) {
1572 /** This filter is documented in wp-admin/includes/file.php */
1573 if ( class_exists( 'ZipArchive', false ) && apply_filters( 'unzip_file_use_ziparchive', true ) ) {
1574 $archive = new ZipArchive();
1575 $archive_is_valid = $archive->open( $file, ZipArchive::CHECKCONS );
1576 if ( true === $archive_is_valid ) {
1577 $archive->close();
1578 return true;
1579 }
1580 }
1581
1582 // Fall through to PclZip if ZipArchive is not available, or encountered an error opening the file.
1583 require_once ABSPATH . 'wp-admin/includes/class-pclzip.php';
1584
1585 $archive = new PclZip( $file );
1586 $archive_is_valid = is_array( $archive->properties() );
1587
1588 return $archive_is_valid;
1589}
1590
1591/**
1592 * Unzips a specified ZIP file to a location on the filesystem via the WordPress
1593 * Filesystem Abstraction.
1594 *
1595 * Assumes that WP_Filesystem() has already been called and set up. Does not extract
1596 * a root-level __MACOSX directory, if present.
1597 *
1598 * Attempts to increase the PHP memory limit to 256M before uncompressing. However,
1599 * the most memory required shouldn't be much larger than the archive itself.
1600 *
1601 * @since 2.5.0
1602 *
1603 * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
1604 *
1605 * @param string $file Full path and filename of ZIP archive.
1606 * @param string $to Full path on the filesystem to extract archive to.
1607 * @return true|WP_Error True on success, WP_Error on failure.
1608 */
1609function unzip_file( $file, $to ) {
1610 global $wp_filesystem;
1611
1612 if ( ! $wp_filesystem || ! is_object( $wp_filesystem ) ) {
1613 return new WP_Error( 'fs_unavailable', __( 'Could not access filesystem.' ) );
1614 }
1615
1616 // Unzip can use a lot of memory, but not this much hopefully.
1617 wp_raise_memory_limit( 'admin' );
1618
1619 $needed_dirs = array();
1620 $to = trailingslashit( $to );
1621
1622 // Determine any parent directories needed (of the upgrade directory).
1623 if ( ! $wp_filesystem->is_dir( $to ) ) { // Only do parents if no children exist.
1624 $path = preg_split( '![/\\\]!', untrailingslashit( $to ) );
1625 for ( $i = count( $path ); $i >= 0; $i-- ) {
1626 if ( empty( $path[ $i ] ) ) {
1627 continue;
1628 }
1629
1630 $dir = implode( '/', array_slice( $path, 0, $i + 1 ) );
1631 if ( preg_match( '!^[a-z]:$!i', $dir ) ) { // Skip it if it looks like a Windows Drive letter.
1632 continue;
1633 }
1634
1635 if ( ! $wp_filesystem->is_dir( $dir ) ) {
1636 $needed_dirs[] = $dir;
1637 } else {
1638 break; // A folder exists, therefore we don't need to check the levels below this.
1639 }
1640 }
1641 }
1642
1643 /**
1644 * Filters whether to use ZipArchive to unzip archives.
1645 *
1646 * @since 3.0.0
1647 *
1648 * @param bool $ziparchive Whether to use ZipArchive. Default true.
1649 */
1650 if ( class_exists( 'ZipArchive', false ) && apply_filters( 'unzip_file_use_ziparchive', true ) ) {
1651 $result = _unzip_file_ziparchive( $file, $to, $needed_dirs );
1652 if ( true === $result ) {
1653 return $result;
1654 } elseif ( is_wp_error( $result ) ) {
1655 if ( 'incompatible_archive' !== $result->get_error_code() ) {
1656 return $result;
1657 }
1658 }
1659 }
1660 // Fall through to PclZip if ZipArchive is not available, or encountered an error opening the file.
1661 return _unzip_file_pclzip( $file, $to, $needed_dirs );
1662}
1663
1664/**
1665 * Attempts to unzip an archive using the ZipArchive class.
1666 *
1667 * This function should not be called directly, use `unzip_file()` instead.
1668 *
1669 * Assumes that WP_Filesystem() has already been called and set up.
1670 *
1671 * @since 3.0.0
1672 * @access private
1673 *
1674 * @see unzip_file()
1675 *
1676 * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
1677 *
1678 * @param string $file Full path and filename of ZIP archive.
1679 * @param string $to Full path on the filesystem to extract archive to.
1680 * @param string[] $needed_dirs A partial list of required folders needed to be created.
1681 * @return true|WP_Error True on success, WP_Error on failure.
1682 */
1683function _unzip_file_ziparchive( $file, $to, $needed_dirs = array() ) {
1684 global $wp_filesystem;
1685
1686 $z = new ZipArchive();
1687
1688 $zopen = $z->open( $file, ZIPARCHIVE::CHECKCONS );
1689
1690 if ( true !== $zopen ) {
1691 return new WP_Error( 'incompatible_archive', __( 'Incompatible Archive.' ), array( 'ziparchive_error' => $zopen ) );
1692 }
1693
1694 $uncompressed_size = 0;
1695
1696 for ( $i = 0; $i < $z->numFiles; $i++ ) {
1697 $info = $z->statIndex( $i );
1698
1699 if ( ! $info ) {
1700 $z->close();
1701 return new WP_Error( 'stat_failed_ziparchive', __( 'Could not retrieve file from archive.' ) );
1702 }
1703
1704 if ( str_starts_with( $info['name'], '__MACOSX/' ) ) { // Skip the OS X-created __MACOSX directory.
1705 continue;
1706 }
1707
1708 // Don't extract invalid files:
1709 if ( 0 !== validate_file( $info['name'] ) ) {
1710 continue;
1711 }
1712
1713 $uncompressed_size += $info['size'];
1714
1715 $dirname = dirname( $info['name'] );
1716
1717 if ( str_ends_with( $info['name'], '/' ) ) {
1718 // Directory.
1719 $needed_dirs[] = $to . untrailingslashit( $info['name'] );
1720 } elseif ( '.' !== $dirname ) {
1721 // Path to a file.
1722 $needed_dirs[] = $to . untrailingslashit( $dirname );
1723 }
1724 }
1725
1726 // Enough space to unzip the file and copy its contents, with a 10% buffer.
1727 $required_space = $uncompressed_size * 2.1;
1728
1729 /*
1730 * disk_free_space() could return false. Assume that any falsey value is an error.
1731 * A disk that has zero free bytes has bigger problems.
1732 * Require we have enough space to unzip the file and copy its contents, with a 10% buffer.
1733 */
1734 if ( wp_doing_cron() ) {
1735 $available_space = function_exists( 'disk_free_space' ) ? @disk_free_space( WP_CONTENT_DIR ) : false;
1736
1737 if ( $available_space && ( $required_space > $available_space ) ) {
1738 $z->close();
1739 return new WP_Error(
1740 'disk_full_unzip_file',
1741 __( 'Could not copy files. You may have run out of disk space.' ),
1742 compact( 'uncompressed_size', 'available_space' )
1743 );
1744 }
1745 }
1746
1747 $needed_dirs = array_unique( $needed_dirs );
1748
1749 foreach ( $needed_dirs as $dir ) {
1750 // Check the parent folders of the folders all exist within the creation array.
1751 if ( untrailingslashit( $to ) === $dir ) { // Skip over the working directory, we know this exists (or will exist).
1752 continue;
1753 }
1754
1755 if ( ! str_contains( $dir, $to ) ) { // If the directory is not within the working directory, skip it.
1756 continue;
1757 }
1758
1759 $parent_folder = dirname( $dir );
1760
1761 while ( ! empty( $parent_folder )
1762 && untrailingslashit( $to ) !== $parent_folder
1763 && ! in_array( $parent_folder, $needed_dirs, true )
1764 ) {
1765 $needed_dirs[] = $parent_folder;
1766 $parent_folder = dirname( $parent_folder );
1767 }
1768 }
1769
1770 asort( $needed_dirs );
1771
1772 // Create those directories if need be:
1773 foreach ( $needed_dirs as $_dir ) {
1774 // Only check to see if the Dir exists upon creation failure. Less I/O this way.
1775 if ( ! $wp_filesystem->mkdir( $_dir, FS_CHMOD_DIR ) && ! $wp_filesystem->is_dir( $_dir ) ) {
1776 $z->close();
1777 return new WP_Error( 'mkdir_failed_ziparchive', __( 'Could not create directory.' ), $_dir );
1778 }
1779 }
1780
1781 /**
1782 * Filters archive unzipping to override with a custom process.
1783 *
1784 * @since 6.4.0
1785 *
1786 * @param null|true|WP_Error $result The result of the override. True on success, otherwise WP Error. Default null.
1787 * @param string $file Full path and filename of ZIP archive.
1788 * @param string $to Full path on the filesystem to extract archive to.
1789 * @param string[] $needed_dirs A full list of required folders that need to be created.
1790 * @param float $required_space The space required to unzip the file and copy its contents, with a 10% buffer.
1791 */
1792 $pre = apply_filters( 'pre_unzip_file', null, $file, $to, $needed_dirs, $required_space );
1793
1794 if ( null !== $pre ) {
1795 // Ensure the ZIP file archive has been closed.
1796 $z->close();
1797
1798 return $pre;
1799 }
1800
1801 for ( $i = 0; $i < $z->numFiles; $i++ ) {
1802 $info = $z->statIndex( $i );
1803
1804 if ( ! $info ) {
1805 $z->close();
1806 return new WP_Error( 'stat_failed_ziparchive', __( 'Could not retrieve file from archive.' ) );
1807 }
1808
1809 if ( str_ends_with( $info['name'], '/' ) ) { // Directory.
1810 continue;
1811 }
1812
1813 if ( str_starts_with( $info['name'], '__MACOSX/' ) ) { // Don't extract the OS X-created __MACOSX directory files.
1814 continue;
1815 }
1816
1817 // Don't extract invalid files:
1818 if ( 0 !== validate_file( $info['name'] ) ) {
1819 continue;
1820 }
1821
1822 $contents = $z->getFromIndex( $i );
1823
1824 if ( false === $contents ) {
1825 $z->close();
1826 return new WP_Error( 'extract_failed_ziparchive', __( 'Could not extract file from archive.' ), $info['name'] );
1827 }
1828
1829 if ( ! $wp_filesystem->put_contents( $to . $info['name'], $contents, FS_CHMOD_FILE ) ) {
1830 $z->close();
1831 return new WP_Error( 'copy_failed_ziparchive', __( 'Could not copy file.' ), $info['name'] );
1832 }
1833 }
1834
1835 $z->close();
1836
1837 /**
1838 * Filters the result of unzipping an archive.
1839 *
1840 * @since 6.4.0
1841 *
1842 * @param true|WP_Error $result The result of unzipping the archive. True on success, otherwise WP_Error. Default true.
1843 * @param string $file Full path and filename of ZIP archive.
1844 * @param string $to Full path on the filesystem the archive was extracted to.
1845 * @param string[] $needed_dirs A full list of required folders that were created.
1846 * @param float $required_space The space required to unzip the file and copy its contents, with a 10% buffer.
1847 */
1848 $result = apply_filters( 'unzip_file', true, $file, $to, $needed_dirs, $required_space );
1849
1850 unset( $needed_dirs );
1851
1852 return $result;
1853}
1854
1855/**
1856 * Attempts to unzip an archive using the PclZip library.
1857 *
1858 * This function should not be called directly, use `unzip_file()` instead.
1859 *
1860 * Assumes that WP_Filesystem() has already been called and set up.
1861 *
1862 * @since 3.0.0
1863 * @access private
1864 *
1865 * @see unzip_file()
1866 *
1867 * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
1868 *
1869 * @param string $file Full path and filename of ZIP archive.
1870 * @param string $to Full path on the filesystem to extract archive to.
1871 * @param string[] $needed_dirs A partial list of required folders needed to be created.
1872 * @return true|WP_Error True on success, WP_Error on failure.
1873 */
1874function _unzip_file_pclzip( $file, $to, $needed_dirs = array() ) {
1875 global $wp_filesystem;
1876
1877 mbstring_binary_safe_encoding();
1878
1879 require_once ABSPATH . 'wp-admin/includes/class-pclzip.php';
1880
1881 $archive = new PclZip( $file );
1882
1883 $archive_files = $archive->extract( PCLZIP_OPT_EXTRACT_AS_STRING );
1884
1885 reset_mbstring_encoding();
1886
1887 // Is the archive valid?
1888 if ( ! is_array( $archive_files ) ) {
1889 return new WP_Error( 'incompatible_archive', __( 'Incompatible Archive.' ), $archive->errorInfo( true ) );
1890 }
1891
1892 if ( 0 === count( $archive_files ) ) {
1893 return new WP_Error( 'empty_archive_pclzip', __( 'Empty archive.' ) );
1894 }
1895
1896 $uncompressed_size = 0;
1897
1898 // Determine any children directories needed (From within the archive).
1899 foreach ( $archive_files as $file ) {
1900 if ( str_starts_with( $file['filename'], '__MACOSX/' ) ) { // Skip the OS X-created __MACOSX directory.
1901 continue;
1902 }
1903
1904 // Don't extract invalid files:
1905 if ( 0 !== validate_file( $file['filename'] ) ) {
1906 continue;
1907 }
1908
1909 $uncompressed_size += $file['size'];
1910
1911 $needed_dirs[] = $to . untrailingslashit( $file['folder'] ? $file['filename'] : dirname( $file['filename'] ) );
1912 }
1913
1914 // Enough space to unzip the file and copy its contents, with a 10% buffer.
1915 $required_space = $uncompressed_size * 2.1;
1916
1917 /*
1918 * disk_free_space() could return false. Assume that any falsey value is an error.
1919 * A disk that has zero free bytes has bigger problems.
1920 * Require we have enough space to unzip the file and copy its contents, with a 10% buffer.
1921 */
1922 if ( wp_doing_cron() ) {
1923 $available_space = function_exists( 'disk_free_space' ) ? @disk_free_space( WP_CONTENT_DIR ) : false;
1924
1925 if ( $available_space && ( $required_space > $available_space ) ) {
1926 return new WP_Error(
1927 'disk_full_unzip_file',
1928 __( 'Could not copy files. You may have run out of disk space.' ),
1929 compact( 'uncompressed_size', 'available_space' )
1930 );
1931 }
1932 }
1933
1934 $needed_dirs = array_unique( $needed_dirs );
1935
1936 foreach ( $needed_dirs as $dir ) {
1937 // Check the parent folders of the folders all exist within the creation array.
1938 if ( untrailingslashit( $to ) === $dir ) { // Skip over the working directory, we know this exists (or will exist).
1939 continue;
1940 }
1941
1942 if ( ! str_contains( $dir, $to ) ) { // If the directory is not within the working directory, skip it.
1943 continue;
1944 }
1945
1946 $parent_folder = dirname( $dir );
1947
1948 while ( ! empty( $parent_folder )
1949 && untrailingslashit( $to ) !== $parent_folder
1950 && ! in_array( $parent_folder, $needed_dirs, true )
1951 ) {
1952 $needed_dirs[] = $parent_folder;
1953 $parent_folder = dirname( $parent_folder );
1954 }
1955 }
1956
1957 asort( $needed_dirs );
1958
1959 // Create those directories if need be:
1960 foreach ( $needed_dirs as $_dir ) {
1961 // Only check to see if the dir exists upon creation failure. Less I/O this way.
1962 if ( ! $wp_filesystem->mkdir( $_dir, FS_CHMOD_DIR ) && ! $wp_filesystem->is_dir( $_dir ) ) {
1963 return new WP_Error( 'mkdir_failed_pclzip', __( 'Could not create directory.' ), $_dir );
1964 }
1965 }
1966
1967 /** This filter is documented in src/wp-admin/includes/file.php */
1968 $pre = apply_filters( 'pre_unzip_file', null, $file, $to, $needed_dirs, $required_space );
1969
1970 if ( null !== $pre ) {
1971 return $pre;
1972 }
1973
1974 // Extract the files from the zip.
1975 foreach ( $archive_files as $file ) {
1976 if ( $file['folder'] ) {
1977 continue;
1978 }
1979
1980 if ( str_starts_with( $file['filename'], '__MACOSX/' ) ) { // Don't extract the OS X-created __MACOSX directory files.
1981 continue;
1982 }
1983
1984 // Don't extract invalid files:
1985 if ( 0 !== validate_file( $file['filename'] ) ) {
1986 continue;
1987 }
1988
1989 if ( ! $wp_filesystem->put_contents( $to . $file['filename'], $file['content'], FS_CHMOD_FILE ) ) {
1990 return new WP_Error( 'copy_failed_pclzip', __( 'Could not copy file.' ), $file['filename'] );
1991 }
1992 }
1993
1994 /** This action is documented in src/wp-admin/includes/file.php */
1995 $result = apply_filters( 'unzip_file', true, $file, $to, $needed_dirs, $required_space );
1996
1997 unset( $needed_dirs );
1998
1999 return $result;
2000}
2001
2002/**
2003 * Copies a directory from one location to another via the WordPress Filesystem
2004 * Abstraction.
2005 *
2006 * Assumes that WP_Filesystem() has already been called and setup.
2007 *
2008 * @since 2.5.0
2009 *
2010 * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
2011 *
2012 * @param string $from Source directory.
2013 * @param string $to Destination directory.
2014 * @param string[] $skip_list An array of files/folders to skip copying.
2015 * @return true|WP_Error True on success, WP_Error on failure.
2016 */
2017function copy_dir( $from, $to, $skip_list = array() ) {
2018 global $wp_filesystem;
2019
2020 $dirlist = $wp_filesystem->dirlist( $from );
2021
2022 if ( false === $dirlist ) {
2023 return new WP_Error( 'dirlist_failed_copy_dir', __( 'Directory listing failed.' ), basename( $from ) );
2024 }
2025
2026 $from = trailingslashit( $from );
2027 $to = trailingslashit( $to );
2028
2029 if ( ! $wp_filesystem->exists( $to ) && ! $wp_filesystem->mkdir( $to ) ) {
2030 return new WP_Error(
2031 'mkdir_destination_failed_copy_dir',
2032 __( 'Could not create the destination directory.' ),
2033 basename( $to )
2034 );
2035 }
2036
2037 foreach ( (array) $dirlist as $filename => $fileinfo ) {
2038 if ( in_array( $filename, $skip_list, true ) ) {
2039 continue;
2040 }
2041
2042 if ( 'f' === $fileinfo['type'] ) {
2043 if ( ! $wp_filesystem->copy( $from . $filename, $to . $filename, true, FS_CHMOD_FILE ) ) {
2044 // If copy failed, chmod file to 0644 and try again.
2045 $wp_filesystem->chmod( $to . $filename, FS_CHMOD_FILE );
2046
2047 if ( ! $wp_filesystem->copy( $from . $filename, $to . $filename, true, FS_CHMOD_FILE ) ) {
2048 return new WP_Error( 'copy_failed_copy_dir', __( 'Could not copy file.' ), $to . $filename );
2049 }
2050 }
2051
2052 wp_opcache_invalidate( $to . $filename );
2053 } elseif ( 'd' === $fileinfo['type'] ) {
2054 if ( ! $wp_filesystem->is_dir( $to . $filename ) ) {
2055 if ( ! $wp_filesystem->mkdir( $to . $filename, FS_CHMOD_DIR ) ) {
2056 return new WP_Error( 'mkdir_failed_copy_dir', __( 'Could not create directory.' ), $to . $filename );
2057 }
2058 }
2059
2060 // Generate the $sub_skip_list for the subdirectory as a sub-set of the existing $skip_list.
2061 $sub_skip_list = array();
2062
2063 foreach ( $skip_list as $skip_item ) {
2064 if ( str_starts_with( $skip_item, $filename . '/' ) ) {
2065 $sub_skip_list[] = preg_replace( '!^' . preg_quote( $filename, '!' ) . '/!i', '', $skip_item );
2066 }
2067 }
2068
2069 $result = copy_dir( $from . $filename, $to . $filename, $sub_skip_list );
2070
2071 if ( is_wp_error( $result ) ) {
2072 return $result;
2073 }
2074 }
2075 }
2076
2077 return true;
2078}
2079
2080/**
2081 * Moves a directory from one location to another.
2082 *
2083 * Recursively invalidates OPcache on success.
2084 *
2085 * If the renaming failed, falls back to copy_dir().
2086 *
2087 * Assumes that WP_Filesystem() has already been called and setup.
2088 *
2089 * This function is not designed to merge directories, copy_dir() should be used instead.
2090 *
2091 * @since 6.2.0
2092 *
2093 * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
2094 *
2095 * @param string $from Source directory.
2096 * @param string $to Destination directory.
2097 * @param bool $overwrite Optional. Whether to overwrite the destination directory if it exists.
2098 * Default false.
2099 * @return true|WP_Error True on success, WP_Error on failure.
2100 */
2101function move_dir( $from, $to, $overwrite = false ) {
2102 global $wp_filesystem;
2103
2104 if ( trailingslashit( strtolower( $from ) ) === trailingslashit( strtolower( $to ) ) ) {
2105 return new WP_Error( 'source_destination_same_move_dir', __( 'The source and destination are the same.' ) );
2106 }
2107
2108 if ( $wp_filesystem->exists( $to ) ) {
2109 if ( ! $overwrite ) {
2110 return new WP_Error( 'destination_already_exists_move_dir', __( 'The destination folder already exists.' ), $to );
2111 } elseif ( ! $wp_filesystem->delete( $to, true ) ) {
2112 // Can't overwrite if the destination couldn't be deleted.
2113 return new WP_Error( 'destination_not_deleted_move_dir', __( 'The destination directory already exists and could not be removed.' ) );
2114 }
2115 }
2116
2117 if ( $wp_filesystem->move( $from, $to ) ) {
2118 /*
2119 * When using an environment with shared folders,
2120 * there is a delay in updating the filesystem's cache.
2121 *
2122 * This is a known issue in environments with a VirtualBox provider.
2123 *
2124 * A 200ms delay gives time for the filesystem to update its cache,
2125 * prevents "Operation not permitted", and "No such file or directory" warnings.
2126 *
2127 * This delay is used in other projects, including Composer.
2128 * @link https://github.com/composer/composer/blob/2.5.1/src/Composer/Util/Platform.php#L228-L233
2129 */
2130 usleep( 200000 );
2131 wp_opcache_invalidate_directory( $to );
2132
2133 return true;
2134 }
2135
2136 // Fall back to a recursive copy.
2137 if ( ! $wp_filesystem->is_dir( $to ) ) {
2138 if ( ! $wp_filesystem->mkdir( $to, FS_CHMOD_DIR ) ) {
2139 return new WP_Error( 'mkdir_failed_move_dir', __( 'Could not create directory.' ), $to );
2140 }
2141 }
2142
2143 $result = copy_dir( $from, $to, array( basename( $to ) ) );
2144
2145 // Clear the source directory.
2146 if ( true === $result ) {
2147 $wp_filesystem->delete( $from, true );
2148 }
2149
2150 return $result;
2151}
2152
2153/**
2154 * Initializes and connects the WordPress Filesystem Abstraction classes.
2155 *
2156 * This function will include the chosen transport and attempt connecting.
2157 *
2158 * Plugins may add extra transports, And force WordPress to use them by returning
2159 * the filename via the {@see 'filesystem_method_file'} filter.
2160 *
2161 * @since 2.5.0
2162 *
2163 * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
2164 *
2165 * @param array|false $args Optional. Connection args, These are passed
2166 * directly to the `WP_Filesystem_*()` classes.
2167 * Default false.
2168 * @param string|false $context Optional. Context for get_filesystem_method().
2169 * Default false.
2170 * @param bool $allow_relaxed_file_ownership Optional. Whether to allow Group/World writable.
2171 * Default false.
2172 * @return bool|null True on success, false on failure,
2173 * null if the filesystem method class file does not exist.
2174 */
2175function WP_Filesystem( $args = false, $context = false, $allow_relaxed_file_ownership = false ) { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionNameInvalid
2176 global $wp_filesystem;
2177
2178 require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php';
2179
2180 $method = get_filesystem_method( $args, $context, $allow_relaxed_file_ownership );
2181
2182 if ( ! $method ) {
2183 return false;
2184 }
2185
2186 if ( ! class_exists( "WP_Filesystem_$method" ) ) {
2187
2188 /**
2189 * Filters the path for a specific filesystem method class file.
2190 *
2191 * @since 2.6.0
2192 *
2193 * @see get_filesystem_method()
2194 *
2195 * @param string $path Path to the specific filesystem method class file.
2196 * @param string $method The filesystem method to use.
2197 */
2198 $abstraction_file = apply_filters( 'filesystem_method_file', ABSPATH . 'wp-admin/includes/class-wp-filesystem-' . $method . '.php', $method );
2199
2200 if ( ! file_exists( $abstraction_file ) ) {
2201 return;
2202 }
2203
2204 require_once $abstraction_file;
2205 }
2206 $method = "WP_Filesystem_$method";
2207
2208 $wp_filesystem = new $method( $args );
2209
2210 /*
2211 * Define the timeouts for the connections. Only available after the constructor is called
2212 * to allow for per-transport overriding of the default.
2213 */
2214 if ( ! defined( 'FS_CONNECT_TIMEOUT' ) ) {
2215 define( 'FS_CONNECT_TIMEOUT', 30 ); // 30 seconds.
2216 }
2217 if ( ! defined( 'FS_TIMEOUT' ) ) {
2218 define( 'FS_TIMEOUT', 30 ); // 30 seconds.
2219 }
2220
2221 if ( is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
2222 return false;
2223 }
2224
2225 if ( ! $wp_filesystem->connect() ) {
2226 return false; // There was an error connecting to the server.
2227 }
2228
2229 // Set the permission constants if not already set.
2230 if ( ! defined( 'FS_CHMOD_DIR' ) ) {
2231 define( 'FS_CHMOD_DIR', ( fileperms( ABSPATH ) & 0777 | 0755 ) );
2232 }
2233 if ( ! defined( 'FS_CHMOD_FILE' ) ) {
2234 define( 'FS_CHMOD_FILE', ( fileperms( ABSPATH . 'index.php' ) & 0777 | 0644 ) );
2235 }
2236
2237 return true;
2238}
2239
2240/**
2241 * Determines which method to use for reading, writing, modifying, or deleting
2242 * files on the filesystem.
2243 *
2244 * The priority of the transports are: Direct, SSH2, FTP PHP Extension, FTP Sockets
2245 * (Via Sockets class, or `fsockopen()`). Valid values for these are: 'direct', 'ssh2',
2246 * 'ftpext' or 'ftpsockets'.
2247 *
2248 * The return value can be overridden by defining the `FS_METHOD` constant in `wp-config.php`,
2249 * or filtering via {@see 'filesystem_method'}.
2250 *
2251 * @link https://developer.wordpress.org/advanced-administration/wordpress/wp-config/#wordpress-upgrade-constants
2252 *
2253 * Plugins may define a custom transport handler, See WP_Filesystem().
2254 *
2255 * @since 2.5.0
2256 *
2257 * @global callable $_wp_filesystem_direct_method
2258 *
2259 * @param array $args Optional. Connection details. Default empty array.
2260 * @param string $context Optional. Full path to the directory that is tested
2261 * for being writable. Default empty.
2262 * @param bool $allow_relaxed_file_ownership Optional. Whether to allow Group/World writable.
2263 * Default false.
2264 * @return string The transport to use, see description for valid return values.
2265 */
2266function get_filesystem_method( $args = array(), $context = '', $allow_relaxed_file_ownership = false ) {
2267 // Please ensure that this is either 'direct', 'ssh2', 'ftpext', or 'ftpsockets'.
2268 $method = defined( 'FS_METHOD' ) ? FS_METHOD : false;
2269
2270 if ( ! $context ) {
2271 $context = WP_CONTENT_DIR;
2272 }
2273
2274 // If the directory doesn't exist (wp-content/languages) then use the parent directory as we'll create it.
2275 if ( WP_LANG_DIR === $context && ! is_dir( $context ) ) {
2276 $context = dirname( $context );
2277 }
2278
2279 $context = trailingslashit( $context );
2280
2281 if ( ! $method ) {
2282
2283 $temp_file_name = $context . 'temp-write-test-' . str_replace( '.', '-', uniqid( '', true ) );
2284 $temp_handle = @fopen( $temp_file_name, 'w' );
2285 if ( $temp_handle ) {
2286
2287 // Attempt to determine the file owner of the WordPress files, and that of newly created files.
2288 $wp_file_owner = false;
2289 $temp_file_owner = false;
2290 if ( function_exists( 'fileowner' ) ) {
2291 $wp_file_owner = @fileowner( __FILE__ );
2292 $temp_file_owner = @fileowner( $temp_file_name );
2293 }
2294
2295 if ( false !== $wp_file_owner && $wp_file_owner === $temp_file_owner ) {
2296 /*
2297 * WordPress is creating files as the same owner as the WordPress files,
2298 * this means it's safe to modify & create new files via PHP.
2299 */
2300 $method = 'direct';
2301 $GLOBALS['_wp_filesystem_direct_method'] = 'file_owner';
2302 } elseif ( $allow_relaxed_file_ownership ) {
2303 /*
2304 * The $context directory is writable, and $allow_relaxed_file_ownership is set,
2305 * this means we can modify files safely in this directory.
2306 * This mode doesn't create new files, only alter existing ones.
2307 */
2308 $method = 'direct';
2309 $GLOBALS['_wp_filesystem_direct_method'] = 'relaxed_ownership';
2310 }
2311
2312 fclose( $temp_handle );
2313 @unlink( $temp_file_name );
2314 }
2315 }
2316
2317 if ( ! $method && isset( $args['connection_type'] ) && 'ssh' === $args['connection_type'] && extension_loaded( 'ssh2' ) ) {
2318 $method = 'ssh2';
2319 }
2320 if ( ! $method && extension_loaded( 'ftp' ) ) {
2321 $method = 'ftpext';
2322 }
2323 if ( ! $method && ( extension_loaded( 'sockets' ) || function_exists( 'fsockopen' ) ) ) {
2324 $method = 'ftpsockets'; // Sockets: Socket extension; PHP Mode: FSockopen / fwrite / fread.
2325 }
2326
2327 /**
2328 * Filters the filesystem method to use.
2329 *
2330 * @since 2.6.0
2331 *
2332 * @param string $method Filesystem method to return.
2333 * @param array $args An array of connection details for the method.
2334 * @param string $context Full path to the directory that is tested for being writable.
2335 * @param bool $allow_relaxed_file_ownership Whether to allow Group/World writable.
2336 */
2337 return apply_filters( 'filesystem_method', $method, $args, $context, $allow_relaxed_file_ownership );
2338}
2339
2340/**
2341 * Displays a form to the user to request for their FTP/SSH details in order
2342 * to connect to the filesystem.
2343 *
2344 * All chosen/entered details are saved, excluding the password.
2345 *
2346 * Hostnames may be in the form of hostname:portnumber (eg: wordpress.org:2467)
2347 * to specify an alternate FTP/SSH port.
2348 *
2349 * Plugins may override this form by returning true|false via the {@see 'request_filesystem_credentials'} filter.
2350 *
2351 * @since 2.5.0
2352 * @since 4.6.0 The `$context` parameter default changed from `false` to an empty string.
2353 *
2354 * @global string $pagenow The filename of the current screen.
2355 *
2356 * @param string $form_post The URL to post the form to.
2357 * @param string $type Optional. Chosen type of filesystem. Default empty.
2358 * @param bool|WP_Error $error Optional. Whether the current request has failed
2359 * to connect, or an error object. Default false.
2360 * @param string $context Optional. Full path to the directory that is tested
2361 * for being writable. Default empty.
2362 * @param array $extra_fields Optional. Extra `POST` fields to be checked
2363 * for inclusion in the post. Default null.
2364 * @param bool $allow_relaxed_file_ownership Optional. Whether to allow Group/World writable.
2365 * Default false.
2366 * @return bool|array True if no filesystem credentials are required,
2367 * false if they are required but have not been provided,
2368 * array of credentials if they are required and have been provided.
2369 */
2370function request_filesystem_credentials( $form_post, $type = '', $error = false, $context = '', $extra_fields = null, $allow_relaxed_file_ownership = false ) {
2371 global $pagenow;
2372
2373 /**
2374 * Filters the filesystem credentials.
2375 *
2376 * Returning anything other than an empty string will effectively short-circuit
2377 * output of the filesystem credentials form, returning that value instead.
2378 *
2379 * A filter should return true if no filesystem credentials are required, false if they are required but have not been
2380 * provided, or an array of credentials if they are required and have been provided.
2381 *
2382 * @since 2.5.0
2383 * @since 4.6.0 The `$context` parameter default changed from `false` to an empty string.
2384 *
2385 * @param mixed $credentials Credentials to return instead. Default empty string.
2386 * @param string $form_post The URL to post the form to.
2387 * @param string $type Chosen type of filesystem.
2388 * @param bool|WP_Error $error Whether the current request has failed to connect,
2389 * or an error object.
2390 * @param string $context Full path to the directory that is tested for
2391 * being writable.
2392 * @param array $extra_fields Extra POST fields.
2393 * @param bool $allow_relaxed_file_ownership Whether to allow Group/World writable.
2394 */
2395 $req_cred = apply_filters( 'request_filesystem_credentials', '', $form_post, $type, $error, $context, $extra_fields, $allow_relaxed_file_ownership );
2396
2397 if ( '' !== $req_cred ) {
2398 return $req_cred;
2399 }
2400
2401 if ( empty( $type ) ) {
2402 $type = get_filesystem_method( array(), $context, $allow_relaxed_file_ownership );
2403 }
2404
2405 if ( 'direct' === $type ) {
2406 return true;
2407 }
2408
2409 if ( is_null( $extra_fields ) ) {
2410 $extra_fields = array( 'version', 'locale' );
2411 }
2412
2413 $credentials = get_option(
2414 'ftp_credentials',
2415 array(
2416 'hostname' => '',
2417 'username' => '',
2418 )
2419 );
2420
2421 $submitted_form = wp_unslash( $_POST );
2422
2423 // Verify nonce, or unset submitted form field values on failure.
2424 if ( ! isset( $_POST['_fs_nonce'] ) || ! wp_verify_nonce( $_POST['_fs_nonce'], 'filesystem-credentials' ) ) {
2425 unset(
2426 $submitted_form['hostname'],
2427 $submitted_form['username'],
2428 $submitted_form['password'],
2429 $submitted_form['public_key'],
2430 $submitted_form['private_key'],
2431 $submitted_form['connection_type']
2432 );
2433 }
2434
2435 $ftp_constants = array(
2436 'hostname' => 'FTP_HOST',
2437 'username' => 'FTP_USER',
2438 'password' => 'FTP_PASS',
2439 'public_key' => 'FTP_PUBKEY',
2440 'private_key' => 'FTP_PRIKEY',
2441 );
2442
2443 /*
2444 * If defined, set it to that. Else, if POST'd, set it to that. If not, set it to an empty string.
2445 * Otherwise, keep it as it previously was (saved details in option).
2446 */
2447 foreach ( $ftp_constants as $key => $constant ) {
2448 if ( defined( $constant ) ) {
2449 $credentials[ $key ] = constant( $constant );
2450 } elseif ( ! empty( $submitted_form[ $key ] ) ) {
2451 $credentials[ $key ] = $submitted_form[ $key ];
2452 } elseif ( ! isset( $credentials[ $key ] ) ) {
2453 $credentials[ $key ] = '';
2454 }
2455 }
2456
2457 // Sanitize the hostname, some people might pass in odd data.
2458 $credentials['hostname'] = preg_replace( '|\w+://|', '', $credentials['hostname'] ); // Strip any schemes off.
2459
2460 if ( strpos( $credentials['hostname'], ':' ) ) {
2461 list( $credentials['hostname'], $credentials['port'] ) = explode( ':', $credentials['hostname'], 2 );
2462 if ( ! is_numeric( $credentials['port'] ) ) {
2463 unset( $credentials['port'] );
2464 }
2465 } else {
2466 unset( $credentials['port'] );
2467 }
2468
2469 if ( ( defined( 'FTP_SSH' ) && FTP_SSH ) || ( defined( 'FS_METHOD' ) && 'ssh2' === FS_METHOD ) ) {
2470 $credentials['connection_type'] = 'ssh';
2471 } elseif ( ( defined( 'FTP_SSL' ) && FTP_SSL ) && 'ftpext' === $type ) { // Only the FTP Extension understands SSL.
2472 $credentials['connection_type'] = 'ftps';
2473 } elseif ( ! empty( $submitted_form['connection_type'] ) ) {
2474 $credentials['connection_type'] = $submitted_form['connection_type'];
2475 } elseif ( ! isset( $credentials['connection_type'] ) ) { // All else fails (and it's not defaulted to something else saved), default to FTP.
2476 $credentials['connection_type'] = 'ftp';
2477 }
2478
2479 if ( ! $error
2480 && ( ! empty( $credentials['hostname'] ) && ! empty( $credentials['username'] ) && ! empty( $credentials['password'] )
2481 || 'ssh' === $credentials['connection_type'] && ! empty( $credentials['public_key'] ) && ! empty( $credentials['private_key'] )
2482 )
2483 ) {
2484 $stored_credentials = $credentials;
2485
2486 if ( ! empty( $stored_credentials['port'] ) ) { // Save port as part of hostname to simplify above code.
2487 $stored_credentials['hostname'] .= ':' . $stored_credentials['port'];
2488 }
2489
2490 unset(
2491 $stored_credentials['password'],
2492 $stored_credentials['port'],
2493 $stored_credentials['private_key'],
2494 $stored_credentials['public_key']
2495 );
2496
2497 if ( ! wp_installing() ) {
2498 update_option( 'ftp_credentials', $stored_credentials, false );
2499 }
2500
2501 return $credentials;
2502 }
2503
2504 $hostname = isset( $credentials['hostname'] ) ? $credentials['hostname'] : '';
2505 $username = isset( $credentials['username'] ) ? $credentials['username'] : '';
2506 $public_key = isset( $credentials['public_key'] ) ? $credentials['public_key'] : '';
2507 $private_key = isset( $credentials['private_key'] ) ? $credentials['private_key'] : '';
2508 $port = isset( $credentials['port'] ) ? $credentials['port'] : '';
2509 $connection_type = isset( $credentials['connection_type'] ) ? $credentials['connection_type'] : '';
2510
2511 if ( $error ) {
2512 $error_string = __( '<strong>Error:</strong> Could not connect to the server. Please verify the settings are correct.' );
2513 if ( is_wp_error( $error ) ) {
2514 $error_string = esc_html( $error->get_error_message() );
2515 }
2516 wp_admin_notice(
2517 $error_string,
2518 array(
2519 'id' => 'message',
2520 'additional_classes' => array( 'error' ),
2521 )
2522 );
2523 }
2524
2525 $types = array();
2526 if ( extension_loaded( 'ftp' ) || extension_loaded( 'sockets' ) || function_exists( 'fsockopen' ) ) {
2527 $types['ftp'] = __( 'FTP' );
2528 }
2529 if ( extension_loaded( 'ftp' ) ) { // Only this supports FTPS.
2530 $types['ftps'] = __( 'FTPS (SSL)' );
2531 }
2532 if ( extension_loaded( 'ssh2' ) ) {
2533 $types['ssh'] = __( 'SSH2' );
2534 }
2535
2536 /**
2537 * Filters the connection types to output to the filesystem credentials form.
2538 *
2539 * @since 2.9.0
2540 * @since 4.6.0 The `$context` parameter default changed from `false` to an empty string.
2541 *
2542 * @param string[] $types Types of connections.
2543 * @param array $credentials Credentials to connect with.
2544 * @param string $type Chosen filesystem method.
2545 * @param bool|WP_Error $error Whether the current request has failed to connect,
2546 * or an error object.
2547 * @param string $context Full path to the directory that is tested for being writable.
2548 */
2549 $types = apply_filters( 'fs_ftp_connection_types', $types, $credentials, $type, $error, $context );
2550 ?>
2551<form action="<?php echo esc_url( $form_post ); ?>" method="post">
2552<div id="request-filesystem-credentials-form" class="request-filesystem-credentials-form">
2553 <?php
2554 // Print a H1 heading in the FTP credentials modal dialog, default is a H2.
2555 $heading_tag = 'h2';
2556 if ( 'plugins.php' === $pagenow || 'plugin-install.php' === $pagenow ) {
2557 $heading_tag = 'h1';
2558 }
2559 echo "<$heading_tag id='request-filesystem-credentials-title'>" . __( 'Connection Information' ) . "</$heading_tag>";
2560 ?>
2561<p id="request-filesystem-credentials-desc">
2562 <?php
2563 $label_user = __( 'Username' );
2564 $label_pass = __( 'Password' );
2565 _e( 'To perform the requested action, WordPress needs to access your web server.' );
2566 echo ' ';
2567 if ( ( isset( $types['ftp'] ) || isset( $types['ftps'] ) ) ) {
2568 if ( isset( $types['ssh'] ) ) {
2569 _e( 'Please enter your FTP or SSH credentials to proceed.' );
2570 $label_user = __( 'FTP/SSH Username' );
2571 $label_pass = __( 'FTP/SSH Password' );
2572 } else {
2573 _e( 'Please enter your FTP credentials to proceed.' );
2574 $label_user = __( 'FTP Username' );
2575 $label_pass = __( 'FTP Password' );
2576 }
2577 echo ' ';
2578 }
2579 _e( 'If you do not remember your credentials, you should contact your web host.' );
2580
2581 $hostname_value = esc_attr( $hostname );
2582 if ( ! empty( $port ) ) {
2583 $hostname_value .= ":$port";
2584 }
2585
2586 $password_value = '';
2587 if ( defined( 'FTP_PASS' ) ) {
2588 $password_value = '*****';
2589 }
2590 ?>
2591</p>
2592<label for="hostname">
2593 <span class="field-title"><?php _e( 'Hostname' ); ?></span>
2594 <input name="hostname" type="text" id="hostname" aria-describedby="request-filesystem-credentials-desc" class="code" placeholder="<?php esc_attr_e( 'example: www.wordpress.org' ); ?>" value="<?php echo $hostname_value; ?>"<?php disabled( defined( 'FTP_HOST' ) ); ?> />
2595</label>
2596<div class="ftp-username">
2597 <label for="username">
2598 <span class="field-title"><?php echo $label_user; ?></span>
2599 <input name="username" type="text" id="username" value="<?php echo esc_attr( $username ); ?>"<?php disabled( defined( 'FTP_USER' ) ); ?> />
2600 </label>
2601</div>
2602<div class="ftp-password">
2603 <label for="password">
2604 <span class="field-title"><?php echo $label_pass; ?></span>
2605 <input name="password" type="password" id="password" value="<?php echo $password_value; ?>"<?php disabled( defined( 'FTP_PASS' ) ); ?> spellcheck="false" />
2606 <?php
2607 if ( ! defined( 'FTP_PASS' ) ) {
2608 _e( 'This password will not be stored on the server.' );
2609 }
2610 ?>
2611 </label>
2612</div>
2613<fieldset>
2614<legend><?php _e( 'Connection Type' ); ?></legend>
2615 <?php
2616 $disabled = disabled( ( defined( 'FTP_SSL' ) && FTP_SSL ) || ( defined( 'FTP_SSH' ) && FTP_SSH ), true, false );
2617 foreach ( $types as $name => $text ) :
2618 ?>
2619 <label for="<?php echo esc_attr( $name ); ?>">
2620 <input type="radio" name="connection_type" id="<?php echo esc_attr( $name ); ?>" value="<?php echo esc_attr( $name ); ?>" <?php checked( $name, $connection_type ); ?> <?php echo $disabled; ?> />
2621 <?php echo $text; ?>
2622 </label>
2623 <?php
2624 endforeach;
2625 ?>
2626</fieldset>
2627 <?php
2628 if ( isset( $types['ssh'] ) ) {
2629 $hidden_class = '';
2630 if ( 'ssh' !== $connection_type ) {
2631 $hidden_class = ' class="hidden"';
2632 }
2633 ?>
2634<fieldset id="ssh-keys"<?php echo $hidden_class; ?>>
2635<legend><?php _e( 'Authentication Keys' ); ?></legend>
2636<label for="public_key">
2637 <span class="field-title"><?php _e( 'Public Key:' ); ?></span>
2638 <input name="public_key" type="text" id="public_key" aria-describedby="auth-keys-desc" value="<?php echo esc_attr( $public_key ); ?>"<?php disabled( defined( 'FTP_PUBKEY' ) ); ?> />
2639</label>
2640<label for="private_key">
2641 <span class="field-title"><?php _e( 'Private Key:' ); ?></span>
2642 <input name="private_key" type="text" id="private_key" value="<?php echo esc_attr( $private_key ); ?>"<?php disabled( defined( 'FTP_PRIKEY' ) ); ?> />
2643</label>
2644<p id="auth-keys-desc"><?php _e( 'Enter the location on the server where the public and private keys are located. If a passphrase is needed, enter that in the password field above.' ); ?></p>
2645</fieldset>
2646 <?php
2647 }
2648
2649 foreach ( (array) $extra_fields as $field ) {
2650 if ( isset( $submitted_form[ $field ] ) ) {
2651 echo '<input type="hidden" name="' . esc_attr( $field ) . '" value="' . esc_attr( $submitted_form[ $field ] ) . '" />';
2652 }
2653 }
2654
2655 /*
2656 * Make sure the `submit_button()` function is available during the REST API call
2657 * from WP_Site_Health_Auto_Updates::test_check_wp_filesystem_method().
2658 */
2659 if ( ! function_exists( 'submit_button' ) ) {
2660 require_once ABSPATH . 'wp-admin/includes/template.php';
2661 }
2662 ?>
2663 <p class="request-filesystem-credentials-action-buttons">
2664 <?php wp_nonce_field( 'filesystem-credentials', '_fs_nonce', false, true ); ?>
2665 <button class="button cancel-button" data-js-action="close" type="button"><?php _e( 'Cancel' ); ?></button>
2666 <?php submit_button( __( 'Proceed' ), 'primary', 'upgrade', false ); ?>
2667 </p>
2668</div>
2669</form>
2670 <?php
2671 return false;
2672}
2673
2674/**
2675 * Prints the filesystem credentials modal when needed.
2676 *
2677 * @since 4.2.0
2678 */
2679function wp_print_request_filesystem_credentials_modal() {
2680 $filesystem_method = get_filesystem_method();
2681
2682 ob_start();
2683 $filesystem_credentials_are_stored = request_filesystem_credentials( self_admin_url() );
2684 ob_end_clean();
2685
2686 $request_filesystem_credentials = ( 'direct' !== $filesystem_method && ! $filesystem_credentials_are_stored );
2687 if ( ! $request_filesystem_credentials ) {
2688 return;
2689 }
2690 ?>
2691 <div id="request-filesystem-credentials-dialog" class="notification-dialog-wrap request-filesystem-credentials-dialog">
2692 <div class="notification-dialog-background"></div>
2693 <div class="notification-dialog" role="dialog" aria-labelledby="request-filesystem-credentials-title" tabindex="0">
2694 <div class="request-filesystem-credentials-dialog-content">
2695 <?php request_filesystem_credentials( site_url() ); ?>
2696 </div>
2697 </div>
2698 </div>
2699 <?php
2700}
2701
2702/**
2703 * Attempts to clear the opcode cache for an individual PHP file.
2704 *
2705 * This function can be called safely without having to check the file extension
2706 * or availability of the OPcache extension.
2707 *
2708 * Whether or not invalidation is possible is cached to improve performance.
2709 *
2710 * @since 5.5.0
2711 *
2712 * @link https://www.php.net/manual/en/function.opcache-invalidate.php
2713 *
2714 * @param string $filepath Path to the file, including extension, for which the opcode cache is to be cleared.
2715 * @param bool $force Invalidate even if the modification time is not newer than the file in cache.
2716 * Default false.
2717 * @return bool True if opcache was invalidated for `$filepath`, or there was nothing to invalidate.
2718 * False if opcache invalidation is not available, or is disabled via filter.
2719 */
2720function wp_opcache_invalidate( $filepath, $force = false ) {
2721 static $can_invalidate = null;
2722
2723 /*
2724 * Check to see if WordPress is able to run `opcache_invalidate()` or not, and cache the value.
2725 *
2726 * First, check to see if the function is available to call, then if the host has restricted
2727 * the ability to run the function to avoid a PHP warning.
2728 *
2729 * `opcache.restrict_api` can specify the path for files allowed to call `opcache_invalidate()`.
2730 *
2731 * If the host has this set, check whether the path in `opcache.restrict_api` matches
2732 * the beginning of the path of the origin file.
2733 *
2734 * `$_SERVER['SCRIPT_FILENAME']` approximates the origin file's path, but `realpath()`
2735 * is necessary because `SCRIPT_FILENAME` can be a relative path when run from CLI.
2736 *
2737 * For more details, see:
2738 * - https://www.php.net/manual/en/opcache.configuration.php
2739 * - https://www.php.net/manual/en/reserved.variables.server.php
2740 * - https://core.trac.wordpress.org/ticket/36455
2741 */
2742 if ( null === $can_invalidate
2743 && function_exists( 'opcache_invalidate' )
2744 && ( ! ini_get( 'opcache.restrict_api' )
2745 || stripos( realpath( $_SERVER['SCRIPT_FILENAME'] ), ini_get( 'opcache.restrict_api' ) ) === 0 )
2746 ) {
2747 $can_invalidate = true;
2748 }
2749
2750 // If invalidation is not available, return early.
2751 if ( ! $can_invalidate ) {
2752 return false;
2753 }
2754
2755 // Verify that file to be invalidated has a PHP extension.
2756 if ( '.php' !== strtolower( substr( $filepath, -4 ) ) ) {
2757 return false;
2758 }
2759
2760 /**
2761 * Filters whether to invalidate a file from the opcode cache.
2762 *
2763 * @since 5.5.0
2764 *
2765 * @param bool $will_invalidate Whether WordPress will invalidate `$filepath`. Default true.
2766 * @param string $filepath The path to the PHP file to invalidate.
2767 */
2768 if ( apply_filters( 'wp_opcache_invalidate_file', true, $filepath ) ) {
2769 return opcache_invalidate( $filepath, $force );
2770 }
2771
2772 return false;
2773}
2774
2775/**
2776 * Attempts to clear the opcode cache for a directory of files.
2777 *
2778 * @since 6.2.0
2779 *
2780 * @see wp_opcache_invalidate()
2781 * @link https://www.php.net/manual/en/function.opcache-invalidate.php
2782 *
2783 * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
2784 *
2785 * @param string $dir The path to the directory for which the opcode cache is to be cleared.
2786 */
2787function wp_opcache_invalidate_directory( $dir ) {
2788 global $wp_filesystem;
2789
2790 if ( ! is_string( $dir ) || '' === trim( $dir ) ) {
2791 if ( WP_DEBUG ) {
2792 $error_message = sprintf(
2793 /* translators: %s: The function name. */
2794 __( '%s expects a non-empty string.' ),
2795 '<code>wp_opcache_invalidate_directory()</code>'
2796 );
2797 wp_trigger_error( '', $error_message );
2798 }
2799 return;
2800 }
2801
2802 $dirlist = $wp_filesystem->dirlist( $dir, false, true );
2803
2804 if ( empty( $dirlist ) ) {
2805 return;
2806 }
2807
2808 /*
2809 * Recursively invalidate opcache of files in a directory.
2810 *
2811 * WP_Filesystem_*::dirlist() returns an array of file and directory information.
2812 *
2813 * This does not include a path to the file or directory.
2814 * To invalidate files within sub-directories, recursion is needed
2815 * to prepend an absolute path containing the sub-directory's name.
2816 *
2817 * @param array $dirlist Array of file/directory information from WP_Filesystem_Base::dirlist(),
2818 * with sub-directories represented as nested arrays.
2819 * @param string $path Absolute path to the directory.
2820 */
2821 $invalidate_directory = static function ( $dirlist, $path ) use ( &$invalidate_directory ) {
2822 $path = trailingslashit( $path );
2823
2824 foreach ( $dirlist as $name => $details ) {
2825 if ( 'f' === $details['type'] ) {
2826 wp_opcache_invalidate( $path . $name, true );
2827 } elseif ( is_array( $details['files'] ) && ! empty( $details['files'] ) ) {
2828 $invalidate_directory( $details['files'], $path . $name );
2829 }
2830 }
2831 };
2832
2833 $invalidate_directory( $dirlist, $dir );
2834}
2835
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