Skip to content

Commit

Permalink
Merge pull request #1 from creative-commoners/pulls/1.0/import-from-cwp
Browse files Browse the repository at this point in the history
NEW Import PDF export functionality from BasePage in cwp/cwp
  • Loading branch information
NightJar authored Jan 24, 2018
2 parents 60e303a + b3a2e74 commit 4c478a8
Show file tree
Hide file tree
Showing 10 changed files with 463 additions and 1 deletion.
3 changes: 2 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ matrix:
- php: 7.0
env: DB=MYSQL PHPUNIT_TEST=1
- php: 7.1
env: DB=MYSQL PHPUNIT_COVERAGE_TEST=1
env: DB=MYSQL PHPUNIT_COVERAGE_TEST=1 CRONTASK=1
- php: 7.2
env: DB=MYSQL PHPUNIT_TEST=1

Expand All @@ -24,6 +24,7 @@ before_script:
# Install composer dependencies
- composer validate
- composer require --no-update silverstripe/recipe-cms:1.0.x-dev
- if [[ $CRONTASK ]]; then composer require --no-update silverstripe/crontask ^2; fi
- composer install --prefer-dist --no-interaction --no-progress --no-suggest --optimize-autoloader --verbose --profile

script:
Expand Down
10 changes: 10 additions & 0 deletions _config/extensions.yml
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
1 change: 1 addition & 0 deletions phpcs.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@
<rule ref="PSR2" >
<!-- Current exclusions -->
<exclude name="PSR1.Methods.CamelCapsMethodName" />
<exclude name="PSR1.Files.SideEffects.FoundWithSymbols" />
</rule>
</ruleset>
173 changes: 173 additions & 0 deletions src/Extensions/PdfExportControllerExtension.php
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');
}
}
97 changes: 97 additions & 0 deletions src/Extensions/PdfExportExtension.php
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);
}
}
}
32 changes: 32 additions & 0 deletions src/Tasks/CleanupGeneratedPdfBuildTask.php
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);
}
}
}
52 changes: 52 additions & 0 deletions src/Tasks/CleanupGeneratedPdfDailyTask.php
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);
}
}
Loading

0 comments on commit 4c478a8

Please sign in to comment.