Changeset 41721 for trunk/src/wp-admin/includes/file.php
- Timestamp:
- 10/04/2017 12:19:16 AM (7 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/wp-admin/includes/file.php
r41457 r41721 71 71 * 72 72 * @global array $wp_file_descriptions Theme file descriptions. 73 * @global array $allowed_files List of allowed files. 73 * @global array $allowed_files List of allowed files. 74 74 * @param string $file Filesystem path or filename 75 75 * @return string Description of file from $wp_file_descriptions or basename of $file if description doesn't exist. … … 152 152 return $files; 153 153 } 154 155 /** 156 * Get list of file extensions that are editable in plugins. 157 * 158 * @since 4.9.0 159 * 160 * @param string $plugin Plugin. 161 * @return array File extensions. 162 */ 163 function wp_get_plugin_file_editable_extensions( $plugin ) { 164 165 $editable_extensions = array( 166 'bash', 167 'conf', 168 'css', 169 'diff', 170 'htm', 171 'html', 172 'http', 173 'inc', 174 'include', 175 'js', 176 'json', 177 'jsx', 178 'less', 179 'md', 180 'patch', 181 'php', 182 'php3', 183 'php4', 184 'php5', 185 'php7', 186 'phps', 187 'phtml', 188 'sass', 189 'scss', 190 'sh', 191 'sql', 192 'svg', 193 'text', 194 'txt', 195 'xml', 196 'yaml', 197 'yml', 198 ); 199 200 /** 201 * Filters file type extensions editable in the plugin editor. 202 * 203 * @since 2.8.0 204 * @since 4.9.0 Adds $plugin param. 205 * 206 * @param string $plugin Plugin file. 207 * @param array $editable_extensions An array of editable plugin file extensions. 208 */ 209 $editable_extensions = (array) apply_filters( 'editable_extensions', $editable_extensions, $plugin ); 210 211 return $editable_extensions; 212 } 213 214 /** 215 * Get list of file extensions that are editable for a given theme. 216 * 217 * @param WP_Theme $theme Theme. 218 * @return array File extensions. 219 */ 220 function wp_get_theme_file_editable_extensions( $theme ) { 221 222 $default_types = array( 223 'bash', 224 'conf', 225 'css', 226 'diff', 227 'htm', 228 'html', 229 'http', 230 'inc', 231 'include', 232 'js', 233 'json', 234 'jsx', 235 'less', 236 'md', 237 'patch', 238 'php', 239 'php3', 240 'php4', 241 'php5', 242 'php7', 243 'phps', 244 'phtml', 245 'sass', 246 'scss', 247 'sh', 248 'sql', 249 'svg', 250 'text', 251 'txt', 252 'xml', 253 'yaml', 254 'yml', 255 ); 256 257 /** 258 * Filters the list of file types allowed for editing in the Theme editor. 259 * 260 * @since 4.4.0 261 * 262 * @param array $default_types List of file types. Default types include 'php' and 'css'. 263 * @param WP_Theme $theme The current Theme object. 264 */ 265 $file_types = apply_filters( 'wp_theme_editor_filetypes', $default_types, $theme ); 266 267 // Ensure that default types are still there. 268 return array_unique( array_merge( $file_types, $default_types ) ); 269 } 270 271 /** 272 * Print file editor templates (for plugins and themes). 273 * 274 * @since 4.9.0 275 */ 276 function wp_print_file_editor_templates() { 277 ?> 278 <script type="text/html" id="tmpl-wp-file-editor-notice"> 279 <div class="notice inline notice-{{ data.type || 'info' }} {{ data.alt ? 'notice-alt' : '' }} {{ data.dismissible ? 'is-dismissible' : '' }} {{ data.classes || '' }}"> 280 <# if ( 'php_error' === data.code ) { #> 281 <p> 282 <?php 283 printf( 284 /* translators: %$1s is line number and %1$s is file path. */ 285 __( '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.' ), 286 '{{ data.line }}', 287 '{{ data.file }}' 288 ); 289 ?> 290 </p> 291 <pre>{{ data.message }}</pre> 292 <# } else if ( 'file_not_writable' === data.code ) { #> 293 <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> 294 <# } else { #> 295 <p>{{ data.message || data.code }}</p> 296 297 <# if ( 'lint_errors' === data.code ) { #> 298 <p> 299 <# var elementId = 'el-' + String( Math.random() ); #> 300 <input id="{{ elementId }}" type="checkbox"> 301 <label for="{{ elementId }}"><?php _e( 'Update anyway, even though it might break your site?' ); ?></label> 302 </p> 303 <# } #> 304 <# } #> 305 <# if ( data.dismissible ) { #> 306 <button type="button" class="notice-dismiss"><span class="screen-reader-text"><?php _e( 'Dismiss' ); ?></span></button> 307 <# } #> 308 </div> 309 </script> 310 <?php 311 } 312 313 /** 314 * Attempt to edit a file for a theme or plugin. 315 * 316 * When editing a PHP file, loopback requests will be made to the admin and the homepage 317 * to attempt to see if there is a fatal error introduced. If so, the PHP change will be 318 * reverted. 319 * 320 * @since 4.9.0 321 * 322 * @param array $args { 323 * Args. Note that all of the arg values are already unslashed. They are, however, 324 * coming straight from $_POST and are not validated or sanitized in any way. 325 * 326 * @type string $file Relative path to file. 327 * @type string $plugin Plugin being edited. 328 * @type string $theme Theme being edited. 329 * @type string $newcontent New content for the file. 330 * @type string $nonce Nonce. 331 * } 332 * @return true|WP_Error True on success or `WP_Error` on failure. 333 */ 334 function wp_edit_theme_plugin_file( $args ) { 335 if ( empty( $args['file'] ) ) { 336 return new WP_Error( 'missing_file' ); 337 } 338 $file = $args['file']; 339 if ( 0 !== validate_file( $file ) ) { 340 return new WP_Error( 'bad_file' ); 341 } 342 343 if ( ! isset( $args['newcontent'] ) ) { 344 return new WP_Error( 'missing_content' ); 345 } 346 $content = $args['newcontent']; 347 348 if ( ! isset( $args['nonce'] ) ) { 349 return new WP_Error( 'missing_nonce' ); 350 } 351 352 $plugin = null; 353 $theme = null; 354 $real_file = null; 355 if ( ! empty( $args['plugin'] ) ) { 356 $plugin = $args['plugin']; 357 358 if ( ! current_user_can( 'edit_plugins' ) ) { 359 return new WP_Error( 'unauthorized', __( 'Sorry, you are not allowed to edit plugins for this site.' ) ); 360 } 361 362 if ( ! wp_verify_nonce( $args['nonce'], 'edit-plugin_' . $file ) ) { 363 return new WP_Error( 'nonce_failure' ); 364 } 365 366 if ( ! array_key_exists( $plugin, get_plugins() ) ) { 367 return new WP_Error( 'invalid_plugin' ); 368 } 369 370 if ( 0 !== validate_file( $file, get_plugin_files( $plugin ) ) ) { 371 return new WP_Error( 'bad_plugin_file_path', __( 'Sorry, that file cannot be edited.' ) ); 372 } 373 374 $editable_extensions = wp_get_plugin_file_editable_extensions( $plugin ); 375 376 $real_file = WP_PLUGIN_DIR . '/' . $file; 377 378 $is_active = in_array( 379 $plugin, 380 (array) get_option( 'active_plugins', array() ), 381 true 382 ); 383 384 } elseif ( ! empty( $args['theme'] ) ) { 385 $stylesheet = $args['theme']; 386 if ( 0 !== validate_file( $stylesheet ) ) { 387 return new WP_Error( 'bad_theme_path' ); 388 } 389 390 if ( ! current_user_can( 'edit_themes' ) ) { 391 return new WP_Error( 'unauthorized', __( 'Sorry, you are not allowed to edit templates for this site.' ) ); 392 } 393 394 $theme = wp_get_theme( $stylesheet ); 395 if ( ! $theme->exists() ) { 396 return new WP_Error( 'non_existent_theme', __( 'The requested theme does not exist.' ) ); 397 } 398 399 $real_file = $theme->get_stylesheet_directory() . '/' . $file; 400 if ( ! wp_verify_nonce( $args['nonce'], 'edit-theme_' . $real_file . $stylesheet ) ) { 401 return new WP_Error( 'nonce_failure' ); 402 } 403 404 if ( $theme->errors() && 'theme_no_stylesheet' === $theme->errors()->get_error_code() ) { 405 return new WP_Error( 406 'theme_no_stylesheet', 407 __( 'The requested theme does not exist.' ) . ' ' . $theme->errors()->get_error_message() 408 ); 409 } 410 411 $editable_extensions = wp_get_theme_file_editable_extensions( $theme ); 412 413 $allowed_files = array(); 414 foreach ( $editable_extensions as $type ) { 415 switch ( $type ) { 416 case 'php': 417 $allowed_files = array_merge( $allowed_files, $theme->get_files( 'php', 1 ) ); 418 break; 419 case 'css': 420 $style_files = $theme->get_files( 'css' ); 421 $allowed_files['style.css'] = $style_files['style.css']; 422 $allowed_files = array_merge( $allowed_files, $style_files ); 423 break; 424 default: 425 $allowed_files = array_merge( $allowed_files, $theme->get_files( $type ) ); 426 break; 427 } 428 } 429 430 if ( 0 !== validate_file( $real_file, $allowed_files ) ) { 431 return new WP_Error( 'disallowed_theme_file', __( 'Sorry, that file cannot be edited.' ) ); 432 } 433 434 $is_active = ( get_stylesheet() === $stylesheet || get_template() === $stylesheet ); 435 } else { 436 return new WP_Error( 'missing_theme_or_plugin' ); 437 } 438 439 // Ensure file is real. 440 if ( ! is_file( $real_file ) ) { 441 return new WP_Error( 'file_does_not_exist', __( 'No such file exists! Double check the name and try again.' ) ); 442 } 443 444 // Ensure file extension is allowed. 445 $extension = null; 446 if ( preg_match( '/\.([^.]+)$/', $real_file, $matches ) ) { 447 $extension = strtolower( $matches[1] ); 448 if ( ! in_array( $extension, $editable_extensions, true ) ) { 449 return new WP_Error( 'illegal_file_type', __( 'Files of this type are not editable.' ) ); 450 } 451 } 452 453 $previous_content = file_get_contents( $real_file ); 454 455 if ( ! is_writeable( $real_file ) ) { 456 return new WP_Error( 'file_not_writable' ); 457 } 458 459 $f = fopen( $real_file, 'w+' ); 460 if ( false === $f ) { 461 return new WP_Error( 'file_not_writable' ); 462 } 463 464 $written = fwrite( $f, $content ); 465 fclose( $f ); 466 if ( false === $written ) { 467 return new WP_Error( 'unable_to_write', __( 'Unable to write to file.' ) ); 468 } 469 if ( 'php' === $extension && function_exists( 'opcache_invalidate' ) ) { 470 opcache_invalidate( $real_file, true ); 471 } 472 473 if ( $is_active && 'php' === $extension ) { 474 475 $scrape_key = md5( rand() ); 476 $transient = 'scrape_key_' . $scrape_key; 477 $scrape_nonce = strval( rand() ); 478 set_transient( $transient, $scrape_nonce, 60 ); // It shouldn't take more than 60 seconds to make the two loopback requests. 479 480 $cookies = wp_unslash( $_COOKIE ); 481 $scrape_params = array( 482 'wp_scrape_key' => $scrape_key, 483 'wp_scrape_nonce' => $scrape_nonce, 484 ); 485 $headers = array( 486 'Cache-Control' => 'no-cache', 487 ); 488 489 $needle = "###### begin_scraped_error:$scrape_key ######"; 490 491 // Attempt loopback request to editor to see if user just whitescreened themselves. 492 if ( $plugin ) { 493 $url = add_query_arg( compact( 'plugin', 'file' ), admin_url( 'plugin-editor.php' ) ); 494 } elseif ( isset( $stylesheet ) ) { 495 $url = add_query_arg( 496 array( 497 'theme' => $stylesheet, 498 'file' => $file, 499 ), 500 admin_url( 'theme-editor.php' ) 501 ); 502 } else { 503 $url = admin_url(); 504 } 505 $url = add_query_arg( $scrape_params, $url ); 506 $r = wp_remote_get( $url, compact( 'cookies', 'headers' ) ); 507 $body = wp_remote_retrieve_body( $r ); 508 $error_position = strpos( $body, $needle ); 509 510 // Try making request to homepage as well to see if visitors have been whitescreened. 511 if ( false === $error_position ) { 512 $url = home_url( '/' ); 513 $url = add_query_arg( $scrape_params, $url ); 514 $r = wp_remote_get( $url, compact( 'cookies', 'headers' ) ); 515 $body = wp_remote_retrieve_body( $r ); 516 $error_position = strpos( $body, $needle ); 517 } 518 519 delete_transient( $transient ); 520 521 if ( false !== $error_position ) { 522 file_put_contents( $real_file, $previous_content ); 523 if ( function_exists( 'opcache_invalidate' ) ) { 524 opcache_invalidate( $real_file, true ); 525 } 526 527 $error_output = trim( substr( $body, $error_position + strlen( $needle ) ) ); 528 $error = json_decode( $error_output, true ); 529 if ( ! isset( $error['message'] ) ) { 530 $message = $error_output; 531 } else { 532 $message = $error['message']; 533 unset( $error['message'] ); 534 } 535 return new WP_Error( 'php_error', $message, $error ); 536 } 537 } 538 539 if ( $theme instanceof WP_Theme ) { 540 $theme->cache_delete(); 541 } 542 543 return true; 544 } 545 154 546 155 547 /**
Note: See TracChangeset
for help on using the changeset viewer.