silverstripe / cwp-pdfexport

Adds PDF export capability for CWP pages
BSD 3-Clause "New" or "Revised" License
4 stars 5 forks source link

BasePage_Controller->generatePDF() fails to download SSL assets #11

Open robbieaverill opened 6 years ago

robbieaverill commented 6 years ago

Migrated from GitLab


@chillu:

If you generate a PDF from https://myproject.cwp.govt.nz, any SSL references in the temporary HTML file will fail. Given that SilverStripe inserts a , this applies to all CSS files and images by default. There's no error output from wkhtmltopdf, the files simply won't show.

Background: Due to an (undocumented?) feature on CWP servers, requests to *.cwp.govt.nz are routed within the Revera network, not through Incapsula. SSL is terminated in Incapsula, which means the actual web server doesn't run SSL, so will decline https:// requests.

Given this is a quirk specific to the CWP infrastructure, and any CWP environment also responds on SSL by default, this is a particularly frustrating experience for developers.

Option A: Whitelist which *.cwp.govt.nz domains need local routing. Use wkhtmltopdf with --proxy for all requests.

Option B: Rewrite HTML file to http:// instead. That's not an ideal option, since some projects might opt to only have their site available via SSL.


Comments

Usernames adjusted for GitHub

Cam Findlay @camfindlay findlay commented a year ago

Has this been addressed @chillu ?

Michal Kleiner @michalkleiner commented a year ago

In fact, it fails downloading even standard, non-SSL assets, if some other domain than *.cwp.govt.nz is used.

Today, there seems to be a solution to this, using the --proxy parameters. We're testing that out now for www.building.govt.nz site. If it's working, I can create a pull request with the code.

Naomi Guyer @adrexia commented a year ago

@michalkleiner as you may know, we're also running into this with tenancy. Did you have any luck with the proxy params?

Michal Kleiner @michal.kleiner commented a year ago

Hi Naomi (@adrexia),

yes, Becs mentioned it. Good news is that the proxy params worked for the images!

We had a custom solution for styles already in place (inlined with updated paths), but I believe the proxy params would resolve that too.

Also if you somehow rely on the base tag (e.g. we use it for CSS generated URLs to be displayed in the PDF exported documents), providing a custom URL as alternate_base_url does the job.

Here is our generatePDF() function from Page_Controller class (Page.php file) overriding the core one. Look for $proxy, $command and alternate_base_url.

/**
 * Render the page as PDF using wkhtmltopdf.
 */
public function generatePDF() {
    if(!Config::inst()->get('BasePage', 'pdf_export')) return false;

    $binaryPath = Config::inst()->get('BasePage', 'wkhtmltopdf_binary');
    if(!$binaryPath || !is_executable($binaryPath)) {
        if(defined('WKHTMLTOPDF_BINARY') && is_executable(WKHTMLTOPDF_BINARY)) {
            $binaryPath = WKHTMLTOPDF_BINARY;
        }
    }

    if(!$binaryPath) {
        user_error('Neither WKHTMLTOPDF_BINARY nor BasePage.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->dataRecord->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));

    // Force http protocol on CWP and ensure a domain which supports https is used - fetching from localhost without using the proxy, SSL terminates on gateway.
    if (defined('CWP_ENVIRONMENT')) {
        Config::inst()->nest();
        Config::inst()->update('Director', 'alternate_protocol', 'http');
        Config::inst()->update('Director', 'alternate_base_url', 'http://www.building.govt.nz/');
    }

    $bodyViewer = $this->getViewer('pdf');

    // write the output of this page to HTML, ready for conversion to PDF
    file_put_contents($bodyFile, $bodyViewer->process($this));

    // get the viewer for the current template with _pdffooter
    $footerViewer = $this->getViewer('pdffooter');

    // write the output of the footer template to HTML, ready for conversion to PDF
    file_put_contents($footerFile, $footerViewer->process($this));

    if (defined('CWP_ENVIRONMENT')) {
        Config::inst()->unnest();
    }

    // finally, generate the PDF
    $proxy = '';
    if (defined('SS_OUTBOUND_PROXY') && defined('SS_OUTBOUND_PROXY_PORT')) {
        $proxy = ' --proxy ' . SS_OUTBOUND_PROXY . ':' . SS_OUTBOUND_PROXY_PORT;
    }
    $command = WKHTMLTOPDF_BINARY . $proxy . ' --outline -B 40pt -L 20pt -R 20pt -T 20pt --encoding utf-8 --orientation Portrait --disable-javascript --quiet --print-media-type ';

    $return_val = 0;
    $output = array();
    $command = $command . " --footer-html \"$footerFile\" \"$bodyFile\" \"$pdfFile\"";
    exec($command . " &> /dev/stdout", $output, $return_val);

    // remove temporary file
    unlink($bodyFile);
    unlink($footerFile);

    // output any errors
    if ($return_val != 0) {
        user_error('wkhtmltopdf failed: ' . implode("\n", $output), E_USER_ERROR);
    }

    // serve the generated file
    return SS_HTTPRequest::send_file(file_get_contents($pdfFile), basename($pdfFile), 'application/pdf');
}

The line Config::inst()->update('Director', 'alternate_base_url', 'http://www.building.govt.nz/'); can be left out, if you don't care about the base tag value (which will be your .cwp.govt.nz domain).

CWP support thas indicated that similar fix will be included in the core code once tidied up.

Cheers Michal

Naomi Guyer @adrexia commented a year ago

@chillu Any word on if this will be fixed in core? Or should we go with @michalkleiner's solution?

robbieaverill commented 5 years ago

Migrated from silverstripe/cwp to this module