PageRenderTime 57ms CodeModel.GetById 15ms app.highlight 19ms RepoModel.GetById 1ms app.codeStats 2ms

/wp-admin/includes/file.php

https://bitbucket.org/devbctph/futura_wp
PHP | 1799 lines | 1077 code | 213 blank | 509 comment | 272 complexity | 50753da05cc993c482ce832784bc7430 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   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	// Archives
  25	'index.php'             => __( 'Main Index Template' ),
  26	'archive.php'           => __( 'Archives' ),
  27	'author.php'            => __( 'Author Template' ),
  28	'taxonomy.php'          => __( 'Taxonomy Template' ),
  29	'category.php'          => __( 'Category Template' ),
  30	'tag.php'               => __( 'Tag Template' ),
  31	'home.php'              => __( 'Posts Page' ),
  32	'search.php'            => __( 'Search Results' ),
  33	'date.php'              => __( 'Date Template' ),
  34	// Content
  35	'singular.php'          => __( 'Singular Template' ),
  36	'single.php'            => __( 'Single Post' ),
  37	'page.php'              => __( 'Single Page' ),
  38	'front-page.php'        => __( 'Homepage' ),
  39	// Attachments
  40	'attachment.php'        => __( 'Attachment Template' ),
  41	'image.php'             => __( 'Image Attachment Template' ),
  42	'video.php'             => __( 'Video Attachment Template' ),
  43	'audio.php'             => __( 'Audio Attachment Template' ),
  44	'application.php'       => __( 'Application Attachment Template' ),
  45	// Embeds
  46	'embed.php'             => __( 'Embed Template' ),
  47	'embed-404.php'         => __( 'Embed 404 Template' ),
  48	'embed-content.php'     => __( 'Embed Content Template' ),
  49	'header-embed.php'      => __( 'Embed Header Template' ),
  50	'footer-embed.php'      => __( 'Embed Footer Template' ),
  51	// Stylesheets
  52	'style.css'             => __( 'Stylesheet' ),
  53	'editor-style.css'      => __( 'Visual Editor Stylesheet' ),
  54	'editor-style-rtl.css'  => __( 'Visual Editor RTL Stylesheet' ),
  55	'rtl.css'               => __( 'RTL Stylesheet' ),
  56	// Other
  57	'my-hacks.php'          => __( 'my-hacks.php (legacy hacks support)' ),
  58	'.htaccess'             => __( '.htaccess (for rewrite rules )' ),
  59	// Deprecated files
  60	'wp-layout.css'         => __( 'Stylesheet' ),
  61	'wp-comments.php'       => __( 'Comments Template' ),
  62	'wp-comments-popup.php' => __( 'Popup Comments Template' ),
  63	'comments-popup.php'    => __( 'Popup Comments' ),
  64);
  65
  66/**
  67 * Get the description for standard WordPress theme files and other various standard
  68 * WordPress files
  69 *
  70 * @since 1.5.0
  71 *
  72 * @global array $wp_file_descriptions Theme file descriptions.
  73 * @global array $allowed_files        List of allowed files.
  74 * @param string $file Filesystem path or filename
  75 * @return string Description of file from $wp_file_descriptions or basename of $file if description doesn't exist.
  76 *                Appends 'Page Template' to basename of $file if the file is a page template
  77 */
  78function get_file_description( $file ) {
  79	global $wp_file_descriptions, $allowed_files;
  80
  81	$dirname = pathinfo( $file, PATHINFO_DIRNAME );
  82
  83	$file_path = $allowed_files[ $file ];
  84	if ( isset( $wp_file_descriptions[ basename( $file ) ] ) && '.' === $dirname ) {
  85		return $wp_file_descriptions[ basename( $file ) ];
  86	} elseif ( file_exists( $file_path ) && is_file( $file_path ) ) {
  87		$template_data = implode( '', file( $file_path ) );
  88		if ( preg_match( '|Template Name:(.*)$|mi', $template_data, $name ) ) {
  89			return sprintf( __( '%s Page Template' ), _cleanup_header_comment( $name[1] ) );
  90		}
  91	}
  92
  93	return trim( basename( $file ) );
  94}
  95
  96/**
  97 * Get the absolute filesystem path to the root of the WordPress installation
  98 *
  99 * @since 1.5.0
 100 *
 101 * @return string Full filesystem path to the root of the WordPress installation
 102 */
 103function get_home_path() {
 104	$home    = set_url_scheme( get_option( 'home' ), 'http' );
 105	$siteurl = set_url_scheme( get_option( 'siteurl' ), 'http' );
 106	if ( ! empty( $home ) && 0 !== strcasecmp( $home, $siteurl ) ) {
 107		$wp_path_rel_to_home = str_ireplace( $home, '', $siteurl ); /* $siteurl - $home */
 108		$pos = strripos( str_replace( '\\', '/', $_SERVER['SCRIPT_FILENAME'] ), trailingslashit( $wp_path_rel_to_home ) );
 109		$home_path = substr( $_SERVER['SCRIPT_FILENAME'], 0, $pos );
 110		$home_path = trailingslashit( $home_path );
 111	} else {
 112		$home_path = ABSPATH;
 113	}
 114
 115	return str_replace( '\\', '/', $home_path );
 116}
 117
 118/**
 119 * Returns a listing of all files in the specified folder and all subdirectories up to 100 levels deep.
 120 * The depth of the recursiveness can be controlled by the $levels param.
 121 *
 122 * @since 2.6.0
 123 * @since 4.9.0 Added the `$exclusions` parameter.
 124 *
 125 * @param string $folder Optional. Full path to folder. Default empty.
 126 * @param int    $levels Optional. Levels of folders to follow, Default 100 (PHP Loop limit).
 127 * @param array  $exclusions Optional. List of folders and files to skip.
 128 * @return bool|array False on failure, Else array of files
 129 */
 130function list_files( $folder = '', $levels = 100, $exclusions = array() ) {
 131	if ( empty( $folder ) ) {
 132		return false;
 133	}
 134
 135	$folder = trailingslashit( $folder );
 136
 137	if ( ! $levels ) {
 138		return false;
 139	}
 140
 141	$files = array();
 142
 143	$dir = @opendir( $folder );
 144	if ( $dir ) {
 145		while ( ( $file = readdir( $dir ) ) !== false ) {
 146			// Skip current and parent folder links.
 147			if ( in_array( $file, array( '.', '..' ), true ) ) {
 148				continue;
 149			}
 150
 151			// Skip hidden and excluded files.
 152			if ( '.' === $file[0] || in_array( $file, $exclusions, true ) ) {
 153				continue;
 154			}
 155
 156			if ( is_dir( $folder . $file ) ) {
 157				$files2 = list_files( $folder . $file, $levels - 1 );
 158				if ( $files2 ) {
 159					$files = array_merge($files, $files2 );
 160				} else {
 161					$files[] = $folder . $file . '/';
 162				}
 163			} else {
 164				$files[] = $folder . $file;
 165			}
 166		}
 167	}
 168	@closedir( $dir );
 169
 170	return $files;
 171}
 172
 173/**
 174 * Get list of file extensions that are editable in plugins.
 175 *
 176 * @since 4.9.0
 177 *
 178 * @param string $plugin Plugin.
 179 * @return array File extensions.
 180 */
 181function wp_get_plugin_file_editable_extensions( $plugin ) {
 182
 183	$editable_extensions = array(
 184		'bash',
 185		'conf',
 186		'css',
 187		'diff',
 188		'htm',
 189		'html',
 190		'http',
 191		'inc',
 192		'include',
 193		'js',
 194		'json',
 195		'jsx',
 196		'less',
 197		'md',
 198		'patch',
 199		'php',
 200		'php3',
 201		'php4',
 202		'php5',
 203		'php7',
 204		'phps',
 205		'phtml',
 206		'sass',
 207		'scss',
 208		'sh',
 209		'sql',
 210		'svg',
 211		'text',
 212		'txt',
 213		'xml',
 214		'yaml',
 215		'yml',
 216	);
 217
 218	/**
 219	 * Filters file type extensions editable in the plugin editor.
 220	 *
 221	 * @since 2.8.0
 222	 * @since 4.9.0 Adds $plugin param.
 223	 *
 224	 * @param string $plugin Plugin file.
 225	 * @param array $editable_extensions An array of editable plugin file extensions.
 226	 */
 227	$editable_extensions = (array) apply_filters( 'editable_extensions', $editable_extensions, $plugin );
 228
 229	return $editable_extensions;
 230}
 231
 232/**
 233 * Get list of file extensions that are editable for a given theme.
 234 *
 235 * @param WP_Theme $theme Theme.
 236 * @return array File extensions.
 237 */
 238function wp_get_theme_file_editable_extensions( $theme ) {
 239
 240	$default_types = array(
 241		'bash',
 242		'conf',
 243		'css',
 244		'diff',
 245		'htm',
 246		'html',
 247		'http',
 248		'inc',
 249		'include',
 250		'js',
 251		'json',
 252		'jsx',
 253		'less',
 254		'md',
 255		'patch',
 256		'php',
 257		'php3',
 258		'php4',
 259		'php5',
 260		'php7',
 261		'phps',
 262		'phtml',
 263		'sass',
 264		'scss',
 265		'sh',
 266		'sql',
 267		'svg',
 268		'text',
 269		'txt',
 270		'xml',
 271		'yaml',
 272		'yml',
 273	);
 274
 275	/**
 276	 * Filters the list of file types allowed for editing in the Theme editor.
 277	 *
 278	 * @since 4.4.0
 279	 *
 280	 * @param array    $default_types List of file types. Default types include 'php' and 'css'.
 281	 * @param WP_Theme $theme         The current Theme object.
 282	 */
 283	$file_types = apply_filters( 'wp_theme_editor_filetypes', $default_types, $theme );
 284
 285	// Ensure that default types are still there.
 286	return array_unique( array_merge( $file_types, $default_types ) );
 287}
 288
 289/**
 290 * Print file editor templates (for plugins and themes).
 291 *
 292 * @since 4.9.0
 293 */
 294function wp_print_file_editor_templates() {
 295	?>
 296	<script type="text/html" id="tmpl-wp-file-editor-notice">
 297		<div class="notice inline notice-{{ data.type || 'info' }} {{ data.alt ? 'notice-alt' : '' }} {{ data.dismissible ? 'is-dismissible' : '' }} {{ data.classes || '' }}">
 298			<# if ( 'php_error' === data.code ) { #>
 299				<p>
 300					<?php
 301					printf(
 302						/* translators: %$1s is line number and %1$s is file path. */
 303						__( 'Your PHP code changes were rolled back due to an error on line %1$s of file %2$s. Please fix and try saving again.' ),
 304						'{{ data.line }}',
 305						'{{ data.file }}'
 306					);
 307					?>
 308				</p>
 309				<pre>{{ data.message }}</pre>
 310			<# } else if ( 'file_not_writable' === data.code ) { #>
 311				<p><?php _e( 'You need to make this file writable before you can save your changes. See <a href="https://codex.wordpress.org/Changing_File_Permissions">the Codex</a> for more information.' ); ?></p>
 312			<# } else { #>
 313				<p>{{ data.message || data.code }}</p>
 314
 315				<# if ( 'lint_errors' === data.code ) { #>
 316					<p>
 317						<# var elementId = 'el-' + String( Math.random() ); #>
 318						<input id="{{ elementId }}"  type="checkbox">
 319						<label for="{{ elementId }}"><?php _e( 'Update anyway, even though it might break your site?' ); ?></label>
 320					</p>
 321				<# } #>
 322			<# } #>
 323			<# if ( data.dismissible ) { #>
 324				<button type="button" class="notice-dismiss"><span class="screen-reader-text"><?php _e( 'Dismiss' ); ?></span></button>
 325			<# } #>
 326		</div>
 327	</script>
 328	<?php
 329}
 330
 331/**
 332 * Attempt to edit a file for a theme or plugin.
 333 *
 334 * When editing a PHP file, loopback requests will be made to the admin and the homepage
 335 * to attempt to see if there is a fatal error introduced. If so, the PHP change will be
 336 * reverted.
 337 *
 338 * @since 4.9.0
 339 *
 340 * @param array $args {
 341 *     Args. Note that all of the arg values are already unslashed. They are, however,
 342 *     coming straight from $_POST and are not validated or sanitized in any way.
 343 *
 344 *     @type string $file       Relative path to file.
 345 *     @type string $plugin     Plugin being edited.
 346 *     @type string $theme      Theme being edited.
 347 *     @type string $newcontent New content for the file.
 348 *     @type string $nonce      Nonce.
 349 * }
 350 * @return true|WP_Error True on success or `WP_Error` on failure.
 351 */
 352function wp_edit_theme_plugin_file( $args ) {
 353	if ( empty( $args['file'] ) ) {
 354		return new WP_Error( 'missing_file' );
 355	}
 356	$file = $args['file'];
 357	if ( 0 !== validate_file( $file ) ) {
 358		return new WP_Error( 'bad_file' );
 359	}
 360
 361	if ( ! isset( $args['newcontent'] ) ) {
 362		return new WP_Error( 'missing_content' );
 363	}
 364	$content = $args['newcontent'];
 365
 366	if ( ! isset( $args['nonce'] ) ) {
 367		return new WP_Error( 'missing_nonce' );
 368	}
 369
 370	$plugin = null;
 371	$theme = null;
 372	$real_file = null;
 373	if ( ! empty( $args['plugin'] ) ) {
 374		$plugin = $args['plugin'];
 375
 376		if ( ! current_user_can( 'edit_plugins' ) ) {
 377			return new WP_Error( 'unauthorized', __( 'Sorry, you are not allowed to edit plugins for this site.' ) );
 378		}
 379
 380		if ( ! wp_verify_nonce( $args['nonce'], 'edit-plugin_' . $file ) ) {
 381			return new WP_Error( 'nonce_failure' );
 382		}
 383
 384		if ( ! array_key_exists( $plugin, get_plugins() ) ) {
 385			return new WP_Error( 'invalid_plugin' );
 386		}
 387
 388		if ( 0 !== validate_file( $file, get_plugin_files( $plugin ) ) ) {
 389			return new WP_Error( 'bad_plugin_file_path', __( 'Sorry, that file cannot be edited.' ) );
 390		}
 391
 392		$editable_extensions = wp_get_plugin_file_editable_extensions( $plugin );
 393
 394		$real_file = WP_PLUGIN_DIR . '/' . $file;
 395
 396		$is_active = in_array(
 397			$plugin,
 398			(array) get_option( 'active_plugins', array() ),
 399			true
 400		);
 401
 402	} elseif ( ! empty( $args['theme'] ) ) {
 403		$stylesheet = $args['theme'];
 404		if ( 0 !== validate_file( $stylesheet ) ) {
 405			return new WP_Error( 'bad_theme_path' );
 406		}
 407
 408		if ( ! current_user_can( 'edit_themes' ) ) {
 409			return new WP_Error( 'unauthorized', __( 'Sorry, you are not allowed to edit templates for this site.' ) );
 410		}
 411
 412		$theme = wp_get_theme( $stylesheet );
 413		if ( ! $theme->exists() ) {
 414			return new WP_Error( 'non_existent_theme', __( 'The requested theme does not exist.' ) );
 415		}
 416
 417		$real_file = $theme->get_stylesheet_directory() . '/' . $file;
 418		if ( ! wp_verify_nonce( $args['nonce'], 'edit-theme_' . $real_file . $stylesheet ) ) {
 419			return new WP_Error( 'nonce_failure' );
 420		}
 421
 422		if ( $theme->errors() && 'theme_no_stylesheet' === $theme->errors()->get_error_code() ) {
 423			return new WP_Error(
 424				'theme_no_stylesheet',
 425				__( 'The requested theme does not exist.' ) . ' ' . $theme->errors()->get_error_message()
 426			);
 427		}
 428
 429		$editable_extensions = wp_get_theme_file_editable_extensions( $theme );
 430
 431		$allowed_files = array();
 432		foreach ( $editable_extensions as $type ) {
 433			switch ( $type ) {
 434				case 'php':
 435					$allowed_files = array_merge( $allowed_files, $theme->get_files( 'php', -1 ) );
 436					break;
 437				case 'css':
 438					$style_files = $theme->get_files( 'css', -1 );
 439					$allowed_files['style.css'] = $style_files['style.css'];
 440					$allowed_files = array_merge( $allowed_files, $style_files );
 441					break;
 442				default:
 443					$allowed_files = array_merge( $allowed_files, $theme->get_files( $type, -1 ) );
 444					break;
 445			}
 446		}
 447
 448		// Compare based on relative paths
 449		if ( 0 !== validate_file( $file, array_keys( $allowed_files ) ) ) {
 450			return new WP_Error( 'disallowed_theme_file', __( 'Sorry, that file cannot be edited.' ) );
 451		}
 452
 453		$is_active = ( get_stylesheet() === $stylesheet || get_template() === $stylesheet );
 454	} else {
 455		return new WP_Error( 'missing_theme_or_plugin' );
 456	}
 457
 458	// Ensure file is real.
 459	if ( ! is_file( $real_file ) ) {
 460		return new WP_Error( 'file_does_not_exist', __( 'No such file exists! Double check the name and try again.' ) );
 461	}
 462
 463	// Ensure file extension is allowed.
 464	$extension = null;
 465	if ( preg_match( '/\.([^.]+)$/', $real_file, $matches ) ) {
 466		$extension = strtolower( $matches[1] );
 467		if ( ! in_array( $extension, $editable_extensions, true ) ) {
 468			return new WP_Error( 'illegal_file_type', __( 'Files of this type are not editable.' ) );
 469		}
 470	}
 471
 472	$previous_content = file_get_contents( $real_file );
 473
 474	if ( ! is_writeable( $real_file ) ) {
 475		return new WP_Error( 'file_not_writable' );
 476	}
 477
 478	$f = fopen( $real_file, 'w+' );
 479	if ( false === $f ) {
 480		return new WP_Error( 'file_not_writable' );
 481	}
 482
 483	$written = fwrite( $f, $content );
 484	fclose( $f );
 485	if ( false === $written ) {
 486		return new WP_Error( 'unable_to_write', __( 'Unable to write to file.' ) );
 487	}
 488	if ( 'php' === $extension && function_exists( 'opcache_invalidate' ) ) {
 489		opcache_invalidate( $real_file, true );
 490	}
 491
 492	if ( $is_active && 'php' === $extension ) {
 493
 494		$scrape_key = md5( rand() );
 495		$transient = 'scrape_key_' . $scrape_key;
 496		$scrape_nonce = strval( rand() );
 497		set_transient( $transient, $scrape_nonce, 60 ); // It shouldn't take more than 60 seconds to make the two loopback requests.
 498
 499		$cookies = wp_unslash( $_COOKIE );
 500		$scrape_params = array(
 501			'wp_scrape_key' => $scrape_key,
 502			'wp_scrape_nonce' => $scrape_nonce,
 503		);
 504		$headers = array(
 505			'Cache-Control' => 'no-cache',
 506		);
 507
 508		// Include Basic auth in loopback requests.
 509		if ( isset( $_SERVER['PHP_AUTH_USER'] ) && isset( $_SERVER['PHP_AUTH_PW'] ) ) {
 510			$headers['Authorization'] = 'Basic ' . base64_encode( wp_unslash( $_SERVER['PHP_AUTH_USER'] ) . ':' . wp_unslash( $_SERVER['PHP_AUTH_PW'] ) );
 511		}
 512
 513		// Make sure PHP process doesn't die before loopback requests complete.
 514		@set_time_limit( 300 );
 515
 516		// Time to wait for loopback requests to finish.
 517		$timeout = 100;
 518
 519		$needle_start = "###### wp_scraping_result_start:$scrape_key ######";
 520		$needle_end = "###### wp_scraping_result_end:$scrape_key ######";
 521
 522		// Attempt loopback request to editor to see if user just whitescreened themselves.
 523		if ( $plugin ) {
 524			$url = add_query_arg( compact( 'plugin', 'file' ), admin_url( 'plugin-editor.php' ) );
 525		} elseif ( isset( $stylesheet ) ) {
 526			$url = add_query_arg(
 527				array(
 528					'theme' => $stylesheet,
 529					'file' => $file,
 530				),
 531				admin_url( 'theme-editor.php' )
 532			);
 533		} else {
 534			$url = admin_url();
 535		}
 536		$url = add_query_arg( $scrape_params, $url );
 537		$r = wp_remote_get( $url, compact( 'cookies', 'headers', 'timeout' ) );
 538		$body = wp_remote_retrieve_body( $r );
 539		$scrape_result_position = strpos( $body, $needle_start );
 540
 541		$loopback_request_failure = array(
 542			'code' => 'loopback_request_failed',
 543			'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.' ),
 544		);
 545		$json_parse_failure = array(
 546			'code' => 'json_parse_error',
 547		);
 548
 549		$result = null;
 550		if ( false === $scrape_result_position ) {
 551			$result = $loopback_request_failure;
 552		} else {
 553			$error_output = substr( $body, $scrape_result_position + strlen( $needle_start ) );
 554			$error_output = substr( $error_output, 0, strpos( $error_output, $needle_end ) );
 555			$result = json_decode( trim( $error_output ), true );
 556			if ( empty( $result ) ) {
 557				$result = $json_parse_failure;
 558			}
 559		}
 560
 561		// Try making request to homepage as well to see if visitors have been whitescreened.
 562		if ( true === $result ) {
 563			$url = home_url( '/' );
 564			$url = add_query_arg( $scrape_params, $url );
 565			$r = wp_remote_get( $url, compact( 'cookies', 'headers', 'timeout' ) );
 566			$body = wp_remote_retrieve_body( $r );
 567			$scrape_result_position = strpos( $body, $needle_start );
 568
 569			if ( false === $scrape_result_position ) {
 570				$result = $loopback_request_failure;
 571			} else {
 572				$error_output = substr( $body, $scrape_result_position + strlen( $needle_start ) );
 573				$error_output = substr( $error_output, 0, strpos( $error_output, $needle_end ) );
 574				$result = json_decode( trim( $error_output ), true );
 575				if ( empty( $result ) ) {
 576					$result = $json_parse_failure;
 577				}
 578			}
 579		}
 580
 581		delete_transient( $transient );
 582
 583		if ( true !== $result ) {
 584
 585			// Roll-back file change.
 586			file_put_contents( $real_file, $previous_content );
 587			if ( function_exists( 'opcache_invalidate' ) ) {
 588				opcache_invalidate( $real_file, true );
 589			}
 590
 591			if ( ! isset( $result['message'] ) ) {
 592				$message = __( 'An unidentified error has occurred.' );
 593			} else {
 594				$message = $result['message'];
 595				unset( $result['message'] );
 596			}
 597			return new WP_Error( 'php_error', $message, $result );
 598		}
 599	}
 600
 601	if ( $theme instanceof WP_Theme ) {
 602		$theme->cache_delete();
 603	}
 604
 605	return true;
 606}
 607
 608
 609/**
 610 * Returns a filename of a Temporary unique file.
 611 * Please note that the calling function must unlink() this itself.
 612 *
 613 * The filename is based off the passed parameter or defaults to the current unix timestamp,
 614 * while the directory can either be passed as well, or by leaving it blank, default to a writable temporary directory.
 615 *
 616 * @since 2.6.0
 617 *
 618 * @param string $filename Optional. Filename to base the Unique file off. Default empty.
 619 * @param string $dir      Optional. Directory to store the file in. Default empty.
 620 * @return string a writable filename
 621 */
 622function wp_tempnam( $filename = '', $dir = '' ) {
 623	if ( empty( $dir ) ) {
 624		$dir = get_temp_dir();
 625	}
 626
 627	if ( empty( $filename ) || '.' == $filename || '/' == $filename || '\\' == $filename ) {
 628		$filename = time();
 629	}
 630
 631	// Use the basename of the given file without the extension as the name for the temporary directory
 632	$temp_filename = basename( $filename );
 633	$temp_filename = preg_replace( '|\.[^.]*$|', '', $temp_filename );
 634
 635	// If the folder is falsey, use its parent directory name instead.
 636	if ( ! $temp_filename ) {
 637		return wp_tempnam( dirname( $filename ), $dir );
 638	}
 639
 640	// Suffix some random data to avoid filename conflicts
 641	$temp_filename .= '-' . wp_generate_password( 6, false );
 642	$temp_filename .= '.tmp';
 643	$temp_filename = $dir . wp_unique_filename( $dir, $temp_filename );
 644
 645	$fp = @fopen( $temp_filename, 'x' );
 646	if ( ! $fp && is_writable( $dir ) && file_exists( $temp_filename ) ) {
 647		return wp_tempnam( $filename, $dir );
 648	}
 649	if ( $fp ) {
 650		fclose( $fp );
 651	}
 652
 653	return $temp_filename;
 654}
 655
 656/**
 657 * Makes sure that the file that was requested to be edited is allowed to be edited.
 658 *
 659 * Function will die if you are not allowed to edit the file.
 660 *
 661 * @since 1.5.0
 662 *
 663 * @param string $file          File the user is attempting to edit.
 664 * @param array  $allowed_files Optional. Array of allowed files to edit, $file must match an entry exactly.
 665 * @return string|null
 666 */
 667function validate_file_to_edit( $file, $allowed_files = array() ) {
 668	$code = validate_file( $file, $allowed_files );
 669
 670	if (!$code )
 671		return $file;
 672
 673	switch ( $code ) {
 674		case 1 :
 675			wp_die( __( 'Sorry, that file cannot be edited.' ) );
 676
 677		// case 2 :
 678		// wp_die( __('Sorry, can&#8217;t call files with their real path.' ));
 679
 680		case 3 :
 681			wp_die( __( 'Sorry, that file cannot be edited.' ) );
 682	}
 683}
 684
 685/**
 686 * Handle PHP uploads in WordPress, sanitizing file names, checking extensions for mime type,
 687 * and moving the file to the appropriate directory within the uploads directory.
 688 *
 689 * @access private
 690 * @since 4.0.0
 691 *
 692 * @see wp_handle_upload_error
 693 *
 694 * @param array       $file      Reference to a single element of $_FILES. Call the function once for each uploaded file.
 695 * @param array|false $overrides An associative array of names => values to override default variables. Default false.
 696 * @param string      $time      Time formatted in 'yyyy/mm'.
 697 * @param string      $action    Expected value for $_POST['action'].
 698 * @return array On success, returns an associative array of file attributes. On failure, returns
 699 *               $overrides['upload_error_handler'](&$file, $message ) or array( 'error'=>$message ).
 700 */
 701function _wp_handle_upload( &$file, $overrides, $time, $action ) {
 702	// The default error handler.
 703	if ( ! function_exists( 'wp_handle_upload_error' ) ) {
 704		function wp_handle_upload_error( &$file, $message ) {
 705			return array( 'error' => $message );
 706		}
 707	}
 708
 709	/**
 710	 * Filters the data for a file before it is uploaded to WordPress.
 711	 *
 712	 * The dynamic portion of the hook name, `$action`, refers to the post action.
 713	 *
 714	 * @since 2.9.0 as 'wp_handle_upload_prefilter'.
 715	 * @since 4.0.0 Converted to a dynamic hook with `$action`.
 716	 *
 717	 * @param array $file An array of data for a single file.
 718	 */
 719	$file = apply_filters( "{$action}_prefilter", $file );
 720
 721	// You may define your own function and pass the name in $overrides['upload_error_handler']
 722	$upload_error_handler = 'wp_handle_upload_error';
 723	if ( isset( $overrides['upload_error_handler'] ) ) {
 724		$upload_error_handler = $overrides['upload_error_handler'];
 725	}
 726
 727	// You may have had one or more 'wp_handle_upload_prefilter' functions error out the file. Handle that gracefully.
 728	if ( isset( $file['error'] ) && ! is_numeric( $file['error'] ) && $file['error'] ) {
 729		return call_user_func_array( $upload_error_handler, array( &$file, $file['error'] ) );
 730	}
 731
 732	// Install user overrides. Did we mention that this voids your warranty?
 733
 734	// You may define your own function and pass the name in $overrides['unique_filename_callback']
 735	$unique_filename_callback = null;
 736	if ( isset( $overrides['unique_filename_callback'] ) ) {
 737		$unique_filename_callback = $overrides['unique_filename_callback'];
 738	}
 739
 740	/*
 741	 * This may not have orignially been intended to be overrideable,
 742	 * but historically has been.
 743	 */
 744	if ( isset( $overrides['upload_error_strings'] ) ) {
 745		$upload_error_strings = $overrides['upload_error_strings'];
 746	} else {
 747		// Courtesy of php.net, the strings that describe the error indicated in $_FILES[{form field}]['error'].
 748		$upload_error_strings = array(
 749			false,
 750			__( 'The uploaded file exceeds the upload_max_filesize directive in php.ini.' ),
 751			__( 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.' ),
 752			__( 'The uploaded file was only partially uploaded.' ),
 753			__( 'No file was uploaded.' ),
 754			'',
 755			__( 'Missing a temporary folder.' ),
 756			__( 'Failed to write file to disk.' ),
 757			__( 'File upload stopped by extension.' )
 758		);
 759	}
 760
 761	// All tests are on by default. Most can be turned off by $overrides[{test_name}] = false;
 762	$test_form = isset( $overrides['test_form'] ) ? $overrides['test_form'] : true;
 763	$test_size = isset( $overrides['test_size'] ) ? $overrides['test_size'] : true;
 764
 765	// If you override this, you must provide $ext and $type!!
 766	$test_type = isset( $overrides['test_type'] ) ? $overrides['test_type'] : true;
 767	$mimes = isset( $overrides['mimes'] ) ? $overrides['mimes'] : false;
 768
 769	// A correct form post will pass this test.
 770	if ( $test_form && ( ! isset( $_POST['action'] ) || ( $_POST['action'] != $action ) ) ) {
 771		return call_user_func_array( $upload_error_handler, array( &$file, __( 'Invalid form submission.' ) ) );
 772	}
 773	// A successful upload will pass this test. It makes no sense to override this one.
 774	if ( isset( $file['error'] ) && $file['error'] > 0 ) {
 775		return call_user_func_array( $upload_error_handler, array( &$file, $upload_error_strings[ $file['error'] ] ) );
 776	}
 777
 778	$test_file_size = 'wp_handle_upload' === $action ? $file['size'] : filesize( $file['tmp_name'] );
 779	// A non-empty file will pass this test.
 780	if ( $test_size && ! ( $test_file_size > 0 ) ) {
 781		if ( is_multisite() ) {
 782			$error_msg = __( 'File is empty. Please upload something more substantial.' );
 783		} else {
 784			$error_msg = __( 'File is empty. Please upload something more substantial. This error could also be caused by uploads being disabled in your php.ini or by post_max_size being defined as smaller than upload_max_filesize in php.ini.' );
 785		}
 786		return call_user_func_array( $upload_error_handler, array( &$file, $error_msg ) );
 787	}
 788
 789	// A properly uploaded file will pass this test. There should be no reason to override this one.
 790	$test_uploaded_file = 'wp_handle_upload' === $action ? @ is_uploaded_file( $file['tmp_name'] ) : @ is_file( $file['tmp_name'] );
 791	if ( ! $test_uploaded_file ) {
 792		return call_user_func_array( $upload_error_handler, array( &$file, __( 'Specified file failed upload test.' ) ) );
 793	}
 794
 795	// A correct MIME type will pass this test. Override $mimes or use the upload_mimes filter.
 796	if ( $test_type ) {
 797		$wp_filetype = wp_check_filetype_and_ext( $file['tmp_name'], $file['name'], $mimes );
 798		$ext = empty( $wp_filetype['ext'] ) ? '' : $wp_filetype['ext'];
 799		$type = empty( $wp_filetype['type'] ) ? '' : $wp_filetype['type'];
 800		$proper_filename = empty( $wp_filetype['proper_filename'] ) ? '' : $wp_filetype['proper_filename'];
 801
 802		// Check to see if wp_check_filetype_and_ext() determined the filename was incorrect
 803		if ( $proper_filename ) {
 804			$file['name'] = $proper_filename;
 805		}
 806		if ( ( ! $type || !$ext ) && ! current_user_can( 'unfiltered_upload' ) ) {
 807			return call_user_func_array( $upload_error_handler, array( &$file, __( 'Sorry, this file type is not permitted for security reasons.' ) ) );
 808		}
 809		if ( ! $type ) {
 810			$type = $file['type'];
 811		}
 812	} else {
 813		$type = '';
 814	}
 815
 816	/*
 817	 * A writable uploads dir will pass this test. Again, there's no point
 818	 * overriding this one.
 819	 */
 820	if ( ! ( ( $uploads = wp_upload_dir( $time ) ) && false === $uploads['error'] ) ) {
 821		return call_user_func_array( $upload_error_handler, array( &$file, $uploads['error'] ) );
 822	}
 823
 824	$filename = wp_unique_filename( $uploads['path'], $file['name'], $unique_filename_callback );
 825
 826	// Move the file to the uploads dir.
 827	$new_file = $uploads['path'] . "/$filename";
 828
 829 	/**
 830	 * Filters whether to short-circuit moving the uploaded file after passing all checks.
 831	 *
 832	 * If a non-null value is passed to the filter, moving the file and any related error
 833	 * reporting will be completely skipped.
 834	 *
 835	 * @since 4.9.0
 836	 *
 837	 * @param string $move_new_file If null (default) move the file after the upload.
 838	 * @param string $file          An array of data for a single file.
 839	 * @param string $new_file      Filename of the newly-uploaded file.
 840	 * @param string $type          File type.
 841	 */
 842	$move_new_file = apply_filters( 'pre_move_uploaded_file', null, $file, $new_file, $type );
 843
 844	if ( null === $move_new_file ) {
 845		if ( 'wp_handle_upload' === $action ) {
 846			$move_new_file = @ move_uploaded_file( $file['tmp_name'], $new_file );
 847		} else {
 848			// use copy and unlink because rename breaks streams.
 849			$move_new_file = @ copy( $file['tmp_name'], $new_file );
 850			unlink( $file['tmp_name'] );
 851		}
 852
 853		if ( false === $move_new_file ) {
 854			if ( 0 === strpos( $uploads['basedir'], ABSPATH ) ) {
 855				$error_path = str_replace( ABSPATH, '', $uploads['basedir'] ) . $uploads['subdir'];
 856			} else {
 857				$error_path = basename( $uploads['basedir'] ) . $uploads['subdir'];
 858			}
 859			return $upload_error_handler( $file, sprintf( __('The uploaded file could not be moved to %s.' ), $error_path ) );
 860		}
 861	}
 862
 863	// Set correct file permissions.
 864	$stat = stat( dirname( $new_file ));
 865	$perms = $stat['mode'] & 0000666;
 866	@ chmod( $new_file, $perms );
 867
 868	// Compute the URL.
 869	$url = $uploads['url'] . "/$filename";
 870
 871	if ( is_multisite() ) {
 872		delete_transient( 'dirsize_cache' );
 873	}
 874
 875	/**
 876	 * Filters the data array for the uploaded file.
 877	 *
 878	 * @since 2.1.0
 879	 *
 880	 * @param array  $upload {
 881	 *     Array of upload data.
 882	 *
 883	 *     @type string $file Filename of the newly-uploaded file.
 884	 *     @type string $url  URL of the uploaded file.
 885	 *     @type string $type File type.
 886	 * }
 887	 * @param string $context The type of upload action. Values include 'upload' or 'sideload'.
 888	 */
 889	return apply_filters( 'wp_handle_upload', array(
 890		'file' => $new_file,
 891		'url'  => $url,
 892		'type' => $type
 893	), 'wp_handle_sideload' === $action ? 'sideload' : 'upload' );
 894}
 895
 896/**
 897 * Wrapper for _wp_handle_upload().
 898 *
 899 * Passes the {@see 'wp_handle_upload'} action.
 900 *
 901 * @since 2.0.0
 902 *
 903 * @see _wp_handle_upload()
 904 *
 905 * @param array      $file      Reference to a single element of `$_FILES`. Call the function once for
 906 *                              each uploaded file.
 907 * @param array|bool $overrides Optional. An associative array of names=>values to override default
 908 *                              variables. Default false.
 909 * @param string     $time      Optional. Time formatted in 'yyyy/mm'. Default null.
 910 * @return array On success, returns an associative array of file attributes. On failure, returns
 911 *               $overrides['upload_error_handler'](&$file, $message ) or array( 'error'=>$message ).
 912 */
 913function wp_handle_upload( &$file, $overrides = false, $time = null ) {
 914	/*
 915	 *  $_POST['action'] must be set and its value must equal $overrides['action']
 916	 *  or this:
 917	 */
 918	$action = 'wp_handle_upload';
 919	if ( isset( $overrides['action'] ) ) {
 920		$action = $overrides['action'];
 921	}
 922
 923	return _wp_handle_upload( $file, $overrides, $time, $action );
 924}
 925
 926/**
 927 * Wrapper for _wp_handle_upload().
 928 *
 929 * Passes the {@see 'wp_handle_sideload'} action.
 930 *
 931 * @since 2.6.0
 932 *
 933 * @see _wp_handle_upload()
 934 *
 935 * @param array      $file      An array similar to that of a PHP `$_FILES` POST array
 936 * @param array|bool $overrides Optional. An associative array of names=>values to override default
 937 *                              variables. Default false.
 938 * @param string     $time      Optional. Time formatted in 'yyyy/mm'. Default null.
 939 * @return array On success, returns an associative array of file attributes. On failure, returns
 940 *               $overrides['upload_error_handler'](&$file, $message ) or array( 'error'=>$message ).
 941 */
 942function wp_handle_sideload( &$file, $overrides = false, $time = null ) {
 943	/*
 944	 *  $_POST['action'] must be set and its value must equal $overrides['action']
 945	 *  or this:
 946	 */
 947	$action = 'wp_handle_sideload';
 948	if ( isset( $overrides['action'] ) ) {
 949		$action = $overrides['action'];
 950	}
 951	return _wp_handle_upload( $file, $overrides, $time, $action );
 952}
 953
 954
 955/**
 956 * Downloads a URL to a local temporary file using the WordPress HTTP Class.
 957 * Please note, That the calling function must unlink() the file.
 958 *
 959 * @since 2.5.0
 960 *
 961 * @param string $url the URL of the file to download
 962 * @param int $timeout The timeout for the request to download the file default 300 seconds
 963 * @return mixed WP_Error on failure, string Filename on success.
 964 */
 965function download_url( $url, $timeout = 300 ) {
 966	//WARNING: The file is not automatically deleted, The script must unlink() the file.
 967	if ( ! $url )
 968		return new WP_Error('http_no_url', __('Invalid URL Provided.'));
 969
 970	$url_filename = basename( parse_url( $url, PHP_URL_PATH ) );
 971
 972	$tmpfname = wp_tempnam( $url_filename );
 973	if ( ! $tmpfname )
 974		return new WP_Error('http_no_file', __('Could not create Temporary file.'));
 975
 976	$response = wp_safe_remote_get( $url, array( 'timeout' => $timeout, 'stream' => true, 'filename' => $tmpfname ) );
 977
 978	if ( is_wp_error( $response ) ) {
 979		unlink( $tmpfname );
 980		return $response;
 981	}
 982
 983	if ( 200 != wp_remote_retrieve_response_code( $response ) ){
 984		unlink( $tmpfname );
 985		return new WP_Error( 'http_404', trim( wp_remote_retrieve_response_message( $response ) ) );
 986	}
 987
 988	$content_md5 = wp_remote_retrieve_header( $response, 'content-md5' );
 989	if ( $content_md5 ) {
 990		$md5_check = verify_file_md5( $tmpfname, $content_md5 );
 991		if ( is_wp_error( $md5_check ) ) {
 992			unlink( $tmpfname );
 993			return $md5_check;
 994		}
 995	}
 996
 997	return $tmpfname;
 998}
 999
1000/**
1001 * Calculates and compares the MD5 of a file to its expected value.
1002 *
1003 * @since 3.7.0
1004 *
1005 * @param string $filename The filename to check the MD5 of.
1006 * @param string $expected_md5 The expected MD5 of the file, either a base64 encoded raw md5, or a hex-encoded md5
1007 * @return bool|object WP_Error on failure, true on success, false when the MD5 format is unknown/unexpected
1008 */
1009function verify_file_md5( $filename, $expected_md5 ) {
1010	if ( 32 == strlen( $expected_md5 ) )
1011		$expected_raw_md5 = pack( 'H*', $expected_md5 );
1012	elseif ( 24 == strlen( $expected_md5 ) )
1013		$expected_raw_md5 = base64_decode( $expected_md5 );
1014	else
1015		return false; // unknown format
1016
1017	$file_md5 = md5_file( $filename, true );
1018
1019	if ( $file_md5 === $expected_raw_md5 )
1020		return true;
1021
1022	return new WP_Error( 'md5_mismatch', sprintf( __( 'The checksum of the file (%1$s) does not match the expected checksum value (%2$s).' ), bin2hex( $file_md5 ), bin2hex( $expected_raw_md5 ) ) );
1023}
1024
1025/**
1026 * Unzips a specified ZIP file to a location on the Filesystem via the WordPress Filesystem Abstraction.
1027 * Assumes that WP_Filesystem() has already been called and set up. Does not extract a root-level __MACOSX directory, if present.
1028 *
1029 * Attempts to increase the PHP Memory limit to 256M before uncompressing,
1030 * However, The most memory required shouldn't be much larger than the Archive itself.
1031 *
1032 * @since 2.5.0
1033 *
1034 * @global WP_Filesystem_Base $wp_filesystem Subclass
1035 *
1036 * @param string $file Full path and filename of zip archive
1037 * @param string $to Full path on the filesystem to extract archive to
1038 * @return mixed WP_Error on failure, True on success
1039 */
1040function unzip_file($file, $to) {
1041	global $wp_filesystem;
1042
1043	if ( ! $wp_filesystem || !is_object($wp_filesystem) )
1044		return new WP_Error('fs_unavailable', __('Could not access filesystem.'));
1045
1046	// Unzip can use a lot of memory, but not this much hopefully.
1047	wp_raise_memory_limit( 'admin' );
1048
1049	$needed_dirs = array();
1050	$to = trailingslashit($to);
1051
1052	// Determine any parent dir's needed (of the upgrade directory)
1053	if ( ! $wp_filesystem->is_dir($to) ) { //Only do parents if no children exist
1054		$path = preg_split('![/\\\]!', untrailingslashit($to));
1055		for ( $i = count($path); $i >= 0; $i-- ) {
1056			if ( empty($path[$i]) )
1057				continue;
1058
1059			$dir = implode('/', array_slice($path, 0, $i+1) );
1060			if ( preg_match('!^[a-z]:$!i', $dir) ) // Skip it if it looks like a Windows Drive letter.
1061				continue;
1062
1063			if ( ! $wp_filesystem->is_dir($dir) )
1064				$needed_dirs[] = $dir;
1065			else
1066				break; // A folder exists, therefor, we dont need the check the levels below this
1067		}
1068	}
1069
1070	/**
1071	 * Filters whether to use ZipArchive to unzip archives.
1072	 *
1073	 * @since 3.0.0
1074	 *
1075	 * @param bool $ziparchive Whether to use ZipArchive. Default true.
1076	 */
1077	if ( class_exists( 'ZipArchive', false ) && apply_filters( 'unzip_file_use_ziparchive', true ) ) {
1078		$result = _unzip_file_ziparchive($file, $to, $needed_dirs);
1079		if ( true === $result ) {
1080			return $result;
1081		} elseif ( is_wp_error($result) ) {
1082			if ( 'incompatible_archive' != $result->get_error_code() )
1083				return $result;
1084		}
1085	}
1086	// Fall through to PclZip if ZipArchive is not available, or encountered an error opening the file.
1087	return _unzip_file_pclzip($file, $to, $needed_dirs);
1088}
1089
1090/**
1091 * This function should not be called directly, use unzip_file instead. Attempts to unzip an archive using the ZipArchive class.
1092 * Assumes that WP_Filesystem() has already been called and set up.
1093 *
1094 * @since 3.0.0
1095 * @see unzip_file
1096 * @access private
1097 *
1098 * @global WP_Filesystem_Base $wp_filesystem Subclass
1099 *
1100 * @param string $file Full path and filename of zip archive
1101 * @param string $to Full path on the filesystem to extract archive to
1102 * @param array $needed_dirs A partial list of required folders needed to be created.
1103 * @return mixed WP_Error on failure, True on success
1104 */
1105function _unzip_file_ziparchive($file, $to, $needed_dirs = array() ) {
1106	global $wp_filesystem;
1107
1108	$z = new ZipArchive();
1109
1110	$zopen = $z->open( $file, ZIPARCHIVE::CHECKCONS );
1111	if ( true !== $zopen )
1112		return new WP_Error( 'incompatible_archive', __( 'Incompatible Archive.' ), array( 'ziparchive_error' => $zopen ) );
1113
1114	$uncompressed_size = 0;
1115
1116	for ( $i = 0; $i < $z->numFiles; $i++ ) {
1117		if ( ! $info = $z->statIndex($i) )
1118			return new WP_Error( 'stat_failed_ziparchive', __( 'Could not retrieve file from archive.' ) );
1119
1120		if ( '__MACOSX/' === substr($info['name'], 0, 9) ) // Skip the OS X-created __MACOSX directory
1121			continue;
1122
1123		// Don't extract invalid files:
1124		if ( 0 !== validate_file( $info['name'] ) ) {
1125			continue;
1126		}
1127
1128		$uncompressed_size += $info['size'];
1129
1130		if ( '/' === substr( $info['name'], -1 ) ) {
1131			// Directory.
1132			$needed_dirs[] = $to . untrailingslashit( $info['name'] );
1133		} elseif ( '.' !== $dirname = dirname( $info['name'] ) ) {
1134			// Path to a file.
1135			$needed_dirs[] = $to . untrailingslashit( $dirname );
1136		}
1137	}
1138
1139	/*
1140	 * disk_free_space() could return false. Assume that any falsey value is an error.
1141	 * A disk that has zero free bytes has bigger problems.
1142	 * Require we have enough space to unzip the file and copy its contents, with a 10% buffer.
1143	 */
1144	if ( wp_doing_cron() ) {
1145		$available_space = @disk_free_space( WP_CONTENT_DIR );
1146		if ( $available_space && ( $uncompressed_size * 2.1 ) > $available_space )
1147			return new WP_Error( 'disk_full_unzip_file', __( 'Could not copy files. You may have run out of disk space.' ), compact( 'uncompressed_size', 'available_space' ) );
1148	}
1149
1150	$needed_dirs = array_unique($needed_dirs);
1151	foreach ( $needed_dirs as $dir ) {
1152		// Check the parent folders of the folders all exist within the creation array.
1153		if ( untrailingslashit($to) == $dir ) // Skip over the working directory, We know this exists (or will exist)
1154			continue;
1155		if ( strpos($dir, $to) === false ) // If the directory is not within the working directory, Skip it
1156			continue;
1157
1158		$parent_folder = dirname($dir);
1159		while ( !empty($parent_folder) && untrailingslashit($to) != $parent_folder && !in_array($parent_folder, $needed_dirs) ) {
1160			$needed_dirs[] = $parent_folder;
1161			$parent_folder = dirname($parent_folder);
1162		}
1163	}
1164	asort($needed_dirs);
1165
1166	// Create those directories if need be:
1167	foreach ( $needed_dirs as $_dir ) {
1168		// Only check to see if the Dir exists upon creation failure. Less I/O this way.
1169		if ( ! $wp_filesystem->mkdir( $_dir, FS_CHMOD_DIR ) && ! $wp_filesystem->is_dir( $_dir ) ) {
1170			return new WP_Error( 'mkdir_failed_ziparchive', __( 'Could not create directory.' ), substr( $_dir, strlen( $to ) ) );
1171		}
1172	}
1173	unset($needed_dirs);
1174
1175	for ( $i = 0; $i < $z->numFiles; $i++ ) {
1176		if ( ! $info = $z->statIndex($i) )
1177			return new WP_Error( 'stat_failed_ziparchive', __( 'Could not retrieve file from archive.' ) );
1178
1179		if ( '/' == substr($info['name'], -1) ) // directory
1180			continue;
1181
1182		if ( '__MACOSX/' === substr($info['name'], 0, 9) ) // Don't extract the OS X-created __MACOSX directory files
1183			continue;
1184
1185		// Don't extract invalid files:
1186		if ( 0 !== validate_file( $info['name'] ) ) {
1187			continue;
1188		}
1189
1190		$contents = $z->getFromIndex($i);
1191		if ( false === $contents )
1192			return new WP_Error( 'extract_failed_ziparchive', __( 'Could not extract file from archive.' ), $info['name'] );
1193
1194		if ( ! $wp_filesystem->put_contents( $to . $info['name'], $contents, FS_CHMOD_FILE) )
1195			return new WP_Error( 'copy_failed_ziparchive', __( 'Could not copy file.' ), $info['name'] );
1196	}
1197
1198	$z->close();
1199
1200	return true;
1201}
1202
1203/**
1204 * This function should not be called directly, use unzip_file instead. Attempts to unzip an archive using the PclZip library.
1205 * Assumes that WP_Filesystem() has already been called and set up.
1206 *
1207 * @since 3.0.0
1208 * @see unzip_file
1209 * @access private
1210 *
1211 * @global WP_Filesystem_Base $wp_filesystem Subclass
1212 *
1213 * @param string $file Full path and filename of zip archive
1214 * @param string $to Full path on the filesystem to extract archive to
1215 * @param array $needed_dirs A partial list of required folders needed to be created.
1216 * @return mixed WP_Error on failure, True on success
1217 */
1218function _unzip_file_pclzip($file, $to, $needed_dirs = array()) {
1219	global $wp_filesystem;
1220
1221	mbstring_binary_safe_encoding();
1222
1223	require_once(ABSPATH . 'wp-admin/includes/class-pclzip.php');
1224
1225	$archive = new PclZip($file);
1226
1227	$archive_files = $archive->extract(PCLZIP_OPT_EXTRACT_AS_STRING);
1228
1229	reset_mbstring_encoding();
1230
1231	// Is the archive valid?
1232	if ( !is_array($archive_files) )
1233		return new WP_Error('incompatible_archive', __('Incompatible Archive.'), $archive->errorInfo(true));
1234
1235	if ( 0 == count($archive_files) )
1236		return new WP_Error( 'empty_archive_pclzip', __( 'Empty archive.' ) );
1237
1238	$uncompressed_size = 0;
1239
1240	// Determine any children directories needed (From within the archive)
1241	foreach ( $archive_files as $file ) {
1242		if ( '__MACOSX/' === substr($file['filename'], 0, 9) ) // Skip the OS X-created __MACOSX directory
1243			continue;
1244
1245		$uncompressed_size += $file['size'];
1246
1247		$needed_dirs[] = $to . untrailingslashit( $file['folder'] ? $file['filename'] : dirname($file['filename']) );
1248	}
1249
1250	/*
1251	 * disk_free_space() could return false. Assume that any falsey value is an error.
1252	 * A disk that has zero free bytes has bigger problems.
1253	 * Require we have enough space to unzip the file and copy its contents, with a 10% buffer.
1254	 */
1255	if ( wp_doing_cron() ) {
1256		$available_space = @disk_free_space( WP_CONTENT_DIR );
1257		if ( $available_space && ( $uncompressed_size * 2.1 ) > $available_space )
1258			return new WP_Error( 'disk_full_unzip_file', __( 'Could not copy files. You may have run out of disk space.' ), compact( 'uncompressed_size', 'available_space' ) );
1259	}
1260
1261	$needed_dirs = array_unique($needed_dirs);
1262	foreach ( $needed_dirs as $dir ) {
1263		// Check the parent folders of the folders all exist within the creation array.
1264		if ( untrailingslashit($to) == $dir ) // Skip over the working directory, We know this exists (or will exist)
1265			continue;
1266		if ( strpos($dir, $to) === false ) // If the directory is not within the working directory, Skip it
1267			continue;
1268
1269		$parent_folder = dirname($dir);
1270		while ( !empty($parent_folder) && untrailingslashit($to) != $parent_folder && !in_array($parent_folder, $needed_dirs) ) {
1271			$needed_dirs[] = $parent_folder;
1272			$parent_folder = dirname($parent_folder);
1273		}
1274	}
1275	asort($needed_dirs);
1276
1277	// Create those directories if need be:
1278	foreach ( $needed_dirs as $_dir ) {
1279		// Only check to see if the dir exists upon creation failure. Less I/O this way.
1280		if ( ! $wp_filesystem->mkdir( $_dir, FS_CHMOD_DIR ) && ! $wp_filesystem->is_dir( $_dir ) )
1281			return new WP_Error( 'mkdir_failed_pclzip', __( 'Could not create directory.' ), substr( $_dir, strlen( $to ) ) );
1282	}
1283	unset($needed_dirs);
1284
1285	// Extract the files from the zip
1286	foreach ( $archive_files as $file ) {
1287		if ( $file['folder'] )
1288			continue;
1289
1290		if ( '__MACOSX/' === substr($file['filename'], 0, 9) ) // Don't extract the OS X-created __MACOSX directory files
1291			continue;
1292
1293		// Don't extract invalid files:
1294		if ( 0 !== validate_file( $file['filename'] ) ) {
1295			continue;
1296		}
1297
1298		if ( ! $wp_filesystem->put_contents( $to . $file['filename'], $file['content'], FS_CHMOD_FILE) )
1299			return new WP_Error( 'copy_failed_pclzip', __( 'Could not copy file.' ), $file['filename'] );
1300	}
1301	return true;
1302}
1303
1304/**
1305 * Copies a directory from one location to another via the WordPress Filesystem Abstraction.
1306 * Assumes that WP_Filesystem() has already been called and setup.
1307 *
1308 * @since 2.5.0
1309 *
1310 * @global WP_Filesystem_Base $wp_filesystem Subclass
1311 *
1312 * @param string $from source directory
1313 * @param string $to destination directory
1314 * @param array $skip_list a list of files/folders to skip copying
1315 * @return mixed WP_Error on failure, True on success.
1316 */
1317function copy_dir($from, $to, $skip_list = array() ) {
1318	global $wp_filesystem;
1319
1320	$dirlist = $wp_filesystem->dirlist($from);
1321
1322	$from = trailingslashit($from);
1323	$to = trailingslashit($to);
1324
1325	foreach ( (array) $dirlist as $filename => $fileinfo ) {
1326		if ( in_array( $filename, $skip_list ) )
1327			continue;
1328
1329		if ( 'f' == $fileinfo['type'] ) {
1330			if ( ! $wp_filesystem->copy($from . $filename, $to . $filename, true, FS_CHMOD_FILE) ) {
1331				// If copy failed, chmod file to 0644 and try again.
1332				$wp_filesystem->chmod( $to . $filename, FS_CHMOD_FILE );
1333				if ( ! $wp_filesystem->copy($from . $filename, $to . $filename, true, FS_CHMOD_FILE) )
1334					return new WP_Error( 'copy_failed_copy_dir', __( 'Could not copy file.' ), $to . $filename );
1335			}
1336		} elseif ( 'd' == $fileinfo['type'] ) {
1337			if ( !$wp_filesystem->is_dir($to . $filename) ) {
1338				if ( !$wp_filesystem->mkdir($to . $filename, FS_CHMOD_DIR) )
1339					return new WP_Error( 'mkdir_failed_copy_dir', __( 'Could not create directory.' ), $to . $filename );
1340			}
1341
1342			// generate the $sub_skip_list for the subdirectory as a sub-set of the existing $skip_list
1343			$sub_skip_list = array();
1344			foreach ( $skip_list as $skip_item ) {
1345				if ( 0 === strpos( $skip_item, $filename . '/' ) )
1346					$sub_skip_list[] = preg_replace( '!^' . preg_quote( $filename, '!' ) . '/!i', '', $skip_item );
1347			}
1348
1349			$result = copy_dir($from . $filename, $to . $filename, $sub_skip_list);
1350			if ( is_wp_error($result) )
1351				return $result;
1352		}
1353	}
1354	return true;
1355}
1356
1357/**
1358 * Initialises and connects the WordPress Filesystem Abstraction classes.
1359 * This function will include the chosen transport and attempt connecting.
1360 *
1361 * Plugins may add extra transports, And force WordPress to use them by returning
1362 * the filename via the {@see 'filesystem_method_file'} filter.
1363 *
1364 * @since 2.5.0
1365 *
1366 * @global WP_Filesystem_Base $wp_filesystem Subclass
1367 *
1368 * @param array|false  $args                         Optional. Connection args, These are passed directly to
1369 *                                                   the `WP_Filesystem_*()` classes. Default false.
1370 * @param string|false $context                      Optional. Context for get_filesystem_method(). Default false.
1371 * @param bool         $allow_relaxed_file_ownership Optional. Whether to allow Group/World writable. Default false.
1372 * @return null|bool false on failure, true on success.
1373 */
1374function WP_Filesystem( $args = false, $context = false, $allow_relaxed_file_ownership = false ) {
1375	global $wp_filesystem;
1376
1377	require_once(ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php');
1378
1379	$method = get_filesystem_method( $args, $context, $allow_relaxed_file_ownership );
1380
1381	if ( ! $method )
1382		return false;
1383
1384	if ( ! class_exists( "WP_Filesystem_$method" ) ) {
1385
1386		/**
1387		 * Filters the path for a specific filesystem method class file.
1388		 *
1389		 * @since 2.6.0
1390		 *
1391		 * @see get_filesystem_method()
1392		 *
1393		 * @param string $path   Path to the specific filesystem method class file.
1394		 * @param string $method The filesystem method to use.
1395		 */
1396		$abstraction_file = apply_filters( 'filesystem_method_file', ABSPATH . 'wp-admin/includes/class-wp-filesystem-' . $method . '.php', $method );
1397
1398		if ( ! file_exists($abstraction_file) )
1399			return;
1400
1401		require_once($abstraction_file);
1402	}
1403	$method = "WP_Filesystem_$method";
1404
1405	$wp_filesystem = new $method($args);
1406
1407	//Define the timeouts for the connections. Only available after the construct is called to allow for per-transport overriding of the default.
1408	if ( ! defined('FS_CONNECT_TIMEOUT') )
1409		define('FS_CONNECT_TIMEOUT', 30);
1410	if ( ! defined('FS_TIMEOUT') )
1411		define('FS_TIMEOUT', 30);
1412
1413	if ( is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code() )
1414		return false;
1415
1416	if ( !$wp_filesystem->connect() )
1417		return false; //There was an error connecting to the server.
1418
1419	// Set the permission constants if not already set.
1420	if ( ! defined('FS_CHMOD_DIR') )
1421		define('FS_CHMOD_DIR', ( fileperms( ABSPATH ) & 0777 | 0755 ) );
1422	if ( ! defined('FS_CHMOD_FILE') )
1423		define('FS_CHMOD_FILE', ( fileperms( ABSPATH . 'index.php' ) & 0777 | 0644 ) );
1424
1425	return true;
1426}
1427
1428/**
1429 * Determines which method to use for reading, writing, modifying, or deleting
1430 * files on the filesystem.
1431 *
1432 * The priority of the transports are: Direct, SSH2, FTP PHP Extension, FTP Sockets
1433 * (Via Sockets class, or `fsockopen()`). Valid values for these are: 'direct', 'ssh2',
1434 * 'ftpext' or 'ftpsockets'.
1435 *
1436 * The return value can be overridden by defining the `FS_METHOD` constant in `wp-config.php`,
1437 * or filtering via {@see 'filesystem_method'}.
1438 *
1439 * @link https://codex.wordpress.org/Editing_wp-config.php#WordPress_Upgrade_Constants
1440 *
1441 * Plugins may define a custom transport handler, See WP_Filesystem().
1442 *
1443 * @since 2.5.0
1444 *
1445 * @global callable $_wp_filesystem_direct_method
1446 *
1447 * @param array  $args                         Optional. Connection details. Default empty array.
1448 * @param string $context               …

Large files files are truncated, but you can click here to view the full file