Looping through directories in PHP? - php

I'm working on a script that replaces all files within a directory and the subdirectories with a single string to get rid of the generic error message our software displays.
I made it work pretty easily with all files in a single directory, but then we ran it in a folder with subdirectories and as you can probably guess, it threw a lot of errors. I completely forgot about the subdirectories.
So now I'm making a script that works with subdirectories, but I'm stumped.
Here's my code:
<?php
$files = explode("\n", shell_exec('ls'));
$count = 0;
foreach ($files as $file)
{
if (empty($file) || $file == $_SERVER['SCRIPT_NAME'])
{
continue;
}
if (is_dir($file))
{
echo "Copying to {$file}/{$_SERVER['SCRIPT_NAME']}\n";
copy($_SERVER['SCRIPT_NAME'], $file . "/" . $_SERVER['SCRIPT_NAME']);
exec("php {$file}/{$_SERVER['SCRIPT_NAME']}");
unlink($file . "/" . $_SERVER['SCRIPT_NAME']);
continue;
}
$fh = fopen($file, 'w');
fwrite($fh, '<!-- Generated %T by %h (%s) -->');
fclose($fh);
echo "Rewrote {$file}\n";
$count++;
}
echo "Finished. Rewrote {$count} files. Don't forget to delete {$_SERVER['SCRIPT_NAME']}.\n";
?>
It ends up outputting this:
[root#proxy1 orgytest]# php p.php
Rewrote blah
Rewrote dfas
Rewrote dfasfsdjkfjsa
Rewrote dfdsafdsaf
Rewrote dfsaf
Rewrote orgy
Rewrote query
Rewrote scsew
Copying to test/p.php
Rewrote blah
Rewrote dfas
Rewrote dfasfsdjkfjsa
Rewrote dfdsafdsaf
Rewrote dfsaf
Rewrote orgy
Rewrote p.php
Rewrote query
Rewrote scsew
Copying to test/test/p.php
PHP Warning: copy(test/test/p.php): failed to open stream: No such file or directory in /root/orgytest/test/p.php on line 15
Could not open input file: test/test/p.php
PHP Warning: unlink(test/test/p.php): No such file or directory in /root/orgytest/test/p.php on line 17
Copying to test2/test/p.php
PHP Warning: copy(test2/test/p.php): failed to open stream: No such file or directory in /root/orgytest/test/p.php on line 15
Could not open input file: test2/test/p.php
PHP Warning: unlink(test2/test/p.php): No such file or directory in /root/orgytest/test/p.php on line 17
Finished. Rewrote 9 files. Don't forget to delete test/p.php.
Copying to test2/p.php
<!-- Generated %T by %h (%s) -->Finished. Rewrote 8 files. Don't forget to delete p.php.
What's weird to me is that it's trying to do things like test/test/p.php rather than test/p.php. I assume it has something to do with the fact that it's running from a higher up directory when it reaches that point.
Anyone know how I can fix this?

The value of $_SERVER['SCRIPT_NAME'] is probably not what you expect, and the reason for constructing a path like test/test/p.php. My guess is when you exec php test/p.php then that's the value php places into SCRIPT_NAME. You could use the basename() function to get around that.
Also, you should be using escapeshellarg() when dynamically creating shell commands.
Alternatively...
$self = realpath(__FILE__);
$ritit = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(__DIR__));
foreach ($ritit as $splFileInfo) {
$fileName = $splFileInfo->getRealPath();
if ($fileName !== $self) {
file_put_contents($fileName, '<!.....stuff');
}
}

The problem stems from LS returning the contents of the base directory, not necessarily the one that houses your script. I honestly would avoid exec, and just try to abstract your copying into a function, use php filesystem functions, and turn it into a recursive algorithm instead of trying to exec ls processes.

SPL's recursive iterator's are ideally suited for this, I'd use something like the following
<?php
// get scriptname from $argv[0]
$scriptname = basename(array_shift($argv));
// optional argument indicating path to run this script in (defaults to current path)
if (!empty($argv[0])) {
$workspace = $argv[0];
}else {
$workspace = getcwd();
}
// Recursively iterate over files in the specified workspace folder and all subfolders
try {
$count = 0;
foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($workspace)) as $file) {
// ignore the folders, and this script if present
if ($file->isDir() || $file->getFilename() == $scriptname) continue;
// write the file
echo "Writing to $file\n";
file_put_contents($file, '<!-- Generated %T by %h (%s) -->');
$count++;
}
echo "Rewrote $count files";
} catch (Exception $e) {
// oops, likely invalid path, or unreadable folder
echo "Problem reading $workspace";
}
?>
SPL Manual:
RecursiveIteratorIterator
RecursiveDirectoryIterator

Try with something like this:
Codingforums
or try to search on google for "php recursive directory listing" or anything similar.
With readdir() function you will get files and subdirectories in specific directory. So the key point is to figure out if it's just a file or it is a subdirectory with is_dir() function. Also make sure you will use full path to files in subdirectories when you want to open and edit it. So like i said try to use google and find something useful.

Related

"No such file or directory" on localhost copy

EDIT: I'm pretty sure the issue has to do with the firewall, which I can't access. Marking Canis' answer as correct and I will figure something else out, possibly wget or just manually scraping the files and hoping no major updates are needed.
EDIT: Here's the latest version of the builder and here's the output. The build directory has the proper structure and most of the files, but only their name and extension - no data inside them.
I am coding a php script that searches the local directory for files, then scrapes my localhost (xampp) for the same files to copy into a build folder (the goal is to build php on the localhost and then put it on a server as html).
Unfortunately I am getting the error: Warning: copy(https:\\localhost\intranet\builder.php): failed to open stream: No such file or directory in C:\xampp\htdocs\intranet\builder.php on line 73.
That's one example - every file in the local directory is spitting the same error back. The source addresses are correct (I can get to the file on localhost from the address in the error log) and the local directory is properly constructed - just moving the files into it doesn't work. The full code is here, the most relevant section is:
// output build files
foreach($paths as $path)
{
echo "<br>";
$path = str_replace($localroot, "", $path);
$source = $hosted . $path;
$dest = $localbuild . $path;
if (is_dir_path($dest))
{
mkdir($dest, 0755, true);
echo "Make folder $source at $dest. <br>";
}
else
{
copy($source, $dest);
echo "Copy $source to $dest. <br>";
}
}
You are trying to use URLs to travers local filesystem directories. URLs are only for webserver to understand web requests.
You will have more luck if you change this:
copy(https:\\localhost\intranet\builder.php)
to this:
copy(C:\xampp\htdocs\intranet\builder.php)
EDIT
Based on your additional info in the comments I understand that you need to generate static HTML-files for hosting on a static only webserver. This is not an issue of copying files really. It's accessing the HMTL that the script generates when run through a webserver.
You can do this in a few different ways actually. I'm not sure exactly how the generator script works, but it seems like that script is trying to copy the supposed output from loads of PHP-files.
To get the generated content from a PHP-file you can either use the command line php command to execute the script like so c:\some\path>php some_php_file.php > my_html_file.html, or use the power of the webserver to do it for you:
<?php
$hosted = "https://localhost/intranet/"; <--- UPDATED
foreach($paths as $path)
{
echo "<br>";
$path = str_replace($localroot, "", $path);
$path = str_replace("\\","/",$path); <--- ADDED
$source = $hosted . $path;
$dest = $localbuild . $path;
if (is_dir_path($dest))
{
mkdir($dest, 0755, true);
echo "Make folder $source at $dest. <br>";
}
else
{
$content = file_get_contents(urlencode($source));
file_put_contents(str_replace(".php", ".html", $dest), $content);
echo "Copy $source to $dest. <br>";
}
}
In the code above I use file_get_contents() to read the html from the URL you are using https://..., which in this case, unlike with copy(), will call up the webserver, triggering the PHP engine to produce the output.
Then I write the pure HTML to a file in the $dest folder, replacing the .php with .htmlin the filename.
EDIT
Added and revised the code a bit above.

Not able to read file in php

I am facing the issue with this code:
<?php
$files = scandir("D:/Dummy");
foreach($files as $file) {
$filenam = $file;
$path_to_file = $filenam;
$file_contents = file_get_contents($path_to_file);
echo "Hello ".$filenam;
$printFileName="";
if(strpos("9222339940", $file_contents) === false)
{
$printFileName=$filenam." ";
}
}
echo $printFileName;
?>
Basically, I have written this code to scan all the files in the directory and from the each file, I need to replace the mobile number. But for some reason, I'm not able to run the script. It is throwing error:
file_get_contents(name of the file) failed to open stream. No such file or directory error.
The scandir() function of PHP will only return the basenames of the files within the directory. That is, if your directory D:\Dummy contains a file test.txt, then scandir() will not return the full path D:\Dummy\test.txt, but only test.txt. So the PHP process will not find the file, because you need to provide the complete path of the file.

rmdir no such file directory error although directory exist

If use this function to remove a directory + all files inside.
function delete_files($target)
{
if(is_dir($target))
{
$files = glob($target . '*', GLOB_MARK);
foreach($files as $file)
{
delete_files($file);
}
rmdir($target);
}
elseif(is_file($target))
{
unlink($target);
}
}
delete_files($directory);
But whenever I do this, I get this error message:
Warning: rmdir(directory/12) [function.rmdir]: No such file or directory
in delete_files.php
"directory/12" is the correct name of the directory I wanted to delete. I don't understand why it says that it does not exist because it does! Weirdly though, even though I got the error message, the directory DID get deleted.
So I added a line of code print_n($files); before the for-loop and it game me two arrays -- one containing the directory ("directory/12") and the other containing all the files of the directory ("directory/12/01.gif", "directory/12/02.gif" etc). So I figured the directory must have gotten deleted in the for-loop and removed the line rmdir($target) and tried it again. This time, all the files within the directory got deleted but the directory itself remained.
So apparently, rmdir DOES indeed remove the directory correctly. But then, why does it give me the error message beforehand that it doesn't exist?
It will work if you append a slash to the directory name.
Explanation: When you initially call the function as delete_files("directory/12"), the parameters passed to the glob() call will look like this:
$files = glob("directory/12*", GLOB_MARK);
Assuming that you have no other files in directory/ with names beginning with 12, this will just return "directory/12/" (with a slash appended because of GLOB_MARK). The function will then recursively call itself with that parameter, resulting in the top-level directory being processed twice.
Of course, if you did happen to have some other file or directory named, say, directory/123, then it would also get deleted, which is presumably not what you want.
To fix this properly, you should make sure your function can properly handle directories even if they get passed in without a trailing slash. The simplest way to do that would be to always append the slash to directory names before globbing them, like this:
$files = glob($target . '/*');
However, note that this could still fail (albeit less destructively) if your directory happened to contain some files not matched by *, such as dotfiles, since they would not get deleted, causing the subsequent rmdir() to fail because the directory will not be empty.
A more robust solution would be to use scandir() instead of glob(), like this:
$files = array_diff( scandir($target), array('.', '..') );
foreach ($files as $file) {
delete_files("$target/$file");
}
(The array_diff() is needed to eliminate the special . and .. directory entries, which would cause the code to recurse forever if they weren't excluded.)
One remaining potential failure mode is that this code will happily follow symlinks to directories and try to delete everything in the directory they point to (and then fail to remove the link itself, because rmdir() can't remove symlinks). To fix this issue, you'll probably want to replace the is_dir($target) test with !is_link($target) && is_dir($target).
All put together, the resulting code would look like this:
function delete_files($target)
{
if(!is_link($target) && is_dir($target))
{
// it's a directory; recursively delete everything in it
$files = array_diff( scandir($target), array('.', '..') );
foreach($files as $file) {
delete_files("$target/$file");
}
rmdir($target);
}
else
{
// probably a normal file or a symlink; either way, just unlink() it
unlink($target);
}
}
delete_files($directory);
Ps. See also How do I recursively delete a directory and its entire contents (files + sub dirs) in PHP?
cause you call it twice the first time it works the second time it gives a error.
I can't prove it, but with recursive code like that it is the problem.

PHP opendir issue

Why do I get this error even though the directory exists? it works fine if I target the parent directory, I tried using %20 instead of space too, and tried removing the last / but nothing works!
Warning: opendir(/home/xxxx/user_files/users/xxxx/test directory/) [function.opendir]: failed to open dir: No such file or directory in /home/xxxx/public_html/beta/stream._pages/file._list._i.php on line 54
(Note: xxxx is just me censoring user names)
Make a file called test.php and put it in your test directory. In that file, put this code:
<? echo dirname(__FILE__);?>
Then, visit test directory/test.php in your web browser, copy and paste the path as given in test.php and try using that exact path in opendir.
Another issue might be that the permissions of your directory aren't right, try chmodding to 777
For anyone trying to find the folder outside of the public_html folder.
This code is provided by php.net for the opendir() function:
if ( $handle = opendir('../../../../') )
{
echo "Directory handle: $handle\n";
echo "Entries:\n";
/* This is the correct way to loop over the directory. */
while ( false !== ( $entry = readdir( $handle ) ) )
{
echo "$entry\n";
}
/* This is the WRONG way to loop over the directory. */
while ($entry = readdir($handle))
{
echo "$entry\n";
}
closedir( $handle );
}
Solution
The $handle I started checking out how far I could go back with '../' adding as much ../ as possible until you find yourself in the folder you need. From there you take I guess.
For me '../../../../' was enough to get there, it's different on every server.

file_exists() returns false, but the file DOES exist

I'm having a very weird issue with file_exists(). I'm using this function to check if 2 different files in the same folders do exist. I've double-checked, they BOTH do exist.
echo $relative . $url['path'] . '/' . $path['filename'] . '.jpg';
Result: ../../images/example/001-001.jpg
echo $relative . $url['path'] . '/' . $path['filename'] . '.' . $path['extension'];
Result: ../../images/example/001-001.PNG
Now let's use file_exists() on these:
var_dump(file_exists($relative . $url['path'] . '/' . $path['filename'] . '.jpg'));
Result: bool(false)
var_dump(file_exists($relative . $url['path'] . '/' . $path['filename'] . '.' . $path['extension']));
Result: bool(true)
I don't get it - both of these files do exist. I'm running Windows, so it's not related to a case-sensitive issue. Safe Mode is off.
What might be worth mentioning though is that the .png one is uploaded by a user via FTP, while the .jpg one is created using a script. But as far as I know, that shouldn't make a difference.
Any tips?
Thanks
file_exists() just doesn't work with HTTP addresses.
It only supports filesystem paths (and FTP, if you're using PHP5.)
Please note:
Works :
if (file_exists($_SERVER['DOCUMENT_ROOT']."/folder/test.txt")
echo "file exists";
Does not work:
if (file_exists("www.mysite.com/folder/test.txt")
echo "file exists";
Results of the file_exists() are cached, so try using clearstatcache(). If that not helped, recheck names - they might be similar, but not same.
I found that what works for me to check if a file exists (relative to the current php file it is being executed from) is this piece of code:
$filename = 'myfile.jpg';
$file_path_and_name = dirname(__FILE__) . DIRECTORY_SEPARATOR . "{$filename}";
if ( file_exists($file_path_and_name) ){
// file exists. Do some magic...
} else {
// file does not exists...
}
Just my $.02: I just had this problem and it was due to a space at the end of the file name. It's not always a path problem - although that is the first thing I check - always. I could cut and paste the file name into a shell window using the ls -l command and of course that locates the file because the command line will ignore the space where as file_exists does not. Very frustrating indeed and nearly impossible to locate were it not for StackOverflow.
HINT: When outputting debug statements enclose values with delimiters () or [] and that will show a space pretty clearly. And always remember to trim your input.
It's because of safe mode. You can turn it off or include the directory in safe_mode_include_dir. Or change file ownership / permissions for those files.
php.net: file_exists()
php.net: safe mode
Try using DIRECTORY_SEPARATOR instead of '/' as separator. Windows uses a different separator for file system paths (backslash) than Linux and Unix systems.
A very simple trick is here that worked for me.
When I write following line, than it returns false.
if(file_exists('/my-dreams-files/'.$_GET['article'].'.html'))
And when I write with removing URL starting slash, then it returns true.
if(file_exists('my-dreams-files/'.$_GET['article'].'.html'))
I have a new reason this happens - I am using PHP inside a Docker container with a mounted volume for the codebase which resides on my local host machine.
I was getting file_exists == FALSE (inside Composer autoload), but if I copied the filepath into terminal - it did exist! I tried the clearstatche(), checked safe-mode was OFF.
Then I remembered the Docker volume mapping: the absolute path on my local host machine certainly doesn't exist inside the Docker container - which is PHP's perspective on the world.
(I keep forgetting I'm using Docker, because I've made shell functions which wrap the docker run commands so nicely...)
It can also be a permission problem on one of the parent folders or the file itself.
Try to open a session as the user running your webserver and cd into it. The folder must be accessible by this user and the file must be readable.
If not, php will return that the file doesn't exist.
have you tried manual entry. also your two extensions seem to be in different case
var_dump(file_exists('../../images/example/001-001.jpg'));
var_dump(file_exists('../../images/example/001-001.PNG'));
A custom_file_exists() function inspired by #Timur, #Brian, #Doug and #Shahar previous answers:
function custom_file_exists($file_path=''){
$file_exists=false;
//clear cached results
//clearstatcache();
//trim path
$file_dir=trim(dirname($file_path));
//normalize path separator
$file_dir=str_replace('/',DIRECTORY_SEPARATOR,$file_dir).DIRECTORY_SEPARATOR;
//trim file name
$file_name=trim(basename($file_path));
//rebuild path
$file_path=$file_dir."{$file_name}";
//If you simply want to check that some file (not directory) exists,
//and concerned about performance, try is_file() instead.
//It seems like is_file() is almost 2x faster when a file exists
//and about the same when it doesn't.
$file_exists=is_file($file_path);
//$file_exists=file_exists($file_path);
return $file_exists;
}
This answer may be a bit hacky, but its been working for me -
$file = 'path/to/file.jpg';
$file = $_SERVER['REQUEST_SCHEME'].'://'.$_SERVER['HTTP_HOST'].'/'.$file;
$file_headers = #get_headers($file);
if($file_headers[0] == 'HTTP/1.1 404 Not Found') {
$exists = false;
}else{
$exists = true;
}
apparently $_SERVER['REQUEST_SCHEME'] is a bit dicey to use with IIS 7.0 + PHP 5.3 so you could probably look for a better way to add in the protocol.
I found this answer here http://php.net/manual/en/function.file-exists.php#75064
I spent the last two hours wondering what was wrong with my if statement: file_exists($file) was returning false, however I could call include($file) with no problem.
It turns out that I didn't realize that the php include_path value I had set in the .htaccess file didn't carry over to file_exists, is_file, etc.
Thus:
<?PHP
// .htaccess php_value include_path '/home/user/public_html/';
// includes lies in /home/user/public_html/includes/
//doesn't work, file_exists returns false
if ( file_exists('includes/config.php') )
{
include('includes/config.php');
}
//does work, file_exists returns true
if ( file_exists('/home/user/public_html/includes/config.php') )
{
include('includes/config.php');
}
?>
Just goes to show that "shortcuts for simplicity" like setting the include_path in .htaccess can just cause more grief in the long run.
In my case, the problem was a misconception of how file_exists() behaves with symbolic links and .. ("dotdot" or double period) parent dir references. In that regard, it differs from functions like require, include or even mkdir().
Given this directory structure:
/home/me/work/example/
www/
/var/www/example.local/
tmp/
public_html -> /home/me/work/example/www/
file_exists('/var/www/example.local/public_html/../tmp/'); would return FALSE even though the subdir exists as we see, because the function traversed up into /home/me/work/example/ which does not have that subdir.
For this reason, I have created this function:
/**
* Resolve any ".." ("dotdots" or double periods) in a given path.
*
* This is especially useful for avoiding the confusing behavior `file_exists()`
* shows with symbolic links.
*
* #param string $path
*
* #return string
*/
function resolve_dotdots( string $path ) {
if (empty($path)) {
return $path;
}
$source = array_reverse(explode(DIRECTORY_SEPARATOR, $path));
$balance = 0;
$parts = array();
// going backwards through the path, keep track of the dotdots and "work
// them off" by skipping a part. Only take over the respective part if the
// balance is at zero.
foreach ($source as $part) {
if ($part === '..') {
$balance++;
} else if ($balance > 0) {
$balance--;
} else {
array_push($parts, $part);
}
}
// special case: path begins with too many dotdots, references "outside
// knowledge".
if ($balance > 0) {
for ($i = 0; $i < $balance; $i++) {
array_push($parts, '..');
}
}
$parts = array_reverse($parts);
return implode(DIRECTORY_SEPARATOR, $parts);
}
I just encountered this same problem and I solved it in a mysterious way. After inserting a a filepath I copied from Windows File explorer. file_exists() keeps returning false continuously, but if I copy same path from VSCode editor it works perfectly.
After dumping variables with var_dump($path); I noticed something mysterious.
For path I copied from file explorer it shows length 94.
For path I copied from VSCode Editor it shows length 88.
Both path look same length on my code Editor.
My suggestion: if string contain hidden characters, it may fail and not work.

Categories