Search and replace entire directory file contents - php

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.
}
}
}

Related

How to create multiple files with different names and in different folders? php

So, I have a .txt file with paths of my missing files. I want to write a little php script, that will just create those files and leave them blank.
xxx/yyy/xyxy/a/w/r/obvestilo.pdf
xxx/yyy/xyxy/b/print.pdf
xxx/yyy/xyxy/c/speach.doc
This is an example of how I have things in my .txt file of missing files. I would like the script to create me those files and also folders if they don't yet exist, but I have no clue where to begin. I was thinking to transfer that .txt file to an Array and then loop throug all array elements creating them.
Please try this
$fp = fopen('files.txt','r');
while(($buffer = fgets($fp,4096)) !== false) {
$directory = substr($buffer,0,strrpos($buffer,'/') + 1);
mkdir($directory, 0755, true);
touch(trim($buffer));
}
files.txt will have your files in the format you have in your post.
The mkdir($directory, 0755, true); will create the required directory recursively and the touch will create a blank file.
Try something like the following:
$data = explode("\n", file_get_contents('file_list.txt'));
foreach($data as $filename) {
if(!file_exists(trim($filename))) {
file_put_contents(trim($filename), '');
}
}
That will write an empty string to each file in the list that doesn't already exist so you will get empty files. It won't create directories for you though, if you want to do that you'll need to do something a bit more complicated...
I am assuming that the paths in your .txt file are absolute paths. If not than you will have to append some sort of ROOT_DIRECTORY constant in front of the paths.
Generally you would want to put this functionality in a class whose sole responsibility is to create these empty files:
class EmptyFileCreater {
const USE_RECUSRION = true;
const DEFAULT_ACCESS = 0777;
public function create($path) {
$this->ensureDirectoryExists(dirname($path));
$this->createEmptyFile($path);
}
private function ensureDirectoryExists($directory) {
if (!is_dir($directory)) {
mkdir($directory, self::DEFAULT_ACCESS, self::USE_RECUSRION);
}
}
private function createEmptyFile($path) {
touch($path);
}
}
Now you can use this class to generate all the files.
// Retrieve the .txt file as an array of the lines in the file
$paths = file('path/to/missing_file_paths.txt');
$empty_file_creater = new EmptyFileCreater();
foreach ($paths as $path) {
$empty_file_creater->create($path);
}
This will work for you:
<?php
$lines = file('test.txt'); ///path to your file
foreach ($lines as $line_num => $line)
{
$fp = fopen($line,"wb");
fwrite($fp,'');
fclose($fp);
}
?>
You can use this method to create random names of specific lengths.
/*
*
* Get Random number( 5 digit )
* #param int: 5
* #return alphnumeric string: length(5)
*/
public function getRandomNumber($length, $charset = 'abcdefghijkmnopqrpdctvwxyz3456789') {
$str = '';
$count = strlen($charset);
while ($length--) {
$str .= $charset[mt_rand(0, $count - 1)];
}
return $str;
}

PHP search inside loaded text file, converted as array

I need to find a *.jpg name in a text document. For example I have a folder with pictures file1, file2 , file3 and a text document with file1, file, file3 each on a new line. I need to write near each *.jpg the text from the file, but first I need to find the corresponding row in the text document.
<?php
$arrayF = explode("\n", file_get_contents('myTxt.txt'));
// arrayF should the the array with each text line from the txt file.
while(($file = readdir($opendir)) !== FALSE)
{
if($file!="." && $file!="..")
{
$string=$file;
$arrayS = explode('.', $string);//get the name only without .jpg extension
$search=$arrayS[0];
$key = array_search($search, $arrayF);
echo $key;
}
$string=file_get_contents($file);
file_get_contents
Or if you want it line by line, just open, read and close
$fp = fopen($file, 'r');
while ($line = fread($fp)) {
//do comparision stuff
}
fclose ($fp);
Below is an example to iterate through a directory using DirectoryIterator which provides viewing contents of filesystem directories.
Example:
foreach(new DirectoryIterator($dir) as $file_info)
{
// sleep a micro bit (up to 1/8th second)
usleep(rand(0, 125000));
// disregard hidden, empty, invalid name files
if($file_info == null or $file_info->getPathname() == ''
or $file_info->isDot())
{
continue;
}
// check if its a file - log otherwise
// code omitted
// get filename and filepath
$filepath = $file_info->getPathname();
$filename = $file_info->getFilename();
// my answer to read file linked here to avoid duplication
// below "read/parse file into key value pairs"
}
read/parse file into key value pairs
To elaborate new DirectoryIterator( path to dir ) provides an iterator which can be also written as:
$iterator = new DirectoryIterator($directory);
foreach ($iterator as $fileinfo) {
// do stuffs
}
Hope this helps, indirectly.

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 can I exclude directories using RecursiveDirectoryIterator

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

PHP read data from several text files, outputting a certain line from all files?

I'm trying to write a script which reads the contents of 25 heavy text files (about 100 lines each). I want my script to output line 5 of each text file. I am also working on a windows workstation (using Apache in a local environment for testing).
Each text file is located in the same directory (i.e. products/product1.txt) which I guess makes it easier.
Each text file, on line 5, looks like this (with different descriptions):
Product Desc: iphone
I would also like to know if its possible, after achieving the above, to have the script remove the text 'Product Desc: ', so that only the actual product description is displayed.
Please provide any code examples as I am a noobie :)
$dir = opendir('directory');
while($file = readdir($dir)){
if ($file != "." or $file != ".."){
$opened = file($file);
echo $opened[4]."<br />";
}
}
So this is essentially three parts: (1) you need to loop through the directory, (2) reading in the fifth line of each file and (3) you need to read the section of that line, after the colon.
// open the products directory
$fileHandle = opendir('products');
// create an array to store the file data
$files = array();
// create an array to store products
$products = array();
if ($fileHandle) {
// loop through each file
while ( false !== ($singleFile = readdir($fileHandle)) ) {
// use file() to read the lines of the file
$lines = file($singleFile);
// store the fifth line in each file (the array starts at 0)
$productDescription = explode(":",$lines[4]);
$products[] = productDescription[1]; // the bit after the colon
}
}
// this should show you an array of the products
print_r($products);
PHP to load multiple files:
<?php
date_default_timezone_set("America/Edmonton");
$products=array();
$dir="../products/"; //Set this for whatever your folder
function getProduct($file) {
$fp=fopen($file,"r");
//Read five lines
for ($j=0;$j<5;$j++) $line=fgets($fp);
$GLOBALS["products"][]=substr($line,14);//Since products is a global var
fclose($fp);
}
$dp=opendir($dir);
while (($file = readdir($dp)) !== false) {
if ($file!="." && $file!="..")
getProduct($dir.$file);
}
closedir($dp);
//Review the product list
print_r($products);
?>
An example file (one.txt):
line 1
line 2
line 3
line 4
Product Desc: iPhone
line 6
Shortcuts:
Because we only want the fifth line we use the $j loop to read the first five lines each overwriting the last. If the file is less than five lines you'll get a null out of $line... you should test for that (and not add it to the product list). Finally because we now the length of the string "Product Desc:" (the way you wrote it) we can just throw that first part of the line away. This isn't terrible robust, better to use a RegEx, or string parsing to make sure the right words are there, and then consume the data following the colon. Still... it answers your original question ;)
You said there could be many more lines, this approach only loads the first 5 lines into memory (each over writing the last) and stops once the fifth line is reached... which means serious performance and memory advantages over reading the whole file into an array (file) and then only using line 5.
Design Decisions:
It looks like you're building a product catalog, it would be much better to use a database to store this data. Doesn't have to be a big deal (MySQL, PostgreSQL) it could be something as easy as SQLite.
Answer: http://pastebin.com/WBBQW9wA (raw code at bottom)
The process is three-fold.
Get all the filenames in the directory.
Read in the fifth line each file.
Split after the colon.
My method is very well documented and understandable. It uses functions to do specific parts of the process. It's quite easy to understand what's going on, and is fairly robust at the same time.
Functions that do the work:
<?php
function readAfterColon($string) {
/**
* #param string $string The string you'd like to split on.
*
* #return string The data after the colon. Multiple colons work just fine.
*/
$array = explode(":",$string, 2);
return isset($array[1]) ? $array[1] : '';
}
function readLine($lineNumber, $fileName) {
/**
* #param int $lineNumber The line number you want to read from the file.
* #param string $fileName The string representing the file you wish to open.
*
* #return string The contents of the line in the file.
*/
$file = file($fileName);
return $file[$lineNumber-1];
}
function readLineFrom($lineNumber, $fileNames) {
/**
* #param int $lineNumber The line number you want to read from the file.
* #param string|array $files Either a string for the file you want to open, or an array of filenames.
*
* #return array An associative array of the filename to the contents of the line.
*/
$lines = array();
if(is_array($fileNames)) {
foreach($fileNames as $fileName) {
$lines[$fileName] = readLine($lineNumber, $fileName);
}
} else {
$lines[$fileNames] = readLine($lineNumber, $fileNames);
}
return $lines;
}
function getFileNamesFromDirectory($directory = '.') {
/**
* #param string $directory The path to directory you'd like to get filenames from. Defaults to current directory.
*
* #return array An array of filenames. Empty if there are no files.
*/
$fileNames = array();
if ($handle = opendir($directory)) {
while (false !== ($file = readdir($handle))) {
if ($file != "." && $file != "..") {
$fileNames[] = $directory . $file;
}
}
closedir($handle);
}
return $fileNames;
}
?>
Example:
<?php
//include the functions, then:
//get the line from every file
$descriptions = readLineFrom(5, getFileNamesFromDirectory('/var/www/test/'));
//get the contents after the colon
foreach($descriptions as $fileName=>$description) {
$descriptions[$fileName] = readAfterColon($description);
}
//display it
echo "<pre>\n";
print_r($descriptions);
echo "</pre>";
?>

Categories