How to download a ZIP file into the wp-plugins folder programmatically? - wordpress

I am creating a WordPress plugin which should download a ZIP file from a remote location and place it into the wp-plugins folder.
So I created a method which downloads the file using Curl (this works fine) and should then place the file into the wp-plugins folder. I am using the WP_Filesystem in order to make sure I have the rights to place a file on the server. This is what I have until now:
public function download_plugin($url, $path)
{
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$data = curl_exec($ch);
curl_close($ch);
global $wp_filesystem;
if(defined('FS_CHMOD_FILE'))
{
$chmod = FS_CHMOD_FILE;
}
else
{
$chmod = 0755;
}
if (empty($wp_filesystem))
{
require_once (ABSPATH . '/wp-admin/includes/file.php');
WP_Filesystem();
}
if(!$wp_filesystem->put_contents(
$path,
$data,
$chmod
))
else
{
return new \WP_Error('writing_error', 'Error when writing file');
}
}
When I run the method no file is being created on the server. The $path variable however has the right path and the $data variable does contain the ZIP file as a string. The WordPress method put_contents however does nothing. It returns null even when I change the method's parameter to something that should definitely work like $wp_filesystem->put_contents(WP_PLUGIN_DIR.'example.txt','Some text',FS_CHMOD_FILE));.
Is there anything I am doing wrong? It's really difficult to debug since I don't get any errors and put_contents always returns null.

Related

Download all files in all folders from URL

I'd like to recursively download all files from nested folders from this URL to my computer in the same nested structure:
https://hazardsdata.geoplatform.gov/?prefix=Region8/R8_MIT/Risk_MAP/Data/BLE/South_Dakota/60601300_BrookingsCO/Brookings%20HYDA/
I've tried several different approaches, using curl and RCurl, including this and some others. There are multiple file types within this folder. But I keep running into cryptic error message such as Error in function (type, msg, asError = TRUE) : error:1407742E:SSL routines:SSL23_GET_SERVER_HELLO:tlsv1 alert protocol version
I'm not even sure how to begin.
in their javascript you'll find the url https://hazards-geoplatform.s3.amazonaws.com/ and there you'll find a xml file containing the path to (seemingly?) all their files, from there it shouldn't be hard, so
1: download the XML list of files from https://hazards-geoplatform.s3.amazonaws.com
2: each of the XML's <content> tag describes a file or a folder. filter out all the tags that is not relevant to you, that means if the content->key tag does not contain the text Brookings HYDA, filter it out.
3: the remaining content tags contain your download path and save path, for every key tag that ends with /: this is a "folder", you can't download a fol6der, just create the path, for example if the key is
<Contents>
<Key>Region8/R8_MIT/Risk_MAP/Data/BLE/South_Dakota/60601300_BrookingsCO/Brookings HYDA/Hydraulics_DataCapture/Correspondence/</Key>
this means you should create the folders Region8/R8_MIT/Risk_MAP/Data/BLE/South_Dakota/60601300_BrookingsCO/Brookings HYDA/Hydraulics_DataCapture/Correspondence and move on, however if the key's value does not end with /, it means you should download it, for example if you find
<Contents>
<Key>Region8/R8_MIT/Risk_MAP/Data/BLE/South_Dakota/60601300_BrookingsCO/Brookings HYDA/Hydraulics_DataCapture/Correspondence/200724-CityBrookings-AirportInfo_Email.pdf</Key>
<LastModified>2022-03-04T17:54:48.000Z</LastModified>
<ETag>"9fe9af393f043faaa8e368f324c8404a"</ETag>
<Size>303737</Size>
<StorageClass>STANDARD</StorageClass>
</Contents>
it means the save filepath is Region8/R8_MIT/Risk_MAP/Data/BLE/South_Dakota/60601300_BrookingsCO/Brookings HYDA/Hydraulics_DataCapture/Correspondence/200724-CityBrookings-AirportInfo_Email.pdf
and the url to download the file is https://hazards-geoplatform.s3.amazonaws.com/ + urlencode(key), in this case:
https://hazards-geoplatform.s3.amazonaws.com/Region8%2FR8_MIT%2FRisk_MAP%2FData%2FBLE%2FSouth_Dakota%2F60601300_BrookingsCO%2FBrookings%20HYDA%2FHydraulics_DataCapture%2FCorrespondence%2F200724-CityBrookings-AirportInfo_Email.pdf
idk how to do it with curl/r, but here's how to do it in PHP, happy porting
<?php
declare(strict_types=1);
function curl_get(string $url): string
{
echo "fetching {$url}\n";
static $ch = null;
if ($ch === null) {
$ch = curl_init();
curl_setopt_array($ch, array(
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_ENCODING => '',
CURLOPT_FOLLOWLOCATION=>1,
CURLOPT_VERBOSE=>0
));
}
curl_setopt($ch, CURLOPT_URL, $url);
$ret = curl_exec($ch);
if(curl_errno($ch)) {
throw new Exception("curl error ".curl_errno($ch).": ".curl_error($ch));
}
return $ret;
}
$base_url = 'https://hazards-geoplatform.s3.amazonaws.com/';
$xml = curl_get($base_url);
$domd = new DOMDocument();
#($domd->loadHTML($xml));
$xp = new DOMXPath($domd);
foreach($xp->query("//key[contains(text(),'Brookings HYDA')]") as $node) {
$relative = $node->nodeValue;
if($relative[-1] === '/'){
// it's a folder, ignore
continue;
}
$dir = dirname($relative);
if(!is_dir($dir)) {
mkdir($dir, 0777, true);
}
$url = $base_url . urlencode($node->nodeValue);
file_put_contents($relative, curl_get($url));
}
after running that for a few seconds i have
$ find
.
./fuk.php
./Region8
./Region8/R8_MIT
./Region8/R8_MIT/Risk_MAP
./Region8/R8_MIT/Risk_MAP/Data
./Region8/R8_MIT/Risk_MAP/Data/BLE
./Region8/R8_MIT/Risk_MAP/Data/BLE/South_Dakota
./Region8/R8_MIT/Risk_MAP/Data/BLE/South_Dakota/60601300_BrookingsCO
./Region8/R8_MIT/Risk_MAP/Data/BLE/South_Dakota/60601300_BrookingsCO/Brookings HYDA
./Region8/R8_MIT/Risk_MAP/Data/BLE/South_Dakota/60601300_BrookingsCO/Brookings HYDA/Hydraulics_DataCapture
./Region8/R8_MIT/Risk_MAP/Data/BLE/South_Dakota/60601300_BrookingsCO/Brookings HYDA/Hydraulics_DataCapture/Correspondence
./Region8/R8_MIT/Risk_MAP/Data/BLE/South_Dakota/60601300_BrookingsCO/Brookings HYDA/Hydraulics_DataCapture/Correspondence/200724-CityBrookings-AirportInfo_Email.pdf
./Region8/R8_MIT/Risk_MAP/Data/BLE/South_Dakota/60601300_BrookingsCO/Brookings HYDA/Hydraulics_DataCapture/Correspondence/2D_Exceptions_2021Update.pdf
./Region8/R8_MIT/Risk_MAP/Data/BLE/South_Dakota/60601300_BrookingsCO/Brookings HYDA/Hydraulics_DataCapture/DCS_Checklist_Hydraulics_BrookingsCoSD.xlsx
./Region8/R8_MIT/Risk_MAP/Data/BLE/South_Dakota/60601300_BrookingsCO/Brookings HYDA/Hydraulics_DataCapture/Simulations
./Region8/R8_MIT/Risk_MAP/Data/BLE/South_Dakota/60601300_BrookingsCO/Brookings HYDA/Hydraulics_DataCapture/Simulations/RAS
./Region8/R8_MIT/Risk_MAP/Data/BLE/South_Dakota/60601300_BrookingsCO/Brookings HYDA/Hydraulics_DataCapture/Simulations/RAS/0.2PAC
soo it seems to be working.
the last output from the command is
fetching https://hazards-geoplatform.s3.amazonaws.com/Region8%2FR8_MIT%2FRisk_MAP%2FData%2FBLE%2FSouth_Dakota%2F60601300_BrookingsCO%2FBrookings+HYDA%2FHydraulics_DataCapture%2FSimulations%2FRAS%2F0.2PAC%2FPostProcessing.hdf
PHP Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 65019904 bytes) in /home/hans/test/fuk.php on line 17
meaning some of their files are over 134MB in size - it's easy to optimize the curl code to write directly to disk instead of storing the entire file in ram before writing to disk, but since you want to do this in R anyway, i won't bother optimizing the sample php script.

mPDF failed to open stream: operation failed

mPDF stopping working for me, pdfs generated from php are now blank, when I turn on debug I get the following error message.
Error detected. PDF file generation aborted: file_get_contents(https://www.myurl.com/pdf.php): failed to open stream: operation failed
When I go directly to the php page it appears fine.
Here is the code I'm using, I changed permissions to 777 on the file and the folder and it still doesn't work yet if I change the url to an external one it works perfectly. So I'm guessing something on my server is blocking it.
require __DIR__ . '/vendor/autoload.php';
$invoice_id = $_GET['invoice_id'];
$url="https://www.myurl.com/pdf.php";
if (ini_get('allow_url_fopen')) {
$html = file_get_contents($url);
} else {
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt ( $ch , CURLOPT_RETURNTRANSFER , 1 );
$html = curl_exec($ch);
curl_close($ch);
}
try
{
$mpdf = new \Mpdf\Mpdf();
$mpdf->debug = true;
$mpdf->SetDisplayMode('fullwidth');
$mpdf->CSSselectMedia='mpdf'; // assuming you used this in the document header
$mpdf->setBasePath($url);
$mpdf->WriteHTML($html);
$mpdf->Output('invoice'.$invoice_id.'.pdf','D');
} catch (\Mpdf\MpdfException $e) { // Note: safer fully qualified exception
echo $e->getMessage();
}

Serving files via symfony2 - path to uploads directory

I have a problem setting a correct path to symfony2 uploads directory.
I am trying to provide user with files that they previously uploaded.
Firstly I tried the following code:
$response = new Response();
$d = $response->headers->makeDisposition(
ResponseHeaderBag::DISPOSITION_ATTACHMENT,
$document->getWebPath()
);
$response->headers->set('Content-Disposition', $d);
as advised in the cookbook and How to get web directory path from inside Entity?.
This however resulted in the following error:
The filename and the fallback cannot contain the "/" and "\" characters.
Therefore I decided to switch to:
$filename = $this->get('kernel')->getRootDir() . '/../web' . $document->getWebPath();
return new Response(file_get_contents($filename), 200, $headers);
this however results in:
Warning: file_get_contents(/***.pl/app/../web/uploads/documents/2.pdf) [<a href='function.file-get-contents'>function.file-get-contents</a>]: failed to open stream: No such file or directory
My file that I want to serve is located in
/web/uploads/documents/2.pdf
What code should I use to provide this file to end users?
In order to serve binary files, it's better to use the BinaryFileResponse, which accepts the absolute file path as its argument. The setContentDisposition() doesn't accept file paths but file names ... and that argument is optional (you should only use it in case you want to change the name of the file being served to end-users):
use Symfony\Component\HttpFoundation\BinaryFileResponse;
$response = new BinaryFileResponse($filePath);
$response->setContentDisposition(
ResponseHeaderBag::DISPOSITION_ATTACHMENT, $fileName
); // This line is optional to modify file name
Regarding the file path, you can keep using the code you showed, but slightly changed:
$filePath = $this->container->getParameter('kernel.root_dir')
.'/../web/'
.$document->getWebPath();

Symfony2 PHPWord response

I am trying to generate a docx document on Symfony2, using the PHPWord bundle.
In my controller, I succeed in returning a docx file, but it is empty, I think it comes from my faulty response format.
public function indexAction($id)
{
$PHPWord = new PHPWord();
$section = $PHPWord->addSection();
$section->addText(htmlspecialchars(
'"Learn from yesterday, live for today, hope for tomorrow. '
. 'The important thing is not to stop questioning." '
. '(Albert Einstein)'
));
// Saving the document
$objWriter = \PhpOffice\PhpWord\IOFactory::createWriter($PHPWord, 'Word2007');
return new Response($objWriter->save('helloWorld.docx'), 200, array('Content-Type' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'));
}
Try this class
<?php
use PhpOffice\PhpWord\IOFactory;
use PhpOffice\PhpWord\PhpWord;
use PhpOffice\PhpWord\Settings;
use Symfony\Component\HttpFoundation\Response;
class WordResponse extends Response
{
/**
* WordResponse constructor.
* #param string $name The name of the word file
* #param PhpWord $word
*/
public function __construct($name, &$word)
{
parent::__construct();
// Set default zip library.
if( !class_exists('ZipArchive')){
Settings::setZipClass(Settings::PCLZIP);
}
$writer = IOFactory::createWriter($word, 'Word2007');
//Set headers.
$this->headers->set("Content-Disposition", 'attachment; filename="' . $name . '"');
$this->headers->set("Content-Type", 'application/vnd.openxmlformats-officedocument.wordprocessingml.document');
$this->headers->set("Content-Transfer-Encoding", 'binary');
$this->headers->set("Cache-Control", 'must-revalidate, post-check=0, pre-check=0');
$this->headers->set("Expires", '0');
$this->sendHeaders();
$writer->save('php://output');
}
}
Then in your controller do:
return new WordResponse($phpWord, "filename.docx");
Thanks a lot for your answer.
I achieve using the 2nd method, which is in my opinion the best.
I just have to return a response, otherwise the file was generated, but stuck in the web directory.
Using this response, everything was fine and a download prompt appeared, with the "full" file.
Here's my code :
$PHPWord = new PHPWord();
$section = $PHPWord->addSection();
$section->addText(htmlspecialchars(
'"Learn from yesterday, live for today, hope for tomorrow. '
. 'The important thing is not to stop questioning." '
. '(Albert Einstein)'
));
// Saving the document
$objWriter = \PhpOffice\PhpWord\IOFactory::createWriter($PHPWord, 'Word2007');
$filename="MyAwesomeFile.docx";
$objWriter->save($filename, 'Word2007', true);
$path = $this->get('kernel')->getRootDir(). "/../web/" . $filename;
$content = file_get_contents($path);
$response = new Response();
$response->headers->set('Content-Type', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document');
$response->headers->set('Content-Disposition', 'attachment;filename="'.$filename);
$response->setContent($content);
return $response;
PHPWord->save() returns a true value so that would be why your file is not being downloaded. With your return new Response() you are setting the content of your response to true (the result of your save call) which is why your response is empty.
You have 2 (and probably more that I haven't thought of) options to generate and download this file..
1. Save your file to a temp folder and server from there
$filename = sprintf(
'%s%sDoc-Storage%s%s.%s',
sys_get_temp_dir(),
DIRECTORY_SEPARATOR,
DIRECTORY_SEPARATOR,
uniqid(),
'docx'
);
$objWriter->save($filename);
$response = new BinaryFileResponse($filename);
For more info on the BinaryFileResponse see the docs.
2. Ignore Symfony and serve directly via the PHPWord action
$objWriter->save($filename, 'Word2007', true);
exit();
The ->save method provides all of the actions to download the generated file internally (see the code) so all you need to do is set the format and the third parameter to true and it will handle all of the headers for you. Granted it won't be returning a Symfony response but you will be exiting out before you get to that exception.

WordPress fopen getting directory link

I'm using fwrite to create an html file in a folder within my plugin. The following code now allows me to write to the folder, but the link it tries to open is the full system path.
function buildFrameFile($html, $filename){
$DOCUMENT_ROOT = $_SERVER['DOCUMENT_ROOT'];
$filename= $DOCUMENT_ROOT. '/wp-content/plugins/my-plugin/html/' . $filename . ".html";
$fh = fopen($filename, 'a');//open file and create if does not exist
fwrite($fh, $html);//write data
fclose($fh);//close file
return $filename;
}
The path it now opens is:
/var/chroot/home/content/##/########/html/wp-content/plugins/my-plugin/html/79dda339bad8e65c425e580d62f41fa1.html
I need it to open from here:
/wp-content/plugins/my-plugin/html/79dda339bad8e65c425e580d62f41fa1.html
I'm not sure how to go about this.
I solved my problem. I ended up changing the code from:
function buildFrameFile($html, $filename){
$DOCUMENT_ROOT = $_SERVER['DOCUMENT_ROOT'];
$filename= $DOCUMENT_ROOT. '/wp-content/plugins/my-plugin/html/' . $filename . ".html";
$fh = fopen($filename, 'a');//open file and create if does not exist
fwrite($fh, $html);//write data
fclose($fh);//close file
return $filename;
}
To:
function buildFrameFile($html, $filename){
$DOCUMENT_ROOT = $_SERVER['DOCUMENT_ROOT'];
$filename2= $DOCUMENT_ROOT. '/wp-content/plugins/my-plugin/html/' . $filename . ".html";
$fh = fopen($filename2, 'a');//open file and create if does not exist
fwrite($fh, $html);//write data
fclose($fh);//close file
return $filename;
}
This way the file gets saved to the folder and only returns the actual name of the file not the whole link to the file.
Then in my header I changed the code from:
header("Location: /confirm" . $nvp_str . "&filename=" . $filename);
To:
header("Location: /confirm?" . $nvp_str . "&filename=" . '/wp-content/plugins/my-plugin/html/' . $filename . ".html");
and the iframe in my page calls the value of &filename which then returns the proper link to my file created and it loads perfectly!
First of all, you can rely on Wordpress' defines (or functions) to determinate the paths without any dirty hacks:
http://codex.wordpress.org/Determining_Plugin_and_Content_Directories#Available_Functions
http://codex.wordpress.org/Determining_Plugin_and_Content_Directories#Constants
Then again you can check things using PHP functions like file_exists(), is_dir(), is_writable():
http://pl1.php.net/manual/en/function.is-dir.php
http://php.net/manual/en/function.file-exists.php
http://pl1.php.net/manual/en/function.is-writable.php
To avoid complex fopen, fwrite, fclose handlers, you can go for file_put_contents() function there too. Either in appending or overwriting mode:
http://pl1.php.net/manual/en/function.file-put-contents.php
Not sure how relevant, but keep in mind if this is written by the webserver, you need to make sure the directory has write permissions there. Easiest way would be chmod 777 directory from shell, or SITE CHMOD 777 directory from FTP.
Your issue is most likely connected to weird godaddy's setup.
You can find more information here: http://www.quest4.org/etc/godaddy_path.htm
There's also a similiar issue posted here:
WordPress's plugins_url() function not working on shared hosting

Resources