Skip to content

Instantly share code, notes, and snippets.

@jjmontgo
Created October 31, 2018 23:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jjmontgo/2be75d3fb36d680563a6d7d40931d13d to your computer and use it in GitHub Desktop.
Save jjmontgo/2be75d3fb36d680563a6d7d40931d13d to your computer and use it in GitHub Desktop.
A PHP Class to fetch a PDF URL from the PDF Service
<?php
/**
* Makes a POST request to a Lambda function through an API Gateway
* which encapsulates a call to wkhtmltopdf, rendering an HTML
* document as a PDF file and returning a signed URL to the
* file in an S3 bucket.
*
* The POST can contain the following parameters:
* body - A complete HTML5 document to be rendered in PDF (required)
* header - A complete HTML5 document to be rendered in the header of every page (optional)
* footer - A complete HTML5 document to be rendered in the footer of every page (optional)
* filename - The preferred filename for the rendered PDF document (optional)
* If not provided it will be 'generated.pdf'
* margin_left, margin_right, margin_top, margin_bottom - optional integer values, in mm
*
* Make sure the following definitions are added to a file outside version control:
* define("AWS_REGION", "");
* define("PDF_API_GATEWAY_URL", "********.execute-api.us-east-1.amazonaws.com");
* define("PDF_API_GATEWAY_STAGE", "***"); // usually "prod"
* define("PDF_ACCESS_KEY", "");
* define("PDF_SECRET_KEY", "");
*
* Then call the generator with:
*
* GeneratePDF::fromHTML("<!doctype html><html>...</html>")
* ->header("<!doctype html><html>...</html>")
* ->footer("<!doctype html><html>...</html>")
* ->leftMargin(0)
* ->rightMargin(0)
* ->andSendToBrowserAs("my-new-pdf-file.pdf"); // must be called, causes a redirect and exit
*/
class GeneratePDF {
private static $instance;
private $postParams = array();
/**
* Instantiate the class and set the required body POST parameter
*/
public static function fromHTML($bodyHtml) {
if (!isset(static::$instance)) {
static::$instance = new GeneratePDF_V2();
}
static::$instance->setParam("body", $bodyHtml);
return static::$instance;
}
/**
* Optionally put the PDF in a folder for the project
* @param string $projectName This should follow appropriate standards for directory names
*/
public function projectName($projectName) {
$this->setParam("project_name", $projectName);
return $this;
}
public function header($headerHtml) {
$this->setParam("header", $headerHtml);
return $this;
}
public function footer($footerHtml) {
$this->setParam("footer", $footerHtml);
return $this;
}
public function topMargin($margin) {
$this->setParam("margin_top", $margin);
return $this;
}
public function leftMargin($margin) {
$this->setParam("margin_left", $margin);
return $this;
}
public function bottomMargin($margin) {
$this->setParam("margin_bottom", $margin);
return $this;
}
public function rightMargin($margin) {
$this->setParam("margin_right", $margin);
return $this;
}
public function andSendToBrowserAs($filename) {
$this->setParam("filename", $filename);
$signedPdfUrl = $this->getSignedPdfUrl();
header("Location: {$signedPdfUrl}");
exit();
}
public function setParam($key, $value) {
$this->postParams[$key] = $value;
return $this;
}
/**
* Delivers HTML to AWS and receives a URL to the rendered PDF
*/
private function getSignedPdfUrl() {
$postBody = http_build_query($this->postParams);
$date = new DateTime('UTC');
$xAmzDate = $date->format('Ymd\THis\Z');
$headers = array(
'Content-Type: application/x-www-form-urlencoded; charset=utf-8',
'Host: ' . PDF_API_GATEWAY_URL,
'X-Amz-Date: ' . $xAmzDate,
);
$authorizationHeader = $this->getAuthorizationHeader($xAmzDate, $headers, $postBody);
$headers[] = "Authorization: " . $authorizationHeader;
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://" . PDF_API_GATEWAY_URL . "/" . PDF_API_GATEWAY_STAGE);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_POST, count($this->postParams));
curl_setopt($ch, CURLOPT_ENCODING, "gzip");
curl_setopt($ch, CURLOPT_POSTFIELDS, $postBody); // the data to post
curl_setopt($ch, CURLOPT_HEADER, 0); // dont include the header in the output
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); // return the response as a string
// curl_setopt($ch, CURLOPT_CAINFO, '/usr/share/ca-certificates/cacert.pem');
if (defined('CURLOPT_IPRESOLVE') && defined('CURL_IPRESOLVE_V4')){
curl_setopt( $ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
}
$info = curl_getinfo($ch);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($httpCode !== 200) {
throw new Exception('Failed to receive PDF file; remote server returned '.$httpCode.' status' . "\n\n" . $response);
}
return $response;
}
/**
* @see https://docs.aws.amazon.com/apigateway/api-reference/signing-requests/
*/
private function getAuthorizationHeader($xAmzDate, $headers, $postBody) {
// Task 1: Create a Canonical Request for Signature Version 4
$canonicalHeaders = "";
foreach ($headers as $header) {
$headerParts = explode(":", $header);
$canonicalHeaders .= trim(strtolower($headerParts[0])) . ":" . trim($headerParts[1]) . "\n";
}
$signedHeaders = "content-type;host;x-amz-date";
$canonicalRequest =
"POST\n" .
"/" . PDF_API_GATEWAY_STAGE . "\n" .
"\n" .
$canonicalHeaders . "\n" .
$signedHeaders . "\n" .
hash("sha256", $postBody);
$hashedCanonicalRequest = hash("sha256", $canonicalRequest);
// Task 2: Create a String to Sign for Signature Version 4
$dateObj = new DateTime('UTC');
$date = $dateObj->format('Ymd');
$region = AWS_REGION;
$service = "execute-api";
$alg = "sha256";
$credentialScope = "{$date}/{$region}/{$service}/aws4_request";
$stringToSign =
"AWS4-HMAC-SHA256\n" .
$xAmzDate . "\n" .
"{$credentialScope}\n" .
$hashedCanonicalRequest;
// Task 3: Calculate the Signature for AWS Signature Version 4
$secretKey = PDF_SECRET_KEY;
$kSecret = "AWS4" . $secretKey; // @todo update this with secret key
$kDate = hash_hmac($alg, $date, $kSecret, true);
$kRegion = hash_hmac($alg, $region, $kDate, true);
$kService = hash_hmac($alg, $service, $kRegion, true );
$kSigning = hash_hmac($alg, 'aws4_request', $kService, true);
$signature = hash_hmac($alg, $stringToSign, $kSigning);
// Task 4: Add the Signature to the HTTP Request
// Using authorization header method
$accessKey = PDF_ACCESS_KEY; // to update
$authorizationHeader =
'AWS4-HMAC-SHA256 ' .
'Credential=' . $accessKey . '/' . $credentialScope . ', ' .
'SignedHeaders=' . $signedHeaders . ', ' . 'Signature=' . $signature;
return $authorizationHeader;
}
/**
* Private constructor called internally
*/
private function __construct() {}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment