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.

Parameters

$dir

(string) (Required) Directory.

$filename

(string) (Required) File name.

$unique_filename_callback

(callable) (Optional) Callback.

Default value: null

Return

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

Source

File: wp-includes/functions.php

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;

	// 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 {
		$number = '';
		$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 );
		}

		// Change '.ext' to lower case.
		if ( $ext && strtolower( $ext ) != $ext ) {
			$ext2      = strtolower( $ext );
			$filename2 = preg_replace( '|' . preg_quote( $ext ) . '$|', $ext2, $filename );

			// Check for both lower and upper case extension or image sub-sizes may be overwritten.
			while ( file_exists( $dir . "/{$filename}" ) || file_exists( $dir . "/{$filename2}" ) ) {
				$new_number = (int) $number + 1;
				$filename   = str_replace( array( "-{$number}{$ext}", "{$number}{$ext}" ), "-{$new_number}{$ext}", $filename );
				$filename2  = str_replace( array( "-{$number}{$ext2}", "{$number}{$ext2}" ), "-{$new_number}{$ext2}", $filename2 );
				$number     = $new_number;
			}

			$filename = $filename2;
		} else {
			while ( file_exists( $dir . "/{$filename}" ) ) {
				$new_number = (int) $number + 1;

				if ( '' === "{$number}{$ext}" ) {
					$filename = "{$filename}-{$new_number}";
				} else {
					$filename = str_replace( array( "-{$number}{$ext}", "{$number}{$ext}" ), "-{$new_number}{$ext}", $filename );
				}

				$number = $new_number;
			}
		}

		// Prevent collisions with existing file names that contain dimension-like strings
		// (whether they are subsizes or originals uploaded prior to #42437).
		$upload_dir = wp_get_upload_dir();

		// 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 ) ) {
				// The extension case may have changed above.
				$new_ext = ! empty( $ext2 ) ? $ext2 : $ext;

				// Ensure this never goes into infinite loop
				// as it uses pathinfo() and regex in the check, but string replacement for the changes.
				$count = count( $files );
				$i     = 0;

				while ( $i <= $count && _wp_check_existing_file_names( $filename, $files ) ) {
					$new_number = (int) $number + 1;
					$filename   = str_replace( array( "-{$number}{$new_ext}", "{$number}{$new_ext}" ), "-{$new_number}{$new_ext}", $filename );
					$number     = $new_number;
					$i++;
				}
			}
		}
	}

	/**
	 * Filters the result when generating a unique file name.
	 *
	 * @since 4.5.0
	 *
	 * @param string        $filename                 Unique file name.
	 * @param string        $ext                      File extension, eg. ".png".
	 * @param string        $dir                      Directory path.
	 * @param callable|null $unique_filename_callback Callback function that generates the unique file name.
	 */
	return apply_filters( 'wp_unique_filename', $filename, $ext, $dir, $unique_filename_callback );
}

Changelog

Version Description
2.5.0 Introduced.

© 2003–2021 WordPress Foundation
Licensed under the GNU GPLv2+ License.
https://developer.wordpress.org/reference/functions/wp_unique_filename