Filename reading on PHP - php

Im trying to read filenames from a directory using the code and adding a filter to readonly files with the current year and month in the filename for example
Julius Robles_Client11_20130508_10-42-42_AM.zip
Julius Robles_Client12_20130508_11-45-42_AM.zip
Julius Robles_Client13_20130508_11-58-42_AM.zip
so the code will only return files with 201305 in their names but it returns a correct filtered set but some files are missing and i dont know why?
also what is the file "." and ".." stored in the first 2 rows of the array?
heres the code
$filenames = array();
if ($handle = opendir('archive/search_logs/')) {
$ctr = 0;
while (false !== ($entry = readdir($handle))) {
//if(strpos($entry,date('Ym')) !== false){
$name = $entry;
$entry = str_replace("-",":",$entry);
$filenames[$ctr] = explode("_", $entry);
$filenames[$ctr][] = $name;
$ctr++;
//}
}
closedir($handle);
}

why scandir? Use DirectoryIterator don't be affraid to use modern PHP
from manual:
The DirectoryIterator class provides a simple interface for viewing the contents of filesystem directories.
example:
<?php
$filenames = array();
$iterator = new DirectoryIterator($directory);
foreach ($iterator as $fileinfo) {
if ($fileinfo->isFile()) {
$filenames[] = $fileinfo->getFilename();
}
}
print_r($filenames);
?>
with DirectoryIterator you can check $fileInfo via this methods:
DirectoryIterator::isDir — Determine if current DirectoryIterator item is a directory
DirectoryIterator::isDot — Determine if current DirectoryIterator item is '.' or '..'
DirectoryIterator::isExecutable — Determine if current DirectoryIterator item is executable
DirectoryIterator::isFile — Determine if current DirectoryIterator item is a regular file
DirectoryIterator::isLink — Determine if current DirectoryIterator item is a symbolic link
DirectoryIterator::isReadable — Determine if current DirectoryIterator item can be read
DirectoryIterator::isWritable — Determine if current DirectoryIterator item can be written

As said in the comment:
. is the current directory
.. is the directory above this one
You can skip both for reading the directory.
What I would personally do is read the filename and information based on a regular expression like so:
$filenames = array();
foreach( scandir( 'archive/search_logs/' ) as $file ) {
// test for current or higher path
if( $file == "." || $file == ".." ) continue;
// test if readable
if( !is_readable( $file )) printf( "File %s is not readable",$file);
// use regular expression to match: <filename><date:yyyymmdd>_<hours:hh>-<minutes:mm>-<seconds:ss>_<am|pm>.zip
preg_match( "/(?<filename>[.]+)(?<date>[0-9]{8})\_(?<hours>[0-9]{2})\-(?<minutes>[0-9]{2})\-(?<seconds>[0-9]{2})\_(?<ampm>AM|PM)\.zip$/i" , $file , $matches );
// now use anything in matches that suits your needs:
echo $matches['filename'];
echo $matches['date'];
echo $matches['hours'];
echo $matches['seconds'];
echo $matches['ampm'];
}
This is untested and I might have overdone escaping the regular expression - and _.
As to your question why the file might not be available, I guess that question belongs to Server Fault. However you can test files for their rights with fileperms(http://www.php.net/manual/en/function.fileperms.php), however that will not provide results when they aren't readable in the first place.

Related

PHP recursively search through subfolders for filenames containing a certain substring and rename that substring with another string

I have a cache folder that contains many other folders. In each of these folders are images (each folder contains a series of images for a particular product). The filenames are in the format something like:
image1-1200x1800.jpg
image1-40x40.jpg
image1-480x600w.jpg
For this example, I'm trying to search through the subfolders of the cache directory and rename all the substrings "image1" that are found in the filenames to "great", so the above would be renamed to:
great-1200x1800.jpg
great-40x40.jpg
great-480x600w.jpg
I have the following but the path to the files is unknown - it could be in any subfolder of the cache directory":
<?php
if ($handle = opendir('/path/to/files')) {
while (false !== ($fileName = readdir($handle))) {
$newName = str_replace("image1","great",$fileName);
rename($fileName, $newName);
}
closedir($handle);
}
?>
Any help would be much appreciated thanks.
Recursion means you have function calling itself. You could make a function which calls itself with the pathname if the current "file" is a directory.
But fortunately you can use a recursive iterator which is included in PHP.
$search = 'image1';
$replace = 'great';
$path = '/path/to/files/';
$iterator = new RecursiveDirectoryIterator($path);
foreach(new RecursiveIteratorIterator($iterator) as $file) {
if ($file->isFile() && str_contains($file->getFilename(), $search)) {
rename($file->getPathName(), str_replace($search, $replace, $file->getPathName()));
}
}
Note: str_contains() works with PHP8+
For prior versions use
false !== strpos($file->getFilename(), $search)

Optimising php file reading code

I have the following which is fairly slow. How can I speed it up?
(it scans a directory and makes headers out of the foldernames and retrieves the pdf files from within and adds them to lists)
$directories= array_diff(scandir("../pdfArchive/subfolder", 0), array('..', '.'));
foreach ($directories as $v) {
echo "<h3>".$v."</h3>";
$current = array_diff(scandir("../pdfArchive/subfolder/".$v, 0), array('..', '.'));
echo "<ul style=\"list-style-image: url(/images/pdf.gif); margin-left: 20px;\">";
foreach ($current as $vone) {
echo "<li><a target=\"blank\" href=\"../pdfArchive/subfolder/".$vone."\">".str_replace(".pdf", "", $vone)."</a>";
echo "</li><br>";
}
echo "</ul>";
}
Don't use array_diff() to filter out current and parent directory, use something like DirectoryIterator or glob() and then test whether it's . or .. via an if statement
glob() has a flag that allows you to retrieve only directories for your loops
Profile your code to see exactly what lines/functions are executing slowly
I'm not sure how fast array_diff() is when the array is very large, isn't it faster to simply add a separate check and make sure that '.' and '..' is not the returned name?
Other than that, I can't see there being anything really wrong.
What did you test to consider the current approach slow?
Here is a snippet of code I use that I adapted from php.net. It is very basic and goes through a given directory and lists the files contained within.
// The # suppresses any errors, $dir is the directory path
if (($handle = #opendir($dir)) != FALSE) {
// Loop over directory contents
while (($file = readdir($handle)) !== FALSE) {
// We don't want the current directory (.) or parent (..)
if ($file != "." && $file != "..") {
var_dump($file);
if (!is_dir($dir . $file)) {
// $file is really a file
} else {
// $file is a directory
}
}
}
closedir($handle);
} else {
// Deal with it
}
You may adapt this further to recurse over subdirectories by using is_dir to identify folders as I have shown above.

Is there an easy way to read filenames in a directory and add to an array?

I have a directory: Audio/ and in that will be mp3 files only. I'm wanting to automate the process of creating links to those files. Is there a way to read a directory and add filenames within that directory to an array?
It'd be doubly cool if we could do an associative array, and have the key be the file name minus the .mp3 tag.
Any ideas?
To elaborate: I actual have several Audio/ folders and each folder contains mp3s of a different event. The event details are being pulled from a database and populating a table. That's why I'm duplicating code, because right now in each Audio/ folder, I'm having to define the filenames for the download links and define the filenames for the mp3 player.
Thank you! This will greatly simplify my code as right now I'm repeating tons of code over and over!
The SPL way is with DirectoryIterator:
$files = array();
foreach (new DirectoryIterator('/path/to/files/') as $fileInfo) {
if($fileInfo->isDot() || !$fileInfo->isFile()) continue;
$files[] = $fileInfo->getFilename();
}
And for completeness : you could use glob as well :
$files = array_filter(glob('/path/to/files/*'), 'is_file');
This will return all files (but not the folders), you can adapt it as needed.
To get just the filenames (instead of files with complete path), just add :
$files = array_map('basename', $files);
Yes: use scandir(). If you just want the name of the file without the extension, use basename() on each element in the array you received from scandir().
This should be able to do what you're looking for:
// Read files
$files = scandir($dirName);
// Filter out non-files ('.' or '..')
$files = array_filter($files, 'is_file');
// Create associative array ('filename' => 'filename.mp3')
$files = array_combine(array_map('basename', $files), $files);
Sure...I think this should work...
$files[] = array();
$dir = opendir("/path/to/Audio") or die("Unable to open folder");
while ($file = readdir($dir)) {
$cleanfile = basename($file);
$files[$cleanfile] = $file;
}
closedir($dir);
I imagine that should work...
$results = array();
$handler = opendir($directory);
while ($file = readdir($handler)) {
if ($file != "." && $file != "..") {
$results[] = $file;
}
}
closedir($handler);
this should work, if you want any files to be excluded from the array, just add them to the if statement, same for file extensions

auto display filename in a directory and auto fill it as value

I want something (final) like this :
<?php
//named as config.php
$fn[0]["long"] = "file name"; $fn[0]["short"] = "file-name.txt";
$fn[1]["long"] = "file name 1"; $fn[1]["short"] = "file-name_1.txt";
?>
What that I want to?:
1. $fn[0], $fn[1], etc.., as auto increasing
2. "file-name.txt", "file-name_1.txt", etc.., as file name from a directory, i want it auto insert.
3. "file name", "file name 1", etc.., is auto split from "file-name.txt", "file-name_1.txt", etc..,
and config.php above needed in another file e.g.
<? //named as form.php
include "config.php";
for($tint = 0;isset($text_index[$tint]);$tint++)
{
if($allok === TRUE && $tint === $index) echo("<option VALUE=\"" . $text_index[$tint]["short"] . "\" SELECTED>" . $text_index[$tint]["long"] . "</option>\n");
else echo("<option VALUE=\"" . $text_index[$tint]["short"] . "\">" . $text_index[$tint]["long"] . "</option>\n");
} ?>
so i try to search and put php code and hope it can handling at all :
e.g.
<?php
$path = ".";
$dh = opendir($path);
//$i=0;
$i= 1;
while (($file = readdir($dh)) !== false) {
if($file != "." && $file != "..") {
echo "\$fn[$i]['short'] = '$file'; $fn[$i]['long'] = '$file(splited)';<br />"; // Test
$i++;
}
}
closedir($dh);
?>
but i'm wrong, the output is not similar to what i want, e.g.
$fn[0]['short'] = 'file-name.txt'; ['long'] = 'file-name.txt'; //<--not splitted
$fn[1]['short'] = 'file-name_1.txt'; ['long'] = 'file-name_1.txt'; //<--not splitted
because i am little known with php so i don't know how to improve code more, there are any good tips of you guys could help me, Please
New answer after OP edited his question
From your edited question, I understand you want to dynamically populate a SelectBox element on an HTML webpage with the files found in a certain directory for option value. The values are supposed to be split by dash, underscore and number to provide the option name, e.g.
Directory with Files > SelectBox Options
filename1.txt > value: filename1.txt, text: Filename 1
file_name2.txt > value: filename1.txt, text: File Name 2
file-name3.txt > value: filename1.txt, text: File Name 3
Based from the code I gave in my other answer, you could achieve this with the DirectoryIterator like this:
$config = array();
$dir = new DirectoryIterator('.');
foreach($dir as $item) {
if($item->isFile()) {
$fileName = $item->getFilename();
// turn dashes and underscores to spaces
$longFileName = str_replace(array('-', '_'), ' ', $fileName);
// prefix numbers with space
$longFileName = preg_replace('/(\d+)/', ' $1', $fileName);
// add to array
$config[] = array('short' => $filename,
'long' => $longFilename);
}
}
However, since filenames in a directory are unique, you could also use this as an array:
$config[$filename] => $longFilename;
when building the config array. The short filename will form the key of the array then and then you can build your selectbox like this:
foreach($config as $short => $long)
{
printf( '<option value="%s">%s</option>' , $short, $long);
}
Alternatively, use the Iterator to just create an array of filenames and do the conversion to long file names when creating the Selectbox options, e.g. in the foreach loop above. In fact, you could build the entire SelectBox right from the iterator instead of building the array first, e.g.
$dir = new DirectoryIterator('.');
foreach($dir as $item) {
if($item->isFile()) {
$fileName = $item->getFilename();
$longFileName = str_replace(array('-', '_'), ' ', $fileName);
$longFileName = preg_replace('/(\d+)/', ' $1', $fileName);
printf( '<option value="%s">%s</option>' , $fileName, $longFileName);
}
}
Hope that's what your're looking for. I strongly suggest having a look at the chapter titled Language Reference in the PHP Manual if you got no or very little experience with PHP so far. There is also a free online book at http://www.tuxradar.com/practicalphp
Use this as the if condition to avoid the '..' from appearing in the result.
if($file != "." && $file != "..")
Change
if($file != "." ) {
to
if($file != "." and $file !== "..") {
and you get the behaviour you want.
If you read all the files from a linux environment you always get . and .. as files, which represent the current directory (.) and the parent directory (..). In your code you only ignore '.', while you also want to ignore '..'.
Edit:
If you want to print out what you wrote change the code in the inner loop to this:
if($file != "." ) {
echo "\$fn[\$i]['long'] = '$file'<br />"; // Test
$i++;
}
If you want to fill an array called $fn:
if($file != "." ) {
$fn[]['long'] = $file;
}
(You can remove the $i, because php auto increments arrays). Make sure you initialize $fn before the while loop:
$fn = array();
Have a look at the following functions:
glob — Find pathnames matching a pattern
scandir — List files and directories inside the specified path
DirectoryIterator — provides a simple interface for viewing the contents of filesystem directories
So, with the DirectoryIterator you simply would do:
$dir = new DirectoryIterator('.');
foreach($dir as $item) {
if($item->isFile()) {
echo $file;
}
}
Notice how every $item in $dir is an SplFileInfo instance and provides access to a number of useful other functions, e.g. isFile().
Doing a recursive directory traversal is equally easy. Just use a RecursiveDirectoryIterator with a RecursiveIteratorIterator and do:
$dir = new RecursiveIteratorIterator(new RecursiveDirectoryIterator('.'));
foreach($dir as $item) {
echo $file;
}
NOTE I am afraid I do not understand what the following line from your question is supposed to mean:
echo "$fn[$i]['long'] = '$file'<br />"; // Test
But with the functions and example code given above, you should be able to do everything you ever wanted to do with files inside directories.
I've had the same thing happen. I've just used array_shift() to trim off the top of the array
check out the documentation. http://ca.php.net/manual/en/function.array-shift.php

PHP (folder) File Listing in Alphabetical Order?

I'm not sure how simple this would be, but I'm using a script which displays the files from a specific folder, however I'd like them to be displayed in alphabetical order, would it be hard to do this? Here's the code I'm using:
if ($handle = opendir($mainframe->getCfg( 'absolute_path' ) ."/images/store/")) {
while (false !== ($file = readdir($handle))) {
if ($file != "." && $file != "..") {
if (($file != "index.html")&&($file != "index.php")&&($file != "Thumbs.db")) {
$strExt = end(explode(".", $file));
if ($strExt == 'jpg') {
$Link = 'index.php?option=com_shop&task=deleteFile&file[]='.$file;
$thelist .= '<tr class="row0"><td nowrap="nowrap">'.$file.'</td>'."\n";
$thelist .= '<td align="center" class="order"><img src="/administrator/images/publish_x.png" width="16" height="16" alt="delete"></td></tr>'."\n";
}
}
}
}
closedir($handle);
}
echo $thelist;
:)
Instead of using readdir you could simply use scandir (documentation) which sorts alphabetically by default.
The return value of scandir is an array instead of a string, so your code would have to be adjusted slightly, to iterate over the array instead of checking for the final null return value. Also, scandir takes a string with the directory path instead of a file handle as input, the new version would look something like this:
foreach(scandir($mainframe->getCfg( 'absolute_path' ) ."/images/store/") as $file) {
// rest of the loop could remain unchanged
}
That code looks pretty messy. You can separate the directory traversing logic with the presentation. A much more concise version (in my opinion):
<?php
// Head of page
$it = new DirectoryIterator($mainframe->getCfg('absolute_path') . '/images/store/'));
foreach ($it as $file) {
if (preg_match('#\.jpe?g$#', $file->getFilename()))
$files[] = $file->getFilename();
}
sort($files);
// Further down
foreach ($files as $file)
// display links to delete file.
?>
You don't even need to worry about opening or closing the handle, and since you're checking the filename with a regular expression, you don't need any of the explode or conditional checks.
I like Glob
It makes directory reading a snap as it returns an array that's easily sortable:
<?php
$files = glob("*.txt");
sort($files);
foreach ($files as $filename) {
echo "$filename size " . filesize($filename) . "\n";
}
?>
If you're using Joomla1.5 you should be using the defined constant JPATH_BASE instead of
$mainframe->getCfg( 'absolute_path' )
If this is a Joomla extension that you will distribute, don't use scandir() as it is PHP5 only.
The best thing to do is to use the Joomla API. It has a classes for directory and file access that is layered to do this over different networks and protocols. So the file system can be over FTP for example, and the classes can be extended for any network/protocol.
jimport( 'joomla.filesystem.folder' );
$files = JFolder::files(JPATH_BASE."/images/store/");
sort($files);
foreach($files as $file) {
// do your filtering and other task
}
You can also pass a regular expression as the second parameter to JFolder::files() that filters the files you receive.
You also don't want to use URL literals like /administrator/ since they can be changed.
use the JURI methods like:
JURI::base();
If you want to make sure of the Joomla CSS classes in the tables, for:
'<tr class="row0">'
use:
'<tr class="row'.($i&1).'">'
where $i is the number of iterations. This gives you a sequence of alternating 0s and 1s.
if we have PHP built in functions, always use it, they are faster.
use glob instead of traversing folders, if it fits for your needs.
$folder_names = array();
$folder_names = glob( '*', GLOB_ONLYDIR + GLOB_MARK + GLOB_NOSORT );
returs everything in the current directory, use chdir() before calling it
remove the GLOB_ONLYDIR to include files too ( . would be only files )
GLOB_MARK is for adding a slash to folders names
Remove GLOB_NOSORT not to sort the array

Categories