wp_unique_filename() WordPress Function

The wp_unique_filename() function is used to generate a unique filename for a given file. This function is used by the WordPress media uploader to ensure that each file has a unique name when it is uploaded to the server.

wp_unique_filename( string $dir, string $filename, callable $unique_filename_callback = null ) #

Get a filename that is sanitized and unique for the given directory.


Description

If the filename is not unique, then a number will be added to the filename before the extension, and will continue adding numbers until the filename is unique.

The callback function allows the caller to use their own method to create unique file names. If defined, the callback should take three arguments:

  • directory, base filename, and extension – and return a unique filename.

Top ↑

Parameters

$dir

(string)(Required)Directory.

$filename

(string)(Required)File name.

$unique_filename_callback

(callable)(Optional)Callback.

Default value: null


Top ↑

Return

(string) New filename, if given wasn't unique.


Top ↑

Source

File: wp-includes/functions.php

2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
function wp_unique_filename( $dir, $filename, $unique_filename_callback = null ) {
    // Sanitize the file name before we begin processing.
    $filename = sanitize_file_name( $filename );
    $ext2     = null;
 
    // Initialize vars used in the wp_unique_filename filter.
    $number        = '';
    $alt_filenames = array();
 
    // Separate the filename into a name and extension.
    $ext  = pathinfo( $filename, PATHINFO_EXTENSION );
    $name = pathinfo( $filename, PATHINFO_BASENAME );
 
    if ( $ext ) {
        $ext = '.' . $ext;
    }
 
    // Edge case: if file is named '.ext', treat as an empty name.
    if ( $name === $ext ) {
        $name = '';
    }
 
    /*
     * Increment the file number until we have a unique file to save in $dir.
     * Use callback if supplied.
     */
    if ( $unique_filename_callback && is_callable( $unique_filename_callback ) ) {
        $filename = call_user_func( $unique_filename_callback, $dir, $name, $ext );
    } else {
        $fname = pathinfo( $filename, PATHINFO_FILENAME );
 
        // Always append a number to file names that can potentially match image sub-size file names.
        if ( $fname && preg_match( '/-(?:\d+x\d+|scaled|rotated)$/', $fname ) ) {
            $number = 1;
 
            // At this point the file name may not be unique. This is tested below and the $number is incremented.
            $filename = str_replace( "{$fname}{$ext}", "{$fname}-{$number}{$ext}", $filename );
        }
 
        /*
         * Get the mime type. Uploaded files were already checked with wp_check_filetype_and_ext()
         * in _wp_handle_upload(). Using wp_check_filetype() would be sufficient here.
         */
        $file_type = wp_check_filetype( $filename );
        $mime_type = $file_type['type'];
 
        $is_image    = ( ! empty( $mime_type ) && 0 === strpos( $mime_type, 'image/' ) );
        $upload_dir  = wp_get_upload_dir();
        $lc_filename = null;
 
        $lc_ext = strtolower( $ext );
        $_dir   = trailingslashit( $dir );
 
        /*
         * If the extension is uppercase add an alternate file name with lowercase extension.
         * Both need to be tested for uniqueness as the extension will be changed to lowercase
         * for better compatibility with different filesystems. Fixes an inconsistency in WP < 2.9
         * where uppercase extensions were allowed but image sub-sizes were created with
         * lowercase extensions.
         */
        if ( $ext && $lc_ext !== $ext ) {
            $lc_filename = preg_replace( '|' . preg_quote( $ext ) . '$|', $lc_ext, $filename );
        }
 
        /*
         * Increment the number added to the file name if there are any files in $dir
         * whose names match one of the possible name variations.
         */
        while ( file_exists( $_dir . $filename ) || ( $lc_filename && file_exists( $_dir . $lc_filename ) ) ) {
            $new_number = (int) $number + 1;
 
            if ( $lc_filename ) {
                $lc_filename = str_replace(
                    array( "-{$number}{$lc_ext}", "{$number}{$lc_ext}" ),
                    "-{$new_number}{$lc_ext}",
                    $lc_filename
                );
            }
 
            if ( '' === "{$number}{$ext}" ) {
                $filename = "{$filename}-{$new_number}";
            } else {
                $filename = str_replace(
                    array( "-{$number}{$ext}", "{$number}{$ext}" ),
                    "-{$new_number}{$ext}",
                    $filename
                );
            }
 
            $number = $new_number;
        }
 
        // Change the extension to lowercase if needed.
        if ( $lc_filename ) {
            $filename = $lc_filename;
        }
 
        /*
         * Prevent collisions with existing file names that contain dimension-like strings
         * (whether they are subsizes or originals uploaded prior to #42437).
         */
 
        $files = array();
        $count = 10000;
 
        // The (resized) image files would have name and extension, and will be in the uploads dir.
        if ( $name && $ext && @is_dir( $dir ) && false !== strpos( $dir, $upload_dir['basedir'] ) ) {
            /**
             * Filters the file list used for calculating a unique filename for a newly added file.
             *
             * Returning an array from the filter will effectively short-circuit retrieval
             * from the filesystem and return the passed value instead.
             *
             * @since 5.5.0
             *
             * @param array|null $files    The list of files to use for filename comparisons.
             *                             Default null (to retrieve the list from the filesystem).
             * @param string     $dir      The directory for the new file.
             * @param string     $filename The proposed filename for the new file.
             */
            $files = apply_filters( 'pre_wp_unique_filename_file_list', null, $dir, $filename );
 
            if ( null === $files ) {
                // List of all files and directories contained in $dir.
                $files = @scandir( $dir );
            }
 
            if ( ! empty( $files ) ) {
                // Remove "dot" dirs.
                $files = array_diff( $files, array( '.', '..' ) );
            }
 
            if ( ! empty( $files ) ) {
                $count = count( $files );
 
                /*
                 * Ensure this never goes into infinite loop as it uses pathinfo() and regex in the check,
                 * but string replacement for the changes.
                 */
                $i = 0;
 
                while ( $i <= $count && _wp_check_existing_file_names( $filename, $files ) ) {
                    $new_number = (int) $number + 1;
 
                    // If $ext is uppercase it was replaced with the lowercase version after the previous loop.
                    $filename = str_replace(
                        array( "-{$number}{$lc_ext}", "{$number}{$lc_ext}" ),
                        "-{$new_number}{$lc_ext}",
                        $filename
                    );
 
                    $number = $new_number;
                    $i++;
                }
            }
        }
 
        /*
         * Check if an image will be converted after uploading or some existing image sub-size file names may conflict
         * when regenerated. If yes, ensure the new file name will be unique and will produce unique sub-sizes.
         */
        if ( $is_image ) {
            /** This filter is documented in wp-includes/class-wp-image-editor.php */
            $output_formats = apply_filters( 'image_editor_output_format', array(), $_dir . $filename, $mime_type );
            $alt_types      = array();
 
            if ( ! empty( $output_formats[ $mime_type ] ) ) {
                // The image will be converted to this format/mime type.
                $alt_mime_type = $output_formats[ $mime_type ];
 
                // Other types of images whose names may conflict if their sub-sizes are regenerated.
                $alt_types   = array_keys( array_intersect( $output_formats, array( $mime_type, $alt_mime_type ) ) );
                $alt_types[] = $alt_mime_type;
            } elseif ( ! empty( $output_formats ) ) {
                $alt_types = array_keys( array_intersect( $output_formats, array( $mime_type ) ) );
            }
 
            // Remove duplicates and the original mime type. It will be added later if needed.
            $alt_types = array_unique( array_diff( $alt_types, array( $mime_type ) ) );
 
            foreach ( $alt_types as $alt_type ) {
                $alt_ext = wp_get_default_extension_for_mime_type( $alt_type );
 
                if ( ! $alt_ext ) {
                    continue;
                }
 
                $alt_ext      = ".{$alt_ext}";
                $alt_filename = preg_replace( '|' . preg_quote( $lc_ext ) . '$|', $alt_ext, $filename );
 
                $alt_filenames[ $alt_ext ] = $alt_filename;
            }
 
            if ( ! empty( $alt_filenames ) ) {
                /*
                 * Add the original filename. It needs to be checked again
                 * together with the alternate filenames when $number is incremented.
                 */
                $alt_filenames[ $lc_ext ] = $filename;
 
                // Ensure no infinite loop.
                $i = 0;
 
                while ( $i <= $count && _wp_check_alternate_file_names( $alt_filenames, $_dir, $files ) ) {
                    $new_number = (int) $number + 1;
 
                    foreach ( $alt_filenames as $alt_ext => $alt_filename ) {
                        $alt_filenames[ $alt_ext ] = str_replace(
                            array( "-{$number}{$alt_ext}", "{$number}{$alt_ext}" ),
                            "-{$new_number}{$alt_ext}",
                            $alt_filename
                        );
                    }
 
                    /*
                     * Also update the $number in (the output) $filename.
                     * If the extension was uppercase it was already replaced with the lowercase version.
                     */
                    $filename = str_replace(
                        array( "-{$number}{$lc_ext}", "{$number}{$lc_ext}" ),
                        "-{$new_number}{$lc_ext}",
                        $filename
                    );
 
                    $number = $new_number;
                    $i++;
                }
            }
        }
    }
 
    /**
     * Filters the result when generating a unique file name.
     *
     * @since 4.5.0
     * @since 5.8.1 The `$alt_filenames` and `$number` parameters were added.
     *
     * @param string        $filename                 Unique file name.
     * @param string        $ext                      File extension. Example: ".png".
     * @param string        $dir                      Directory path.
     * @param callable|null $unique_filename_callback Callback function that generates the unique file name.
     * @param string[]      $alt_filenames            Array of alternate file names that were checked for collisions.
     * @param int|string    $number                   The highest number that was used to make the file name unique
     *                                                or an empty string if unused.
     */
    return apply_filters( 'wp_unique_filename', $filename, $ext, $dir, $unique_filename_callback, $alt_filenames, $number );
}


Top ↑

Changelog

Changelog
VersionDescription
2.5.0Introduced.

The content displayed on this page has been created in part by processing WordPress source code files which are made available under the GPLv2 (or a later version) license by the Free Software Foundation. In addition to this, the content includes user-written examples and information. All material is subject to review and curation by the WPPaste.com community.

Show More
Show More