函数文档

wp_unique_filename()

💡 云策文档标注

概述

wp_unique_filename() 函数用于生成一个在指定目录中经过清理且唯一的文件名。如果文件名不唯一,会在扩展名前添加数字,直到文件名唯一为止。支持通过回调函数自定义唯一文件名生成逻辑。

关键要点

  • 函数接受三个参数:目录路径、文件名和可选的回调函数,返回唯一文件名。
  • 文件名会先通过 sanitize_file_name() 清理,然后处理扩展名和名称部分。
  • 如果提供了回调函数,则使用回调生成唯一文件名;否则,自动添加数字以确保唯一性。
  • 对于可能匹配图像子尺寸文件名的文件(如以 -数字x数字、-scaled 或 -rotated 结尾),会自动添加数字。
  • 函数包含过滤器 wp_unique_filename,允许开发者修改生成的文件名。
  • 相关函数包括 wp_get_upload_dir()、sanitize_file_name() 和 wp_check_filetype() 等。

代码示例

$newfilename = wp_unique_filename( $path_being_saved_to, $filename_to_check );

注意事项

  • $filename 参数应为包含扩展名的完整文件名,例如 new-filename.jpg。

📄 原文内容

Gets 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

$dirstringrequired
Directory.
$filenamestringrequired
File name.
$unique_filename_callbackcallableoptional
Callback.

Default:null

Return

string New filename, if given wasn’t unique.

Source

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+xd+|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 ) && str_starts_with( $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 ) && str_contains( $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 ) {
			$output_formats = wp_get_image_editor_output_format( $_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 );
}

Hooks

apply_filters( ‘pre_wp_unique_filename_file_list’, array|null $files, string $dir, string $filename )

Filters the file list used for calculating a unique filename for a newly added file.

apply_filters( ‘wp_unique_filename’, string $filename, string $ext, string $dir, callable|null $unique_filename_callback, string[] $alt_filenames, int|string $number )

Filters the result when generating a unique file name.

Changelog

Version Description
2.5.0 Introduced.

User Contributed Notes