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
Related
I'm trying to make an image gallery that scans a main directory and creates a separate album for each subdirectory.
My structure is similar to this:
-Gallery
--Subdir 1
---Image 1
---Image 2
--Subdir 2
---Image 1
---Image 2
The idea is that each album is going to be made of a div with a class of web-gallery. Then there will be a header for the album title made from the subdirectories name. After that a list is generated of each image. This is going to be a one page gallery. If possible I would like to have a variable that sets how many albums are listed that way if I have 30 subdirectories my page doesn't get too crowded.
So far I've written this but it doesn't work. I'm not getting any errors or logs though it just doesn't work.
$dirs = glob('img/gallery_temp/*', GLOB_ONLYDIR);
foreach($dirs as $val) {
echo '<div class="web-gallery">';
echo "<h3><span>»</span> ".basename($val). "</h3>";
echo '<ul class="web-gallery-list">';
$files = glob($val.'*.{jpg,png,gif}', GLOB_BRACE);
foreach($files as $file) {
echo "<li><a href='".$file."'><img src='" . $file . "' alt='description'></a></li> \r\n";
}
echo "</ul>";
echo "</div>";
}
Simply add a / before *.{jpg,png,gif} like this:
$files = glob($val.'/*.{jpg,png,gif}', GLOB_BRACE);
This is because $val doesn't have a final / for the directory.
You might consider using "readdir" instead of glob. Glob is to find pathnames matching a pattern, see here: http://php.net/manual/en/function.glob.php and is known to be a bit problematic.
Readdir, if your directory is entirely images might be easier to use: http://php.net/manual/en/function.readdir.php
Couple this with is_dir() http://php.net/manual/en/function.is-dir.php to resolve your directories vs files. Here is a snippet
<?php
if ($handle = opendir('/galleries')) {
while (false !== ($entry = readdir($handle))) {
// this is a subdirectory
if (is_dir($entry)) {
}
// this is a file
else {
echo $entry;
}
}
closedir($handle);
}
?>
If you make it a recursive function you could actually have it traverse a number of subdirectories creating galleries within galleries.
I also found this fantastic little snippet that is very elegant on another stack question: Get Images In Directory and Subdirectory With Glob
$rdi = new RecursiveDirectoryIterator("uploads/prevImgs/");
$it = new RecursiveIteratorIterator($rdi);
foreach($it as $oneThing)
if (is_file($oneThing))
echo '<img src="'.$oneThing.'" /><br />';
Using SPL Library (PHP >= 5)
Better solution in your case
is to use SPL library (the most cross-platform)
$directory = new RecursiveDirectoryIterator("./img/gallery_temp", FilesystemIterator::SKIP_DOTS);
// Flatten the recursive iterator, folders come before their files
$it = new RecursiveIteratorIterator($directory, RecursiveIteratorIterator::SELF_FIRST);
foreach($it as $fileinfo)
{
if($fileinfo->isDir())
{
// prevPath used to separate each directory listing and closing the bracket UL list
$prevPath = $it->getSubPath().DIRECTORY_SEPARATOR.$fileinfo->getFilename();
echo sprintf
(
"<div class='web-gallery'>
<h3><span>></span> %s</h3>
<ul>".PHP_EOL,
$fileinfo->getFilename()
);
}
if($fileinfo->isFile())
{
echo sprintf("<li><a href=''><img src='%s/%s' alt='description'></a></li>".PHP_EOL, $it->getSubPath(), $fileinfo->getFilename());
if($prevPath != $it->getSubPath())
echo("</ul>");
}
}
Note:
For more informations : SPL Documentation
DIRECTORY_SEPARATOR is a cross-platform constant, will use the
correct directory separator of the OS where are executed the code
FilesystemIterator::SKIP_DOTS, avoid to fetch the '.' and '..' dir
link level.
you can limit the depth of scanning with $it->setMaxDepth(5);
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.
I'm in the process of creating an experiment for a psychology researcher, there are various tasks involved which the participant has to complete. I have been asked to randomise the order of the folders, but keep the order of files inside the directories the same.
I have looked at using glob() but I think I'm implementing it wrong.
My directory looks like this:
Clock
>>Task 1/Files.>>Task 2/Files.>>Task 3/Files.
At the moment I have this:
<?php
function random_folders($dir = 'Clock')
{
$folder = glob($dir. '/Task.*');
$folderRand = array_rand($folder);
return $folder[$folderRand];
}
echo random_folders();
?>
I've tried googling and using stackoverflow to search for a solution but I can't seem to find one, any help will be greatly appreciated.
Edit
I should mention that I'm using HTML5, JavaScript, PHP and MySQL to create the website, if that's relevant.
Thanks.
Your glob() is a little off. You don't want the . before the *. Then use shuffle() to shuffle them:
function random_folders($dir = 'Clock')
{
$folder = glob($dir. '/Task*');
shuffle($folder);
// Returns the randomized array of folders
return $folder;
}
$random_folders = random_folders();
// List them and their contents:
foreach ($random_folders as $rf) {
echo "Folder: $rf\n";
// List files in ascending order
$files = scandir($rf, SCANDIR_SORT_ASCENDING);
foreach ($files as $f) {
if ($file !== "." && $file !== "..") {
echo $file . "\n";
}
}
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.
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