[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

/wp-admin/includes/ -> file.php (source)

   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      'privacy-policy.php'    => __( 'Privacy Policy Page' ),
  40      // Attachments
  41      'attachment.php'        => __( 'Attachment Template' ),
  42      'image.php'             => __( 'Image Attachment Template' ),
  43      'video.php'             => __( 'Video Attachment Template' ),
  44      'audio.php'             => __( 'Audio Attachment Template' ),
  45      'application.php'       => __( 'Application Attachment Template' ),
  46      // Embeds
  47      'embed.php'             => __( 'Embed Template' ),
  48      'embed-404.php'         => __( 'Embed 404 Template' ),
  49      'embed-content.php'     => __( 'Embed Content Template' ),
  50      'header-embed.php'      => __( 'Embed Header Template' ),
  51      'footer-embed.php'      => __( 'Embed Footer Template' ),
  52      // Stylesheets
  53      'style.css'             => __( 'Stylesheet' ),
  54      'editor-style.css'      => __( 'Visual Editor Stylesheet' ),
  55      'editor-style-rtl.css'  => __( 'Visual Editor RTL Stylesheet' ),
  56      'rtl.css'               => __( 'RTL Stylesheet' ),
  57      // Other
  58      'my-hacks.php'          => __( 'my-hacks.php (legacy hacks support)' ),
  59      '.htaccess'             => __( '.htaccess (for rewrite rules )' ),
  60      // Deprecated files
  61      'wp-layout.css'         => __( 'Stylesheet' ),
  62      'wp-comments.php'       => __( 'Comments Template' ),
  63      'wp-comments-popup.php' => __( 'Popup Comments Template' ),
  64      'comments-popup.php'    => __( 'Popup Comments' ),
  65  );
  66  
  67  /**
  68   * Get the description for standard WordPress theme files and other various standard
  69   * WordPress 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   * @param string $file Filesystem path or filename
  76   * @return string Description of file from $wp_file_descriptions or basename of $file if description doesn't exist.
  77   *                Appends 'Page Template' to basename of $file if the file is a page template
  78   */
  79  function get_file_description( $file ) {
  80      global $wp_file_descriptions, $allowed_files;
  81  
  82      $dirname = pathinfo( $file, PATHINFO_DIRNAME );
  83  
  84      $file_path = $allowed_files[ $file ];
  85      if ( isset( $wp_file_descriptions[ basename( $file ) ] ) && '.' === $dirname ) {
  86          return $wp_file_descriptions[ basename( $file ) ];
  87      } elseif ( file_exists( $file_path ) && is_file( $file_path ) ) {
  88          $template_data = implode( '', file( $file_path ) );
  89          if ( preg_match( '|Template Name:(.*)$|mi', $template_data, $name ) ) {
  90              return sprintf( __( '%s Page Template' ), _cleanup_header_comment( $name[1] ) );
  91          }
  92      }
  93  
  94      return trim( basename( $file ) );
  95  }
  96  
  97  /**
  98   * Get the absolute filesystem path to the root of the WordPress installation
  99   *
 100   * @since 1.5.0
 101   *
 102   * @return string Full filesystem path to the root of the WordPress installation
 103   */
 104  function get_home_path() {
 105      $home    = set_url_scheme( get_option( 'home' ), 'http' );
 106      $siteurl = set_url_scheme( get_option( 'siteurl' ), 'http' );
 107      if ( ! empty( $home ) && 0 !== strcasecmp( $home, $siteurl ) ) {
 108          $wp_path_rel_to_home = str_ireplace( $home, '', $siteurl ); /* $siteurl - $home */
 109          $pos                 = strripos( str_replace( '\\', '/', $_SERVER['SCRIPT_FILENAME'] ), trailingslashit( $wp_path_rel_to_home ) );
 110          $home_path           = substr( $_SERVER['SCRIPT_FILENAME'], 0, $pos );
 111          $home_path           = trailingslashit( $home_path );
 112      } else {
 113          $home_path = ABSPATH;
 114      }
 115  
 116      return str_replace( '\\', '/', $home_path );
 117  }
 118  
 119  /**
 120   * Returns a listing of all files in the specified folder and all subdirectories up to 100 levels deep.
 121   * The depth of the recursiveness can be controlled by the $levels param.
 122   *
 123   * @since 2.6.0
 124   * @since 4.9.0 Added the `$exclusions` parameter.
 125   *
 126   * @param string   $folder     Optional. Full path to folder. Default empty.
 127   * @param int      $levels     Optional. Levels of folders to follow, Default 100 (PHP Loop limit).
 128   * @param string[] $exclusions Optional. List of folders and files to skip.
 129   * @return bool|string[] False on failure, else array of files.
 130   */
 131  function list_files( $folder = '', $levels = 100, $exclusions = array() ) {
 132      if ( empty( $folder ) ) {
 133          return false;
 134      }
 135  
 136      $folder = trailingslashit( $folder );
 137  
 138      if ( ! $levels ) {
 139          return false;
 140      }
 141  
 142      $files = array();
 143  
 144      $dir = @opendir( $folder );
 145      if ( $dir ) {
 146          while ( ( $file = readdir( $dir ) ) !== false ) {
 147              // Skip current and parent folder links.
 148              if ( in_array( $file, array( '.', '..' ), true ) ) {
 149                  continue;
 150              }
 151  
 152              // Skip hidden and excluded files.
 153              if ( '.' === $file[0] || in_array( $file, $exclusions, true ) ) {
 154                  continue;
 155              }
 156  
 157              if ( is_dir( $folder . $file ) ) {
 158                  $files2 = list_files( $folder . $file, $levels - 1 );
 159                  if ( $files2 ) {
 160                      $files = array_merge( $files, $files2 );
 161                  } else {
 162                      $files[] = $folder . $file . '/';
 163                  }
 164              } else {
 165                  $files[] = $folder . $file;
 166              }
 167          }
 168  
 169          closedir( $dir );
 170      }
 171  
 172      return $files;
 173  }
 174  
 175  /**
 176   * Get list of file extensions that are editable in plugins.
 177   *
 178   * @since 4.9.0
 179   *
 180   * @param string $plugin Path to the plugin file relative to the plugins directory.
 181   * @return string[] Array of editable file extensions.
 182   */
 183  function wp_get_plugin_file_editable_extensions( $plugin ) {
 184  
 185      $editable_extensions = array(
 186          'bash',
 187          'conf',
 188          'css',
 189          'diff',
 190          'htm',
 191          'html',
 192          'http',
 193          'inc',
 194          'include',
 195          'js',
 196          'json',
 197          'jsx',
 198          'less',
 199          'md',
 200          'patch',
 201          'php',
 202          'php3',
 203          'php4',
 204          'php5',
 205          'php7',
 206          'phps',
 207          'phtml',
 208          'sass',
 209          'scss',
 210          'sh',
 211          'sql',
 212          'svg',
 213          'text',
 214          'txt',
 215          'xml',
 216          'yaml',
 217          'yml',
 218      );
 219  
 220      /**
 221       * Filters file type extensions editable in the plugin editor.
 222       *
 223       * @since 2.8.0
 224       * @since 4.9.0 Added the `$plugin` parameter.
 225       *
 226       * @param string[] $editable_extensions An array of editable plugin file extensions.
 227       * @param string   $plugin              Path to the plugin file relative to the plugins directory.
 228       */
 229      $editable_extensions = (array) apply_filters( 'editable_extensions', $editable_extensions, $plugin );
 230  
 231      return $editable_extensions;
 232  }
 233  
 234  /**
 235   * Get list of file extensions that are editable for a given theme.
 236   *
 237   * @param WP_Theme $theme Theme object.
 238   * @return string[] Array of editable file extensions.
 239   */
 240  function wp_get_theme_file_editable_extensions( $theme ) {
 241  
 242      $default_types = array(
 243          'bash',
 244          'conf',
 245          'css',
 246          'diff',
 247          'htm',
 248          'html',
 249          'http',
 250          'inc',
 251          'include',
 252          'js',
 253          'json',
 254          'jsx',
 255          'less',
 256          'md',
 257          'patch',
 258          'php',
 259          'php3',
 260          'php4',
 261          'php5',
 262          'php7',
 263          'phps',
 264          'phtml',
 265          'sass',
 266          'scss',
 267          'sh',
 268          'sql',
 269          'svg',
 270          'text',
 271          'txt',
 272          'xml',
 273          'yaml',
 274          'yml',
 275      );
 276  
 277      /**
 278       * Filters the list of file types allowed for editing in the Theme editor.
 279       *
 280       * @since 4.4.0
 281       *
 282       * @param string[] $default_types List of allowed file types.
 283       * @param WP_Theme $theme         The current Theme object.
 284       */
 285      $file_types = apply_filters( 'wp_theme_editor_filetypes', $default_types, $theme );
 286  
 287      // Ensure that default types are still there.
 288      return array_unique( array_merge( $file_types, $default_types ) );
 289  }
 290  
 291  /**
 292   * Print file editor templates (for plugins and themes).
 293   *
 294   * @since 4.9.0
 295   */
 296  function wp_print_file_editor_templates() {
 297      ?>
 298      <script type="text/html" id="tmpl-wp-file-editor-notice">
 299          <div class="notice inline notice-{{ data.type || 'info' }} {{ data.alt ? 'notice-alt' : '' }} {{ data.dismissible ? 'is-dismissible' : '' }} {{ data.classes || '' }}">
 300              <# if ( 'php_error' === data.code ) { #>
 301                  <p>
 302                      <?php
 303                      printf(
 304                          /* translators: 1: line number, 2: file path */
 305                          __( '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.' ),
 306                          '{{ data.line }}',
 307                          '{{ data.file }}'
 308                      );
 309                      ?>
 310                  </p>
 311                  <pre>{{ data.message }}</pre>
 312              <# } else if ( 'file_not_writable' === data.code ) { #>
 313                  <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>
 314              <# } else { #>
 315                  <p>{{ data.message || data.code }}</p>
 316  
 317                  <# if ( 'lint_errors' === data.code ) { #>
 318                      <p>
 319                          <# var elementId = 'el-' + String( Math.random() ); #>
 320                          <input id="{{ elementId }}"  type="checkbox">
 321                          <label for="{{ elementId }}"><?php _e( 'Update anyway, even though it might break your site?' ); ?></label>
 322                      </p>
 323                  <# } #>
 324              <# } #>
 325              <# if ( data.dismissible ) { #>
 326                  <button type="button" class="notice-dismiss"><span class="screen-reader-text"><?php _e( 'Dismiss' ); ?></span></button>
 327              <# } #>
 328          </div>
 329      </script>
 330      <?php
 331  }
 332  
 333  /**
 334   * Attempt to edit a file for a theme or plugin.
 335   *
 336   * When editing a PHP file, loopback requests will be made to the admin and the homepage
 337   * to attempt to see if there is a fatal error introduced. If so, the PHP change will be
 338   * reverted.
 339   *
 340   * @since 4.9.0
 341   *
 342   * @param string[] $args {
 343   *     Args. Note that all of the arg values are already unslashed. They are, however,
 344   *     coming straight from `$_POST` and are not validated or sanitized in any way.
 345   *
 346   *     @type string $file       Relative path to file.
 347   *     @type string $plugin     Path to the plugin file relative to the plugins directory.
 348   *     @type string $theme      Theme being edited.
 349   *     @type string $newcontent New content for the file.
 350   *     @type string $nonce      Nonce.
 351   * }
 352   * @return true|WP_Error True on success or `WP_Error` on failure.
 353   */
 354  function wp_edit_theme_plugin_file( $args ) {
 355      if ( empty( $args['file'] ) ) {
 356          return new WP_Error( 'missing_file' );
 357      }
 358      $file = $args['file'];
 359      if ( 0 !== validate_file( $file ) ) {
 360          return new WP_Error( 'bad_file' );
 361      }
 362  
 363      if ( ! isset( $args['newcontent'] ) ) {
 364          return new WP_Error( 'missing_content' );
 365      }
 366      $content = $args['newcontent'];
 367  
 368      if ( ! isset( $args['nonce'] ) ) {
 369          return new WP_Error( 'missing_nonce' );
 370      }
 371  
 372      $plugin    = null;
 373      $theme     = null;
 374      $real_file = null;
 375      if ( ! empty( $args['plugin'] ) ) {
 376          $plugin = $args['plugin'];
 377  
 378          if ( ! current_user_can( 'edit_plugins' ) ) {
 379              return new WP_Error( 'unauthorized', __( 'Sorry, you are not allowed to edit plugins for this site.' ) );
 380          }
 381  
 382          if ( ! wp_verify_nonce( $args['nonce'], 'edit-plugin_' . $file ) ) {
 383              return new WP_Error( 'nonce_failure' );
 384          }
 385  
 386          if ( ! array_key_exists( $plugin, get_plugins() ) ) {
 387              return new WP_Error( 'invalid_plugin' );
 388          }
 389  
 390          if ( 0 !== validate_file( $file, get_plugin_files( $plugin ) ) ) {
 391              return new WP_Error( 'bad_plugin_file_path', __( 'Sorry, that file cannot be edited.' ) );
 392          }
 393  
 394          $editable_extensions = wp_get_plugin_file_editable_extensions( $plugin );
 395  
 396          $real_file = WP_PLUGIN_DIR . '/' . $file;
 397  
 398          $is_active = in_array(
 399              $plugin,
 400              (array) get_option( 'active_plugins', array() ),
 401              true
 402          );
 403  
 404      } elseif ( ! empty( $args['theme'] ) ) {
 405          $stylesheet = $args['theme'];
 406          if ( 0 !== validate_file( $stylesheet ) ) {
 407              return new WP_Error( 'bad_theme_path' );
 408          }
 409  
 410          if ( ! current_user_can( 'edit_themes' ) ) {
 411              return new WP_Error( 'unauthorized', __( 'Sorry, you are not allowed to edit templates for this site.' ) );
 412          }
 413  
 414          $theme = wp_get_theme( $stylesheet );
 415          if ( ! $theme->exists() ) {
 416              return new WP_Error( 'non_existent_theme', __( 'The requested theme does not exist.' ) );
 417          }
 418  
 419          if ( ! wp_verify_nonce( $args['nonce'], 'edit-theme_' . $stylesheet . '_' . $file ) ) {
 420              return new WP_Error( 'nonce_failure' );
 421          }
 422  
 423          if ( $theme->errors() && 'theme_no_stylesheet' === $theme->errors()->get_error_code() ) {
 424              return new WP_Error(
 425                  'theme_no_stylesheet',
 426                  __( 'The requested theme does not exist.' ) . ' ' . $theme->errors()->get_error_message()
 427              );
 428          }
 429  
 430          $editable_extensions = wp_get_theme_file_editable_extensions( $theme );
 431  
 432          $allowed_files = array();
 433          foreach ( $editable_extensions as $type ) {
 434              switch ( $type ) {
 435                  case 'php':
 436                      $allowed_files = array_merge( $allowed_files, $theme->get_files( 'php', -1 ) );
 437                      break;
 438                  case 'css':
 439                      $style_files                = $theme->get_files( 'css', -1 );
 440                      $allowed_files['style.css'] = $style_files['style.css'];
 441                      $allowed_files              = array_merge( $allowed_files, $style_files );
 442                      break;
 443                  default:
 444                      $allowed_files = array_merge( $allowed_files, $theme->get_files( $type, -1 ) );
 445                      break;
 446              }
 447          }
 448  
 449          // Compare based on relative paths
 450          if ( 0 !== validate_file( $file, array_keys( $allowed_files ) ) ) {
 451              return new WP_Error( 'disallowed_theme_file', __( 'Sorry, that file cannot be edited.' ) );
 452          }
 453  
 454          $real_file = $theme->get_stylesheet_directory() . '/' . $file;
 455  
 456          $is_active = ( get_stylesheet() === $stylesheet || get_template() === $stylesheet );
 457  
 458      } else {
 459          return new WP_Error( 'missing_theme_or_plugin' );
 460      }
 461  
 462      // Ensure file is real.
 463      if ( ! is_file( $real_file ) ) {
 464          return new WP_Error( 'file_does_not_exist', __( 'File does not exist! Please double check the name and try again.' ) );
 465      }
 466  
 467      // Ensure file extension is allowed.
 468      $extension = null;
 469      if ( preg_match( '/\.([^.]+)$/', $real_file, $matches ) ) {
 470          $extension = strtolower( $matches[1] );
 471          if ( ! in_array( $extension, $editable_extensions, true ) ) {
 472              return new WP_Error( 'illegal_file_type', __( 'Files of this type are not editable.' ) );
 473          }
 474      }
 475  
 476      $previous_content = file_get_contents( $real_file );
 477  
 478      if ( ! is_writeable( $real_file ) ) {
 479          return new WP_Error( 'file_not_writable' );
 480      }
 481  
 482      $f = fopen( $real_file, 'w+' );
 483      if ( false === $f ) {
 484          return new WP_Error( 'file_not_writable' );
 485      }
 486  
 487      $written = fwrite( $f, $content );
 488      fclose( $f );
 489      if ( false === $written ) {
 490          return new WP_Error( 'unable_to_write', __( 'Unable to write to file.' ) );
 491      }
 492      if ( 'php' === $extension && function_exists( 'opcache_invalidate' ) ) {
 493          opcache_invalidate( $real_file, true );
 494      }
 495  
 496      if ( $is_active && 'php' === $extension ) {
 497  
 498          $scrape_key   = md5( rand() );
 499          $transient    = 'scrape_key_' . $scrape_key;
 500          $scrape_nonce = strval( rand() );
 501          set_transient( $transient, $scrape_nonce, 60 ); // It shouldn't take more than 60 seconds to make the two loopback requests.
 502  
 503          $cookies       = wp_unslash( $_COOKIE );
 504          $scrape_params = array(
 505              'wp_scrape_key'   => $scrape_key,
 506              'wp_scrape_nonce' => $scrape_nonce,
 507          );
 508          $headers       = array(
 509              'Cache-Control' => 'no-cache',
 510          );
 511  
 512          // Include Basic auth in loopback requests.
 513          if ( isset( $_SERVER['PHP_AUTH_USER'] ) && isset( $_SERVER['PHP_AUTH_PW'] ) ) {
 514              $headers['Authorization'] = 'Basic ' . base64_encode( wp_unslash( $_SERVER['PHP_AUTH_USER'] ) . ':' . wp_unslash( $_SERVER['PHP_AUTH_PW'] ) );
 515          }
 516  
 517          // Make sure PHP process doesn't die before loopback requests complete.
 518          set_time_limit( 300 );
 519  
 520          // Time to wait for loopback requests to finish.
 521          $timeout = 100;
 522  
 523          $needle_start = "###### wp_scraping_result_start:$scrape_key ######";
 524          $needle_end   = "###### wp_scraping_result_end:$scrape_key ######";
 525  
 526          // Attempt loopback request to editor to see if user just whitescreened themselves.
 527          if ( $plugin ) {
 528              $url = add_query_arg( compact( 'plugin', 'file' ), admin_url( 'plugin-editor.php' ) );
 529          } elseif ( isset( $stylesheet ) ) {
 530              $url = add_query_arg(
 531                  array(
 532                      'theme' => $stylesheet,
 533                      'file'  => $file,
 534                  ),
 535                  admin_url( 'theme-editor.php' )
 536              );
 537          } else {
 538              $url = admin_url();
 539          }
 540          $url                    = add_query_arg( $scrape_params, $url );
 541          $r                      = wp_remote_get( $url, compact( 'cookies', 'headers', 'timeout' ) );
 542          $body                   = wp_remote_retrieve_body( $r );
 543          $scrape_result_position = strpos( $body, $needle_start );
 544  
 545          $loopback_request_failure = array(
 546              'code'    => 'loopback_request_failed',
 547              '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.' ),
 548          );
 549          $json_parse_failure       = array(
 550              'code' => 'json_parse_error',
 551          );
 552  
 553          $result = null;
 554          if ( false === $scrape_result_position ) {
 555              $result = $loopback_request_failure;
 556          } else {
 557              $error_output = substr( $body, $scrape_result_position + strlen( $needle_start ) );
 558              $error_output = substr( $error_output, 0, strpos( $error_output, $needle_end ) );
 559              $result       = json_decode( trim( $error_output ), true );
 560              if ( empty( $result ) ) {
 561                  $result = $json_parse_failure;
 562              }
 563          }
 564  
 565          // Try making request to homepage as well to see if visitors have been whitescreened.
 566          if ( true === $result ) {
 567              $url                    = home_url( '/' );
 568              $url                    = add_query_arg( $scrape_params, $url );
 569              $r                      = wp_remote_get( $url, compact( 'cookies', 'headers', 'timeout' ) );
 570              $body                   = wp_remote_retrieve_body( $r );
 571              $scrape_result_position = strpos( $body, $needle_start );
 572  
 573              if ( false === $scrape_result_position ) {
 574                  $result = $loopback_request_failure;
 575              } else {
 576                  $error_output = substr( $body, $scrape_result_position + strlen( $needle_start ) );
 577                  $error_output = substr( $error_output, 0, strpos( $error_output, $needle_end ) );
 578                  $result       = json_decode( trim( $error_output ), true );
 579                  if ( empty( $result ) ) {
 580                      $result = $json_parse_failure;
 581                  }
 582              }
 583          }
 584  
 585          delete_transient( $transient );
 586  
 587          if ( true !== $result ) {
 588  
 589              // Roll-back file change.
 590              file_put_contents( $real_file, $previous_content );
 591              if ( function_exists( 'opcache_invalidate' ) ) {
 592                  opcache_invalidate( $real_file, true );
 593              }
 594  
 595              if ( ! isset( $result['message'] ) ) {
 596                  $message = __( 'Something went wrong.' );
 597              } else {
 598                  $message = $result['message'];
 599                  unset( $result['message'] );
 600              }
 601              return new WP_Error( 'php_error', $message, $result );
 602          }
 603      }
 604  
 605      if ( $theme instanceof WP_Theme ) {
 606          $theme->cache_delete();
 607      }
 608  
 609      return true;
 610  }
 611  
 612  
 613  /**
 614   * Returns a filename of a Temporary unique file.
 615   * Please note that the calling function must unlink() this itself.
 616   *
 617   * The filename is based off the passed parameter or defaults to the current unix timestamp,
 618   * while the directory can either be passed as well, or by leaving it blank, default to a writable temporary directory.
 619   *
 620   * @since 2.6.0
 621   *
 622   * @param string $filename Optional. Filename to base the Unique file off. Default empty.
 623   * @param string $dir      Optional. Directory to store the file in. Default empty.
 624   * @return string a writable filename
 625   */
 626  function wp_tempnam( $filename = '', $dir = '' ) {
 627      if ( empty( $dir ) ) {
 628          $dir = get_temp_dir();
 629      }
 630  
 631      if ( empty( $filename ) || '.' == $filename || '/' == $filename || '\\' == $filename ) {
 632          $filename = uniqid();
 633      }
 634  
 635      // Use the basename of the given file without the extension as the name for the temporary directory
 636      $temp_filename = basename( $filename );
 637      $temp_filename = preg_replace( '|\.[^.]*$|', '', $temp_filename );
 638  
 639      // If the folder is falsey, use its parent directory name instead.
 640      if ( ! $temp_filename ) {
 641          return wp_tempnam( dirname( $filename ), $dir );
 642      }
 643  
 644      // Suffix some random data to avoid filename conflicts
 645      $temp_filename .= '-' . wp_generate_password( 6, false );
 646      $temp_filename .= '.tmp';
 647      $temp_filename  = $dir . wp_unique_filename( $dir, $temp_filename );
 648  
 649      $fp = @fopen( $temp_filename, 'x' );
 650      if ( ! $fp && is_writable( $dir ) && file_exists( $temp_filename ) ) {
 651          return wp_tempnam( $filename, $dir );
 652      }
 653      if ( $fp ) {
 654          fclose( $fp );
 655      }
 656  
 657      return $temp_filename;
 658  }
 659  
 660  /**
 661   * Makes sure that the file that was requested to be edited is allowed to be edited.
 662   *
 663   * Function will die if you are not allowed to edit the file.
 664   *
 665   * @since 1.5.0
 666   *
 667   * @param string   $file          File the user is attempting to edit.
 668   * @param string[] $allowed_files Optional. Array of allowed files to edit. `$file` must match an entry exactly.
 669   * @return string|void Returns the file name on success, dies on failure.
 670   */
 671  function validate_file_to_edit( $file, $allowed_files = array() ) {
 672      $code = validate_file( $file, $allowed_files );
 673  
 674      if ( ! $code ) {
 675          return $file;
 676      }
 677  
 678      switch ( $code ) {
 679          case 1:
 680              wp_die( __( 'Sorry, that file cannot be edited.' ) );
 681  
 682              // case 2 :
 683              // wp_die( __('Sorry, can&#8217;t call files with their real path.' ));
 684  
 685          case 3:
 686              wp_die( __( 'Sorry, that file cannot be edited.' ) );
 687      }
 688  }
 689  
 690  /**
 691   * Handle PHP uploads in WordPress, sanitizing file names, checking extensions for mime type,
 692   * and moving the file to the appropriate directory within the uploads directory.
 693   *
 694   * @access private
 695   * @since 4.0.0
 696   *
 697   * @see wp_handle_upload_error
 698   *
 699   * @param string[]       $file      Reference to a single element of `$_FILES`. Call the function once for each uploaded file.
 700   * @param string[]|false $overrides An associative array of names => values to override default variables. Default false.
 701   * @param string         $time      Time formatted in 'yyyy/mm'.
 702   * @param string         $action    Expected value for `$_POST['action']`.
 703   * @return string[] On success, returns an associative array of file attributes. On failure, returns
 704   *               `$overrides['upload_error_handler'](&$file, $message )` or `array( 'error'=>$message )`.
 705   */
 706  function _wp_handle_upload( &$file, $overrides, $time, $action ) {
 707      // The default error handler.
 708      if ( ! function_exists( 'wp_handle_upload_error' ) ) {
 709  		function wp_handle_upload_error( &$file, $message ) {
 710              return array( 'error' => $message );
 711          }
 712      }
 713  
 714      /**
 715       * Filters the data for a file before it is uploaded to WordPress.
 716       *
 717       * The dynamic portion of the hook name, `$action`, refers to the post action.
 718       *
 719       * @since 2.9.0 as 'wp_handle_upload_prefilter'.
 720       * @since 4.0.0 Converted to a dynamic hook with `$action`.
 721       *
 722       * @param string[] $file An array of data for a single file.
 723       */
 724      $file = apply_filters( "{$action}_prefilter", $file );
 725  
 726      // You may define your own function and pass the name in $overrides['upload_error_handler']
 727      $upload_error_handler = 'wp_handle_upload_error';
 728      if ( isset( $overrides['upload_error_handler'] ) ) {
 729          $upload_error_handler = $overrides['upload_error_handler'];
 730      }
 731  
 732      // You may have had one or more 'wp_handle_upload_prefilter' functions error out the file. Handle that gracefully.
 733      if ( isset( $file['error'] ) && ! is_numeric( $file['error'] ) && $file['error'] ) {
 734          return call_user_func_array( $upload_error_handler, array( &$file, $file['error'] ) );
 735      }
 736  
 737      // Install user overrides. Did we mention that this voids your warranty?
 738  
 739      // You may define your own function and pass the name in $overrides['unique_filename_callback']
 740      $unique_filename_callback = null;
 741      if ( isset( $overrides['unique_filename_callback'] ) ) {
 742          $unique_filename_callback = $overrides['unique_filename_callback'];
 743      }
 744  
 745      /*
 746       * This may not have originally been intended to be overridable,
 747       * but historically has been.
 748       */
 749      if ( isset( $overrides['upload_error_strings'] ) ) {
 750          $upload_error_strings = $overrides['upload_error_strings'];
 751      } else {
 752          // Courtesy of php.net, the strings that describe the error indicated in $_FILES[{form field}]['error'].
 753          $upload_error_strings = array(
 754              false,
 755              __( 'The uploaded file exceeds the upload_max_filesize directive in php.ini.' ),
 756              __( 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.' ),
 757              __( 'The uploaded file was only partially uploaded.' ),
 758              __( 'No file was uploaded.' ),
 759              '',
 760              __( 'Missing a temporary folder.' ),
 761              __( 'Failed to write file to disk.' ),
 762              __( 'File upload stopped by extension.' ),
 763          );
 764      }
 765  
 766      // All tests are on by default. Most can be turned off by $overrides[{test_name}] = false;
 767      $test_form = isset( $overrides['test_form'] ) ? $overrides['test_form'] : true;
 768      $test_size = isset( $overrides['test_size'] ) ? $overrides['test_size'] : true;
 769  
 770      // If you override this, you must provide $ext and $type!!
 771      $test_type = isset( $overrides['test_type'] ) ? $overrides['test_type'] : true;
 772      $mimes     = isset( $overrides['mimes'] ) ? $overrides['mimes'] : false;
 773  
 774      // A correct form post will pass this test.
 775      if ( $test_form && ( ! isset( $_POST['action'] ) || ( $_POST['action'] != $action ) ) ) {
 776          return call_user_func_array( $upload_error_handler, array( &$file, __( 'Invalid form submission.' ) ) );
 777      }
 778      // A successful upload will pass this test. It makes no sense to override this one.
 779      if ( isset( $file['error'] ) && $file['error'] > 0 ) {
 780          return call_user_func_array( $upload_error_handler, array( &$file, $upload_error_strings[ $file['error'] ] ) );
 781      }
 782  
 783      // A properly uploaded file will pass this test. There should be no reason to override this one.
 784      $test_uploaded_file = 'wp_handle_upload' === $action ? is_uploaded_file( $file['tmp_name'] ) : @is_readable( $file['tmp_name'] );
 785      if ( ! $test_uploaded_file ) {
 786          return call_user_func_array( $upload_error_handler, array( &$file, __( 'Specified file failed upload test.' ) ) );
 787      }
 788  
 789      $test_file_size = 'wp_handle_upload' === $action ? $file['size'] : filesize( $file['tmp_name'] );
 790      // A non-empty file will pass this test.
 791      if ( $test_size && ! ( $test_file_size > 0 ) ) {
 792          if ( is_multisite() ) {
 793              $error_msg = __( 'File is empty. Please upload something more substantial.' );
 794          } else {
 795              $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.' );
 796          }
 797          return call_user_func_array( $upload_error_handler, array( &$file, $error_msg ) );
 798      }
 799  
 800      // A correct MIME type will pass this test. Override $mimes or use the upload_mimes filter.
 801      if ( $test_type ) {
 802          $wp_filetype     = wp_check_filetype_and_ext( $file['tmp_name'], $file['name'], $mimes );
 803          $ext             = empty( $wp_filetype['ext'] ) ? '' : $wp_filetype['ext'];
 804          $type            = empty( $wp_filetype['type'] ) ? '' : $wp_filetype['type'];
 805          $proper_filename = empty( $wp_filetype['proper_filename'] ) ? '' : $wp_filetype['proper_filename'];
 806  
 807          // Check to see if wp_check_filetype_and_ext() determined the filename was incorrect
 808          if ( $proper_filename ) {
 809              $file['name'] = $proper_filename;
 810          }
 811          if ( ( ! $type || ! $ext ) && ! current_user_can( 'unfiltered_upload' ) ) {
 812              return call_user_func_array( $upload_error_handler, array( &$file, __( 'Sorry, this file type is not permitted for security reasons.' ) ) );
 813          }
 814          if ( ! $type ) {
 815              $type = $file['type'];
 816          }
 817      } else {
 818          $type = '';
 819      }
 820  
 821      /*
 822       * A writable uploads dir will pass this test. Again, there's no point
 823       * overriding this one.
 824       */
 825      $uploads = wp_upload_dir( $time );
 826      if ( ! ( $uploads && false === $uploads['error'] ) ) {
 827          return call_user_func_array( $upload_error_handler, array( &$file, $uploads['error'] ) );
 828      }
 829  
 830      $filename = wp_unique_filename( $uploads['path'], $file['name'], $unique_filename_callback );
 831  
 832      // Move the file to the uploads dir.
 833      $new_file = $uploads['path'] . "/$filename";
 834  
 835      /**
 836       * Filters whether to short-circuit moving the uploaded file after passing all checks.
 837       *
 838       * If a non-null value is passed to the filter, moving the file and any related error
 839       * reporting will be completely skipped.
 840       *
 841       * @since 4.9.0
 842       *
 843       * @param string $move_new_file If null (default) move the file after the upload.
 844       * @param string $file          An array of data for a single file.
 845       * @param string $new_file      Filename of the newly-uploaded file.
 846       * @param string $type          File type.
 847       */
 848      $move_new_file = apply_filters( 'pre_move_uploaded_file', null, $file, $new_file, $type );
 849  
 850      if ( null === $move_new_file ) {
 851          if ( 'wp_handle_upload' === $action ) {
 852              $move_new_file = @move_uploaded_file( $file['tmp_name'], $new_file );
 853          } else {
 854              // use copy and unlink because rename breaks streams.
 855              // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
 856              $move_new_file = @copy( $file['tmp_name'], $new_file );
 857              unlink( $file['tmp_name'] );
 858          }
 859  
 860          if ( false === $move_new_file ) {
 861              if ( 0 === strpos( $uploads['basedir'], ABSPATH ) ) {
 862                  $error_path = str_replace( ABSPATH, '', $uploads['basedir'] ) . $uploads['subdir'];
 863              } else {
 864                  $error_path = basename( $uploads['basedir'] ) . $uploads['subdir'];
 865              }
 866              return $upload_error_handler( $file, sprintf( __( 'The uploaded file could not be moved to %s.' ), $error_path ) );
 867          }
 868      }
 869  
 870      // Set correct file permissions.
 871      $stat  = stat( dirname( $new_file ) );
 872      $perms = $stat['mode'] & 0000666;
 873      chmod( $new_file, $perms );
 874  
 875      // Compute the URL.
 876      $url = $uploads['url'] . "/$filename";
 877  
 878      if ( is_multisite() ) {
 879          delete_transient( 'dirsize_cache' );
 880      }
 881  
 882      /**
 883       * Filters the data array for the uploaded file.
 884       *
 885       * @since 2.1.0
 886       *
 887       * @param array  $upload {
 888       *     Array of upload data.
 889       *
 890       *     @type string $file Filename of the newly-uploaded file.
 891       *     @type string $url  URL of the uploaded file.
 892       *     @type string $type File type.
 893       * }
 894       * @param string $context The type of upload action. Values include 'upload' or 'sideload'.
 895       */
 896      return apply_filters(
 897          'wp_handle_upload',
 898          array(
 899              'file' => $new_file,
 900              'url'  => $url,
 901              'type' => $type,
 902          ),
 903          'wp_handle_sideload' === $action ? 'sideload' : 'upload'
 904      );
 905  }
 906  
 907  /**
 908   * Wrapper for _wp_handle_upload().
 909   *
 910   * Passes the {@see 'wp_handle_upload'} action.
 911   *
 912   * @since 2.0.0
 913   *
 914   * @see _wp_handle_upload()
 915   *
 916   * @param array      $file      Reference to a single element of `$_FILES`. Call the function once for
 917   *                              each uploaded file.
 918   * @param array|bool $overrides Optional. An associative array of names=>values to override default
 919   *                              variables. Default false.
 920   * @param string     $time      Optional. Time formatted in 'yyyy/mm'. Default null.
 921   * @return array On success, returns an associative array of file attributes. On failure, returns
 922   *               $overrides['upload_error_handler'](&$file, $message ) or array( 'error'=>$message ).
 923   */
 924  function wp_handle_upload( &$file, $overrides = false, $time = null ) {
 925      /*
 926       *  $_POST['action'] must be set and its value must equal $overrides['action']
 927       *  or this:
 928       */
 929      $action = 'wp_handle_upload';
 930      if ( isset( $overrides['action'] ) ) {
 931          $action = $overrides['action'];
 932      }
 933  
 934      return _wp_handle_upload( $file, $overrides, $time, $action );
 935  }
 936  
 937  /**
 938   * Wrapper for _wp_handle_upload().
 939   *
 940   * Passes the {@see 'wp_handle_sideload'} action.
 941   *
 942   * @since 2.6.0
 943   *
 944   * @see _wp_handle_upload()
 945   *
 946   * @param array      $file      An array similar to that of a PHP `$_FILES` POST array
 947   * @param array|bool $overrides Optional. An associative array of names=>values to override default
 948   *                              variables. Default false.
 949   * @param string     $time      Optional. Time formatted in 'yyyy/mm'. Default null.
 950   * @return array On success, returns an associative array of file attributes. On failure, returns
 951   *               $overrides['upload_error_handler'](&$file, $message ) or array( 'error'=>$message ).
 952   */
 953  function wp_handle_sideload( &$file, $overrides = false, $time = null ) {
 954      /*
 955       *  $_POST['action'] must be set and its value must equal $overrides['action']
 956       *  or this:
 957       */
 958      $action = 'wp_handle_sideload';
 959      if ( isset( $overrides['action'] ) ) {
 960          $action = $overrides['action'];
 961      }
 962      return _wp_handle_upload( $file, $overrides, $time, $action );
 963  }
 964  
 965  
 966  /**
 967   * Downloads a URL to a local temporary file using the WordPress HTTP API.
 968   *
 969   * Please note that the calling function must unlink() the file.
 970   *
 971   * @since 2.5.0
 972   * @since 5.2.0 Signature Verification with SoftFail was added.
 973   *
 974   * @param string $url                    The URL of the file to download.
 975   * @param int    $timeout                The timeout for the request to download the file. Default 300 seconds.
 976   * @param bool   $signature_verification Whether to perform Signature Verification. Default false.
 977   * @return string|WP_Error Filename on success, WP_Error on failure.
 978   */
 979  function download_url( $url, $timeout = 300, $signature_verification = false ) {
 980      //WARNING: The file is not automatically deleted, The script must unlink() the file.
 981      if ( ! $url ) {
 982          return new WP_Error( 'http_no_url', __( 'Invalid URL Provided.' ) );
 983      }
 984  
 985      $url_filename = basename( parse_url( $url, PHP_URL_PATH ) );
 986  
 987      $tmpfname = wp_tempnam( $url_filename );
 988      if ( ! $tmpfname ) {
 989          return new WP_Error( 'http_no_file', __( 'Could not create Temporary file.' ) );
 990      }
 991  
 992      $response = wp_safe_remote_get(
 993          $url,
 994          array(
 995              'timeout'  => $timeout,
 996              'stream'   => true,
 997              'filename' => $tmpfname,
 998          )
 999      );
1000  
1001      if ( is_wp_error( $response ) ) {
1002          unlink( $tmpfname );
1003          return $response;
1004      }
1005  
1006      $response_code = wp_remote_retrieve_response_code( $response );
1007  
1008      if ( 200 != $response_code ) {
1009          $data = array(
1010              'code' => $response_code,
1011          );
1012  
1013          // Retrieve a sample of the response body for debugging purposes.
1014          $tmpf = fopen( $tmpfname, 'rb' );
1015          if ( $tmpf ) {
1016              /**
1017               * Filters the maximum error response body size in `download_url()`.
1018               *
1019               * @since 5.1.0
1020               *
1021               * @see download_url()
1022               *
1023               * @param int $size The maximum error response body size. Default 1 KB.
1024               */
1025              $response_size = apply_filters( 'download_url_error_max_body_size', KB_IN_BYTES );
1026              $data['body']  = fread( $tmpf, $response_size );
1027              fclose( $tmpf );
1028          }
1029  
1030          unlink( $tmpfname );
1031          return new WP_Error( 'http_404', trim( wp_remote_retrieve_response_message( $response ) ), $data );
1032      }
1033  
1034      $content_md5 = wp_remote_retrieve_header( $response, 'content-md5' );
1035      if ( $content_md5 ) {
1036          $md5_check = verify_file_md5( $tmpfname, $content_md5 );
1037          if ( is_wp_error( $md5_check ) ) {
1038              unlink( $tmpfname );
1039              return $md5_check;
1040          }
1041      }
1042  
1043      // If the caller expects signature verification to occur, check to see if this URL supports it.
1044      if ( $signature_verification ) {
1045          /**
1046           * Filters the list of hosts which should have Signature Verification attempteds on.
1047           *
1048           * @since 5.2.0
1049           *
1050           * @param array List of hostnames.
1051           */
1052          $signed_hostnames       = apply_filters( 'wp_signature_hosts', array( 'wordpress.org', 'downloads.wordpress.org', 's.w.org' ) );
1053          $signature_verification = in_array( parse_url( $url, PHP_URL_HOST ), $signed_hostnames, true );
1054      }
1055  
1056      // Perform signature valiation if supported.
1057      if ( $signature_verification ) {
1058          $signature = wp_remote_retrieve_header( $response, 'x-content-signature' );
1059          if ( ! $signature ) {
1060              // Retrieve signatures from a file if the header wasn't included.
1061              // WordPress.org stores signatures at $package_url.sig
1062  
1063              $signature_url = false;
1064              $url_path      = parse_url( $url, PHP_URL_PATH );
1065              if ( substr( $url_path, -4 ) == '.zip' || substr( $url_path, -7 ) == '.tar.gz' ) {
1066                  $signature_url = str_replace( $url_path, $url_path . '.sig', $url );
1067              }
1068  
1069              /**
1070               * Filter the URL where the signature for a file is located.
1071               *
1072               * @since 5.2.0
1073               *
1074               * @param false|string $signature_url The URL where signatures can be found for a file, or false if none are known.
1075               * @param string $url                 The URL being verified.
1076               */
1077              $signature_url = apply_filters( 'wp_signature_url', $signature_url, $url );
1078  
1079              if ( $signature_url ) {
1080                  $signature_request = wp_safe_remote_get(
1081                      $signature_url,
1082                      array(
1083                          'limit_response_size' => 10 * 1024, // 10KB should be large enough for quite a few signatures.
1084                      )
1085                  );
1086  
1087                  if ( ! is_wp_error( $signature_request ) && 200 === wp_remote_retrieve_response_code( $signature_request ) ) {
1088                      $signature = explode( "\n", wp_remote_retrieve_body( $signature_request ) );
1089                  }
1090              }
1091          }
1092  
1093          // Perform the checks.
1094          $signature_verification = verify_file_signature( $tmpfname, $signature, basename( parse_url( $url, PHP_URL_PATH ) ) );
1095      }
1096  
1097      if ( is_wp_error( $signature_verification ) ) {
1098          if (
1099              /**
1100               * Filters whether Signature Verification failures should be allowed to soft fail.
1101               *
1102               * WARNING: This may be removed from a future release.
1103               *
1104               * @since 5.2.0
1105               *
1106               * @param bool   $signature_softfail If a softfail is allowed.
1107               * @param string $url                The url being accessed.
1108               */
1109              apply_filters( 'wp_signature_softfail', true, $url )
1110          ) {
1111              $signature_verification->add_data( $tmpfname, 'softfail-filename' );
1112          } else {
1113              // Hard-fail.
1114              unlink( $tmpfname );
1115          }
1116  
1117          return $signature_verification;
1118      }
1119  
1120      return $tmpfname;
1121  }
1122  
1123  /**
1124   * Calculates and compares the MD5 of a file to its expected value.
1125   *
1126   * @since 3.7.0
1127   *
1128   * @param string $filename     The filename to check the MD5 of.
1129   * @param string $expected_md5 The expected MD5 of the file, either a base64-encoded raw md5,
1130   *                             or a hex-encoded md5.
1131   * @return bool|WP_Error True on success, false when the MD5 format is unknown/unexpected,
1132   *                       WP_Error on failure.
1133   */
1134  function verify_file_md5( $filename, $expected_md5 ) {
1135      if ( 32 == strlen( $expected_md5 ) ) {
1136          $expected_raw_md5 = pack( 'H*', $expected_md5 );
1137      } elseif ( 24 == strlen( $expected_md5 ) ) {
1138          $expected_raw_md5 = base64_decode( $expected_md5 );
1139      } else {
1140          return false; // unknown format
1141      }
1142  
1143      $file_md5 = md5_file( $filename, true );
1144  
1145      if ( $file_md5 === $expected_raw_md5 ) {
1146          return true;
1147      }
1148  
1149      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 ) ) );
1150  }
1151  
1152  /**
1153   * Verifies the contents of a file against its ED25519 signature.
1154   *
1155   * @since 5.2.0
1156   *
1157   * @param string       $filename            The file to validate.
1158   * @param string|array $signatures          A Signature provided for the file.
1159   * @param string       $filename_for_errors A friendly filename for errors. Optional.
1160   *
1161   * @return bool|WP_Error true on success, false if verification not attempted, or WP_Error describing an error condition.
1162   */
1163  function verify_file_signature( $filename, $signatures, $filename_for_errors = false ) {
1164      if ( ! $filename_for_errors ) {
1165          $filename_for_errors = wp_basename( $filename );
1166      }
1167  
1168      // Check we can process signatures.
1169      if ( ! function_exists( 'sodium_crypto_sign_verify_detached' ) || ! in_array( 'sha384', array_map( 'strtolower', hash_algos() ) ) ) {
1170          return new WP_Error(
1171              'signature_verification_unsupported',
1172              sprintf(
1173                  /* translators: %s: The filename of the package. */
1174                  __( 'The authenticity of %s could not be verified as signature verification is unavailable on this system.' ),
1175                  '<span class="code">' . esc_html( $filename_for_errors ) . '</span>'
1176              ),
1177              ( ! function_exists( 'sodium_crypto_sign_verify_detached' ) ? 'sodium_crypto_sign_verify_detached' : 'sha384' )
1178          );
1179      }
1180  
1181      // Check for a edge-case affecting PHP Maths abilities
1182      if (
1183          ! extension_loaded( 'sodium' ) &&
1184          in_array( PHP_VERSION_ID, [ 70200, 70201, 70202 ], true ) &&
1185          extension_loaded( 'opcache' )
1186      ) {
1187          // Sodium_Compat isn't compatible with PHP 7.2.0~7.2.2 due to a bug in the PHP Opcache extension, bail early as it'll fail.
1188          // https://bugs.php.net/bug.php?id=75938
1189  
1190          return new WP_Error(
1191              'signature_verification_unsupported',
1192              sprintf(
1193                  /* translators: %s: The filename of the package. */
1194                  __( 'The authenticity of %s could not be verified as signature verification is unavailable on this system.' ),
1195                  '<span class="code">' . esc_html( $filename_for_errors ) . '</span>'
1196              ),
1197              array(
1198                  'php'    => phpversion(),
1199                  'sodium' => defined( 'SODIUM_LIBRARY_VERSION' ) ? SODIUM_LIBRARY_VERSION : ( defined( 'ParagonIE_Sodium_Compat::VERSION_STRING' ) ? ParagonIE_Sodium_Compat::VERSION_STRING : false ),
1200              )
1201          );
1202  
1203      }
1204  
1205      // Verify runtime speed of Sodium_Compat is acceptable.
1206      if ( ! extension_loaded( 'sodium' ) && ! ParagonIE_Sodium_Compat::polyfill_is_fast() ) {
1207          $sodium_compat_is_fast = false;
1208  
1209          // Allow for an old version of Sodium_Compat being loaded before the bundled WordPress one.
1210          if ( method_exists( 'ParagonIE_Sodium_Compat', 'runtime_speed_test' ) ) {
1211              // Run `ParagonIE_Sodium_Compat::runtime_speed_test()` in optimized integer mode, as that's what WordPress utilises during signing verifications.
1212              // phpcs:disable WordPress.NamingConventions.ValidVariableName
1213              $old_fastMult                      = ParagonIE_Sodium_Compat::$fastMult;
1214              ParagonIE_Sodium_Compat::$fastMult = true;
1215              $sodium_compat_is_fast             = ParagonIE_Sodium_Compat::runtime_speed_test( 100, 10 );
1216              ParagonIE_Sodium_Compat::$fastMult = $old_fastMult;
1217              // phpcs:enable
1218          }
1219  
1220          // This cannot be performed in a reasonable amount of time
1221          // https://github.com/paragonie/sodium_compat#help-sodium_compat-is-slow-how-can-i-make-it-fast
1222          if ( ! $sodium_compat_is_fast ) {
1223              return new WP_Error(
1224                  'signature_verification_unsupported',
1225                  sprintf(
1226                      /* translators: %s: The filename of the package. */
1227                      __( 'The authenticity of %s could not be verified as signature verification is unavailable on this system.' ),
1228                      '<span class="code">' . esc_html( $filename_for_errors ) . '</span>'
1229                  ),
1230                  array(
1231                      'php'                => phpversion(),
1232                      'sodium'             => defined( 'SODIUM_LIBRARY_VERSION' ) ? SODIUM_LIBRARY_VERSION : ( defined( 'ParagonIE_Sodium_Compat::VERSION_STRING' ) ? ParagonIE_Sodium_Compat::VERSION_STRING : false ),
1233                      'polyfill_is_fast'   => false,
1234                      'max_execution_time' => ini_get( 'max_execution_time' ),
1235                  )
1236              );
1237          }
1238      }
1239  
1240      if ( ! $signatures ) {
1241          return new WP_Error(
1242              'signature_verification_no_signature',
1243              sprintf(
1244                  /* translators: %s: The filename of the package. */
1245                  __( 'The authenticity of %s could not be verified as no signature was found.' ),
1246                  '<span class="code">' . esc_html( $filename_for_errors ) . '</span>'
1247              ),
1248              array(
1249                  'filename' => $filename_for_errors,
1250              )
1251          );
1252      }
1253  
1254      $trusted_keys = wp_trusted_keys();
1255      $file_hash    = hash_file( 'sha384', $filename, true );
1256  
1257      mbstring_binary_safe_encoding();
1258  
1259      $skipped_key       = 0;
1260      $skipped_signature = 0;
1261  
1262      foreach ( (array) $signatures as $signature ) {
1263          $signature_raw = base64_decode( $signature );
1264  
1265          // Ensure only valid-length signatures are considered.
1266          if ( SODIUM_CRYPTO_SIGN_BYTES !== strlen( $signature_raw ) ) {
1267              $skipped_signature++;
1268              continue;
1269          }
1270  
1271          foreach ( (array) $trusted_keys as $key ) {
1272              $key_raw = base64_decode( $key );
1273  
1274              // Only pass valid public keys through.
1275              if ( SODIUM_CRYPTO_SIGN_PUBLICKEYBYTES !== strlen( $key_raw ) ) {
1276                  $skipped_key++;
1277                  continue;
1278              }
1279  
1280              if ( sodium_crypto_sign_verify_detached( $signature_raw, $file_hash, $key_raw ) ) {
1281                  reset_mbstring_encoding();
1282                  return true;
1283              }
1284          }
1285      }
1286  
1287      reset_mbstring_encoding();
1288  
1289      return new WP_Error(
1290          'signature_verification_failed',
1291          sprintf(
1292              /* translators: %s: The filename of the package. */
1293              __( 'The authenticity of %s could not be verified.' ),
1294              '<span class="code">' . esc_html( $filename_for_errors ) . '</span>'
1295          ),
1296          // Error data helpful for debugging:
1297          array(
1298              'filename'    => $filename_for_errors,
1299              'keys'        => $trusted_keys,
1300              'signatures'  => $signatures,
1301              'hash'        => bin2hex( $file_hash ),
1302              'skipped_key' => $skipped_key,
1303              'skipped_sig' => $skipped_signature,
1304              'php'         => phpversion(),
1305              'sodium'      => defined( 'SODIUM_LIBRARY_VERSION' ) ? SODIUM_LIBRARY_VERSION : ( defined( 'ParagonIE_Sodium_Compat::VERSION_STRING' ) ? ParagonIE_Sodium_Compat::VERSION_STRING : false ),
1306          )
1307      );
1308  }
1309  
1310  /**
1311   * Retrieve the list of signing keys trusted by WordPress.
1312   *
1313   * @since 5.2.0
1314   *
1315   * @return array List of base64-encoded Signing keys.
1316   */
1317  function wp_trusted_keys() {
1318      $trusted_keys = array();
1319  
1320      if ( time() < 1617235200 ) {
1321          // WordPress.org Key #1 - This key is only valid before April 1st, 2021.
1322          $trusted_keys[] = 'fRPyrxb/MvVLbdsYi+OOEv4xc+Eqpsj+kkAS6gNOkI0=';
1323      }
1324  
1325      // TODO: Add key #2 with longer expiration.
1326  
1327      /**
1328       * Filter the valid Signing keys used to verify the contents of files.
1329       *
1330       * @since 5.2.0
1331       *
1332       * @param array $trusted_keys The trusted keys that may sign packages.
1333       */
1334      return apply_filters( 'wp_trusted_keys', $trusted_keys );
1335  }
1336  
1337  /**
1338   * Unzips a specified ZIP file to a location on the filesystem via the WordPress
1339   * Filesystem Abstraction.
1340   *
1341   * Assumes that WP_Filesystem() has already been called and set up. Does not extract
1342   * a root-level __MACOSX directory, if present.
1343   *
1344   * Attempts to increase the PHP memory limit to 256M before uncompressing. However,
1345   * the most memory required shouldn't be much larger than the archive itself.
1346   *
1347   * @since 2.5.0
1348   *
1349   * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
1350   *
1351   * @param string $file Full path and filename of ZIP archive.
1352   * @param string $to   Full path on the filesystem to extract archive to.
1353   * @return true|WP_Error True on success, WP_Error on failure.
1354   */
1355  function unzip_file( $file, $to ) {
1356      global $wp_filesystem;
1357  
1358      if ( ! $wp_filesystem || ! is_object( $wp_filesystem ) ) {
1359          return new WP_Error( 'fs_unavailable', __( 'Could not access filesystem.' ) );
1360      }
1361  
1362      // Unzip can use a lot of memory, but not this much hopefully.
1363      wp_raise_memory_limit( 'admin' );
1364  
1365      $needed_dirs = array();
1366      $to          = trailingslashit( $to );
1367  
1368      // Determine any parent directories needed (of the upgrade directory).
1369      if ( ! $wp_filesystem->is_dir( $to ) ) { // Only do parents if no children exist.
1370          $path = preg_split( '![/\\\]!', untrailingslashit( $to ) );
1371          for ( $i = count( $path ); $i >= 0; $i-- ) {
1372              if ( empty( $path[ $i ] ) ) {
1373                  continue;
1374              }
1375  
1376              $dir = implode( '/', array_slice( $path, 0, $i + 1 ) );
1377              if ( preg_match( '!^[a-z]:$!i', $dir ) ) { // Skip it if it looks like a Windows Drive letter.
1378                  continue;
1379              }
1380  
1381              if ( ! $wp_filesystem->is_dir( $dir ) ) {
1382                  $needed_dirs[] = $dir;
1383              } else {
1384                  break; // A folder exists, therefore we don't need to check the levels below this.
1385              }
1386          }
1387      }
1388  
1389      /**
1390       * Filters whether to use ZipArchive to unzip archives.
1391       *
1392       * @since 3.0.0
1393       *
1394       * @param bool $ziparchive Whether to use ZipArchive. Default true.
1395       */
1396      if ( class_exists( 'ZipArchive', false ) && apply_filters( 'unzip_file_use_ziparchive', true ) ) {
1397          $result = _unzip_file_ziparchive( $file, $to, $needed_dirs );
1398          if ( true === $result ) {
1399              return $result;
1400          } elseif ( is_wp_error( $result ) ) {
1401              if ( 'incompatible_archive' != $result->get_error_code() ) {
1402                  return $result;
1403              }
1404          }
1405      }
1406      // Fall through to PclZip if ZipArchive is not available, or encountered an error opening the file.
1407      return _unzip_file_pclzip( $file, $to, $needed_dirs );
1408  }
1409  
1410  /**
1411   * Attempts to unzip an archive using the ZipArchive class.
1412   *
1413   * This function should not be called directly, use `unzip_file()` instead.
1414   *
1415   * Assumes that WP_Filesystem() has already been called and set up.
1416   *
1417   * @since 3.0.0
1418   * @see unzip_file()
1419   * @access private
1420   *
1421   * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
1422   *
1423   * @param string $file       Full path and filename of ZIP archive.
1424   * @param string $to         Full path on the filesystem to extract archive to.
1425   * @param array $needed_dirs A partial list of required folders needed to be created.
1426   * @return true|WP_Error True on success, WP_Error on failure.
1427   */
1428  function _unzip_file_ziparchive( $file, $to, $needed_dirs = array() ) {
1429      global $wp_filesystem;
1430  
1431      $z = new ZipArchive();
1432  
1433      $zopen = $z->open( $file, ZIPARCHIVE::CHECKCONS );
1434      if ( true !== $zopen ) {
1435          return new WP_Error( 'incompatible_archive', __( 'Incompatible Archive.' ), array( 'ziparchive_error' => $zopen ) );
1436      }
1437  
1438      $uncompressed_size = 0;
1439  
1440      for ( $i = 0; $i < $z->numFiles; $i++ ) {
1441          $info = $z->statIndex( $i );
1442          if ( ! $info ) {
1443              return new WP_Error( 'stat_failed_ziparchive', __( 'Could not retrieve file from archive.' ) );
1444          }
1445  
1446          if ( '__MACOSX/' === substr( $info['name'], 0, 9 ) ) { // Skip the OS X-created __MACOSX directory
1447              continue;
1448          }
1449  
1450          // Don't extract invalid files:
1451          if ( 0 !== validate_file( $info['name'] ) ) {
1452              continue;
1453          }
1454  
1455          $uncompressed_size += $info['size'];
1456  
1457          $dirname = dirname( $info['name'] );
1458  
1459          if ( '/' === substr( $info['name'], -1 ) ) {
1460              // Directory.
1461              $needed_dirs[] = $to . untrailingslashit( $info['name'] );
1462          } elseif ( '.' !== $dirname ) {
1463              // Path to a file.
1464              $needed_dirs[] = $to . untrailingslashit( $dirname );
1465          }
1466      }
1467  
1468      /*
1469       * disk_free_space() could return false. Assume that any falsey value is an error.
1470       * A disk that has zero free bytes has bigger problems.
1471       * Require we have enough space to unzip the file and copy its contents, with a 10% buffer.
1472       */
1473      if ( wp_doing_cron() ) {
1474          $available_space = @disk_free_space( WP_CONTENT_DIR );
1475          if ( $available_space && ( $uncompressed_size * 2.1 ) > $available_space ) {
1476              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' ) );
1477          }
1478      }
1479  
1480      $needed_dirs = array_unique( $needed_dirs );
1481      foreach ( $needed_dirs as $dir ) {
1482          // Check the parent folders of the folders all exist within the creation array.
1483          if ( untrailingslashit( $to ) == $dir ) { // Skip over the working directory, We know this exists (or will exist)
1484              continue;
1485          }
1486          if ( strpos( $dir, $to ) === false ) { // If the directory is not within the working directory, Skip it
1487              continue;
1488          }
1489  
1490          $parent_folder = dirname( $dir );
1491          while ( ! empty( $parent_folder ) && untrailingslashit( $to ) != $parent_folder && ! in_array( $parent_folder, $needed_dirs ) ) {
1492              $needed_dirs[] = $parent_folder;
1493              $parent_folder = dirname( $parent_folder );
1494          }
1495      }
1496      asort( $needed_dirs );
1497  
1498      // Create those directories if need be:
1499      foreach ( $needed_dirs as $_dir ) {
1500          // Only check to see if the Dir exists upon creation failure. Less I/O this way.
1501          if ( ! $wp_filesystem->mkdir( $_dir, FS_CHMOD_DIR ) && ! $wp_filesystem->is_dir( $_dir ) ) {
1502              return new WP_Error( 'mkdir_failed_ziparchive', __( 'Could not create directory.' ), substr( $_dir, strlen( $to ) ) );
1503          }
1504      }
1505      unset( $needed_dirs );
1506  
1507      for ( $i = 0; $i < $z->numFiles; $i++ ) {
1508          $info = $z->statIndex( $i );
1509          if ( ! $info ) {
1510              return new WP_Error( 'stat_failed_ziparchive', __( 'Could not retrieve file from archive.' ) );
1511          }
1512  
1513          if ( '/' == substr( $info['name'], -1 ) ) { // directory
1514              continue;
1515          }
1516  
1517          if ( '__MACOSX/' === substr( $info['name'], 0, 9 ) ) { // Don't extract the OS X-created __MACOSX directory files
1518              continue;
1519          }
1520  
1521          // Don't extract invalid files:
1522          if ( 0 !== validate_file( $info['name'] ) ) {
1523              continue;
1524          }
1525  
1526          $contents = $z->getFromIndex( $i );
1527          if ( false === $contents ) {
1528              return new WP_Error( 'extract_failed_ziparchive', __( 'Could not extract file from archive.' ), $info['name'] );
1529          }
1530  
1531          if ( ! $wp_filesystem->put_contents( $to . $info['name'], $contents, FS_CHMOD_FILE ) ) {
1532              return new WP_Error( 'copy_failed_ziparchive', __( 'Could not copy file.' ), $info['name'] );
1533          }
1534      }
1535  
1536      $z->close();
1537  
1538      return true;
1539  }
1540  
1541  /**
1542   * Attempts to unzip an archive using the PclZip library.
1543   *
1544   * This function should not be called directly, use `unzip_file()` instead.
1545   *
1546   * Assumes that WP_Filesystem() has already been called and set up.
1547   *
1548   * @since 3.0.0
1549   * @see unzip_file()
1550   * @access private
1551   *
1552   * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
1553   *
1554   * @param string $file       Full path and filename of ZIP archive.
1555   * @param string $to         Full path on the filesystem to extract archive to.
1556   * @param array $needed_dirs A partial list of required folders needed to be created.
1557   * @return true|WP_Error True on success, WP_Error on failure.
1558   */
1559  function _unzip_file_pclzip( $file, $to, $needed_dirs = array() ) {
1560      global $wp_filesystem;
1561  
1562      mbstring_binary_safe_encoding();
1563  
1564      require_once( ABSPATH . 'wp-admin/includes/class-pclzip.php' );
1565  
1566      $archive = new PclZip( $file );
1567  
1568      $archive_files = $archive->extract( PCLZIP_OPT_EXTRACT_AS_STRING );
1569  
1570      reset_mbstring_encoding();
1571  
1572      // Is the archive valid?
1573      if ( ! is_array( $archive_files ) ) {
1574          return new WP_Error( 'incompatible_archive', __( 'Incompatible Archive.' ), $archive->errorInfo( true ) );
1575      }
1576  
1577      if ( 0 == count( $archive_files ) ) {
1578          return new WP_Error( 'empty_archive_pclzip', __( 'Empty archive.' ) );
1579      }
1580  
1581      $uncompressed_size = 0;
1582  
1583      // Determine any children directories needed (From within the archive)
1584      foreach ( $archive_files as $file ) {
1585          if ( '__MACOSX/' === substr( $file['filename'], 0, 9 ) ) { // Skip the OS X-created __MACOSX directory
1586              continue;
1587          }
1588  
1589          $uncompressed_size += $file['size'];
1590  
1591          $needed_dirs[] = $to . untrailingslashit( $file['folder'] ? $file['filename'] : dirname( $file['filename'] ) );
1592      }
1593  
1594      /*
1595       * disk_free_space() could return false. Assume that any falsey value is an error.
1596       * A disk that has zero free bytes has bigger problems.
1597       * Require we have enough space to unzip the file and copy its contents, with a 10% buffer.
1598       */
1599      if ( wp_doing_cron() ) {
1600          $available_space = @disk_free_space( WP_CONTENT_DIR );
1601          if ( $available_space && ( $uncompressed_size * 2.1 ) > $available_space ) {
1602              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' ) );
1603          }
1604      }
1605  
1606      $needed_dirs = array_unique( $needed_dirs );
1607      foreach ( $needed_dirs as $dir ) {
1608          // Check the parent folders of the folders all exist within the creation array.
1609          if ( untrailingslashit( $to ) == $dir ) { // Skip over the working directory, We know this exists (or will exist)
1610              continue;
1611          }
1612          if ( strpos( $dir, $to ) === false ) { // If the directory is not within the working directory, Skip it
1613              continue;
1614          }
1615  
1616          $parent_folder = dirname( $dir );
1617          while ( ! empty( $parent_folder ) && untrailingslashit( $to ) != $parent_folder && ! in_array( $parent_folder, $needed_dirs ) ) {
1618              $needed_dirs[] = $parent_folder;
1619              $parent_folder = dirname( $parent_folder );
1620          }
1621      }
1622      asort( $needed_dirs );
1623  
1624      // Create those directories if need be:
1625      foreach ( $needed_dirs as $_dir ) {
1626          // Only check to see if the dir exists upon creation failure. Less I/O this way.
1627          if ( ! $wp_filesystem->mkdir( $_dir, FS_CHMOD_DIR ) && ! $wp_filesystem->is_dir( $_dir ) ) {
1628              return new WP_Error( 'mkdir_failed_pclzip', __( 'Could not create directory.' ), substr( $_dir, strlen( $to ) ) );
1629          }
1630      }
1631      unset( $needed_dirs );
1632  
1633      // Extract the files from the zip
1634      foreach ( $archive_files as $file ) {
1635          if ( $file['folder'] ) {
1636              continue;
1637          }
1638  
1639          if ( '__MACOSX/' === substr( $file['filename'], 0, 9 ) ) { // Don't extract the OS X-created __MACOSX directory files
1640              continue;
1641          }
1642  
1643          // Don't extract invalid files:
1644          if ( 0 !== validate_file( $file['filename'] ) ) {
1645              continue;
1646          }
1647  
1648          if ( ! $wp_filesystem->put_contents( $to . $file['filename'], $file['content'], FS_CHMOD_FILE ) ) {
1649              return new WP_Error( 'copy_failed_pclzip', __( 'Could not copy file.' ), $file['filename'] );
1650          }
1651      }
1652      return true;
1653  }
1654  
1655  /**
1656   * Copies a directory from one location to another via the WordPress Filesystem
1657   * Abstraction.
1658   *
1659   * Assumes that WP_Filesystem() has already been called and setup.
1660   *
1661   * @since 2.5.0
1662   *
1663   * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
1664   *
1665   * @param string $from     Source directory.
1666   * @param string $to       Destination directory.
1667   * @param array $skip_list A list of files/folders to skip copying.
1668   * @return true|WP_Error True on success, WP_Error on failure.
1669   */
1670  function copy_dir( $from, $to, $skip_list = array() ) {
1671      global $wp_filesystem;
1672  
1673      $dirlist = $wp_filesystem->dirlist( $from );
1674  
1675      $from = trailingslashit( $from );
1676      $to   = trailingslashit( $to );
1677  
1678      foreach ( (array) $dirlist as $filename => $fileinfo ) {
1679          if ( in_array( $filename, $skip_list ) ) {
1680              continue;
1681          }
1682  
1683          if ( 'f' == $fileinfo['type'] ) {
1684              if ( ! $wp_filesystem->copy( $from . $filename, $to . $filename, true, FS_CHMOD_FILE ) ) {
1685                  // If copy failed, chmod file to 0644 and try again.
1686                  $wp_filesystem->chmod( $to . $filename, FS_CHMOD_FILE );
1687                  if ( ! $wp_filesystem->copy( $from . $filename, $to . $filename, true, FS_CHMOD_FILE ) ) {
1688                      return new WP_Error( 'copy_failed_copy_dir', __( 'Could not copy file.' ), $to . $filename );
1689                  }
1690              }
1691          } elseif ( 'd' == $fileinfo['type'] ) {
1692              if ( ! $wp_filesystem->is_dir( $to . $filename ) ) {
1693                  if ( ! $wp_filesystem->mkdir( $to . $filename, FS_CHMOD_DIR ) ) {
1694                      return new WP_Error( 'mkdir_failed_copy_dir', __( 'Could not create directory.' ), $to . $filename );
1695                  }
1696              }
1697  
1698              // generate the $sub_skip_list for the subdirectory as a sub-set of the existing $skip_list
1699              $sub_skip_list = array();
1700              foreach ( $skip_list as $skip_item ) {
1701                  if ( 0 === strpos( $skip_item, $filename . '/' ) ) {
1702                      $sub_skip_list[] = preg_replace( '!^' . preg_quote( $filename, '!' ) . '/!i', '', $skip_item );
1703                  }
1704              }
1705  
1706              $result = copy_dir( $from . $filename, $to . $filename, $sub_skip_list );
1707              if ( is_wp_error( $result ) ) {
1708                  return $result;
1709              }
1710          }
1711      }
1712      return true;
1713  }
1714  
1715  /**
1716   * Initialises and connects the WordPress Filesystem Abstraction classes.
1717   *
1718   * This function will include the chosen transport and attempt connecting.
1719   *
1720   * Plugins may add extra transports, And force WordPress to use them by returning
1721   * the filename via the {@see 'filesystem_method_file'} filter.
1722   *
1723   * @since 2.5.0
1724   *
1725   * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
1726   *
1727   * @param array|false  $args                         Optional. Connection args, These are passed directly to
1728   *                                                   the `WP_Filesystem_*()` classes. Default false.
1729   * @param string|false $context                      Optional. Context for get_filesystem_method(). Default false.
1730   * @param bool         $allow_relaxed_file_ownership Optional. Whether to allow Group/World writable. Default false.
1731   * @return bool|null True on success, false on failure, null if the filesystem method class file does not exist.
1732   */
1733  function WP_Filesystem( $args = false, $context = false, $allow_relaxed_file_ownership = false ) { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionNameInvalid
1734      global $wp_filesystem;
1735  
1736      require_once( ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php' );
1737  
1738      $method = get_filesystem_method( $args, $context, $allow_relaxed_file_ownership );
1739  
1740      if ( ! $method ) {
1741          return false;
1742      }
1743  
1744      if ( ! class_exists( "WP_Filesystem_$method" ) ) {
1745  
1746          /**
1747           * Filters the path for a specific filesystem method class file.
1748           *
1749           * @since 2.6.0
1750           *
1751           * @see get_filesystem_method()
1752           *
1753           * @param string $path   Path to the specific filesystem method class file.
1754           * @param string $method The filesystem method to use.
1755           */
1756          $abstraction_file = apply_filters( 'filesystem_method_file', ABSPATH . 'wp-admin/includes/class-wp-filesystem-' . $method . '.php', $method );
1757  
1758          if ( ! file_exists( $abstraction_file ) ) {
1759              return;
1760          }
1761  
1762          require_once( $abstraction_file );
1763      }
1764      $method = "WP_Filesystem_$method";
1765  
1766      $wp_filesystem = new $method( $args );
1767  
1768      //Define the timeouts for the connections. Only available after the construct is called to allow for per-transport overriding of the default.
1769      if ( ! defined( 'FS_CONNECT_TIMEOUT' ) ) {
1770          define( 'FS_CONNECT_TIMEOUT', 30 );
1771      }
1772      if ( ! defined( 'FS_TIMEOUT' ) ) {
1773          define( 'FS_TIMEOUT', 30 );
1774      }
1775  
1776      if ( is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
1777          return false;
1778      }
1779  
1780      if ( ! $wp_filesystem->connect() ) {
1781          return false; //There was an error connecting to the server.
1782      }
1783  
1784      // Set the permission constants if not already set.
1785      if ( ! defined( 'FS_CHMOD_DIR' ) ) {
1786          define( 'FS_CHMOD_DIR', ( fileperms( ABSPATH ) & 0777 | 0755 ) );
1787      }
1788      if ( ! defined( 'FS_CHMOD_FILE' ) ) {
1789          define( 'FS_CHMOD_FILE', ( fileperms( ABSPATH . 'index.php' ) & 0777 | 0644 ) );
1790      }
1791  
1792      return true;
1793  }
1794  
1795  /**
1796   * Determines which method to use for reading, writing, modifying, or deleting
1797   * files on the filesystem.
1798   *
1799   * The priority of the transports are: Direct, SSH2, FTP PHP Extension, FTP Sockets
1800   * (Via Sockets class, or `fsockopen()`). Valid values for these are: 'direct', 'ssh2',
1801   * 'ftpext' or 'ftpsockets'.
1802   *
1803   * The return value can be overridden by defining the `FS_METHOD` constant in `wp-config.php`,
1804   * or filtering via {@see 'filesystem_method'}.
1805   *
1806   * @link https://codex.wordpress.org/Editing_wp-config.php#WordPress_Upgrade_Constants
1807   *
1808   * Plugins may define a custom transport handler, See WP_Filesystem().
1809   *
1810   * @since 2.5.0
1811   *
1812   * @global callable $_wp_filesystem_direct_method
1813   *
1814   * @param array  $args                         Optional. Connection details. Default empty array.
1815   * @param string $context                      Optional. Full path to the directory that is tested
1816   *                                             for being writable. Default empty.
1817   * @param bool   $allow_relaxed_file_ownership Optional. Whether to allow Group/World writable.
1818   *                                             Default false.
1819   * @return string The transport to use, see description for valid return values.
1820   */
1821  function get_filesystem_method( $args = array(), $context = '', $allow_relaxed_file_ownership = false ) {
1822      $method = defined( 'FS_METHOD' ) ? FS_METHOD : false; // Please ensure that this is either 'direct', 'ssh2', 'ftpext' or 'ftpsockets'
1823  
1824      if ( ! $context ) {
1825          $context = WP_CONTENT_DIR;
1826      }
1827  
1828      // If the directory doesn't exist (wp-content/languages) then use the parent directory as we'll create it.
1829      if ( WP_LANG_DIR == $context && ! is_dir( $context ) ) {
1830          $context = dirname( $context );
1831      }
1832  
1833      $context = trailingslashit( $context );
1834  
1835      if ( ! $method ) {
1836  
1837          $temp_file_name = $context . 'temp-write-test-' . str_replace( '.', '-', uniqid( '', true ) );
1838          $temp_handle    = @fopen( $temp_file_name, 'w' );
1839          if ( $temp_handle ) {
1840  
1841              // Attempt to determine the file owner of the WordPress files, and that of newly created files
1842              $wp_file_owner   = false;
1843              $temp_file_owner = false;
1844              if ( function_exists( 'fileowner' ) ) {
1845                  $wp_file_owner   = @fileowner( __FILE__ );
1846                  $temp_file_owner = @fileowner( $temp_file_name );
1847              }
1848  
1849              if ( $wp_file_owner !== false && $wp_file_owner === $temp_file_owner ) {
1850                  // WordPress is creating files as the same owner as the WordPress files,
1851                  // this means it's safe to modify & create new files via PHP.
1852                  $method                                  = 'direct';
1853                  $GLOBALS['_wp_filesystem_direct_method'] = 'file_owner';
1854              } elseif ( $allow_relaxed_file_ownership ) {
1855                  // The $context directory is writable, and $allow_relaxed_file_ownership is set, this means we can modify files
1856                  // safely in this directory. This mode doesn't create new files, only alter existing ones.
1857                  $method                                  = 'direct';
1858                  $GLOBALS['_wp_filesystem_direct_method'] = 'relaxed_ownership';
1859              }
1860  
1861              fclose( $temp_handle );
1862              @unlink( $temp_file_name );
1863          }
1864      }
1865  
1866      if ( ! $method && isset( $args['connection_type'] ) && 'ssh' == $args['connection_type'] && extension_loaded( 'ssh2' ) && function_exists( 'stream_get_contents' ) ) {
1867          $method = 'ssh2';
1868      }
1869      if ( ! $method && extension_loaded( 'ftp' ) ) {
1870          $method = 'ftpext';
1871      }
1872      if ( ! $method && ( extension_loaded( 'sockets' ) || function_exists( 'fsockopen' ) ) ) {
1873          $method = 'ftpsockets'; //Sockets: Socket extension; PHP Mode: FSockopen / fwrite / fread
1874      }
1875  
1876      /**
1877       * Filters the filesystem method to use.
1878       *
1879       * @since 2.6.0
1880       *
1881       * @param string $method  Filesystem method to return.
1882       * @param array  $args    An array of connection details for the method.
1883       * @param string $context Full path to the directory that is tested for being writable.
1884       * @param bool   $allow_relaxed_file_ownership Whether to allow Group/World writable.
1885       */
1886      return apply_filters( 'filesystem_method', $method, $args, $context, $allow_relaxed_file_ownership );
1887  }
1888  
1889  /**
1890   * Displays a form to the user to request for their FTP/SSH details in order
1891   * to connect to the filesystem.
1892   *
1893   * All chosen/entered details are saved, excluding the password.
1894   *
1895   * Hostnames may be in the form of hostname:portnumber (eg: wordpress.org:2467)
1896   * to specify an alternate FTP/SSH port.
1897   *
1898   * Plugins may override this form by returning true|false via the {@see 'request_filesystem_credentials'} filter.
1899   *
1900   * @since 2.5.0
1901   * @since 4.6.0 The `$context` parameter default changed from `false` to an empty string.
1902   *
1903   * @global string $pagenow
1904   *
1905   * @param string $form_post                    The URL to post the form to.
1906   * @param string $type                         Optional. Chosen type of filesystem. Default empty.
1907   * @param bool   $error                        Optional. Whether the current request has failed to connect.
1908   *                                             Default false.
1909   * @param string $context                      Optional. Full path to the directory that is tested for being
1910   *                                             writable. Default empty.
1911   * @param array  $extra_fields                 Optional. Extra `POST` fields to be checked for inclusion in
1912   *                                             the post. Default null.
1913   * @param bool   $allow_relaxed_file_ownership Optional. Whether to allow Group/World writable. Default false.
1914   *
1915   * @return bool True on success, false on failure.
1916   */
1917  function request_filesystem_credentials( $form_post, $type = '', $error = false, $context = '', $extra_fields = null, $allow_relaxed_file_ownership = false ) {
1918      global $pagenow;
1919  
1920      /**
1921       * Filters the filesystem credentials form output.
1922       *
1923       * Returning anything other than an empty string will effectively short-circuit
1924       * output of the filesystem credentials form, returning that value instead.
1925       *
1926       * @since 2.5.0
1927       * @since 4.6.0 The `$context` parameter default changed from `false` to an empty string.
1928       *
1929       * @param mixed  $output                       Form output to return instead. Default empty.
1930       * @param string $form_post                    The URL to post the form to.
1931       * @param string $type                         Chosen type of filesystem.
1932       * @param bool   $error                        Whether the current request has failed to connect.
1933       *                                             Default false.
1934       * @param string $context                      Full path to the directory that is tested for
1935       *                                             being writable.
1936       * @param bool   $allow_relaxed_file_ownership Whether to allow Group/World writable.
1937       *                                             Default false.
1938       * @param array  $extra_fields                 Extra POST fields.
1939       */
1940      $req_cred = apply_filters( 'request_filesystem_credentials', '', $form_post, $type, $error, $context, $extra_fields, $allow_relaxed_file_ownership );
1941      if ( '' !== $req_cred ) {
1942          return $req_cred;
1943      }
1944  
1945      if ( empty( $type ) ) {
1946          $type = get_filesystem_method( array(), $context, $allow_relaxed_file_ownership );
1947      }
1948  
1949      if ( 'direct' == $type ) {
1950          return true;
1951      }
1952  
1953      if ( is_null( $extra_fields ) ) {
1954          $extra_fields = array( 'version', 'locale' );
1955      }
1956  
1957      $credentials = get_option(
1958          'ftp_credentials',
1959          array(
1960              'hostname' => '',
1961              'username' => '',
1962          )
1963      );
1964  
1965      $submitted_form = wp_unslash( $_POST );
1966  
1967      // Verify nonce, or unset submitted form field values on failure
1968      if ( ! isset( $_POST['_fs_nonce'] ) || ! wp_verify_nonce( $_POST['_fs_nonce'], 'filesystem-credentials' ) ) {
1969          unset(
1970              $submitted_form['hostname'],
1971              $submitted_form['username'],
1972              $submitted_form['password'],
1973              $submitted_form['public_key'],
1974              $submitted_form['private_key'],
1975              $submitted_form['connection_type']
1976          );
1977      }
1978  
1979      // If defined, set it to that, Else, If POST'd, set it to that, If not, Set it to whatever it previously was(saved details in option)
1980      $credentials['hostname'] = defined( 'FTP_HOST' ) ? FTP_HOST : ( ! empty( $submitted_form['hostname'] ) ? $submitted_form['hostname'] : $credentials['hostname'] );
1981      $credentials['username'] = defined( 'FTP_USER' ) ? FTP_USER : ( ! empty( $submitted_form['username'] ) ? $submitted_form['username'] : $credentials['username'] );
1982      $credentials['password'] = defined( 'FTP_PASS' ) ? FTP_PASS : ( ! empty( $submitted_form['password'] ) ? $submitted_form['password'] : '' );
1983  
1984      // Check to see if we are setting the public/private keys for ssh
1985      $credentials['public_key']  = defined( 'FTP_PUBKEY' ) ? FTP_PUBKEY : ( ! empty( $submitted_form['public_key'] ) ? $submitted_form['public_key'] : '' );
1986      $credentials['private_key'] = defined( 'FTP_PRIKEY' ) ? FTP_PRIKEY : ( ! empty( $submitted_form['private_key'] ) ? $submitted_form['private_key'] : '' );
1987  
1988      // Sanitize the hostname, Some people might pass in odd-data:
1989      $credentials['hostname'] = preg_replace( '|\w+://|', '', $credentials['hostname'] ); //Strip any schemes off
1990  
1991      if ( strpos( $credentials['hostname'], ':' ) ) {
1992          list( $credentials['hostname'], $credentials['port'] ) = explode( ':', $credentials['hostname'], 2 );
1993          if ( ! is_numeric( $credentials['port'] ) ) {
1994              unset( $credentials['port'] );
1995          }
1996      } else {
1997          unset( $credentials['port'] );
1998      }
1999  
2000      if ( ( defined( 'FTP_SSH' ) && FTP_SSH ) || ( defined( 'FS_METHOD' ) && 'ssh2' == FS_METHOD ) ) {
2001          $credentials['connection_type'] = 'ssh';
2002      } elseif ( ( defined( 'FTP_SSL' ) && FTP_SSL ) && 'ftpext' == $type ) { //Only the FTP Extension understands SSL
2003          $credentials['connection_type'] = 'ftps';
2004      } elseif ( ! empty( $submitted_form['connection_type'] ) ) {
2005          $credentials['connection_type'] = $submitted_form['connection_type'];
2006      } elseif ( ! isset( $credentials['connection_type'] ) ) { //All else fails (And it's not defaulted to something else saved), Default to FTP
2007          $credentials['connection_type'] = 'ftp';
2008      }
2009      if ( ! $error &&
2010              (
2011                  ( ! empty( $credentials['password'] ) && ! empty( $credentials['username'] ) && ! empty( $credentials['hostname'] ) ) ||
2012                  ( 'ssh' == $credentials['connection_type'] && ! empty( $credentials['public_key'] ) && ! empty( $credentials['private_key'] ) )
2013              ) ) {
2014          $stored_credentials = $credentials;
2015          if ( ! empty( $stored_credentials['port'] ) ) { //save port as part of hostname to simplify above code.
2016              $stored_credentials['hostname'] .= ':' . $stored_credentials['port'];
2017          }
2018  
2019          unset( $stored_credentials['password'], $stored_credentials['port'], $stored_credentials['private_key'], $stored_credentials['public_key'] );
2020          if ( ! wp_installing() ) {
2021              update_option( 'ftp_credentials', $stored_credentials );
2022          }
2023          return $credentials;
2024      }
2025      $hostname        = isset( $credentials['hostname'] ) ? $credentials['hostname'] : '';
2026      $username        = isset( $credentials['username'] ) ? $credentials['username'] : '';
2027      $public_key      = isset( $credentials['public_key'] ) ? $credentials['public_key'] : '';
2028      $private_key     = isset( $credentials['private_key'] ) ? $credentials['private_key'] : '';
2029      $port            = isset( $credentials['port'] ) ? $credentials['port'] : '';
2030      $connection_type = isset( $credentials['connection_type'] ) ? $credentials['connection_type'] : '';
2031  
2032      if ( $error ) {
2033          $error_string = __( '<strong>ERROR:</strong> There was an error connecting to the server, Please verify the settings are correct.' );
2034          if ( is_wp_error( $error ) ) {
2035              $error_string = esc_html( $error->get_error_message() );
2036          }
2037          echo '<div id="message" class="error"><p>' . $error_string . '</p></div>';
2038      }
2039  
2040      $types = array();
2041      if ( extension_loaded( 'ftp' ) || extension_loaded( 'sockets' ) || function_exists( 'fsockopen' ) ) {
2042          $types['ftp'] = __( 'FTP' );
2043      }
2044      if ( extension_loaded( 'ftp' ) ) { //Only this supports FTPS
2045          $types['ftps'] = __( 'FTPS (SSL)' );
2046      }
2047      if ( extension_loaded( 'ssh2' ) && function_exists( 'stream_get_contents' ) ) {
2048          $types['ssh'] = __( 'SSH2' );
2049      }
2050  
2051      /**
2052       * Filters the connection types to output to the filesystem credentials form.
2053       *
2054       * @since 2.9.0
2055       * @since 4.6.0 The `$context` parameter default changed from `false` to an empty string.
2056       *
2057       * @param array  $types       Types of connections.
2058       * @param array  $credentials Credentials to connect with.
2059       * @param string $type        Chosen filesystem method.
2060       * @param object $error       Error object.
2061       * @param string $context     Full path to the directory that is tested
2062       *                            for being writable.
2063       */
2064      $types = apply_filters( 'fs_ftp_connection_types', $types, $credentials, $type, $error, $context );
2065  
2066      ?>
2067  <form action="<?php echo esc_url( $form_post ); ?>" method="post">
2068  <div id="request-filesystem-credentials-form" class="request-filesystem-credentials-form">
2069      <?php
2070      // Print a H1 heading in the FTP credentials modal dialog, default is a H2.
2071      $heading_tag = 'h2';
2072      if ( 'plugins.php' === $pagenow || 'plugin-install.php' === $pagenow ) {
2073          $heading_tag = 'h1';
2074      }
2075      echo "<$heading_tag id='request-filesystem-credentials-title'>" . __( 'Connection Information' ) . "</$heading_tag>";
2076      ?>
2077  <p id="request-filesystem-credentials-desc">
2078      <?php
2079      $label_user = __( 'Username' );
2080      $label_pass = __( 'Password' );
2081      _e( 'To perform the requested action, WordPress needs to access your web server.' );
2082      echo ' ';
2083      if ( ( isset( $types['ftp'] ) || isset( $types['ftps'] ) ) ) {
2084          if ( isset( $types['ssh'] ) ) {
2085              _e( 'Please enter your FTP or SSH credentials to proceed.' );
2086              $label_user = __( 'FTP/SSH Username' );
2087              $label_pass = __( 'FTP/SSH Password' );
2088          } else {
2089              _e( 'Please enter your FTP credentials to proceed.' );
2090              $label_user = __( 'FTP Username' );
2091              $label_pass = __( 'FTP Password' );
2092          }
2093          echo ' ';
2094      }
2095      _e( 'If you do not remember your credentials, you should contact your web host.' );
2096  
2097      $hostname_value = esc_attr( $hostname );
2098      if ( ! empty( $port ) ) {
2099          $hostname_value .= ":$port";
2100      }
2101  
2102      $password_value = '';
2103      if ( defined( 'FTP_PASS' ) ) {
2104          $password_value = '*****';
2105      }
2106      ?>
2107  </p>
2108  <label for="hostname">
2109      <span class="field-title"><?php _e( 'Hostname' ); ?></span>
2110      <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' ) ); ?> />
2111  </label>
2112  <div class="ftp-username">
2113      <label for="username">
2114          <span class="field-title"><?php echo $label_user; ?></span>
2115          <input name="username" type="text" id="username" value="<?php echo esc_attr( $username ); ?>"<?php disabled( defined( 'FTP_USER' ) ); ?> />
2116      </label>
2117  </div>
2118  <div class="ftp-password">
2119      <label for="password">
2120          <span class="field-title"><?php echo $label_pass; ?></span>
2121          <input name="password" type="password" id="password" value="<?php echo $password_value; ?>"<?php disabled( defined( 'FTP_PASS' ) ); ?> />
2122          <em>
2123          <?php
2124          if ( ! defined( 'FTP_PASS' ) ) {
2125              _e( 'This password will not be stored on the server.' );}
2126          ?>
2127  </em>
2128      </label>
2129  </div>
2130  <fieldset>
2131  <legend><?php _e( 'Connection Type' ); ?></legend>
2132      <?php
2133      $disabled = disabled( ( defined( 'FTP_SSL' ) && FTP_SSL ) || ( defined( 'FTP_SSH' ) && FTP_SSH ), true, false );
2134      foreach ( $types as $name => $text ) :
2135          ?>
2136      <label for="<?php echo esc_attr( $name ); ?>">
2137          <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; ?> />
2138          <?php echo $text; ?>
2139      </label>
2140          <?php
2141      endforeach;
2142      ?>
2143  </fieldset>
2144      <?php
2145      if ( isset( $types['ssh'] ) ) {
2146          $hidden_class = '';
2147          if ( 'ssh' != $connection_type || empty( $connection_type ) ) {
2148              $hidden_class = ' class="hidden"';
2149          }
2150          ?>
2151  <fieldset id="ssh-keys"<?php echo $hidden_class; ?>>
2152  <legend><?php _e( 'Authentication Keys' ); ?></legend>
2153  <label for="public_key">
2154      <span class="field-title"><?php _e( 'Public Key:' ); ?></span>
2155      <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' ) ); ?> />
2156  </label>
2157  <label for="private_key">
2158      <span class="field-title"><?php _e( 'Private Key:' ); ?></span>
2159      <input name="private_key" type="text" id="private_key" value="<?php echo esc_attr( $private_key ); ?>"<?php disabled( defined( 'FTP_PRIKEY' ) ); ?> />
2160  </label>
2161  <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>
2162  </fieldset>
2163          <?php
2164      }
2165  
2166      foreach ( (array) $extra_fields as $field ) {
2167          if ( isset( $submitted_form[ $field ] ) ) {
2168              echo '<input type="hidden" name="' . esc_attr( $field ) . '" value="' . esc_attr( $submitted_form[ $field ] ) . '" />';
2169          }
2170      }
2171      ?>
2172      <p class="request-filesystem-credentials-action-buttons">
2173          <?php wp_nonce_field( 'filesystem-credentials', '_fs_nonce', false, true ); ?>
2174          <button class="button cancel-button" data-js-action="close" type="button"><?php _e( 'Cancel' ); ?></button>
2175          <?php submit_button( __( 'Proceed' ), '', 'upgrade', false ); ?>
2176      </p>
2177  </div>
2178  </form>
2179      <?php
2180      return false;
2181  }
2182  
2183  /**
2184   * Print the filesystem credentials modal when needed.
2185   *
2186   * @since 4.2.0
2187   */
2188  function wp_print_request_filesystem_credentials_modal() {
2189      $filesystem_method = get_filesystem_method();
2190      ob_start();
2191      $filesystem_credentials_are_stored = request_filesystem_credentials( self_admin_url() );
2192      ob_end_clean();
2193      $request_filesystem_credentials = ( $filesystem_method != 'direct' && ! $filesystem_credentials_are_stored );
2194      if ( ! $request_filesystem_credentials ) {
2195          return;
2196      }
2197      ?>
2198      <div id="request-filesystem-credentials-dialog" class="notification-dialog-wrap request-filesystem-credentials-dialog">
2199          <div class="notification-dialog-background"></div>
2200          <div class="notification-dialog" role="dialog" aria-labelledby="request-filesystem-credentials-title" tabindex="0">
2201              <div class="request-filesystem-credentials-dialog-content">
2202                  <?php request_filesystem_credentials( site_url() ); ?>
2203              </div>
2204          </div>
2205      </div>
2206      <?php
2207  }


Generated: Wed Jul 17 01:00:03 2019 Cross-referenced by PHPXref 0.7.1