Random file from folder (no repetition) - php

So I'm using the following code to pull a random file from a folder and I would like to make it so there is never a chance of pulling up the current file again (ie: seeing the same image/document twice in a row).
How would I go about this? Thanks in advance!
function random_file($dir = 'destinations')
{
$files = glob($dir . '/*.*');
$file = array_rand($files);
return $files[$file];
}

Store the last viewed filename in a cookie or in the session.
Here's how to do it with a cookie:
function random_file($dir = 'destinations') {
$files = glob($dir . '/*.*');
if (!$files) return false;
$files = array_diff($files, array(#$_COOKIE['last_file']));
$file = array_rand($files);
setcookie('last_file', $files[$file]);
return $files[$file];
}

$picker = new FilePicker();
$picker->randomFile();
$picker->randomFile(); // never the same as the previous
--
class FilePicker
{
private $lastFile;
public function randomFile($dir = 'destinations')
{
$files = glob($dir . '/*.*');
do {
$file = array_rand($files);
} while ($this->lastFile == $file);
$this->lastFile = $file;
return $files[$file];
}
}

Essentially: store the name of every file used in an array; every time you pull a new name, check whether it already exists in the array.
in_array() will help you with that. array_push() will help fill the "used files" array.
You could make the array a static one to have the list available whenever you call the function (instead of using global variables).

If you want to present a fixed set of files in random order,
then read all file names into an array, shuffle the array
and then just use the array from start to end.

Related

PHP, need file to be overwritten or deleted after use

I have written a function to get a file from S3 and get the number of pages from it. (It's a PDF.) Everything works fine until I try and do the same for multiple files. Now it is just returning the number of pages of the last file. I think that the problem is that local.pdf needs to be deleted or overwritten, but I'm not sure how. I thought it would automatically overwrite it.
I tried using $pdf->cleanUp(); but that doesn't seem to do anything. It is still returning the page count of the last file.
This is the function that gets called for each child job:
public function getPageCountPDF($jobid) {
$this->load->library('Awss3', null, 'S3');
$PdfTranscriptInfo = $this->MJob->getDOCCSPdfTranscript($jobid);
$filename = $PdfTranscriptInfo['origfilename'];
$PdfFilename = 'uploads/' . $jobid . '/' . $filename;
$localfilename = FCPATH . 'tmp\local.pdf';
$this->S3->readfile($PdfFilename, false, 'bucket');
require_once 'application/libraries/fpdi/fpdf.php';
require_once 'application/libraries/fpdi/fpdi.php';
$pdf = new FPDI();
$pageCount = $pdf->setSourceFile($localfilename);
$pdf->cleanUp();
return $pageCount;
}
I am not getting any error messages, but the $pageCount should be 8 and then 6. The return I am getting from the function is 6 and 6.
I also tried adding
ob_clean();
flush();
But that clears the whole page, which I don't want.
I also tried using a generated name instead of local.pdf, but that doesn't work (I get a "cannot open file" message).
EDIT: This is the part that calls the function getPageCountPDF($jobid):
foreach ($copies as $copy) {
.
.
$CI = &get_instance();
$pageCount = $CI->getPageCountPDF($copy['jobid']);
.
.
}
What happens to $pageCount after
$pageCount = $CI->getPageCountPDF($copy['jobid']); ?
This gets overwritten after each iteration. Maybe you want something like $pageCount[$copy['jobid']] = $CI->getPageCountPDF($copy['jobid']); so you actually keep an array of $pageCounts to be processed further down the line.
function get_all_directory_files($directory_path) {
$scanned_directory = array_diff(scandir($directory_path), array(
'..',
'.'
));
return custom_shuffle($scanned_directory);
}
function custom_shuffle($my_array = array()) {
$copy = array();
while (count($my_array)) {
// takes a rand array elements by its key
$element = array_rand($my_array);
// assign the array and its value to an another array
$copy [$element] = $my_array [$element];
// delete the element from source array
unset($my_array [$element]);
}
return $copy;
}
So you can call get_all_directory_files which calls custom_shuffle
Then you can loop through while reading them. In case you want to delete the files
You can proceed with :
$arrayfiles = get_all_directory_files('mypath');
foreach ($arrayfiles as $filename) {
echo 'mypath/'.$filename; //You can do as you please with each file here
// unlink($filename); if you want to delete
}

Searching for a specific string from all PHP files in the parent directory

I am trying to figure out a way of searching through all of the *.php files inside the parent directory, parent directory example:
/content/themes/default/
I am not wanting to search through all of the files in the sub-directories. I am wanting to search for a string embedded in the PHP comment syntax, such as:
/* Name: default */
If the variable is found, then get the file name and/or path. I have tried googling this, and thinking of custom ways to do it, this is what I have attempted so far:
public function build_active_theme() {
$dir = CONTENT_DIR . 'themes/' . $this->get_active_theme() . '/';
$theme_files = array();
foreach(glob($dir . '*.php') as $file) {
$theme_files[] = $file;
}
$count = null;
foreach($theme_files as $file) {
$file_contents = file_get_contents($file);
$count++;
if(strpos($file_contents, 'Main')) {
$array_pos = $count;
$main_file = $theme_files[$array_pos];
echo $main_file;
}
}
}
So as you can see I added all the found files into an array, then got the content of each file, and search through it looking for the variable 'Main', if the variable was found, get the current auto-incremented number, and get the path from the array, however it was always telling me the wrong file, which had nothing close to 'Main'.
I believe CMS's such as Wordpress use a similar feature for plugin development, where it searches through all the files for the correct plugin details (which is what I want to make, but for themes).
Thanks,
Kieron
Like David said in his comment arrays are zero indexed in php. $count is being incremented ($count++) before being used as the index for $theme_files. Move $count++ to the end of the loop, And it will be incremented after the index look up.
public function build_active_theme() {
$dir = CONTENT_DIR . 'themes/' . $this->get_active_theme() . '/';
$theme_files = array();
foreach(glob($dir . '*.php') as $file) {
$theme_files[] = $file;
}
$count = null;
foreach($theme_files as $file) {
$file_contents = file_get_contents($file);
if(strpos($file_contents, 'Main')) {
$array_pos = $count;
$main_file = $theme_files[$array_pos];
echo $main_file;
}
$count++;
}
}

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);
}
}

How to get the newest file in a directory in php

So I have this app that processes CSV files. I have a line of code to load the file.
$myFile = "data/FrontlineSMS_Message_Export_20120721.csv"; //The name of the CSV file
$fh = fopen($myFile, 'r'); //Open the file
I would like to find a way in which I could look in the data directory and get the newest file (they all have date tags so they would be in order inside of data) and set the name equal to $myFile.
I really couldn't find and understand the documentation of php directories so any helpful resources would be appreciated as well. Thank you.
Here's an attempt using scandir, assuming the only files in the directory have timestamped filenames:
$files = scandir('data', SCANDIR_SORT_DESCENDING);
$newest_file = $files[0];
We first list all files in the directory in descending order, then, whichever one is first in that list has the "greatest" filename — and therefore the greatest timestamp value — and is therefore the newest.
Note that scandir was added in PHP 5, but its documentation page shows how to implement that behavior in PHP 4.
For a search with wildcard you can use:
<?php
$path = "/var/www/html/*";
$latest_ctime = 0;
$latest_filename = '';
$files = glob($path);
foreach($files as $file)
{
if (is_file($file) && filectime($file) > $latest_ctime)
{
$latest_ctime = filectime($file);
$latest_filename = $file;
}
}
return $latest_filename;
?>
My solution, improved solution from Max Hofmann:
$ret = [];
$dir = Yii::getAlias("#app") . "/web/uploads/problem-letters/{$this->id}"; // set directory in question
if(is_dir($dir)) {
$ret = array_diff(scandir($dir), array(".", "..")); // get all files in dir as array and remove . and .. from it
}
usort($ret, function ($a, $b) use ($dir) {
if(filectime($dir . "/" . $a) < filectime($dir . "/" . $b)) {
return -1;
} else if(filectime($dir . "/" . $a) == filectime($dir . "/" . $b)) {
return 0;
} else {
return 1;
}
}); // sort array by file creation time, older first
echo $ret[count($ret)-1]; // filename of last created file
Here's an example where I felt more confident in using my own validator rather than simply relying on a timestamp with scandir().
In this context, I want to check if my server has a more recent file version than the client's version. So I compare version numbers from the file names.
$clientAppVersion = "1.0.5";
$latestVersionFileName = "";
$directory = "../../download/updates/darwin/"
$arrayOfFiles = scandir($directory);
foreach ($arrayOfFiles as $file) {
if (is_file($directory . $file)) {
// Your custom code here... For example:
$serverFileVersion = getVersionNumberFromFileName($file);
if (isVersionNumberGreater($serverFileVersion, $clientAppVersion)) {
$latestVersionFileName = $file;
}
}
}
// function declarations in my php file (used in the forEach loop)
function getVersionNumberFromFileName($fileName) {
// extract the version number with regEx replacement
return preg_replace("/Finance D - Tenue de livres-darwin-(x64|arm64)-|\.zip/", "", $fileName);
}
function removeAllNonDigits($semanticVersionString) {
// use regex replacement to keep only numeric values in the semantic version string
return preg_replace("/\D+/", "", $semanticVersionString);
}
function isVersionNumberGreater($serverFileVersion, $clientFileVersion): bool {
// receives two semantic versions (1.0.4) and compares their numeric value (104)
// true when server version is greater than client version (105 > 104)
return removeAllNonDigits($serverFileVersion) > removeAllNonDigits($clientFileVersion);
}
Using this manual comparison instead of a timestamp I can achieve a more surgical result. I hope this can give you some useful ideas if you have a similar requirement.
(PS: I took time to post because I was not satisfied with the answers I found relating to the specific requirement I had. Please be kind I'm also not very used to StackOverflow - Thanks!)

PHP - Displaying random output more than once, prevent duplicate

I have the following code that I am using to randomly display PHP widgets from a folder:
<?php
function random_widget($dir = 'wp-content/themes/zonza/elements')
{
$files = glob($dir . '/*.*');
$file = array_rand($files);
return $files[$file];
}
?>
<?php include random_widget();?>
<?php include random_widget();?>
<?php include random_widget();?>
random_widget(); outputs a URL, which I then use in the include function to display the widget.
The code randomly chooses between 6 php files and displays one randomly. I include it 3 times to get 3 widgets. However, I get the same widget displayed more than once sometimes.
What can I do to modify the code to prevent this from happening?
Try this:
<?php
function random_widget($dir = 'wp-content/themes/zonza/elements')
{
static $files = false;
if(!$files) $files=glob($dir . '/*.*');
$key = array_rand($files);
$file=$files[$key];
unset($files[$key]);
return $file;
}
?>
It works by removing the file returned from $files, and maintaining $files over multiple function calls (it only globs() on the first time you call the function)
Declare files at the beginning of the page $files = glob($dir . '/*.*');
In random_widget, do this unset($files[$file]) after you pull the value.
array_rand takes a number $num_req as an optional second parameter which specifies the number of entries you want to pick. So add this parameter to random_widget, pass it to array_rand to get an array of keys instead of a single key, return the array of files, and then iterate over this array to include the widgets (instead of calling random_widget three times).
<?php
function random_widget($num_req, $dir = 'wp-content/themes/zonza/elements')
{
$files = glob($dir . '/*.*');
$keys = array_rand($files, $num_req);
$chosen = array();
foreach($keys as $key) {
$chosen[] = $files[$key];
}
return $chosen;
}
$widgets = random_widget(3);
foreach($widgets as $widget) {
include $widget;
}
?>
An advantage of this solution over the ones proposed in the other answers is that it is stateless: you can reuse the function in different contexts as much as you want.
Source: http://php.net/manual/en/function.array-rand.php

Categories