How can I exclude directories using RecursiveDirectoryIterator - php

I have the function below. Which goes through directories and recursively searches through them to grab a random image file and then attaches that to a post. What I want to do is exclude some files from the search.
I have a comma separated list which I explode into an array, I tried using a filter but couldn't get this to work.
Current function without filter is
function swmc_get_imgs($start_dir, $ext, $exclude=array()){
$dir = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($start_dir));
$files = array();
// Force array of extensions and make them all lower-case
if ( ! is_array($ext))
{
$ext = (array) $ext;
}
$ext = array_unique(array_map('strtolower', $ext));
foreach($dir as $file)
{
// Skip anything that isn't a file
if ( ! $file->isFile())
continue;
// If the file has one of our desired extensions, add it to files array
if (in_array(strtolower(pathinfo($file->getFilename(), PATHINFO_EXTENSION)), $ext)) {
$files[] = $file->getPathname();
}
}
return $files;
}
So the above works but can be fairly expensive still especially with a lot of directories, as such I want to exclude a list of directories stored in a comma list.
I tried the following
class SwmcOnlyFilter extends RecursiveFilterIterator {
public function accept() {
// Accept the current item if we can recurse into it
// or it is a value starting with "test"
return $this->hasChildren() || !in_array($this->current(), explode(",",get_option('swmc_image_excl')));
}
}
And then changing the first part of the swmc_get_imgs function to
$dirIterator = new RecursiveDirectoryIterator($start_dir);
$filter = new SwmcOnlyFilter($dirIterator);
$dir = new RecursiveIteratorIterator($filter);
However the filter doesn't jump over that directory but instead goes into it.
The directories could look like
/uploads/2009/1/2011_pic.jpg
/uploads/2011/1/john_smith.jpg
and so on.
So I may want to exclude 2011 as a directory but not exclude the image that lives in 2009 with 2011 in its title.
CLARIFICATION:
I could filter out these manually by skipping them in the foreach loop, however this still checks them and wastes memory and time. I would prefer to skip these at the time of the grab if possible.

figured it out using the following
function swmc_iterate_imgs($start_dir) {
$directory = $start_dir;
$excludedDirs = explode(",",get_option('swmc_image_excl')); // array of subdirectory paths, relative to $directory, to exclude from search
$it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($directory));
$fileArr = array(); // numerically indexed array with your files
$x = -1;
while ($it->valid())
{
if (!$it->isDot() && !in_array($it->getSubPath(), $excludedDirs) && preg_match('/(\.(jpg|jpeg|gif|png))$/i', $it->key()) == 1)
{
$fileArr[] = $it->key();
}
$it->next();
}
return $fileArr;
}

Related

Is this the correct way to hide a file or folder in PHP

I am just learning more about using classes in PHP. I know the code below is crap has I need help. Can someone just let me know if I am going in the right direction?
while($entryName=readdir($myDirectory)) {
$type = array("index.php", "style.css", "sorttable.js", "host-img");
if($entryName != $type[0]){
if($entryName != $type[1]){
if($entryName != $type[2]){
if($entryName != $type[3]){
$dirArray[]=$entryName;
}
}
}
}
}
What you seem to want is a list of all the files in your directory that do not have one of four specific names.
The code that most resembles yours that would do it more efficiently is
$exclude = array("index.php", "style.css", "sorttable.js", "host-img");
$dirArray = [];
while ($entryName = readdir($myDirectory)) {
if (!in_array($entryName, $exclude)) {
$dirArray[] = $entryName;
}
}
Alternately, you can dispense with the loop (as written, will include both files and directories in the directory you supply)
$exclude = array("index.php", "style.css", "sorttable.js", "host-img");
$contents = scandir($myDirectory);
$dirArray = array_diff($contents, $exclude);
Edit to add for posterity:
#arkascha had an answer that used array_filter, and while that example was just an implementation of array_diff, the motivation for that pattern is a good one: There may be times when you want to exclude more than just a simple list. It is entirely reasonable, for instance, to imagine you want to exclude specific files and all directories. So you have to filter directories from your list. And just for fun, let's also not return any file whose name begins with ..
$exclude = ["index.php", "style.css", "sorttable.js", "host-img"];
$contents = scandir($myDirectory); // myDirectory is a valid path to the directory
$dirArray = array_filter($contents, function($fileName) use ($myDirectory, $exclude) {
if (!in_array($fileName, $exclude) && strpos('.', $fileName) !== 0) {
return !is_dir($myDirectory.$fileName));
} else {
return false;
}
}
You actually want to filter your input:
<?php
$input = [".", "..", "folderA", "folderB", "file1", "file2", "file3"];
$blacklist = [".", "..", "folderA", "file1"];
$output = array_filter($input, function($entry) use ($blacklist) {
return !in_array($entry, $blacklist);
});
print_r($output);
The output is:
Array
(
[3] => folderB
[5] => file2
[6] => file3
)
Such approach allows to implement more complex filter conditions without having to pass over the input data multiple times. For example if you want to add another filter condition based on file name extensions or file creation time, even on file content.

Delete files not matching a list

So I'm trying to make a simple script, it will have a list of predefined files, search for anything that's not on the list and delete it.
I have this for now
<?php
$directory = "/home/user/public_html";
$files = glob($directory . "*.*");
foreach($files as $file)
{
$sql = mysql_query("SELECT id FROM files WHERE FileName='$file'");
if(mysql_num_rows($sql) == 0)
unlink($directory . $file);
}
?>
However, I'd like to avoid the query so I can run the script more often (there's about 60-70 files, and I want to run this every 20 seconds or so?) so how would I embedd a file list into the php file and check against that instead of database?
Thanks!
You are missing a trailing / twice.. In glob() you are giving /home/user/public_html*.* as the argument, I think you mean /home/user/public_html/*.*.
This is why I bet nothing matches the files in your table..
This won't give an error either because the syntax is fine.
Then where you unlink() you do this again.. your argument home/user/public_htmltestfile.html should be home/user/public_html/testfile.html.
I like this syntax style: "{$directory}/{$file}" because it's short and more readable. If the / is missing, you see it immediately. You can also change it to $directory . "/" . $file, it you prefer it. The same goes for one line conditional statements.. So here it comes..
<?php
$directory = "/home/user/public_html";
$files = glob("{$directory}/*.*");
foreach($files as $file)
{
$sql = mysql_query("SELECT id FROM files WHERE FileName=\"{$file}\";");
if(mysql_num_rows($sql) == 0)
{
unlink("{$directory}/{$file}");
}
}
?>
EDIT: You requested recursion. Here it goes..
You need to make a function that you can run once with a path as it's argument. Then you can run that function from inside that function on subdirectories. Like this:
<?php
/*
ListDir list files under directories recursively
Arguments:
$dir = directory to be scanned
$recursive = in how many levels of recursion do you want to search? (0 for none), default: -1 (for "unlimited")
*/
function ListDir($dir, $recursive=-1)
{
// if recursive == -1 do "unlimited" but that's no good on a live server! so let's say 999 is enough..
$recursive = ($recursive == -1 ? 999 : $recursive);
// array to hold return value
$retval = array();
// remove trailing / if it is there and then add it, to make sure there is always just 1 /
$dir = rtrim($dir,"/") . "/*";
// read the directory contents and process each node
foreach(glob($dir) as $node)
{
// skip hidden files
if(substr($node,-1) == ".") continue;
// if $node is a dir and recursive is greater than 0 (meaning not at the last level or disabled)
if(is_dir($node) && $recursive > 0)
{
// substract 1 of recursive for ever recursion.
$recursive--;
// run this same function again on itself, merging the return values with the return array
$retval = array_merge($retval, ListDir($node, $recursive));
}
// if $node is a file, we add it to the array that will be returned from this function
elseif(is_file($node))
{
$retval[] = $node;
// NOTE: if you want you can do some action here in your case you can unlink($node) if it matches your requirements..
}
}
return $retval;
}
// Output the result
echo "<pre>";
print_r(ListDir("/path/to/dir/",1));
echo "</pre>";
?>
If the list is not dynamic, store it in an array:
$myFiles = array (
'some.ext',
'next.ext',
'more.ext'
);
$directory = "/home/user/public_html/";
$files = glob($directory . "*.*");
foreach($files as $file)
{
if (!in_array($file, $myFiles)) {
unlink($directory . $file);
}
}

Search and replace entire directory file contents

I need to re-write multiple files in a single directory based on the contents of a single CSV file.
For example the CSV file would contain something like this:
define("LANG_BLABLA", "NEW");
In one of the files in the directory it would contain this:
define("LANG_BLABLA", "OLD");
The script will search through the directory and any occurrences where the CSV "LANG_BLABLA" matches the old directory LANG it will update the "OLD" with the "NEW"
My question is how exactly can I list the contents of the files in the directory in 1 array so I can easily search through them and replace where necessary.
Thanks.
Searching through a directory is relatively easy:
<?
clearstatcache();
$folder = "C:/web/website.com/some/folder";
$objects = scandir($folder, SCANDIR_SORT_NONE);
foreach ($objects as $obj) {
if ($obj === '.' || $obj === '..')
continue; // current and parent dirs
$path = "{$folder}/{$obj}";
if (strcasecmp(substr($path, -4), '.php') !== 0)
continue // Not a PHP file
if (is_link($path))
$path = realpath($path);
if ( ! is_file($path))
continue; // Not a file, probably a folder
$data = file_get_contents($path);
if ($data === false)
die('Some error occured...')
// ...
// Do your magic here
// ...
if (file_put_contents($path, $data) === false)
die('Failed to write file...');
}
As for modifying PHP files dynamically, it is probably a sign that you need to put that stuff into a database or in-memory data-store... MySQL, SQLite, MongoDB, memcached, Redis, etc. should do. Which you should use would depend on the nature of your project.
You can parse a CSV file into an array using fgetcsv http://php.net/manual/en/function.fgetcsv.php
First of all I would not recommend this workflow if you working with .php files. Try to centralize your define statements and then change it in a single location.
But here is a solution that should work for you csv files. It's not complete, you have to add some of your desired logic.
/**
* Will return an array with key value coding of your csv
* #param $defineFile Your file which contains multiple definitions e.g. define("LANG_BLABLA", "NEW");\n define("LANG_ROFL", "LOL");
* #return array
*/
public function getKeyValueArray($defineFile)
{
if (!file_exists($defineFile)) {
return array();
} else {
$fp = #fopen($defineFile, 'r');
$values = explode("\n", fread($fp, filesize($defineFile)));
$newValues = array();
foreach ($values as $val) {
preg_match("%.*\"(.*)?\",\s+\"(.*)?\".*%", $val, $matches);
$newValues[$matches[1]] = $matches[2];
}
}
}
/**
* This is s stub! You should implement the rest yourself.
*/
public function updateThings()
{
//Read your definition into an array
$defs=$this->getKeyValueArray("/some/path/to/your/file");
$scanDir="/your/desired/path/with/input/files/";
$otherFiles= scandir($scanDir);
foreach($otherFiles as $file){
if($file!="." && $file!=".."){
//read in the file definition
$oldDefinitionArray=$this->getKeyValueArray($scanDir.$file);
//Now you have your old file in an array e.g. array("LANG_BLABLA" => "OLD")
//and you already have your new file in $defs
//You now loop over both and check for each key in $defs
//if its value equals the value in the $oldDefinitionArray.
//You then update your csv or rewrite or do whatever you like.
}
}
}

Iterate through a folder?

I'm looking for the best way to iterate through a folder and put all the file names inside it into an array and in another one the count of the files.
I have found glob() as a good solution, but also a lot of alternatives for it on php.net. I'm not sure which I should use, so I'm asking here. If you're wondering for what I want to use it, it's to get all the .sql files inside a backup folder and display them as <li>thesqlfile.sql</li> and have a count of all of them too.
So I thought of having two arrays, one with their names, and one with the count of all of them. So in this case which method would be best fit to iterate ?
Method I:
<?php
$files = array();
foreach (glob("backup/*.txt") as $filename) {
$files[]= $filename;
}
$count = sizeof($files);
?>
Method II:
function getfoldercontents($dir, $file_display = array(), $exclusions = array()) {
if(!file_exists($dir)){
return FALSE;
}
$dir_contents = scandir($dir);
$contents = array();
foreach ($dir_contents as $file){
$file_parts = explode('.', $file);
$file_type = strtolower(end($file_parts));
if ($file !== '.' && $file !== '..' && in_array($file_type, $file_display) && !in_array($file, $exclusions)) {
$contents[] = $dir. '/'. $file;
}
}
return $contents;
}
Since glob() already returns an array, you don't need to iterate over it to append to an array at all. Your first method is a little over-complicated. This accomplishes the same thing:
// Just assign the array output of glob() to a variable
$files = glob("backup/*.txt");
$num_files = count($files);
I would say the second is probably better in terms of file-control (through $file_display and $file_exclude), but maybe you should add a check to ensure the current file is not a directory named something.typeyouwishtodisplay

Get folders and files recursively from a folder in alphabetical order in PHP?

I need to get all the folders and files from a folder recursively in alphabetical order (folders first, files after)
Is there an implemented PHP function which caters for this?
I have this function:
function dir_tree($dir) {
$path = '';
$stack[] = $dir;
while ($stack) {
$thisdir = array_pop($stack);
if ($dircont = scandir($thisdir)) {
$i=0;
while (isset($dircont[$i])) {
if ($dircont[$i] !== '.' && $dircont[$i] !== '..' && $dircont[$i] !== '.svn') {
$current_file = "{$thisdir}/{$dircont[$i]}";
if (is_file($current_file)) {
$path[] = "{$thisdir}/{$dircont[$i]}";
} elseif (is_dir($current_file)) {
$path[] = "{$thisdir}/{$dircont[$i]}";
$stack[] = $current_file;
}
}
$i++;
}
}
}
return $path;
}
I have sorted the array and printed it like so:
$filesArray = dir_tree("myDir");
natsort($filesArray);
foreach ($filesArray as $file) {
echo "$file<br/>";
}
What I need is to know when a new sub directory is found, so I can add some spaces to print it in a directory like structure instead of just a list.
Any help?
Many thanks
Look at the RecursiveDirectoryIterator.
$directory_iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir));
foreach($directory_iterator as $filename => $path_object)
{
echo $filename;
}
I'm not sure though if it returns the files in alphabetical order.
Edit:
As you say it does not, I think the only way is to sort them yourself.
I would loop through each directory and put directories and files in a seperate arrays, and then sort them, and then recurse in the directories.
I found a link which helped me a lot in what I was trying to achieve:
http://snippets.dzone.com/posts/show/1917
This might help someone else, it creates a list with folders first, files after. When you click on a subfolder, it submits and another page with the folders and files in the partent folder is generated.

Categories