Related
I've tried several ways to include a PDF file using PHP on Linux, it works normally on Windows, but not on Linux.
I have several directories with accents and I need to include PDF files that are inside the directories.
I pass the name of the PDF files and include the PDF.
My problem is with the encoding and accentuation of the folders. The files doesn't have accents, only the folders.
Examples of folders / files:
files/ño/hash1.pdf
files/nó/hash2.pdf
files/ção/hash.pdf
function getFileInContents($dir, $filename)
{
$files = scandir($dir);
foreach ($files as $key => $value) {
$path = realpath($dir . '/' . $value);
if (!is_dir($path)) {
if ($filename == $value) {
return $path;
}
} elseif ($value != "." && $value != "..") {
getFileInContents($path, $filename);
}
}
return null;
}
if (!isset($_GET['f'])) {
echo 'File not found';
exit;
}
$local = 'files/';
$path = getFileInContents($local, $_GET['f']);
if (!$path) {
echo 'File not found';
exit;
}
$mime = mime_content_type($path);
header('Content-Type: ' . $mime);
include_once($path);
My answer adds on and expounds on the one offered up by #James, because you have additional issues:
As pointed out in the comment made by #Olivier, you should be using readfile() instead of include.
You should not include the final '/' in your declaration of $local since you will be concatenating a '/' to the passed $dir argument in function getFileInContents.
Presumably function getFileInContents is intended to recursively search subdirectories, but it is not doing this correctly; it only is searching the first subdirectory it finds and if the sought file is not present in that subdirectory it returns with a "not found" condition and never searches any other subdirectories that might be present in the directory.
function getFileInContents($dir, $filename)
{
$files = scandir($dir);
foreach ($files as $key => $value) {
$path = realpath($dir . '/' . $value);
if (!is_dir($path)) {
if ($filename == $value) {
return $path;
}
} elseif ($value != "." && $value != "..") {
$new_path = getFileInContents($path, $filename);
if ($new_path) {
return $new_path;
}
}
}
return null;
}
if (!isset($_GET['f'])) {
echo 'File not found';
exit;
}
$local = 'files';
$path = getFileInContents($local, $_GET['f']);
if (!$path) {
echo 'File not found';
exit;
}
$mime = mime_content_type($path);
header('Content-Type: ' . $mime);
readfile($path);
I don't think the problem is anything to do with the folder names. I think the problem is that your recursive function is not actually returning the value when it finds the file.
When you call getFileInContents($path, $filename); you then need to return the value, if it's not null, to break the loop.
function getFileInContents($dir, $filename)
{
$files = scandir($dir);
foreach ($files as $key => $value) {
$path = realpath($dir . '/' . $value);
if (!is_dir($path)) {
if ($filename == $value) {
return $path;
}
} elseif ($value != "." && $value != "..") {
$testValue = getFileInContents($path, $filename);
if ($testValue!=null){
return $testValue;
}
}
}
return null;
}
I have a directory /main with folders and files inside those folders.
I want to keep directory /main but delete everything in it:
$di = new RecursiveDirectoryIterator('/main');
foreach (new RecursiveIteratorIterator($di) as $filename => $file) {
if(!is_dir($filename)){
unlink($filename);
}
};
This is deleting the files inside the folders, leaving /main with a list of empty folders, how do I delete these now?
I know this should be possible to integrate inside the loop, I tried rmdir without success.
Try this code
UPDATED ANSWER:
$dir = '/home/XXXXX/public_html/';
foreach(glob($dir.'*.*') as $v){
echo $v;
// unlink($v);
}
destroy_dir($dir);
function destroy_dir($dir) {
if (!is_dir($dir) || is_link($dir)) return unlink($dir);
foreach (scandir($dir) as $file) {
if ($file == '.' || $file == '..') continue;
if (!destroy_dir($dir . DIRECTORY_SEPARATOR . $file)) {
chmod($dir . DIRECTORY_SEPARATOR . $file, 0777);
if (!destroy_dir($dir . DIRECTORY_SEPARATOR . $file)) return false;
};
}
return rmdir($dir);
}
Here, Code is not tested.
function delete_directory_files($dirname) {
if (is_dir($dirname))
$dir_handle = opendir($dirname);
if (!$dir_handle)
return false;
while($file = readdir($dir_handle)) {
if ($file != "." && $file != "..") {
if (!is_dir($dirname."/".$file))
unlink($dirname."/".$file);
else
delete_directory($dirname.'/'.$file);
}
}
closedir($dir_handle);
return true;
}
This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
how to delete a folder with contents using PHP
I know that you can remove a folder that is empty with rmdir. And I know you can clear a folder with the following three lines.
foreach($directory_path as $file) {
unlink($file);
}
But what if one of the files is actually a sub directory. How would one get rid of that but in an infinite amount like the dual mirror effect. Is there any force delete directory in php?
Thanks
This function will delete a directory recursively:
function rmdir_recursive($dir) {
foreach(scandir($dir) as $file) {
if ('.' === $file || '..' === $file) continue;
if (is_dir("$dir/$file")) rmdir_recursive("$dir/$file");
else unlink("$dir/$file");
}
rmdir($dir);
}
This one too:
function rmdir_recursive($dir) {
$it = new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS);
$it = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST);
foreach($it as $file) {
if ($file->isDir()) rmdir($file->getPathname());
else unlink($file->getPathname());
}
rmdir($dir);
}
From PHP rmdir page:
<?php
function rrmdir($dir) {
if (is_dir($dir)) {
$objects = scandir($dir);
foreach ($objects as $object) {
if ($object != "." && $object != "..") {
if (filetype($dir."/".$object) == "dir") rrmdir($dir."/".$object); else unlink($dir."/".$object);
}
}
reset($objects);
rmdir($dir);
}
}
?>
And
<?php
function delTree($dir) {
$files = glob( $dir . '*', GLOB_MARK );
foreach( $files as $file ){
if( substr( $file, -1 ) == '/' )
delTree( $file );
else
unlink( $file );
}
if (is_dir($dir)) rmdir( $dir );
}
?>
<?php
function rrmdir($dir) {
if (is_dir($dir)) {
$objects = scandir($dir);
foreach ($objects as $object) {
if ($object != "." && $object != "..") {
if (filetype($dir."/".$object) == "dir") rrmdir($dir."/".$object); else unlink($dir."/".$object);
}
}
reset($objects);
rmdir($dir);
}
}
?>
from PHP's documentation
On my old VPS I was using the following code to copy the files and directories within a directory to a new directory that was created after the user submitted their form.
function copyr($source, $dest)
{
// Simple copy for a file
if (is_file($source)) {
return copy($source, $dest);
}
// Make destination directory
if (!is_dir($dest)) {
mkdir($dest);
$company = ($_POST['company']);
}
// Loop through the folder
$dir = dir($source);
while (false !== $entry = $dir->read()) {
// Skip pointers
if ($entry == '.' || $entry == '..') {
continue;
}
// Deep copy directories
if ($dest !== "$source/$entry") {
copyr("$source/$entry", "$dest/$entry");
}
}
// Clean up
$dir->close();
return true;
}
copyr('Template/MemberPages', "Members/$company")
However now on my new VPS it will only create the main directory, but will not copy any of the files to it. I don't understand what could have changed between the 2 VPS's?
Try something like this:
$source = "dir/dir/dir";
$dest= "dest/dir";
mkdir($dest, 0755);
foreach (
$iterator = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($source, \RecursiveDirectoryIterator::SKIP_DOTS),
\RecursiveIteratorIterator::SELF_FIRST) as $item
) {
if ($item->isDir()) {
mkdir($dest . DIRECTORY_SEPARATOR . $iterator->getSubPathname());
} else {
copy($item, $dest . DIRECTORY_SEPARATOR . $iterator->getSubPathname());
}
}
Iterator iterate through all folders and subfolders and make copy of files from $source to $dest
Could I suggest that (assuming it's a *nix VPS) that you just do a system call to cp -r and let that do the copy for you.
This function copies folder recursivley very solid. I've copied it from the comments section on copy command of php.net
function recurse_copy($src,$dst) {
$dir = opendir($src);
#mkdir($dst);
while(false !== ( $file = readdir($dir)) ) {
if (( $file != '.' ) && ( $file != '..' )) {
if ( is_dir($src . '/' . $file) ) {
recurse_copy($src . '/' . $file,$dst . '/' . $file);
}
else {
copy($src . '/' . $file,$dst . '/' . $file);
}
}
}
closedir($dir);
}
I have changed Joseph's code (below), because it wasn't working for me. This is what works:
function cpy($source, $dest){
if(is_dir($source)) {
$dir_handle=opendir($source);
while($file=readdir($dir_handle)){
if($file!="." && $file!=".."){
if(is_dir($source."/".$file)){
if(!is_dir($dest."/".$file)){
mkdir($dest."/".$file);
}
cpy($source."/".$file, $dest."/".$file);
} else {
copy($source."/".$file, $dest."/".$file);
}
}
}
closedir($dir_handle);
} else {
copy($source, $dest);
}
}
[EDIT] added test before creating a directory (line 7)
The Symfony's FileSystem Component offers a good error handling as well as recursive remove and other useful stuffs. Using #OzzyCzech's great answer, we can do a robust recursive copy this way:
use Symfony\Component\Filesystem\Filesystem;
// ...
$fileSystem = new FileSystem();
if (file_exists($target))
{
$this->fileSystem->remove($target);
}
$this->fileSystem->mkdir($target);
$directoryIterator = new \RecursiveDirectoryIterator($source, \RecursiveDirectoryIterator::SKIP_DOTS);
$iterator = new \RecursiveIteratorIterator($directoryIterator, \RecursiveIteratorIterator::SELF_FIRST);
foreach ($iterator as $item)
{
if ($item->isDir())
{
$fileSystem->mkdir($target . DIRECTORY_SEPARATOR . $iterator->getSubPathName());
}
else
{
$fileSystem->copy($item, $target . DIRECTORY_SEPARATOR . $iterator->getSubPathName());
}
}
Note: you can use this component as well as all other Symfony2 components standalone.
Here's what we use at our company:
static public function copyr($source, $dest)
{
// recursive function to copy
// all subdirectories and contents:
if(is_dir($source)) {
$dir_handle=opendir($source);
$sourcefolder = basename($source);
mkdir($dest."/".$sourcefolder);
while($file=readdir($dir_handle)){
if($file!="." && $file!=".."){
if(is_dir($source."/".$file)){
self::copyr($source."/".$file, $dest."/".$sourcefolder);
} else {
copy($source."/".$file, $dest."/".$file);
}
}
}
closedir($dir_handle);
} else {
// can also handle simple copy commands
copy($source, $dest);
}
}
function recurse_copy($source, $dest)
{
// Check for symlinks
if (is_link($source)) {
return symlink(readlink($source), $dest);
}
// Simple copy for a file
if (is_file($source)) {
return copy($source, $dest);
}
// Make destination directory
if (!is_dir($dest)) {
mkdir($dest);
}
// Loop through the folder
$dir = dir($source);
while (false !== $entry = $dir->read()) {
// Skip pointers
if ($entry == '.' || $entry == '..') {
continue;
}
// Deep copy directories
recurse_copy("$source/$entry", "$dest/$entry");
}
// Clean up
$dir->close();
return true;
}
OzzyCheck's is elegant and original, but he forgot the initial mkdir($dest);
See below. No copy command is ever provided with contents only. It must fulfill its entire role.
$source = "dir/dir/dir";
$dest= "dest/dir";
mkdir($dest, 0755);
foreach (
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($source, RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::SELF_FIRST) as $item) {
if ($item->isDir()) {
mkdir($dest . DIRECTORY_SEPARATOR . $iterator->getSubPathName());
} else {
copy($item, $dest . DIRECTORY_SEPARATOR . $iterator->getSubPathName());
}
}
Here's a simple recursive function to copy entire directories
source: http://php.net/manual/de/function.copy.php
<?php
function recurse_copy($src,$dst) {
$dir = opendir($src);
#mkdir($dst);
while(false !== ( $file = readdir($dir)) ) {
if (( $file != '.' ) && ( $file != '..' )) {
if ( is_dir($src . '/' . $file) ) {
recurse_copy($src . '/' . $file,$dst . '/' . $file);
}
else {
copy($src . '/' . $file,$dst . '/' . $file);
}
}
}
closedir($dir);
}
?>
Why not just ask the OS to take care of this?
system("cp -r olddir newdir");
Done.
<?php
/**
* code by Nk (nk.have.a#gmail.com)
*/
class filesystem
{
public static function normalizePath($path)
{
return $path.(is_dir($path) && !preg_match('#/$#', $path) ? '/' : '');
}
public static function rscandir($dir, $sort = SCANDIR_SORT_ASCENDING)
{
$results = array();
if(!is_dir($dir))
return $results;
$dir = self::normalizePath($dir);
$objects = scandir($dir, $sort);
foreach($objects as $object)
if($object != '.' && $object != '..')
{
if(is_dir($dir.$object))
$results = array_merge($results, self::rscandir($dir.$object, $sort));
else
array_push($results, $dir.$object);
}
array_push($results, $dir);
return $results;
}
public static function rcopy($source, $dest, $destmode = null)
{
$files = self::rscandir($source);
if(empty($files))
return;
if(!file_exists($dest))
mkdir($dest, is_int($destmode) ? $destmode : fileperms($source), true);
$source = self::normalizePath(realpath($source));
$dest = self::normalizePath(realpath($dest));
foreach($files as $file)
{
$file_dest = str_replace($source, $dest, $file);
if(is_dir($file))
{
if(!file_exists($file_dest))
mkdir($file_dest, is_int($destmode) ? $destmode : fileperms($file), true);
}
else
copy($file, $file_dest);
}
}
}
?>
/var/www/websiteA/backup.php :
<?php /* include.. */ filesystem::rcopy('/var/www/websiteA/', '../websiteB'); ?>
hmm. as that's complicated ))
function mkdir_recursive( $dir ){
$prev = dirname($dir);
if( ! file_exists($prev))
{
mkdir_recursive($prev);
}
if( ! file_exists($dir))
{
mkdir($dir);
}
}
...
$srcDir = "/tmp/from"
$dstDir = "/tmp/to"
mkdir_recursive($dstDir);
foreach( $files as $file){
copy( $srcDir."/".$file, $dstDir."/".$file);
}
$file - somthing like that file.txt
I guess you should check user(group)rights. You should consider chmod for example, depending on how you run (su?)PHP. You could possibly also choose to modify your php configuration.
There were some issues with the functions that I tested in the thread and here is a powerful function that covers everything. Highlights:
No need to have an initial or intermediate source directories. All of the directories up to the source directory and to the copied directories will be handled.
Ability to skip directory or files from an array. (optional) With the global $skip; skipping of files even under sub level directories are handled.
Full recursive support, all the files and directories in multiple depth are supported.
$from = "/path/to/source_dir";
$to = "/path/to/destination_dir";
$skip = array('some_file.php', 'somedir');
copy_r($from, $to, $skip);
function copy_r($from, $to, $skip=false) {
global $skip;
$dir = opendir($from);
if (!file_exists($to)) {mkdir ($to, 0775, true);}
while (false !== ($file = readdir($dir))) {
if ($file == '.' OR $file == '..' OR in_array($file, $skip)) {continue;}
if (is_dir($from . DIRECTORY_SEPARATOR . $file)) {
copy_r($from . DIRECTORY_SEPARATOR . $file, $to . DIRECTORY_SEPARATOR . $file);
}
else {
copy($from . DIRECTORY_SEPARATOR . $file, $to . DIRECTORY_SEPARATOR . $file);
}
}
closedir($dir);
}
Recursively copies files from source to target, with choice of overwriting existing files on the target.
function copyRecursive($sourcePath, $targetPath, $overwriteExisting) {
$dir = opendir($sourcePath);
while (($file = readdir($dir)) !== false) {
if ($file == "." || $file == "..") continue; // ignore these.
$source = $sourcePath . "/" . $file;
$target = $targetPath. "/" . $file;
if (is_dir($source) == true) {
// create the target directory, if it does not exist.
if (file_exists($target) == false) #mkdir($target);
copyRecursive($source, $target, $overwriteExisting);
} else {
if ((file_exists($target) == false) || ($overwriteExisting == true)) {
copy($source, $target);
}
}
}
closedir($dir);
}
I am trying to remove a directory with rmdir, but I received the 'Directory not empty' message, because it still has files in it.
What function can I use to remove a directory with all the files in it as well?
There is no built-in function to do this, but see the comments at the bottom of http://us3.php.net/rmdir. A number of commenters posted their own recursive directory deletion functions. You can take your pick from those.
Here's one that looks decent:
function deleteDirectory($dir) {
if (!file_exists($dir)) {
return true;
}
if (!is_dir($dir)) {
return unlink($dir);
}
foreach (scandir($dir) as $item) {
if ($item == '.' || $item == '..') {
continue;
}
if (!deleteDirectory($dir . DIRECTORY_SEPARATOR . $item)) {
return false;
}
}
return rmdir($dir);
}
You could just invoke rm -rf if you want to keep things simple. That does make your script UNIX-only, so beware of that. If you go that route I would try something like:
function deleteDirectory($dir) {
system('rm -rf -- ' . escapeshellarg($dir), $retval);
return $retval == 0; // UNIX commands return zero on success
}
function rrmdir($dir)
{
if (is_dir($dir))
{
$objects = scandir($dir);
foreach ($objects as $object)
{
if ($object != '.' && $object != '..')
{
if (filetype($dir.'/'.$object) == 'dir') {rrmdir($dir.'/'.$object);}
else {unlink($dir.'/'.$object);}
}
}
reset($objects);
rmdir($dir);
}
}
You could always try to use system commands.
If on linux use: rm -rf /dir
If on windows use: rd c:\dir /S /Q
In the post above (John Kugelman) I suppose the PHP parser will optimize that scandir in the foreach but it just seems wrong to me to have the scandir in the foreach condition statement.
You could also just do two array_shift commands to remove the . and .. instead of always checking in the loop.
Can't think of an easier and more efficient way to do that than this
function removeDir($dirname) {
if (is_dir($dirname)) {
$dir = new RecursiveDirectoryIterator($dirname, RecursiveDirectoryIterator::SKIP_DOTS);
foreach (new RecursiveIteratorIterator($dir, RecursiveIteratorIterator::CHILD_FIRST) as $object) {
if ($object->isFile()) {
unlink($object);
} elseif($object->isDir()) {
rmdir($object);
} else {
throw new Exception('Unknown object type: '. $object->getFileName());
}
}
rmdir($dirname); // Now remove myfolder
} else {
throw new Exception('This is not a directory');
}
}
removeDir('./myfolder');
My case had quite a few tricky directories (names containing special characters, deep nesting, etc) and hidden files that produced "Directory not empty" errors with other suggested solutions. Since a Unix-only solution was unacceptable, I tested until I arrived at the following solution (which worked well in my case):
function removeDirectory($path) {
// The preg_replace is necessary in order to traverse certain types of folder paths (such as /dir/[[dir2]]/dir3.abc#/)
// The {,.}* with GLOB_BRACE is necessary to pull all hidden files (have to remove or get "Directory not empty" errors)
$files = glob(preg_replace('/(\*|\?|\[)/', '[$1]', $path).'/{,.}*', GLOB_BRACE);
foreach ($files as $file) {
if ($file == $path.'/.' || $file == $path.'/..') { continue; } // skip special dir entries
is_dir($file) ? removeDirectory($file) : unlink($file);
}
rmdir($path);
return;
}
There are plenty of solutions already, still another possibility with less code using PHP arrow function:
function rrmdir(string $directory): bool
{
array_map(fn (string $file) => is_dir($file) ? rrmdir($file) : unlink($file), glob($directory . '/' . '*'));
return rmdir($directory);
}
Here what I used:
function emptyDir($dir) {
if (is_dir($dir)) {
$scn = scandir($dir);
foreach ($scn as $files) {
if ($files !== '.') {
if ($files !== '..') {
if (!is_dir($dir . '/' . $files)) {
unlink($dir . '/' . $files);
} else {
emptyDir($dir . '/' . $files);
rmdir($dir . '/' . $files);
}
}
}
}
}
}
$dir = 'link/to/your/dir';
emptyDir($dir);
rmdir($dir);
From http://php.net/manual/en/function.rmdir.php#91797
Glob function doesn't return the hidden files, therefore scandir can be more useful, when trying to delete recursively a tree.
<?php
public static function delTree($dir) {
$files = array_diff(scandir($dir), array('.','..'));
foreach ($files as $file) {
(is_dir("$dir/$file")) ? delTree("$dir/$file") : unlink("$dir/$file");
}
return rmdir($dir);
}
?>
Try the following handy function:
/**
* Removes directory and its contents
*
* #param string $path Path to the directory.
*/
function _delete_dir($path) {
if (!is_dir($path)) {
throw new InvalidArgumentException("$path must be a directory");
}
if (substr($path, strlen($path) - 1, 1) != DIRECTORY_SEPARATOR) {
$path .= DIRECTORY_SEPARATOR;
}
$files = glob($path . '*', GLOB_MARK);
foreach ($files as $file) {
if (is_dir($file)) {
_delete_dir($file);
} else {
unlink($file);
}
}
rmdir($path);
}
I didn't arrive to delete a folder because PHP said me it was not empty. But it was. The function by Naman was the good solution to complete mine. So this is what I use :
function emptyDir($dir) {
if (is_dir($dir)) {
$scn = scandir($dir);
foreach ($scn as $files) {
if ($files !== '.') {
if ($files !== '..') {
if (!is_dir($dir . '/' . $files)) {
unlink($dir . '/' . $files);
} else {
emptyDir($dir . '/' . $files);
rmdir($dir . '/' . $files);
}
}
}
}
}
}
function deleteDir($dir) {
foreach(glob($dir . '/' . '*') as $file) {
if(is_dir($file)){
deleteDir($file);
} else {
unlink($file);
}
}
emptyDir($dir);
rmdir($dir);
}
So, to delete a directory and recursively its content :
deleteDir($dir);
With this function, you will be able to delete any file or folder
function deleteDir($dirPath)
{
if (!is_dir($dirPath)) {
if (file_exists($dirPath) !== false) {
unlink($dirPath);
}
return;
}
if ($dirPath[strlen($dirPath) - 1] != '/') {
$dirPath .= '/';
}
$files = glob($dirPath . '*', GLOB_MARK);
foreach ($files as $file) {
if (is_dir($file)) {
deleteDir($file);
} else {
unlink($file);
}
}
rmdir($dirPath);
}
function delTree($dir) {
$files = array_diff(scandir($dir), array('.','..'));
foreach ($files as $file)
(is_dir("$dir/$file")) ? delTree("$dir/$file") : #unlink("$dir/$file");
return #rmdir($dir);
}