WP filesystem class for direct work with files and folders via PHP.
It can read, write, copy, and delete files and folders directly through PHP.
Works with the PHP process permissions. FTP or SSH connection is not required.
Directly creating the object bypasses the WordPress transport selection and does not define FS_CHMOD_FILE and FS_CHMOD_DIR. If these constants are not set yet, pass permissions explicitly in put_contents(), mkdir(), and chmod(): for example, 0644 for files and 0755 for folders.
Use WP_Filesystem() when you need WordPress to select the appropriate transport and create the global object $wp_filesystem.
No Hooks.
Usage
$fs = new WP_Filesystem_Direct( null );
// use class methods
Methods
Main methods:
get_contents( $file )
Читает файл в строку.
get_contents_array( $file )
Читает файл в массив строк.
put_contents( $file, $contents, $mode )
Создаёт или перезаписывает файл.
copy( $source, $destination, $overwrite, $mode )
Копирует файл.
move( $source, $destination, $overwrite )
Перемещает файл или папку.
delete( $file, $recursive, $type )
Удаляет файл или папку, поддерживает рекурсию.
rmdir( $path, $recursive )
Удаляет папку, поддерживает рекурсию.
mkdir( $path, $chmod, $chown, $chgrp )
Создаёт папку.
dirlist( $path, $include_hidden, $recursive )
Получает содержимое папки с метаданными.
exists( $path )
Проверяет существование файла или папки.
is_file( $file )
Проверяет, является ли путь файлом.
is_dir( $path )
Проверяет, является ли путь папкой.
is_readable( $file )
Проверяет доступность файла для чтения.
is_writable( $path )
Проверяет доступность файла или папки для записи.
size( $file )
Получает размер файла в байтах.
atime( $file )
Получает время последнего доступа к файлу.
mtime( $file )
Получает время последнего изменения файла.
touch( $file, $time, $atime )
Меняет время доступа и изменения; создаёт отсутствующий файл.
chmod( $file, $mode, $recursive )
Меняет права доступа к файлу или папке.
chown( $file, $owner, $recursive )
Меняет владельца файла или папки.
chgrp( $file, $group, $recursive )
Меняет группу файла или папки.
getchmod( $file )
Получает права доступа в восьмеричном представлении.
owner( $file )
Получает имя или ID владельца файла.
group( $file )
Получает имя или ID группы файла.
cwd( )
Получает текущую рабочую папку PHP.
chdir( $dir )
Меняет текущую рабочую папку PHP.
All methods:
public __construct( $arg )
public get_contents( $file )
public get_contents_array( $file )
public put_contents( $file, $contents, $mode = false )
public cwd()
public chdir( $dir )
public chgrp( $file, $group, $recursive = false )
public chmod( $file, $mode = false, $recursive = false )
public chown( $file, $owner, $recursive = false )
public owner( $file )
public getchmod( $file )
public group( $file )
public copy( $source, $destination, $overwrite = false, $mode = false )
public move( $source, $destination, $overwrite = false )
public delete( $file, $recursive = false, $type = false )
public exists( $path )
public is_file( $file )
public is_dir( $path )
public is_readable( $file )
public is_writable( $path )
public atime( $file )
public mtime( $file )
public size( $file )
public touch( $file, $time = 0, $atime = 0 )
public mkdir( $path, $chmod = false, $chown = false, $chgrp = false )
public rmdir( $path, $recursive = false )
public dirlist( $path, $include_hidden = true, $recursive = false )
Examples
#1 Delete the cache folder of the plugin
Suppose our plugin creates cache files in the folder /wp-content/cache/my-plugin. We need to delete this folder and all files or folders in it.
$cache_dir = WP_CONTENT_DIR . '/cache/my-plugin';
// connect the necessary files where the WP_Filesystem code is located
require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php';
require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-direct.php';
$fs = new \WP_Filesystem_Direct( null );
$r = $fs->rmdir( $cache_dir, true );
if ( ! $r ) {
echo sprintf( 'ERROR: Could not recursively delete %s.', $cache_dir );
}
#2 Create an object, write and read a file
The examples run after WordPress is loaded. To directly create the object you need to include the base class and the transport class:
require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php';
require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-direct.php';
$fs = new WP_Filesystem_Direct( null );
$folder = WP_CONTENT_DIR . '/my-plugin-cache';
if ( ! $fs->is_dir( $folder ) && ! $fs->mkdir( $folder, 0755 ) ) {
return;
}
$file = $folder . '/example.txt';
if ( ! $fs->put_contents( $file, 'Hello, WordPress!', 0644 ) ) {
return;
}
$content = $fs->get_contents( $file );
if ( false !== $content ) {
echo esc_html( $content );
}
put_contents() overwrites the entire contents. Returns true if all data was written, and false on an open error or incomplete write. A successful result does not guarantee that permissions were changed.
get_contents() returns a string, including '' for an empty file, or false on an error. get_contents_array() gets an array of strings while preserving line breaks; for an empty file it is [], and false on error.
mkdir() creates only one folder: the parent folder must exist. If the folder being created already exists, the method returns false. A false value in the permissions argument means applying FS_CHMOD_DIR, not skipping permissions changes. A true result confirms the folder was created, but does not confirm the success of any subsequent permission change, owner change, or group change.
Use wp_mkdir_p() when you need to create a local folder along with missing parent folders.
In the following examples the $fs object created above is used.
#3 Delete a folder along with its contents
$folder = WP_CONTENT_DIR . '/my-plugin-cache';
$deleted = $fs->delete( $folder, true );
The second argument enables recursive deletion. Nested folders, regular and hidden files are deleted, and then the specified folder itself. The method rmdir( $folder, true ) calls the same operation.
Without recursion, you can delete a file or an empty folder. An empty path returns false. For a non-existent path, delete() may return true: this is not proof that the object existed and was deleted.
During recursive traversal, the class follows symbolic links to folders. Therefore, deletion may affect contents outside the specified folder. This follows from the implementation of dirlist() via is_dir() without a separate symlink check.
#4 Clear the folder while keeping it itself
There is no separate method for clearing a folder. Get its contents via dirlist() and delete each item:
$folder = trailingslashit( WP_CONTENT_DIR . '/my-plugin-cache' );
$items = $fs->dirlist( $folder, true );
if ( false === $items ) {
return;
}
$cleared = true;
foreach ( $items as $name => $item ) {
if ( ! $fs->delete( $folder . $name, true, $item['type'] ) ) {
$cleared = false;
}
}
The second argument dirlist() includes hidden elements with a dot at the beginning of the name. The service . and .. are always skipped. On delete error, $cleared becomes false; already deleted elements are not restored.
#5 Get a file tree with metadata
$items = $fs->dirlist( WP_CONTENT_DIR . '/my-plugin-cache', true, true );
if ( false === $items ) {
return;
}
Arguments dirlist( $path, $include_hidden = true, $recursive = false ) set the path, inclusion of hidden elements, and recursive traversal. You can pass a path to an individual file: the result will still be an array, where the key is the file name.
The method returns false if the list could not be retrieved, and [] for an empty folder. Result keys are element names. Each element contains:
name - the name.
type - 'd' for a folder, 'f' for other elements. This transport does not allocate links with type 'l'.
perms, permsn - permissions in symbolic and octal representations.
owner, group - the owner and group.
size - size in bytes.
lastmodunix - modification time in Unix timestamp format.
lastmod, time - modification date and time in UTC.
number - always false.
files - the contents of the nested folder during recursion; without recursion, for a folder it is [] here. On nested folder read error - false.
#6 Copy or move a file
$folder = WP_CONTENT_DIR . '/my-plugin-cache/';
$copied = $fs->copy( $folder . 'example.txt', $folder . 'backup.txt', false, 0644 );
if ( $copied ) {
$moved = $fs->move( $folder . 'backup.txt', $folder . 'archive.txt' );
}
By default, copy() and move() do not overwrite an existing destination path. To allow overwriting, pass true as the third argument.
move() first tries renaming. If it fails, the fallback option with copying is applied only to individual files. In this option, the result of deleting the source file is not checked: true does not guarantee that it is absent.
When $overwrite = true, the move() method first deletes the existing destination path, including the folder contents. If the subsequent move ends in an error, deleted data will not be restored. The method does not itself reset OPcache.
Use copy_dir() when you need to recursively copy a folder.
Use move_dir() when you need to move a folder with OPcache reset and with a backup when rename fails.
Notes Changelog
WP_Filesystem_Direct{} WP Filesystem Direct{} code
WP 7.1.2
class WP_Filesystem_Direct extends WP_Filesystem_Base {
/**
* Constructor.
*
* @since 2.5.0
*
* @param mixed $arg Not used.
*/
public function __construct( $arg ) {
// The $arg parameter is required for signature parity with the other transports, but is unused here.
unset( $arg );
$this->method = 'direct';
$this->errors = new WP_Error();
}
/**
* Reads entire file into a string.
*
* @since 2.5.0
*
* @param string $file Name of the file to read.
* @return string|false Read data on success, false on failure.
*/
public function get_contents( $file ) {
return @file_get_contents( $file );
}
/**
* Reads entire file into an array.
*
* @since 2.5.0
*
* @param string $file Path to the file.
* @return string[]|false File contents in an array on success, false on failure.
*/
public function get_contents_array( $file ) {
return @file( $file );
}
/**
* Writes a string to a file.
*
* @since 2.5.0
*
* @param string $file Remote path to the file where to write the data.
* @param string $contents The data to write.
* @param int|false $mode Optional. The file permissions as octal number, usually 0644.
* Default false.
* @return bool True on success, false on failure.
*/
public function put_contents( $file, $contents, $mode = false ) {
$fp = @fopen( $file, 'wb' );
if ( ! $fp ) {
return false;
}
mbstring_binary_safe_encoding();
$data_length = strlen( $contents );
$bytes_written = fwrite( $fp, $contents );
reset_mbstring_encoding();
fclose( $fp );
if ( $data_length !== $bytes_written ) {
return false;
}
$this->chmod( $file, $mode );
return true;
}
/**
* Gets the current working directory.
*
* @since 2.5.0
*
* @return string|false The current working directory on success, false on failure.
*/
public function cwd() {
return getcwd();
}
/**
* Changes current directory.
*
* @since 2.5.0
*
* @param string $dir The new current directory.
* @return bool True on success, false on failure.
*/
public function chdir( $dir ) {
return @chdir( $dir );
}
/**
* Changes the file group.
*
* @since 2.5.0
*
* @param string $file Path to the file.
* @param string|int $group A group name or number.
* @param bool $recursive Optional. If set to true, changes file group recursively.
* Default false.
* @return bool True on success, false on failure.
*/
public function chgrp( $file, $group, $recursive = false ) {
if ( ! $this->exists( $file ) ) {
return false;
}
if ( ! $recursive ) {
return chgrp( $file, $group );
}
if ( ! $this->is_dir( $file ) ) {
return chgrp( $file, $group );
}
// Is a directory, and we want recursive.
$file = trailingslashit( $file );
$filelist = $this->dirlist( $file );
if ( false === $filelist ) {
return false;
}
foreach ( $filelist as $file_listing ) {
$this->chgrp( $file . $file_listing['name'], $group, $recursive );
}
return true;
}
/**
* Changes filesystem permissions.
*
* @since 2.5.0
*
* @param string $file Path to the file.
* @param int|false $mode Optional. The permissions as octal number, usually 0644 for files,
* 0755 for directories. Default false.
* @param bool $recursive Optional. If set to true, changes file permissions recursively.
* Default false.
* @return bool True on success, false on failure.
*/
public function chmod( $file, $mode = false, $recursive = false ) {
if ( ! $mode ) {
if ( $this->is_file( $file ) ) {
$mode = FS_CHMOD_FILE;
} elseif ( $this->is_dir( $file ) ) {
$mode = FS_CHMOD_DIR;
} else {
return false;
}
}
if ( ! $recursive || ! $this->is_dir( $file ) ) {
$current_mode = fileperms( $file ) & 0777;
/*
* fileperms() populates the stat cache, so have to clear it
* to maintain parity with the previous behavior.
*/
clearstatcache( true, $file );
/*
* Avoid calling chmod() if the requested mode is already set,
* to prevent throwing a warning when we aren't the owner.
*/
if ( $current_mode === $mode ) {
return true;
}
return chmod( $file, $mode );
}
// Is a directory, and we want recursive.
$file = trailingslashit( $file );
$filelist = $this->dirlist( $file );
foreach ( (array) $filelist as $filename => $filemeta ) {
$this->chmod( $file . $filename, $mode, $recursive );
}
return true;
}
/**
* Changes the owner of a file or directory.
*
* @since 2.5.0
*
* @param string $file Path to the file or directory.
* @param string|int $owner A user name or number.
* @param bool $recursive Optional. If set to true, changes file owner recursively.
* Default false.
* @return bool True on success, false on failure.
*/
public function chown( $file, $owner, $recursive = false ) {
if ( ! $this->exists( $file ) ) {
return false;
}
if ( ! $recursive ) {
return chown( $file, $owner );
}
if ( ! $this->is_dir( $file ) ) {
return chown( $file, $owner );
}
// Is a directory, and we want recursive.
$filelist = $this->dirlist( $file );
if ( false === $filelist ) {
return false;
}
foreach ( $filelist as $file_listing ) {
$this->chown( $file . '/' . $file_listing['name'], $owner, $recursive );
}
return true;
}
/**
* Gets the file owner.
*
* @since 2.5.0
*
* @param string $file Path to the file.
* @return string|int<1, max>|false Username of the owner on success, or UID of file owner if not available; false on failure.
*/
public function owner( $file ) {
$owneruid = @fileowner( $file );
if ( ! $owneruid ) {
return false;
}
if ( ! function_exists( 'posix_getpwuid' ) ) {
return $owneruid;
}
$ownerarray = posix_getpwuid( $owneruid );
if ( ! $ownerarray ) {
return false;
}
return $ownerarray['name'];
}
/**
* Gets the permissions of the specified file or filepath in their octal format.
*
* @since 2.5.0
*
* @param string $file Path to the file.
* @return string Mode of the file (the last 3 digits), or the string "0" on failure.
*/
public function getchmod( $file ) {
$perms = @fileperms( $file );
if ( false === $perms ) {
return '0';
}
return substr( decoct( $perms ), -3 );
}
/**
* Gets the file's group.
*
* @since 2.5.0
*
* @param string $file Path to the file.
* @return string|int<1, max>|false Group name on success, or GID of the file's group if not available; false on failure.
*/
public function group( $file ) {
$gid = @filegroup( $file );
if ( ! $gid ) {
return false;
}
if ( ! function_exists( 'posix_getgrgid' ) ) {
return $gid;
}
$grouparray = posix_getgrgid( $gid );
if ( ! $grouparray ) {
return false;
}
return $grouparray['name'];
}
/**
* Copies a file.
*
* @since 2.5.0
*
* @param string $source Path to the source file.
* @param string $destination Path to the destination file.
* @param bool $overwrite Optional. Whether to overwrite the destination file if it exists.
* Default false.
* @param int|false $mode Optional. The permissions as octal number, usually 0644 for files,
* 0755 for dirs. Default false.
* @return bool True on success, false on failure.
*/
public function copy( $source, $destination, $overwrite = false, $mode = false ) {
if ( ! $overwrite && $this->exists( $destination ) ) {
return false;
}
$rtval = copy( $source, $destination );
if ( $mode ) {
$this->chmod( $destination, $mode );
}
return $rtval;
}
/**
* Moves a file or directory.
*
* After moving files or directories, OPcache will need to be invalidated.
*
* If moving a directory fails, `copy_dir()` can be used for a recursive copy.
*
* Use `move_dir()` for moving directories with OPcache invalidation and a
* fallback to `copy_dir()`.
*
* @since 2.5.0
*
* @param string $source Path to the source file.
* @param string $destination Path to the destination file.
* @param bool $overwrite Optional. Whether to overwrite the destination file if it exists.
* Default false.
* @return bool True on success, false on failure.
*/
public function move( $source, $destination, $overwrite = false ) {
if ( ! $overwrite && $this->exists( $destination ) ) {
return false;
}
if ( $overwrite && $this->exists( $destination ) && ! $this->delete( $destination, true ) ) {
// Can't overwrite if the destination couldn't be deleted.
return false;
}
// Try using rename first. if that fails (for example, source is read only) try copy.
if ( @rename( $source, $destination ) ) {
return true;
}
// Backward compatibility: Only fall back to `::copy()` for single files.
if ( $this->is_file( $source ) && $this->copy( $source, $destination, $overwrite ) && $this->exists( $destination ) ) {
$this->delete( $source );
return true;
} else {
return false;
}
}
/**
* Deletes a file or directory.
*
* @since 2.5.0
*
* @param string $file Path to the file or directory.
* @param bool $recursive Optional. If set to true, deletes files and folders recursively.
* Default false.
* @param string|false $type Type of resource. 'f' for file, 'd' for directory.
* Default false.
* @return bool True on success, false on failure.
*/
public function delete( $file, $recursive = false, $type = false ) {
if ( empty( $file ) ) {
// Some filesystems report this as /, which can cause non-expected recursive deletion of all files in the filesystem.
return false;
}
$file = str_replace( '\\', '/', $file ); // For Win32, occasional problems deleting files otherwise.
if ( 'f' === $type || $this->is_file( $file ) ) {
return @unlink( $file );
}
if ( ! $recursive && $this->is_dir( $file ) ) {
return @rmdir( $file );
}
// At this point it's a folder, and we're in recursive mode.
$file = trailingslashit( $file );
$filelist = $this->dirlist( $file, true );
$retval = true;
if ( is_array( $filelist ) ) {
foreach ( $filelist as $filename => $fileinfo ) {
if ( ! $this->delete( $file . $filename, $recursive, $fileinfo['type'] ) ) {
$retval = false;
}
}
}
if ( file_exists( $file ) && ! @rmdir( $file ) ) {
$retval = false;
}
return $retval;
}
/**
* Checks if a file or directory exists.
*
* @since 2.5.0
*
* @param string $path Path to file or directory.
* @return bool Whether $path exists or not.
*/
public function exists( $path ) {
return @file_exists( $path );
}
/**
* Checks if resource is a file.
*
* @since 2.5.0
*
* @param string $file File path.
* @return bool Whether $file is a file.
*/
public function is_file( $file ) {
return @is_file( $file );
}
/**
* Checks if resource is a directory.
*
* @since 2.5.0
*
* @param string $path Directory path.
* @return bool Whether $path is a directory.
*/
public function is_dir( $path ) {
return @is_dir( $path );
}
/**
* Checks if a file is readable.
*
* @since 2.5.0
*
* @param string $file Path to file.
* @return bool Whether $file is readable.
*/
public function is_readable( $file ) {
return @is_readable( $file );
}
/**
* Checks if a file or directory is writable.
*
* @since 2.5.0
*
* @param string $path Path to file or directory.
* @return bool Whether $path is writable.
*/
public function is_writable( $path ) {
return @is_writable( $path );
}
/**
* Gets the file's last access time.
*
* @since 2.5.0
*
* @param string $file Path to file.
* @return int|false Unix timestamp representing last access time, false on failure.
*/
public function atime( $file ) {
return @fileatime( $file );
}
/**
* Gets the file modification time.
*
* @since 2.5.0
*
* @param string $file Path to file.
* @return int|false Unix timestamp representing modification time, false on failure.
*/
public function mtime( $file ) {
return @filemtime( $file );
}
/**
* Gets the file size (in bytes).
*
* @since 2.5.0
*
* @param string $file Path to file.
* @return int|false Size of the file in bytes on success, false on failure.
*/
public function size( $file ) {
return @filesize( $file );
}
/**
* Sets the access and modification times of a file.
*
* Note: If $file doesn't exist, it will be created.
*
* @since 2.5.0
*
* @param string $file Path to file.
* @param int $time Optional. Modified time to set for file.
* Default 0.
* @param int $atime Optional. Access time to set for file.
* Default 0.
* @return bool True on success, false on failure.
*/
public function touch( $file, $time = 0, $atime = 0 ) {
if ( 0 === $time ) {
$time = time();
}
if ( 0 === $atime ) {
$atime = time();
}
return touch( $file, $time, $atime );
}
/**
* Creates a directory.
*
* @since 2.5.0
*
* @param string $path Path for new directory.
* @param int|false $chmod Optional. The permissions as octal number (or false to skip chmod).
* Default false.
* @param string|int|false $chown Optional. A user name or number (or false to skip chown).
* Default false.
* @param string|int|false $chgrp Optional. A group name or number (or false to skip chgrp).
* Default false.
* @return bool True on success, false on failure.
*/
public function mkdir( $path, $chmod = false, $chown = false, $chgrp = false ) {
// Safe mode fails with a trailing slash under certain PHP versions.
$path = untrailingslashit( $path );
if ( empty( $path ) ) {
return false;
}
if ( ! $chmod ) {
$chmod = FS_CHMOD_DIR;
}
if ( ! @mkdir( $path ) ) {
return false;
}
$this->chmod( $path, $chmod );
if ( $chown ) {
$this->chown( $path, $chown );
}
if ( $chgrp ) {
$this->chgrp( $path, $chgrp );
}
return true;
}
/**
* Deletes a directory.
*
* @since 2.5.0
*
* @param string $path Path to directory.
* @param bool $recursive Optional. Whether to recursively remove files/directories.
* Default false.
* @return bool True on success, false on failure.
*/
public function rmdir( $path, $recursive = false ) {
return $this->delete( $path, $recursive );
}
/**
* Gets details for files in a directory or a specific file.
*
* @since 2.5.0
*
* @param string $path Path to directory or file.
* @param bool $include_hidden Optional. Whether to include details of hidden ("." prefixed) files.
* Default true.
* @param bool $recursive Optional. Whether to recursively include file details in nested directories.
* Default false.
* @return array|false {
* Array of arrays containing file information. False if unable to list directory contents.
*
* @type array ...$0 {
* Array of file information. Note that some elements may not be available on all filesystems.
*
* @type string $name Name of the file or directory.
* @type string $perms *nix representation of permissions.
* @type string $permsn Octal representation of permissions.
* @type false $number File number. Always false in this context.
* @type string|false $owner Owner name or ID, or false if not available.
* @type string|false $group File permissions group, or false if not available.
* @type int|string|false $size Size of file in bytes. May be a numeric string.
* False if not available.
* @type int|string|false $lastmodunix Last modified unix timestamp. May be a numeric string.
* False if not available.
* @type string|false $lastmod Last modified month (3 letters) and day (without leading 0), or
* false if not available.
* @type string|false $time Last modified time, or false if not available.
* @type string $type Type of resource. 'f' for file, 'd' for directory, 'l' for link.
* @type array|false $files If a directory and `$recursive` is true, contains another array of
* files. False if unable to list directory contents.
* }
* }
* @phpstan-return array<string, FileListing>|false
*/
public function dirlist( $path, $include_hidden = true, $recursive = false ) {
if ( $this->is_file( $path ) ) {
$limit_file = basename( $path );
$path = dirname( $path );
} else {
$limit_file = false;
}
if ( ! $this->is_dir( $path ) || ! $this->is_readable( $path ) ) {
return false;
}
$dir = dir( $path );
if ( ! $dir ) {
return false;
}
$path = trailingslashit( $path );
$ret = array();
while ( false !== ( $entry = $dir->read() ) ) {
$struc = array();
$struc['name'] = $entry;
if ( '.' === $struc['name'] || '..' === $struc['name'] ) {
continue;
}
if ( ! $include_hidden && '.' === $struc['name'][0] ) {
continue;
}
if ( $limit_file && $struc['name'] !== $limit_file ) {
continue;
}
$struc['perms'] = $this->gethchmod( $path . $entry );
$struc['permsn'] = $this->getnumchmodfromh( $struc['perms'] );
$struc['number'] = false;
$struc['owner'] = $this->owner( $path . $entry );
$struc['group'] = $this->group( $path . $entry );
$struc['size'] = $this->size( $path . $entry );
$struc['lastmodunix'] = $this->mtime( $path . $entry );
$struc['lastmod'] = is_int( $struc['lastmodunix'] ) ? gmdate( 'M j', $struc['lastmodunix'] ) : false;
$struc['time'] = is_int( $struc['lastmodunix'] ) ? gmdate( 'h:i:s', $struc['lastmodunix'] ) : false;
$struc['type'] = $this->is_dir( $path . $entry ) ? 'd' : 'f';
if ( 'd' === $struc['type'] ) {
if ( $recursive ) {
$struc['files'] = $this->dirlist( $path . $struc['name'], $include_hidden, $recursive );
} else {
$struc['files'] = array();
}
}
$ret[ $struc['name'] ] = $struc;
}
$dir->close();
unset( $dir );
return $ret;
}
}
Related Functions