I have some files that I store at /storage/app/public/clientA/files/*.pdf. I have models that I can use to access these, and I want users to be able to download multiple files by selecting them in Nova and then using an action. Here is my code for the action:
public function handle(ActionFields $fields, Collection $models)
{
$files = array();
foreach ($models as $file) {
$path = FileHelper::getPathFromUrl($file->url);
array_push($files, $path);
}
$zip_file = 'myfiles.zip';
$zip = new \ZipArchive();
if ($zip->open($zip_file, \ZipArchive::CREATE | \ZipArchive::OVERWRITE) === true)
{
foreach ($files as $item) {
$zip->addFile(public_path('storage/' . $item), $item);
}
$zip->close();
}
return Action::download($zip_file, $zip_file);
}
Also, here is my code for the getPathFromUrl method:
public static function getPathFromUrl ($url)
{
$path = '';
$url_path = parse_url($url, PHP_URL_PATH);
return substr($url_path, strpos($url_path, "/") + 1);
} // returns the format storage/clientA/files/fileName.pdf
My issue at the moment is that its generating an empty zip file. I'm guessing that my paths are wrong when I try and reference the files, but I don't know how to fix it. I've also tried accessing these locations using Storage::get and found that it can't see the files at valid locations (and yes, I have done Storage:link).
Can anyone give me some insight into what I need to change to my addFile to ensure that these pdfs get added to the zip file?
Related
I have a folder with subfolders containing all kind of files..
When I trigger action to delete them, some of them got deleted but some of them don't. What I am trying to say is that they are all pretty much same structure.. I don't know what is causing that? Is it something with timing or? I am deleting the folder with files once the zip files are created.
This is the method which is deleting folder with subfolders and files..
private function removeFiles($name)
{
// Locate folder to remove
$rootDirElements = [
$this->kernel->getProjectDir(),
$name
];
$rootDir = implode(DIRECTORY_SEPARATOR, $rootDirElements);
$rdi = new RecursiveDirectoryIterator($rootDir, RecursiveDirectoryIterator::SKIP_DOTS);
$files = new RecursiveIteratorIterator(
$rdi,
RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($files as $file) {
if ($file->isDir()) {
rmdir($file->getRealPath());
} else {
unlink($file->getRealPath());
}
}
rmdir($rootDir);
}
And I am calling it in another method where zip logic is happening:
private function zipFolder($source, $destination, $name, $includeDir = false)
{
$zip = new ZipArchive();
if ($zip->open($destination, ZipArchive::CREATE | ZipArchive::OVERWRITE) === true) {
//zipping logic
}
}
}
}
$zip->close();
// After zip file is created delete folder
$this->removeFiles($name);
I don't know what is causing that?
I know there is quite some code out there about that topic. But I can't get the code I found to work...
I'm using a function that comes from a Wordpress plugin called Zip-Attachments.
So I modified it to zip files from a specific folder. BUT it doesn't work....
It seems as if I'm missing something when it comes to accurately assigning a paths within the RecursiveDirectoryIterator class.
I was able to zip a single file and downloading it without the recursiveIteration functionality and passing the absolute path as a string. So the main part of the function works.
What am I missing to make that function work?
Here is my non working code:
function za_create_zip_callback(){
$upload_dir = wp_upload_dir();
$rootPath = $upload_dir['basedir'];
$upload_dir_Knippsbox = 'Knippsbox';
// Prepare File
$file = tempnam($upload_dir['path'], "zip");
$zip = new ZipArchive();
$zip->open($file, ZipArchive::OVERWRITE);
// create recursive directory iterator
$files = new RecursiveIteratorIterator (new RecursiveDirectoryIterator("{$rootPath}/{$upload_dir_Knippsbox}/"), RecursiveIteratorIterator::LEAVES_ONLY);
// let's iterate
foreach ($files as $name => $fileX) {
$filePath = $fileX->getRealPath();
$zip->addFile($filePath);
}
//Close the file
$zip->close();
// Add a download to the Counter
global $wpdb;
$meta_name = "_za_counter";
// Retrieve the meta value from the DB
$za_download_count = get_post_meta($postId, $meta_name, true) != '' ? get_post_meta($postId, $meta_name, true) : '0';
$za_download_count = $za_download_count + 1;
// Update the meta value
update_post_meta($postId, $meta_name, $za_download_count);
// We have to return an actual URL, that URL will set the headers to force the download
echo zip_attachments_url."/download.php?za_pretty_filename=".sanitize_file_name($pretty_filename)."&za_real_filename=".$filename;
die();}
Desperately looking for your expert views...
Thanks,
Ben
I'm trying to list all PHP files in a specified directory and for it to recursively check all sub-directories until it finds no more, there could be numerous levels.
The function I have below works fine with the exception that it only recurses down one level.
I've spent hours trying to see where I'm going wrong, I'm calling the scanFiles() when it finds a new directory but this only seems to work one level down and stop, any help greatly appreciated.
Updated:
function scanFiles($pParentDirectory)
{
$vFileArray = scandir($pParentDirectory);
$vDirectories = array();
foreach ($vFileArray as $vKey => $vValue)
{
if (!in_array($vValue, array('.', '..')) && (strpos($vValue, '.php') || is_dir($vValue)))
{
if (!is_dir($vValue))
$vDirectories[] = $vValue;
else
{
$vDirectory = $vValue;
$vSubFiles = scanFiles($vDirectory);
foreach ($vSubFiles as $vKey => $vValue)
$vDirectories[] = $vDirectory.DIRECTORY_SEPARATOR.$vValue;
}
}
}
return $vDirectories;
}
You can do this easily like this:
// helper function
function getFiles(&$files, $dir) {
$items = glob($dir . "/*");
foreach ($items as $item) {
if (is_dir($item)) {
getFiles($files, $item);
} else {
if (end(explode('.', $item)) == 'php') {
$files[] = basename($item);
}
}
}
}
// usage
$files = array();
getFiles($files, "myDir");
// debug
var_dump($files);
myDir looks like this: has php files in all dirs
Output:
P.S. if you want the function to return the full path to the found .php files, remove the basename() from this line:
$files[] = basename($item);
This will then produce result like this:
hope this helps.
This is because $vDirectory is just a folder name, so scanDir looks in the current folder for it, not the sub folder.
What you want to do is to pass in the path to the folder, not just the name. This should be as simple as changing your recursive call to scanFiles($pParentDirectory . DIRECTORY_SEPARATOR . $vDirectory)
Your main problem is functions like scanDir or isDir need the full file path to work.
If you pass the full file path to them, it should work correctly.
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.
}
}
}
I have a server with a lot of files inside various folders, sub-folders, and sub-sub-folders.
I'm trying to make a search.php page that would be used to search the whole server for a specific file. If the file is found, then return the location path to display a download link.
Here's what i have so far:
$root = $_SERVER['DOCUMENT_ROOT'];
$search = "test.zip";
$found_files = glob("$root/*/test.zip");
$downloadlink = str_replace("$root/", "", $found_files[0]);
if (!empty($downloadlink)) {
echo "$search";
}
The script is working perfectly if the file is inside the root of my domain name... Now i'm trying to find a way to make it also scan sub-folders and sub-sub-folders but i'm stuck here.
There are 2 ways.
Use glob to do recursive search:
<?php
// Does not support flag GLOB_BRACE
function rglob($pattern, $flags = 0) {
$files = glob($pattern, $flags);
foreach (glob(dirname($pattern).'/*', GLOB_ONLYDIR|GLOB_NOSORT) as $dir) {
$files = array_merge(
[],
...[$files, rglob($dir . "/" . basename($pattern), $flags)]
);
}
return $files;
}
// usage: to find the test.zip file recursively
$result = rglob($_SERVER['DOCUMENT_ROOT'] . '/test.zip');
var_dump($result);
// to find the all files that names ends with test.zip
$result = rglob($_SERVER['DOCUMENT_ROOT'] . '/*test.zip');
?>
Use RecursiveDirectoryIterator
<?php
// $regPattern should be using regular expression
function rsearch($folder, $regPattern) {
$dir = new RecursiveDirectoryIterator($folder);
$ite = new RecursiveIteratorIterator($dir);
$files = new RegexIterator($ite, $regPattern, RegexIterator::GET_MATCH);
$fileList = array();
foreach($files as $file) {
$fileList = array_merge($fileList, $file);
}
return $fileList;
}
// usage: to find the test.zip file recursively
$result = rsearch($_SERVER['DOCUMENT_ROOT'], '/.*\/test\.zip/'));
var_dump($result);
?>
RecursiveDirectoryIterator comes with PHP5 while glob is from PHP4. Both can do the job, it's up to you.
I want to provide another simple alternative for cases where you can predict a max depth. You can use a pattern with braces listing all possible subfolder depths.
This example allows 0-3 arbitrary subfolders:
glob("$root/{,*/,*/*/,*/*/*/}test_*.zip", GLOB_BRACE);
Of course the braced pattern could be procedurally generated.
This returns fullpath to the file
function rsearch($folder, $pattern) {
$iti = new RecursiveDirectoryIterator($folder);
foreach(new RecursiveIteratorIterator($iti) as $file){
if(strpos($file , $pattern) !== false){
return $file;
}
}
return false;
}
call the function:
$filepath = rsearch('/home/directory/thisdir/', "/findthisfile.jpg");
And this is returns like:
/home/directory/thisdir/subdir/findthisfile.jpg
You can improve this function to find several files like all jpeg file:
function rsearch($folder, $pattern_array) {
$return = array();
$iti = new RecursiveDirectoryIterator($folder);
foreach(new RecursiveIteratorIterator($iti) as $file){
if (in_array(strtolower(array_pop(explode('.', $file))), $pattern_array)){
$return[] = $file;
}
}
return $return;
}
This can call as:
$filepaths = rsearch('/home/directory/thisdir/', array('jpeg', 'jpg') );
Ref: https://stackoverflow.com/a/1860417/219112
As a full solution for your problem (this was also my problem):
<?php
function rsearch($folder, $pattern) {
$dir = new RecursiveDirectoryIterator($folder);
$ite = new RecursiveIteratorIterator($dir);
$files = new RegexIterator($ite, $pattern, RegexIterator::MATCH);
foreach($files as $file) {
yield $file->getPathName();
}
}
Will get you the full path of the items that you wish to find.
Edit: Thanks to Rousseau Alexandre for pointing out , $pattern must be regular expression.