List folders only from FTP server with PHP - php

I would like to list all my folder on my web application without the files in it. At the moment I'm using this code but this will give me all files and folders:
//Connect
$conn = ftp_connect($ftp_server);
$login = ftp_login($conn, $ftp_username, $ftp_userpassword);
//
//Enable PASV ( Note: must be done after ftp_login() )
//
$mode = ftp_pasv($conn, TRUE);
$file_list = ftp_nlist($conn, "/webspace/httpdocs/brentstevens.nl/u_media/$uid/");
foreach ($file_list as $file)
{
// process folder
}
But this will output all files and folders. How can I adjust this code to only serve the folders?

If your server supports MLSD command and you have PHP 7.2 or newer, you can use ftp_mlsd function:
$files = ftp_mlsd($conn, $path);
foreach ($files as $file)
{
if ($file["type"] == "dir")
{
echo $file["name"]."\n";
}
}
If you do not have PHP 7.2, you can try to implement the MLSD command on your own. For a start, see user comment of the ftp_rawlist command:
https://www.php.net/manual/en/function.ftp-rawlist.php#101071
It is not easy to implement this without MLSD in a way that works for any FTP server. See also Check if FTP entry is file or folder with PHP.
But if you need to work against one specific FTP server only, you can use ftp_rawlist to retrieve a file listing in a platform-specific format and parse that.
The following code assumes a common *nix format.
$lines = ftp_rawlist($conn, $path);
foreach ($lines as $line)
{
$tokens = explode(" ", $line);
$name = $tokens[count($tokens) - 1];
$type = $tokens[0][0];
if ($type == 'd')
{
echo "$name\n";
}
}

If your server not supports MLSD command or you have PHP <7.2 and ftp_rawlist marks directory as <DIR>
function getList($connection, string $path): array
{
$rawlist = ftp_rawlist($connection, $path) ?: [];
$items = [];
foreach ($rawlist as $data) {
$item = array_combine(['date', 'time', 'size', 'name'], preg_split('/\s/', $data, -1, PREG_SPLIT_NO_EMPTY));
$item['type'] = $item['size'] === '<DIR>' ? 'dir' : 'file';
$item['size'] = (int)$item['size'];
$items[] = $item;
}
return $items;
}
function getFileList($connection, string $path): array
{
return array_values(array_filter(getList($connection, $path), function ($file) { return $file['type'] === 'file'; }));
}
function getDirList($connection, string $path): array
{
return array_values(array_filter(getList($connection, $path), function ($file) { return $file['type'] === 'dir'; }));
}

You have to use http://php.net/manual/en/function.ftp-rawlist.php function and with regular expression parse type which in your case is letter d

Related

Get directory structure from FTP using PHP

I have ftp server with a lot of subfolders and files in it. I need to retrieve the folder structure from ftp, which shows names of all folders and subfolders from a specified starting path. I'm not interested in files included in each folder, only the directory tree. I'm using PHP and my server does not support mlsd.
Thanks for help.
I implemented my own recursive function, which for some reason is not working.
function ftp_list_files_recursive($ftp_stream, $path) {
$lines = ftp_rawlist($ftp_stream, $path);
$result = [];
if (is_array($lines) || is_object($lines)) {
foreach ($lines as $line) {
$exp0 = explode('<', $line);
if (sizeof($exp0) > 1):
$exp1 = explode('>', $exp0[1]);
if ($exp1[0] == 'DIR') {
$file_path=$path . "/" . ltrim($exp1[1]);
$result = array_merge($result, ftp_list_files_recursive($ftp_stream, $file_path));
} else {
$result[] = $file_path;
}
endif;
}
}
return $result;
}
The ftp_rawlist returns directory info as: 01-18-20 01:00PM <DIR> DirName so first I explode on < and check whether it was successful. If yes, then it means a string had DIR in it and it can be further exploded on >. It could have been done with regular expression, but that works for me now. If I print $file_path variable I see that it changes, so I assume the recursion works. However, the $result array is always empty. Any thoughts on that?
Start here: PHP FTP recursive directory listing.
You just need to adjust the code to:
the DOS-style listing you have from your FTP server (IIS probably) and
to collect only the folders.
function ftp_list_dirs_recursive($ftp_stream, $directory)
{
$result = [];
$lines = ftp_rawlist($ftp_stream, $directory);
if ($lines === false)
{
die("Cannot list $directory");
}
foreach ($lines as $line)
{
// rather lame parsing as a quick example:
if (strpos($line, "<DIR>") !== false)
{
$dir_path = $directory . "/" . ltrim(substr($line, strpos($line, ">") + 1));
$subdirs = ftp_list_dirs_recursive($ftp_stream, $dir_path);
$result = array_merge($result, [$dir_path], $subdirs);
}
}
return $result;
}

How to calculate entire directory size with FTP access using PHP

I have a number of different hosting accounts set up for clients and need to calculate the amount of storage space being used on each account, which would update regularly.
I have a database set up to record each clients storage usage.
I attempted this first using a PHP file on each account, run by a Cron Job. If run manually by myself, it would output the correct filesize and update the correct size to the database, although when run from the Cron Job, it would output 0.
I then attempted to run this file from a Cron Job from the main account but figured this wouldn't actually work as my hosting would block files from another server and I would end up with the same result as before.
I am now playing around with FTP access to each account from a Cron Job from the main account which looks something like below, the only problem is I don't know how to calculate directory size rather than single file sizes using FTP access, and don't know how to reiterate this way? Hoping somebody might be able to help here before I end up going around in circles?
I will also add the previous first attempt too.
$ftp_conn = ftp_connect($ftp_host, 21, 420) or die("Could not connect to server");
$ftp_login = ftp_login($ftp_conn, $ftp_username, 'mypassword');
$total_size = 0;
$contents = ftp_nlist($ftp_conn, ".");
// output $contents
foreach($contents as $folder){
while($search == true){
if($folder == '..' || $folder == '.'){
} else {
$file = $folder;
$res = ftp_size($ftp_conn, $file);
if ($res != -1) {
$total_size = $total_size + $res;
} else {
$total_size = $total_size;
}
}
}
}
ftp_close($ftp_conn);
This doesn't work as it doesn't calculate folder sizes and I don't know how to open the reiterate using this method?
This second script did work but would only work if opened manually, and return 0 if run by the cron job.
class Directory_Calculator {
function calculate_whole_directory($directory)
{
if ($handle = opendir($directory))
{
$size = 0;
$folders = 0;
$files = 0;
while (false !== ($file = readdir($handle)))
{
if ($file != "." && $file != "..")
{
if(is_dir($directory.$file))
{
$array = $this->calculate_whole_directory($directory.$file.'/');
$size += $array['size'];
$files += $array['files'];
$folders += $array['folders'];
}
else
{
$size += filesize($directory.$file);
$files++;
}
}
}
closedir($handle);
}
$folders++;
return array('size' => $size, 'files' => $files, 'folders' => $folders);
}
}
/* Path to Directory - IMPORTANT: with '/' at the end */
$directory = '../public_html/';
// return an array with: size, total files & folders
$array = $directory_size->size($directory);
$size_of_site = $array['size'];
echo $size_of_site;
Please bare in mind that I am currently testing and none of the MySQLi or PHP scripts are secure yet.
If your server supports MLSD command and you have PHP 7.2 or newer, you can use ftp_mlsd function:
function calculate_whole_directory($ftp_conn, $directory)
{
$files = ftp_mlsd($ftp_conn, $directory) or die("Cannot list $directory");
$result = 0;
foreach ($files as $file)
{
if (($file["type"] == "cdir") || ($file["type"] == "pdir"))
{
$size = 0;
}
else if ($file["type"] == "dir")
{
$size = calculate_whole_directory($ftp_conn, $directory."/".$file["name"]);
}
else
{
$size = intval($file["size"]);
}
$result += $size;
}
return $result;
}
If you do not have PHP 7.2, you can try to implement the MLSD command on your own. For a start, see user comment of the ftp_rawlist command:
https://www.php.net/manual/en/function.ftp-rawlist.php#101071
If you cannot use MLSD, you will particularly have problems telling if an entry is a file or folder. While you can use the ftp_size trick, as you do, calling ftp_size for each entry can take ages.
But if you need to work against one specific FTP server only, you can use ftp_rawlist to retrieve a file listing in a platform-specific format and parse that.
The following code assumes a common *nix format.
function calculate_whole_directory($ftp_conn, $directory)
{
$lines = ftp_rawlist($ftp_conn, $directory) or die("Cannot list $directory");
$result = 0;
foreach ($lines as $line)
{
$tokens = preg_split("/\s+/", $line, 9);
$name = $tokens[8];
if ($tokens[0][0] === 'd')
{
$size = calculate_whole_directory($ftp_conn, "$directory/$name");
}
else
{
$size = intval($tokens[4]);
}
$result += $size;
}
return $result;
}
Based on PHP FTP recursive directory listing.
Regarding cron: I'd guess that the cron does not start your script with a correct working directory, so you calculate a size of a non-existing directory.
Use an absolute path here:
$directory = '../public_html/';
Though you better add some error checking so that you can see yourself what goes wrong.

How to get path of file returned by PHP ftp_rawlist()

I got this PHP function which returns an array of all files using PHP's ftp_rawlist() and it is working well for me...
However, I currently have no way of knowing the path the returned filename is located in on the FTP server. Does anyone have any ideas on how I can also get the path to the file location on the FTP server along with the name of the file ?
function listDetailed($resource, $directory) {
if (is_array($children = #ftp_rawlist($resource, $directory,true))) {
$items = array();
foreach ($children as $child) {
$chunks = preg_split("/\s+/", $child);
#list($item['rights'], $item['number'], $item['user'], $item['group'], $item['size'],$item['month'], $item['day'], $item['time'], $item['filename']) = $chunks;
#$item['type'] = $chunks[0]{0} === 'd' ? 'directory' : 'file';
#array_splice($chunks, 0, 8);
#$items[implode(" ", $chunks)] = $item;
}
return $items;
}
// Throw exception or return false < up to you
}
You just need to add $directory path to start of a filename .
for example :
/*... some codes This exactly */
#list($item['time'], $item['filename'], $directory.'/'.$item['filename']) = $chunks;

php glob - scan in subfolders for a file

I have a server with a lot of files inside various folders, sub-folders, and sub-sub-folders.
I'm trying to make a search.php page that would be used to search the whole server for a specific file. If the file is found, then return the location path to display a download link.
Here's what i have so far:
$root = $_SERVER['DOCUMENT_ROOT'];
$search = "test.zip";
$found_files = glob("$root/*/test.zip");
$downloadlink = str_replace("$root/", "", $found_files[0]);
if (!empty($downloadlink)) {
echo "$search";
}
The script is working perfectly if the file is inside the root of my domain name... Now i'm trying to find a way to make it also scan sub-folders and sub-sub-folders but i'm stuck here.
There are 2 ways.
Use glob to do recursive search:
<?php
// Does not support flag GLOB_BRACE
function rglob($pattern, $flags = 0) {
$files = glob($pattern, $flags);
foreach (glob(dirname($pattern).'/*', GLOB_ONLYDIR|GLOB_NOSORT) as $dir) {
$files = array_merge(
[],
...[$files, rglob($dir . "/" . basename($pattern), $flags)]
);
}
return $files;
}
// usage: to find the test.zip file recursively
$result = rglob($_SERVER['DOCUMENT_ROOT'] . '/test.zip');
var_dump($result);
// to find the all files that names ends with test.zip
$result = rglob($_SERVER['DOCUMENT_ROOT'] . '/*test.zip');
?>
Use RecursiveDirectoryIterator
<?php
// $regPattern should be using regular expression
function rsearch($folder, $regPattern) {
$dir = new RecursiveDirectoryIterator($folder);
$ite = new RecursiveIteratorIterator($dir);
$files = new RegexIterator($ite, $regPattern, RegexIterator::GET_MATCH);
$fileList = array();
foreach($files as $file) {
$fileList = array_merge($fileList, $file);
}
return $fileList;
}
// usage: to find the test.zip file recursively
$result = rsearch($_SERVER['DOCUMENT_ROOT'], '/.*\/test\.zip/'));
var_dump($result);
?>
RecursiveDirectoryIterator comes with PHP5 while glob is from PHP4. Both can do the job, it's up to you.
I want to provide another simple alternative for cases where you can predict a max depth. You can use a pattern with braces listing all possible subfolder depths.
This example allows 0-3 arbitrary subfolders:
glob("$root/{,*/,*/*/,*/*/*/}test_*.zip", GLOB_BRACE);
Of course the braced pattern could be procedurally generated.
This returns fullpath to the file
function rsearch($folder, $pattern) {
$iti = new RecursiveDirectoryIterator($folder);
foreach(new RecursiveIteratorIterator($iti) as $file){
if(strpos($file , $pattern) !== false){
return $file;
}
}
return false;
}
call the function:
$filepath = rsearch('/home/directory/thisdir/', "/findthisfile.jpg");
And this is returns like:
/home/directory/thisdir/subdir/findthisfile.jpg
You can improve this function to find several files like all jpeg file:
function rsearch($folder, $pattern_array) {
$return = array();
$iti = new RecursiveDirectoryIterator($folder);
foreach(new RecursiveIteratorIterator($iti) as $file){
if (in_array(strtolower(array_pop(explode('.', $file))), $pattern_array)){
$return[] = $file;
}
}
return $return;
}
This can call as:
$filepaths = rsearch('/home/directory/thisdir/', array('jpeg', 'jpg') );
Ref: https://stackoverflow.com/a/1860417/219112
As a full solution for your problem (this was also my problem):
<?php
function rsearch($folder, $pattern) {
$dir = new RecursiveDirectoryIterator($folder);
$ite = new RecursiveIteratorIterator($dir);
$files = new RegexIterator($ite, $pattern, RegexIterator::MATCH);
foreach($files as $file) {
yield $file->getPathName();
}
}
Will get you the full path of the items that you wish to find.
Edit: Thanks to Rousseau Alexandre for pointing out , $pattern must be regular expression.

GREP function from Python to PHP

I have a python script I wrote that I need to port to php. It recursively searches a given directory and builds a string based on regex searches. The first function I am trying to port is below. It takes a regex and a base dir, recursively searches all files in that dir for the regex, and builds a list of the string matches.
def grep(regex, base_dir):
matches = list()
for path, dirs, files in os.walk(base_dir):
for filename in files:
fullpath = os.path.join(path, filename)
with open(fullpath, 'r') as f:
content = f.read()
matches = matches + re.findall(regex, content)
return matches
I never use PHP except for basic GET param manipulation. I grabbed some directory walking code from the web, and am struggling to make it work like the python function above due to my utter lack of the php API.
function findFiles($dir = '.', $pattern = '/./'){
$prefix = $dir . '/';
$dir = dir($dir);
while (false !== ($file = $dir->read())){
if ($file === '.' || $file === '..') continue;
$file = $prefix . $file;
if (is_dir($file)) findFiles($file, $pattern);
if (preg_match($pattern, $file)){
echo $file . "\n";
}
}
}
Here is my solution:
<?php
class FileGrep {
private $dirs; // Scanned directories list
private $files; // Found files list
private $matches; // Matches list
function __construct() {
$this->dirs = array();
$this->files = array();
$this->matches = array();
}
function findFiles($path, $recursive = TRUE) {
$this->dirs[] = realpath($path);
foreach (scandir($path) as $file) {
if (($file != '.') && ($file != '..')) {
$fullname = realpath("{$path}/{$file}");
if (is_dir($fullname) && !is_link($fullname) && $recursive) {
if (!in_array($fullname, $this->dirs)) {
$this->findFiles($fullname, $recursive);
}
} else if (is_file($fullname)){
$this->files[] = $fullname;
}
}
}
return($this->files);
}
function searchFiles($pattern) {
$this->matches = array();
foreach ($this->files as $file) {
if ($contents = file_get_contents($file)) {
if (preg_match($pattern, $contents, $matches) > 0) {
//echo $file."\n";
$this->matches = array_merge($this->matches, $matches);
}
}
}
return($this->matches);
}
}
// Usage example:
$fg = new FileGrep();
$files = $fg->findFiles('.'); // List all the files in current directory and its subdirectories
$matches = $fg->searchFiles('/open/'); // Search for the "open" string in all those files
?>
<html>
<body>
<pre><?php print_r($matches) ?></pre>
</body>
</html>
Be aware that:
It reads each file to search for the pattern, so it may require a lot of memory (check the "memory_limit" configuration in your PHP.INI file).
It does'nt work with unicode files. If you are working with unicode files you should use the "mb_ereg_match" function rather than the "preg_match" function.
It does'nt follow symbolic links
In conclusion, even if it's not the most efficient solution at all, it should work.

Categories