-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from creative-commoners/pulls/1.0/import-from-cwp
NEW Import PDF export functionality from BasePage in cwp/cwp
- Loading branch information
Showing
10 changed files
with
463 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
--- | ||
Name: cwppdfexportextensions | ||
--- | ||
CWP\CWP\PageTypes\BasePage: | ||
extensions: | ||
- CWP\PDFExport\Extensions\PdfExportExtension | ||
|
||
CWP\CWP\PageTypes\BasePageController: | ||
extensions: | ||
- CWP\PDFExport\Extensions\PdfExportControllerExtension |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
<?php | ||
|
||
namespace CWP\PDFExport\Extensions; | ||
|
||
use SilverStripe\Assets\Filesystem; | ||
use SilverStripe\Control\Director; | ||
use SilverStripe\Control\HTTPRequest; | ||
use SilverStripe\Control\HTTPResponse; | ||
use SilverStripe\Core\Config\Config; | ||
use SilverStripe\Core\Environment; | ||
use SilverStripe\Core\Extension; | ||
use SilverStripe\Versioned\Versioned; | ||
|
||
class PdfExportControllerExtension extends Extension | ||
{ | ||
private static $allowed_actions = [ | ||
'downloadpdf', | ||
]; | ||
|
||
/** | ||
* Serve the page rendered as PDF. | ||
* | ||
* @return HTTPResponse|false | ||
*/ | ||
public function downloadpdf() | ||
{ | ||
if (!$this->owner->data()->config()->get('pdf_export')) { | ||
return false; | ||
} | ||
|
||
// We only allow producing live pdf. There is no way to secure the draft files. | ||
Versioned::set_stage(Versioned::LIVE); | ||
|
||
$path = $this->owner->data()->getPdfFilename(); | ||
if (!file_exists($path)) { | ||
$this->owner->generatePDF(); | ||
} | ||
|
||
return HTTPRequest::send_file(file_get_contents($path), basename($path), 'application/pdf'); | ||
} | ||
|
||
/** | ||
* This will return either pdf_base_url from YML, CWP_SECURE_DOMAIN from _ss_environment, or blank. In that | ||
* order of importance. | ||
* | ||
* @return string | ||
*/ | ||
public function getPDFBaseURL() | ||
{ | ||
// if base url YML is defined in YML, use that | ||
if ($this->owner->data()->config()->get('pdf_base_url')) { | ||
$pdfBaseUrl = $this->owner->data()->config()->get('pdf_base_url').'/'; | ||
// otherwise, if we are CWP use the secure domain | ||
} elseif (Environment::getEnv('CWP_SECURE_DOMAIN')) { | ||
$pdfBaseUrl = Environment::getEnv('CWP_SECURE_DOMAIN') . '/'; | ||
// or if neither, leave blank | ||
} else { | ||
$pdfBaseUrl = ''; | ||
} | ||
return $pdfBaseUrl; | ||
} | ||
|
||
/** | ||
* Don't use the proxy if the pdf domain is the CWP secure domain or if we aren't on a CWP server | ||
* | ||
* @return string | ||
*/ | ||
public function getPDFProxy($pdfBaseUrl) | ||
{ | ||
if (!Environment::getEnv('CWP_SECURE_DOMAIN') | ||
|| $pdfBaseUrl == Environment::getEnv('CWP_SECURE_DOMAIN') . '/' | ||
) { | ||
$proxy = ''; | ||
} else { | ||
$proxy = ' --proxy ' . Environment::getEnv('SS_OUTBOUND_PROXY') | ||
. ':' . Environment::getEnv('SS_OUTBOUND_PROXY_PORT'); | ||
} | ||
return $proxy; | ||
} | ||
|
||
/** | ||
* Render the page as PDF using wkhtmltopdf. | ||
* | ||
* @return HTTPResponse|false | ||
*/ | ||
public function generatePDF() | ||
{ | ||
if (!$this->owner->data()->config()->get('pdf_export')) { | ||
return false; | ||
} | ||
|
||
$binaryPath = $this->owner->data()->config()->get('wkhtmltopdf_binary'); | ||
if (!$binaryPath || !is_executable($binaryPath)) { | ||
if (Environment::getEnv('WKHTMLTOPDF_BINARY') | ||
&& is_executable(Environment::getEnv('WKHTMLTOPDF_BINARY')) | ||
) { | ||
$binaryPath = Environment::getEnv('WKHTMLTOPDF_BINARY'); | ||
} | ||
} | ||
|
||
if (!$binaryPath) { | ||
user_error( | ||
'Neither WKHTMLTOPDF_BINARY nor ' . get_class($this->owner->data()) . '.wkhtmltopdf_binary are defined', | ||
E_USER_ERROR | ||
); | ||
} | ||
|
||
if (Versioned::get_reading_mode() == 'Stage.Stage') { | ||
user_error('Generating PDFs on draft is not supported', E_USER_ERROR); | ||
} | ||
|
||
set_time_limit(60); | ||
|
||
// prepare the paths | ||
$pdfFile = $this->owner->data()->getPdfFilename(); | ||
$bodyFile = str_replace('.pdf', '_pdf.html', $pdfFile); | ||
$footerFile = str_replace('.pdf', '_pdffooter.html', $pdfFile); | ||
|
||
// make sure the work directory exists | ||
if (!file_exists(dirname($pdfFile))) { | ||
Filesystem::makeFolder(dirname($pdfFile)); | ||
} | ||
|
||
//decide the domain to use in generation | ||
$pdfBaseUrl = $this->owner->getPDFBaseURL(); | ||
|
||
// Force http protocol on CWP - fetching from localhost without using the proxy, SSL terminates on gateway. | ||
if (Environment::getEnv('CWP_ENVIRONMENT')) { | ||
Config::modify()->set(Director::class, 'alternate_protocol', 'http'); | ||
//only set alternate protocol if CWP_SECURE_DOMAIN is defined OR pdf_base_url is | ||
if ($pdfBaseUrl) { | ||
Config::modify()->set(Director::class, 'alternate_base_url', 'http://' . $pdfBaseUrl); | ||
} | ||
} | ||
|
||
$bodyViewer = $this->owner->getViewer('pdf'); | ||
|
||
// write the output of this page to HTML, ready for conversion to PDF | ||
file_put_contents($bodyFile, $bodyViewer->process($this->owner)); | ||
|
||
// get the viewer for the current template with _pdffooter | ||
$footerViewer = $this->owner->getViewer('pdffooter'); | ||
|
||
// write the output of the footer template to HTML, ready for conversion to PDF | ||
file_put_contents($footerFile, $footerViewer->process($this->owner)); | ||
|
||
//decide what the proxy should look like | ||
$proxy = $this->owner->getPDFProxy($pdfBaseUrl); | ||
|
||
// finally, generate the PDF | ||
$command = $binaryPath . $proxy . ' --outline -B 40pt -L 20pt -R 20pt -T 20pt --encoding utf-8 ' | ||
. '--orientation Portrait --disable-javascript --quiet --print-media-type '; | ||
$retVal = 0; | ||
$output = []; | ||
exec( | ||
$command . " --footer-html \"$footerFile\" \"$bodyFile\" \"$pdfFile\" &> /dev/stdout", | ||
$output, | ||
$retVal | ||
); | ||
|
||
// remove temporary file | ||
unlink($bodyFile); | ||
unlink($footerFile); | ||
|
||
// output any errors | ||
if ($retVal != 0) { | ||
user_error('wkhtmltopdf failed: ' . implode("\n", $output), E_USER_ERROR); | ||
} | ||
|
||
// serve the generated file | ||
return HTTPRequest::send_file(file_get_contents($pdfFile), basename($pdfFile), 'application/pdf'); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
<?php | ||
|
||
namespace CWP\PDFExport\Extensions; | ||
|
||
use SilverStripe\Control\Director; | ||
use SilverStripe\ORM\DataExtension; | ||
use SilverStripe\Versioned\Versioned; | ||
|
||
class PdfExportExtension extends DataExtension | ||
{ | ||
/** | ||
* @config | ||
* @var bool | ||
*/ | ||
private static $pdf_export = false; | ||
|
||
/** | ||
* Domain to generate PDF's from, DOES not include protocol | ||
* i.e. google.com not http://google.com | ||
* @config | ||
* @var string | ||
*/ | ||
private static $pdf_base_url = ''; | ||
|
||
/** | ||
* Allow custom overriding of the path to the WKHTMLTOPDF binary, in cases | ||
* where multiple versions of the binary are available to choose from. This | ||
* should be the full path to the binary (e.g. /usr/local/bin/wkhtmltopdf) | ||
* @see BasePage_Controller::generatePDF(); | ||
* | ||
* @config | ||
* @var string|null | ||
*/ | ||
private static $wkhtmltopdf_binary = null; | ||
|
||
/** | ||
* Where to store generated PDF files | ||
* | ||
* @config | ||
* @var string | ||
*/ | ||
private static $generated_pdf_path = 'assets/_generated_pdfs'; | ||
|
||
/** | ||
* Return the full filename of the pdf file, including path & extension | ||
*/ | ||
public function getPdfFilename() | ||
{ | ||
$baseName = sprintf('%s-%s', $this->owner->URLSegment, $this->owner->ID); | ||
|
||
$folderPath = $this->owner->config()->get('generated_pdf_path'); | ||
if ($folderPath[0] != '/') { | ||
$folderPath = BASE_PATH . '/' . $folderPath; | ||
} | ||
|
||
return sprintf('%s/%s.pdf', $folderPath, $baseName); | ||
} | ||
|
||
/** | ||
* Build pdf link for template. | ||
*/ | ||
public function PdfLink() | ||
{ | ||
if (!$this->owner->config()->get('pdf_export')) { | ||
return false; | ||
} | ||
|
||
$path = $this->getPdfFilename(); | ||
|
||
if ((Versioned::get_stage() === Versioned::LIVE) && file_exists($path)) { | ||
return Director::baseURL() . preg_replace('#^/#', '', Director::makeRelative($path)); | ||
} | ||
return $this->owner->Link('downloadpdf'); | ||
} | ||
|
||
/** | ||
* Remove linked pdf when publishing the page, as it would be out of date. | ||
*/ | ||
public function onAfterPublish() | ||
{ | ||
$filepath = $this->getPdfFilename(); | ||
if (file_exists($filepath)) { | ||
unlink($filepath); | ||
} | ||
} | ||
|
||
/** | ||
* Remove linked pdf when unpublishing the page, so it's no longer valid. | ||
*/ | ||
public function doUnpublish() | ||
{ | ||
$filepath = $this->getPdfFilename(); | ||
if (file_exists($filepath)) { | ||
unlink($filepath); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
<?php | ||
|
||
namespace CWP\PDFExport\Tasks; | ||
|
||
use CWP\CWP\PageTypes\BasePage; | ||
use SilverStripe\Dev\BuildTask; | ||
|
||
class CleanupGeneratedPdfBuildTask extends BuildTask | ||
{ | ||
private static $segment = 'CleanupGeneratedPdfBuildTask'; | ||
|
||
protected $title = 'Cleanup generated PDFs'; | ||
|
||
protected $description = 'Removes generated PDFs on the site, forcing a regeneration of all exports to PDF ' | ||
. 'when users go to download them. This is most useful when templates have been changed so users should ' | ||
. 'receive a new copy'; | ||
|
||
public function run($request) | ||
{ | ||
$path = sprintf('%s/%s', BASE_PATH, BasePage::config()->get('generated_pdf_path')); | ||
if (!file_exists($path)) { | ||
return false; | ||
} | ||
|
||
exec(sprintf('if [ "$(ls -A %s 2> /dev/null)" != "" ]; then rm %s/*; fi', $path, $path), $output, $return_val); | ||
|
||
// output any errors | ||
if ($return_val != 0) { | ||
user_error(sprintf('%s failed: ', get_class($this)) . implode("\n", $output), E_USER_ERROR); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
<?php | ||
|
||
namespace CWP\PDFExport\Tasks; | ||
|
||
use SilverStripe\Core\Config\Configurable; | ||
use SilverStripe\Core\Injector\Injector; | ||
use SilverStripe\CronTask\Interfaces\CronTask; | ||
|
||
if (!interface_exists(CronTask::class)) { | ||
return; | ||
} | ||
|
||
/** | ||
* If the silverstripe/crontask module is installed, this will enable the PDF cleanup task to be run on a schedule | ||
*/ | ||
class CleanupGeneratedPdfDailyTask implements CronTask | ||
{ | ||
use Configurable; | ||
|
||
/** | ||
* The cron schedule for this task (default: midnight every day) | ||
* | ||
* @config | ||
* @var string | ||
*/ | ||
private static $schedule = '0 0 * * *'; | ||
|
||
/** | ||
* Whether this task is enabled (default false) | ||
* | ||
* @config | ||
* @return bool | ||
*/ | ||
private static $enabled = false; | ||
|
||
private static $segment = 'CleanupGeneratedPdfDailyTask'; | ||
|
||
public function getSchedule() | ||
{ | ||
return $this->config()->get('schedule'); | ||
} | ||
|
||
public function process() | ||
{ | ||
if (!$this->config()->get('enabled')) { | ||
return; | ||
} | ||
|
||
$task = Injector::inst()->create(CleanupGeneratedPdfBuildTask::class); | ||
$task->run(null); | ||
} | ||
} |
Oops, something went wrong.