Make WordPress Core

Changeset 55204


Ignore:
Timestamp:
02/03/2023 01:48:36 AM (15 months ago)
Author:
azaozz
Message:

Filesystem API: Add directory support to WP_Filesystem_Direct::move().

Introduces:

  • New function: wp_opcache_invalidate_directory(), to recursively call wp_opcache_invalidate() after overwriting .php files.
  • New function: move_dir(), similar to copy_dir() that uses WP_Filesystem::move() followed by wp_opcache_invalidate_directory(), and has a fallback to copy_dir().

Props: costdev, afragen, peterwilsoncc, sergeybiryukov, ironprogrammer, flixos90, bronsonquick, mukesh27, azaozz.
Fixes #57375.

Location:
trunk
Files:
2 added
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-admin/includes/class-wp-filesystem-direct.php

    r53872 r55204  
    317317
    318318    /**
    319      * Moves a file.
     319     * Moves a file or directory.
     320     *
     321     * After moving files or directories, OPcache will need to be invalidated.
     322     *
     323     * If moving a directory fails, `copy_dir()` can be used for a recursive copy.
     324     *
     325     * Use `move_dir()` for moving directories with OPcache invalidation and a
     326     * fallback to `copy_dir()`.
    320327     *
    321328     * @since 2.5.0
     
    332339        }
    333340
     341        if ( $overwrite && $this->exists( $destination ) && ! $this->delete( $destination, true ) ) {
     342            // Can't overwrite if the destination couldn't be deleted.
     343            return false;
     344        }
     345
    334346        // Try using rename first. if that fails (for example, source is read only) try copy.
    335347        if ( @rename( $source, $destination ) ) {
     
    337349        }
    338350
    339         if ( $this->copy( $source, $destination, $overwrite ) && $this->exists( $destination ) ) {
     351        // Backward compatibility: Only fall back to `::copy()` for single files.
     352        if ( $this->is_file( $source ) && $this->copy( $source, $destination, $overwrite ) && $this->exists( $destination ) ) {
    340353            $this->delete( $source );
    341354
  • trunk/src/wp-admin/includes/file.php

    r55094 r55204  
    19481948
    19491949/**
     1950 * Moves a directory from one location to another.
     1951 *
     1952 * Recursively invalidates OPcache on success.
     1953 *
     1954 * If the renaming failed, falls back to copy_dir().
     1955 *
     1956 * Assumes that WP_Filesystem() has already been called and setup.
     1957 *
     1958 * @since 6.2.0
     1959 *
     1960 * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
     1961 *
     1962 * @param string $from      Source directory.
     1963 * @param string $to        Destination directory.
     1964 * @param bool   $overwrite Optional. Whether to overwrite the destination directory if it exists.
     1965 *                          Default false.
     1966 * @return true|WP_Error True on success, WP_Error on failure.
     1967 */
     1968function move_dir( $from, $to, $overwrite = false ) {
     1969    global $wp_filesystem;
     1970
     1971    if ( trailingslashit( strtolower( $from ) ) === trailingslashit( strtolower( $to ) ) ) {
     1972        return new WP_Error(
     1973            'source_destination_same_move_dir',
     1974            __( 'The source and destination are the same.' )
     1975        );
     1976    }
     1977
     1978    if ( ! $overwrite && $wp_filesystem->exists( $to ) ) {
     1979        return new WP_Error( 'destination_already_exists_move_dir', __( 'The destination folder already exists.' ), $to );
     1980    }
     1981
     1982    if ( $wp_filesystem->move( $from, $to, $overwrite ) ) {
     1983        /*
     1984         * When using an environment with shared folders,
     1985         * there is a delay in updating the filesystem's cache.
     1986         *
     1987         * This is a known issue in environments with a VirtualBox provider.
     1988         *
     1989         * A 200ms delay gives time for the filesystem to update its cache,
     1990         * prevents "Operation not permitted", and "No such file or directory" warnings.
     1991         *
     1992         * This delay is used in other projects, including Composer.
     1993         * @link https://github.com/composer/composer/blob/2.5.1/src/Composer/Util/Platform.php#L228-L233
     1994         */
     1995        usleep( 200000 );
     1996        wp_opcache_invalidate_directory( $to );
     1997
     1998        return true;
     1999    }
     2000
     2001    // Fall back to a recursive copy.
     2002    if ( ! $wp_filesystem->is_dir( $to ) ) {
     2003        if ( ! $wp_filesystem->mkdir( $to, FS_CHMOD_DIR ) ) {
     2004            return new WP_Error( 'mkdir_failed_move_dir', __( 'Could not create directory.' ), $to );
     2005        }
     2006    }
     2007
     2008    $result = copy_dir( $from, $to, array( basename( $to ) ) );
     2009
     2010    // Clear the source directory.
     2011    if ( true === $result ) {
     2012        $wp_filesystem->delete( $from, true );
     2013    }
     2014
     2015    return $result;
     2016}
     2017
     2018/**
    19502019 * Initializes and connects the WordPress Filesystem Abstraction classes.
    19512020 *
     
    25582627    return false;
    25592628}
     2629
     2630/**
     2631 * Attempts to clear the opcode cache for a directory of files.
     2632 *
     2633 * @since 6.2.0
     2634 *
     2635 * @see wp_opcache_invalidate()
     2636 * @link https://www.php.net/manual/en/function.opcache-invalidate.php
     2637 *
     2638 * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
     2639 *
     2640 * @param string $dir The path to the directory for which the opcode cache is to be cleared.
     2641 */
     2642function wp_opcache_invalidate_directory( $dir ) {
     2643    global $wp_filesystem;
     2644
     2645    if ( ! is_string( $dir ) || '' === trim( $dir ) ) {
     2646        if ( WP_DEBUG ) {
     2647            $error_message = sprintf(
     2648                /* translators: %s: The function name. */
     2649                __( '%s expects a non-empty string.' ),
     2650                '<code>wp_opcache_invalidate_directory()</code>'
     2651            );
     2652            // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error
     2653            trigger_error( $error_message );
     2654        }
     2655        return;
     2656    }
     2657
     2658    $dirlist = $wp_filesystem->dirlist( $dir, false, true );
     2659
     2660    if ( empty( $dirlist ) ) {
     2661        return;
     2662    }
     2663
     2664    /*
     2665     * Recursively invalidate opcache of files in a directory.
     2666     *
     2667     * WP_Filesystem_*::dirlist() returns an array of file and directory information.
     2668     *
     2669     * This does not include a path to the file or directory.
     2670     * To invalidate files within sub-directories, recursion is needed
     2671     * to prepend an absolute path containing the sub-directory's name.
     2672     *
     2673     * @param array  $dirlist Array of file/directory information from WP_Filesystem_Base::dirlist(),
     2674     *                        with sub-directories represented as nested arrays.
     2675     * @param string $path    Absolute path to the directory.
     2676     */
     2677    $invalidate_directory = function( $dirlist, $path ) use ( &$invalidate_directory ) {
     2678        $path = trailingslashit( $path );
     2679
     2680        foreach ( $dirlist as $name => $details ) {
     2681            if ( 'f' === $details['type'] ) {
     2682                wp_opcache_invalidate( $path . $name, true );
     2683            } elseif ( is_array( $details['files'] ) && ! empty( $details['files'] ) ) {
     2684                $invalidate_directory( $details['files'], $path . $name );
     2685            }
     2686        }
     2687    };
     2688
     2689    $invalidate_directory( $dirlist, $dir );
     2690}
Note: See TracChangeset for help on using the changeset viewer.