The code below is part of a function for grabbing 5 image files from a given directory.
At the moment readdir returns the images 'in the order in which they are stored by the filesystem' as per the spec.
My question is, how can I modify it to get the latest 5 images? Either based on the last_modified date or the filename (which look like 0000009-16-5-2009.png, 0000012-17-5-2009.png, etc.).
if ( $handle = opendir($absolute_dir) )
{
$i = 0;
$image_array = array();
while ( count($image_array) < 5 && ( ($file = readdir($handle)) !== false) )
{
if ( $file != "." && $file != ".." && $file != ".svn" && $file != 'img' )
{
$image_array[$i]['url'] = $relative_dir . $file;
$image_array[$i]['last_modified'] = date ("F d Y H:i:s", filemtime($absolute_dir . '/' . $file));
}
$i++;
}
closedir($handle);
}
If you want to do this entirely in PHP, you must find all the files and their last modification times:
$images = array();
foreach (scandir($folder) as $node) {
$nodePath = $folder . DIRECTORY_SEPARATOR . $node;
if (is_dir($nodePath)) continue;
$images[$nodePath] = filemtime($nodePath);
}
arsort($images);
$newest = array_slice($images, 0, 5);
If you are really only interested in pictures you could use glob() instead of soulmerge's scandir:
$images = array();
foreach (glob("*.{png,jpg,jpeg}", GLOB_BRACE) as $filename) {
$images[$filename] = filemtime($filename);
}
arsort($images);
$newest = array_slice($images, 0, 5);
Or you can create function for the latest 5 files in specified folder.
private function getlatestfivefiles() {
$files = array();
foreach (glob("application/reports/*.*", GLOB_BRACE) as $filename) {
$files[$filename] = filemtime($filename);
}
arsort($files);
$newest = array_slice($files, 0, 5);
return $newest;
}
btw im using CI framework. cheers!
Related
I want to keep only 10 newest files in a folder and delete others.
I created a script that deletes only the oldest ones if a file number is larger than 10.
How can I adapt this script to my needs?
$directory = "/home/dir";
// Returns array of files
$files = scandir($directory);
// Count number of files and store them to variable..
$num_files = count($files)-2;
if($num_files>10){
$smallest_time=INF;
$oldest_file='';
if ($handle = opendir($directory)) {
while (false !== ($file = readdir($handle))) {
$time=filemtime($directory.'/'.$file);
if (is_file($directory.'/'.$file)) {
if ($time < $smallest_time) {
$oldest_file = $file;
$smallest_time = $time;
}
}
}
closedir($handle);
}
echo $oldest_file;
unlink($oldest_file);
}
Basic script to give you the idea. Push all the files with their times into an array, sort it by descending time order and walk trough. if($count > 10) says when the deletion should start, i.e. currently it keeps the newest 10.
<?php
$directory = ".";
$files = array();
foreach(scandir($directory) as $file){
if(is_file($file)) {
//get all the files
$files[$file] = filemtime($file);
}
}
//sort descending by filemtime;
arsort($files);
$count = 1;
foreach ($files as $file => $time){
if($count > 10){
unlink($file);
}
$count++;
}
You could simply sort the result of scandir by the returned files' modification dates:
/**
* #return string[]
*/
function getOldestFiles(string $folderPath, int $count): array
{
// Grab all the filenames
$filenames = #scandir($folderPath);
if ($filenames === false) {
throw new InvalidArgumentException("{$folderPath} is not a valid folder.");
}
// Ignore folders (remove from array)
$filenames = array_filter($filenames, static function (string $filename) use ($folderPath) {
return is_file($folderPath . DIRECTORY_SEPARATOR . $filename);
});
// Sort by ascending last modification date (older first)
usort($filenames, static function (string $file1Name, string $file2Name) use ($folderPath) {
return filemtime($folderPath . DIRECTORY_SEPARATOR . $file1Name) <=> filemtime($folderPath . DIRECTORY_SEPARATOR . $file2Name);
});
// Return the first $count
return array_slice($filenames, 0, $count);
}
Usage:
$folder = '/some/folder';
$oldestFiles = getOldestFiles($folder, 10);
foreach ($oldestFiles as $file) {
unlink($folder . '/' . $file);
}
Note: this is obviously over-commented for the purpose of this answer.
$dir = '/master/files';
$files = scandir($dir);
foreach($files as $file){
if(($file != '.') && ($file != '..')){
if(is_dir($dir.'/'.$file)){
echo '<li class="folder">'.$file.'</li>';
}else{
echo '<li class="file">'.$file.'</li>';
}
}
}
From the script above, I get result:
images (folder)
index.html
javascript (folder)
style.css
How to sort the folder first and then files?
Try this :
$dir = '/master/files';
$directories = array();
$files_list = array();
$files = scandir($dir);
foreach($files as $file){
if(($file != '.') && ($file != '..')){
if(is_dir($dir.'/'.$file)){
$directories[] = $file;
}else{
$files_list[] = $file;
}
}
}
foreach($directories as $directory){
echo '<li class="folder">'.$directory.'</li>';
}
foreach($files_list as $file_list){
echo '<li class="file">'.$file_list.'</li>';
}
You don't need to make 2 loops, you can do the job with this piece of code:
<?php
function scandirSorted($path) {
$sortedData = array();
foreach(scandir($path) as $file) {
// Skip the . and .. folder
if($file == '.' || $file == '..')
continue;
if(is_file($path . $file)) {
// Add entry at the end of the array
array_push($sortedData, '<li class="folder">' . $file . '</li>');
} else {
// Add entry at the begin of the array
array_unshift($sortedData, '<li class="file">' . $file . '</li>');
}
}
return $sortedData;
}
?>
This function will return the list of entries of your path, folders first, then files.
Modifying your code as little as possible:
$folder_list = "";
$file_list = "";
$dir = '/master/files';
$files = scandir($dir);
foreach($files as $file){
if(($file != '.') && ($file != '..')){
if(is_dir($dir.'/'.$file)){
$folder_list .= '<li class="folder">'.$file.'</li>';
}else{
$file_list .= '<li class="file">'.$file.'</li>';
}
}
}
print $folder_list;
print $file_list;
This only loops through everything once, rather than requiring multiple passes.
I have a solution for N deep recursive directory scan:
function scanRecursively($dir = "/") {
$scan = array_diff(scandir($dir), array('.', '..'));
$tree = array();
$queue = array();
foreach ( $scan as $item )
if ( is_file($item) ) $queue[] = $item;
else $tree[] = scanRecursively($dir . '/' . $item);
return array_merge($tree, $queue);
}
Store the output in 2 arrays, then iterate through the arrays to output them in the right order.
$dir = '/master/files';
$contents = scandir($dir);
// create blank arrays to store folders and files
$folders = $files = array();
foreach ($contents as $file) {
if (($file != '.') && ($file != '..')) {
if (is_dir($dir.'/'.$file)) {
// add to folders array
$folders[] = $file;
} else {
// add to files array
$files[] = $file;
}
}
}
// output folders
foreach ($folders as $folder) {
echo '<li class="folder">' . $folder . '</li>';
}
// output files
foreach ($files as $file) {
echo '<li class="file">' . $file . '</li>';
}
Being a CodeIgniter lover, I have in fact modified the core directory_helper from that to include the ability to have certain files exempt from the scanning in addition to setting the depth and choosing if hidden files should be included.
All credit goes to the original authors of CI. I simply added to it
with the exempt array and building in the sorting.
It uses ksort to order the folders, as the folder name is set as the key and natsort to order the files inside each folder.
The only thing you may need to do is define what the DIRECTORY_SEPARATOR is for your environment but I don't think you will need to modify much else.
function directory_map($source_dir, $directory_depth = 0, $hidden = FALSE, $exempt = array())
{
if ($fp = #opendir($source_dir))
{
$folddata = array();
$filedata = array();
$new_depth = $directory_depth - 1;
$source_dir = rtrim($source_dir, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR;
while (FALSE !== ($file = readdir($fp)))
{
// Remove '.', '..', and hidden files [optional]
if ($file === '.' OR $file === '..' OR ($hidden === FALSE && $file[0] === '.'))
{
continue;
}
is_dir($source_dir.$file) && $file .= DIRECTORY_SEPARATOR;
if (($directory_depth < 1 OR $new_depth > 0) && is_dir($source_dir.$file))
{
$folddata[$file] = directory_map($source_dir.$file, $new_depth, $hidden, $exempt);
}
elseif(empty($exempt) || !empty($exempt) && !in_array($file, $exempt))
{
$filedata[] = $file;
}
}
!empty($folddata) ? ksort($folddata) : false;
!empty($filedata) ? natsort($filedata) : false;
closedir($fp);
return array_merge($folddata, $filedata);
}
return FALSE;
}
Usage example would be:
$filelist = directory_map('full_server_path');
As mentioned above, it will set the folder name as the key for the child array, so you can expect something along the lines of the following:
Array(
[documents/] => Array(
[0] => 'document_a.pdf',
[1] => 'document_b.pdf'
),
[images/] => Array(
[tn/] = Array(
[0] => 'picture_a.jpg',
[1] => 'picture_b.jpg'
),
[0] => 'picture_a.jpg',
[1] => 'picture_b.jpg'
),
[0] => 'file_a.jpg',
[1] => 'file_b.jpg'
);
Just keep in mind that the exempt will be applied to all folders. This is handy if you want to skip out a index.html file or other file that is used in the directories you don't want included.
Try
<?php
$path = $_SERVER['DOCUMENT_ROOT'];
chdir ($path);
$dir = array_diff (scandir ('.'), array ('.', '..', '.DS_Store', 'Thumbs.db'));
usort ($dir, create_function ('$a,$b', '
return is_dir ($a)
? (is_dir ($b) ? strnatcasecmp ($a, $b) : -1)
: (is_dir ($b) ? 1 : (
strcasecmp (pathinfo ($a, PATHINFO_EXTENSION), pathinfo ($b, PATHINFO_EXTENSION)) == 0
? strnatcasecmp ($a, $b)
: strcasecmp (pathinfo ($a, PATHINFO_EXTENSION), pathinfo ($b, PATHINFO_EXTENSION))
))
;
'));
header ('content-type: text/plain');
print_r ($dir);
?>
public function sortedScanDir($dir) {
// scan the current folder
$content = scandir($dir);
// create arrays
$folders = [];
$files = [];
// loop through
foreach ($content as $file) {
$fileName = $dir . '/' . $file;
if (is_dir($fileName)) {
$folders[] = $file;
} else {
$files[] = $file;
}
}
// combine
return array_merge($folders, $files);
}
I'm trying to make scandir(); function go beyond its written limits, I need more than the alpha sorting it currently supports. I need to sort the scandir(); results to be sorted by modification date.
I've tried a few solutions I found here and some other solutions from different websites, but none worked for me, so I think it's reasonable for me to post here.
What I've tried so far is this:
function scan_dir($dir)
{
$files_array = scandir($dir);
$img_array = array();
$img_dsort = array();
$final_array = array();
foreach($files_array as $file)
{
if(($file != ".") && ($file != "..") && ($file != ".svn") && ($file != ".htaccess"))
{
$img_array[] = $file;
$img_dsort[] = filemtime($dir . '/' . $file);
}
}
$merge_arrays = array_combine($img_dsort, $img_array);
krsort($merge_arrays);
foreach($merge_arrays as $key => $value)
{
$final_array[] = $value;
}
return (is_array($final_array)) ? $final_array : false;
}
But, this doesn't seem to work for me, it returns 3 results only, but it should return 16 results, because there are 16 images in the folder.
function scan_dir($dir) {
$ignored = array('.', '..', '.svn', '.htaccess');
$files = array();
foreach (scandir($dir) as $file) {
if (in_array($file, $ignored)) continue;
$files[$file] = filemtime($dir . '/' . $file);
}
arsort($files);
$files = array_keys($files);
return ($files) ? $files : false;
}
This is a great question and Ryon Sherman’s answer provides a solid answer, but I needed a bit more flexibility for my needs so I created this newer function: better_scandir.
The goal is to allow having scandir sorting order flags work as expected; not just the reverse array sort method in Ryon’s answer. And also explicitly setting SORT_NUMERIC for the array sort since those time values are clearly numbers.
Usage is like this; just switch out SCANDIR_SORT_DESCENDING to SCANDIR_SORT_ASCENDING or even leave it empty for default:
better_scandir(<filepath goes here>, SCANDIR_SORT_DESCENDING);
And here is the function itself:
function better_scandir($dir, $sorting_order = SCANDIR_SORT_ASCENDING) {
/****************************************************************************/
// Roll through the scandir values.
$files = array();
foreach (scandir($dir, $sorting_order) as $file) {
if ($file[0] === '.') {
continue;
}
$files[$file] = filemtime($dir . '/' . $file);
} // foreach
/****************************************************************************/
// Sort the files array.
if ($sorting_order == SCANDIR_SORT_ASCENDING) {
asort($files, SORT_NUMERIC);
}
else {
arsort($files, SORT_NUMERIC);
}
/****************************************************************************/
// Set the final return value.
$ret = array_keys($files);
/****************************************************************************/
// Return the final value.
return ($ret) ? $ret : false;
} // better_scandir
Alternative example..
$dir = "/home/novayear/public_html/backups";
chdir($dir);
array_multisort(array_map('filemtime', ($files = glob("*.{sql,php,7z}", GLOB_BRACE))), SORT_DESC, $files);
foreach($files as $filename)
{
echo "<a>".substr($filename, 0, -4)."</a><br>";
}
Another scandir keep latest 5 files:
public function checkmaxfiles()
{
$dir = APPLICATION_PATH . '\\modules\\yourmodulename\\public\\backup\\';
// '../notes/';
$ignored = array('.', '..', '.svn', '.htaccess');
$files = array();
foreach (scandir($dir) as $file) {
if (in_array($file, $ignored)) continue;
$files[$file] = filemtime($dir . '/' . $file);
}
arsort($files);
$files = array_keys($files);
$length = count($files);
if($length < 4 ){
return;
}
for ($i = $length; $i > 4; $i--) {
echo "Erase : " .$dir.$files[$i];
unlink($dir.$files[$i]);
}
}
How get first modified file?
So far I've had:
$dir = $path.'/';
$firstMod = '';
$p = 1;
foreach (scandir($dir) as $file) {
if (is_file($dir.$file) && ... ) {
if($p == 1) break;
}
}
If I would like to get last file but not last modified but last in an folder order what should I use?
you can try to write all the files in the array make key as the date modified than reorder the array based on date and get the first element of array which would first modified.
<?php
$folder = "files/";
$handle = opendir($folder);
$files=array();
while ($file = readdir($handle)){
if( $file != ".." && $file != "." ){
$key = filemtime($file);
$files[$key] = $file ;
}
}
closedir($handle);
ksort($files) ;
echo reset($files);
?>
I'd like to randomly load images from a directory and have a button somewhere that refreshes the entire page. Here's the current code I have now:
<?php
$a = array();
$dir = '../public/wp-content/uploads/2012/01';
if ($handle = opendir($dir)) {
while (false !== ($file = readdir($handle))) {
if (preg_match("/\.png$/", $file)) $a[] = $file;
elseif (preg_match("/\.jpg$/", $file)) $a[] = $file;
elseif (preg_match("/\.jpeg$/", $file)) $a[] = $file;
}
closedir($handle);
}
foreach ($a as $i) {
echo "<img src='" . $dir . '/' . $i . "' />";
}
?>
The problem is it loads all 400,000 images at once. I only want 30 to load. 30 random images from the directory. I tried looking up some code such as modifying the above to this:
<?php
$a = array();
$dir = '../public/wp-content/uploads/2012/01';
if ($handle = opendir($dir)) {
while (false !== ($file = readdir($handle))) {
if (preg_match("/\.png$/", $file)) $a[] = $file;
elseif (preg_match("/\.jpg$/", $file)) $a[] = $file;
elseif (preg_match("/\.jpeg$/", $file)) $a[] = $file;
}
closedir($handle);
}
foreach ($a as $i) {
echo "<img src='" . $dir . '/' . $i . "' />";
if (++$i == 2) break;
}
?>
But it seems to do absolutely nothing.. So if someone can help me get 30 random photos from that directory to load and have some type of reload button, that would be of great help.
Thank you in advance
Here is my solution with a cache:
<?php
define('CACHE_FILE', 'mycache.tmp');
define('CACHE_TIME', 20); // 20 seconds (for testing!)
define('IMG_COUNT', 30);
define('IMG_DIR', '../public/wp-content/uploads/2012/01');
/**
* Loads the list (an array) from the cache
* Returns FALSE if the file couldn't be opened or the cache was expired, otherwise the list (as an array) will be returned.
*/
function LoadListFromCache($cacheFile, $cacheTime)
{
if ( file_exists($cacheFile) )
{
$fileHandle = fopen($cacheFile, 'r');
if ( !$fileHandle )
return false;
// Read timestamp (separated by "\n" from the content)
$timestamp = intval( fgets($fileHandle) );
fclose($fileHandle);
// Expired?
if ( $timestamp+$cacheTime > time() )
return false;
else
{
// Unserialize the content!
$content = file_get_contents($cacheFile);
$content = substr( $content, strpos($content, "\n") );
$list = unserialize($content);
return $list;
}
}
return false;
}
/**
* Caches the passed array
* Returns FALSE if the file couldn't be opened, otherwise TRUE.
*/
function SaveListToCache($cacheFile, $list)
{
$fileHandle = fopen($cacheFile, 'w');
if ( $fileHandle === FALSE ) return false;
fwrite($fileHandle, time());
fwrite($fileHandle, "\n");
fwrite($fileHandle, serialize($list));
fclose($fileHandle);
return true;
}
/**
* Generates the list of all image files (png, jpg, jpeg) and caches it.
* Returns the list as an array.
*/
function GenerateList()
{
$a = array();
$dir = IMG_DIR;
if ($handle = opendir($dir))
{
while (false !== ($file = readdir($handle)))
{
if (preg_match("/\.png$/", $file)) $a[] = $file;
elseif (preg_match("/\.jpg$/", $file)) $a[] = $file;
elseif (preg_match("/\.jpeg$/", $file)) $a[] = $file;
}
closedir($handle);
}
SaveListToCache(CACHE_FILE, $a);
return $a;
}
function GetRandomImages($list, $count)
{
$listCount = count($list);
$randomEntries = array();
for ($i=0; $i<$count; $i++)
{
$randomEntries[] = $list[ rand(0, $listCount) ];
}
return $randomEntries;
}
// This code will execute the other functions!
$list = LoadListFromCache(CACHE_FILE, CACHE_TIME);
if ( $list === FALSE )
{
$list = GenerateList();
}
$images = GetRandomImages($list, IMG_COUNT);
foreach ($images as $image)
{
echo '<img src="', IMG_DIR.DIRECTORY_SEPARATOR.$image, '" />';
}
If you have 400,000 images then I think reading the entire directory everytime is going to be an expensive means of showing random images. I would use a database instead and store the file paths in it.
If you want to use your existing code then think of it this way. You have an array of length n containing image names. You want to generate thirty random numbers between 0 and n-1. Then display the image associated with that position in the array. I'm not a php expert, but here is some pseudocode:
$a = array();
$dir = '../public/wp-content/uploads/2012/01';
if (preg_match("/\.png$/", $file)) $a[] = $file;
elseif (preg_match("/\.jpg$/", $file)) $a[] = $file;
elseif (preg_match("/\.jpeg$/", $file)) $a[] = $file;
for ( i=0; i < 30; i++) {
//generate a random number between 0 and N-1
random = rand(0, $a.length - 1);
//display that image in the array
echo "<img src='" . $dir . '/' . $a[random] . "' />";
}
You need to create a new variable for the counter instead of using $i
For example, you can do this instead
$j = 0;
foreach ($a as $i) {
echo "<img src='" . $dir . '/' . $i . "' />";
$j++;
if ($j >= 30)
{
break;
}
}
EDIT: Perhaps for the random part you can first generate a random number between 0 and n-1 where n is the total number of the images and then just echo out the image from the array with the index number.
Instead of using foreach, I think you'll need a for loop instead.
$totalImgs = count($a);
$imgUsed = array();
for ($j = 0; $j < 30; $j++)
{
do
{
$randIndex = mt_rand(0, $totalImgs);
}
while ($imgUsed[$randIndex] === TRUE);
$imgUsed[$randIndex] = TRUE;
echo "<img src='" . $dir . '/' . $a[$randIndex] . "' />";
}
You should maybe only read 30 file from your directory. Stop looking in the directory when readdir return false or your array's length is 30.
This should work
$a = array();
$dir = '../public/wp-content/uploads/2012/01';
if ($handle = opendir($dir)) {
while (false !== ($file = readdir($handle)) && (count($a) <= 30) {
if (preg_match("/\.png$/", $file)) $a[] = $file;
elseif (preg_match("/\.jpg$/", $file)) $a[] = $file;
elseif (preg_match("/\.jpeg$/", $file)) $a[] = $file;
}
closedir($handle);
}
It may not execute (I didn't try). But the idea is here
For randomize the image: shuffle($a) should do the trick
in simplest way ,
you can use
find , sort , head
commands in linux,in conjuction with PHP's built in
exec()
function to get 30 random image links easily , the folowing snippet lists how to do it
(How to get random 30 image links in an array.)
<?php
$picdir = "directory/containing/pictures"; // directory containing only pictures
exec("find " . $picdir . " | sort -R | head -30 ",$links);
while(list($index,$val) = each($links) ) {
echo "<img src =" .$val . "> <br/>"; // shows image
}
?>
Here $links array contain random 30 image names(from folder) with complete path . This is used with img tag in echo to generate images
Here $picdir has the path of the directory having images and it is assumed that dirrectory is having only image files . in other case its only matter of modifying find command to exclude non image files(such as using grep command to exclude )