Commit f8110268 authored by jaapjansma's avatar jaapjansma
Browse files

refactored createPDF action

parent ee586ecd
......@@ -7,11 +7,11 @@
namespace Civi\ActionProvider\Action\Communication;
use Civi\ActionProvider\Action\AbstractAction;
use Civi\ActionProvider\Parameter\OptionGroupByNameSpecification;
use \Civi\ActionProvider\Parameter\ParameterBagInterface;
use Civi\ActionProvider\Parameter\SpecificationBag;
use Civi\ActionProvider\Parameter\Specification;
use Civi\ActionProvider\Utils\FileWriter;
use Civi\ActionProvider\Utils\Files;
use CRM_ActionProvider_ExtensionUtil as E;
class CreatePdf extends AbstractAction {
......@@ -21,43 +21,47 @@ class CreatePdf extends AbstractAction {
*/
protected $zip;
protected $messages = array();
protected $pdfLetterActivityType;
public function doAction(ParameterBagInterface $parameters, ParameterBagInterface $output) {
$domain = \CRM_Core_BAO_Domain::getDomain();
$message = $parameters->getParameter('message');
$contactId = $parameters->getParameter('contact_id');
$filename = $this->configuration->getParameter('filename');
$fileNameWithoutContactId = $filename . '.pdf';
$filenameWithContactId = $filename . '_' . $contactId . '.pdf';
$subdir = $this->createSubDir($this->currentBatch);
$contact = civicrm_api3('Contact', 'getsingle', array('id' => $contactId));
$tokens = \CRM_Utils_Token::getTokens($message);
\CRM_Utils_Hook::tokenValues($contact, $contactId, NULL, $tokens);
// call token hook
$hookTokens = array();
\CRM_Utils_Hook::tokens($hookTokens);
$categories = array_keys($hookTokens);
$message = \CRM_Utils_Token::replaceDomainTokens($message, $domain, TRUE, $tokens, TRUE);
$message = \CRM_Utils_Token::replaceHookTokens($message, $contact, $categories, TRUE);
\CRM_Utils_Token::replaceGreetingTokens($message, $contact, $contactId);
$message = \CRM_Utils_Token::replaceContactTokens($message, $contact, FALSE, $tokens, FALSE, TRUE);
$message = \CRM_Utils_Token::replaceComponentTokens($message, $contact, $tokens, TRUE);
if (defined('CIVICRM_MAIL_SMARTY') && CIVICRM_MAIL_SMARTY) {
$smarty = \CRM_Core_Smarty::singleton();
$message = $smarty->fetch("string:{$message}");
$processedMessage = $this->getProcessedMessage($contactId, $message);
if ($processedMessage === false) {
return;
}
$this->messages[] = $processedMessage;
$pdfContents = \CRM_Utils_PDF_Utils::html2pdf(array($processedMessage), $fileNameWithoutContactId, TRUE);
$contents = \CRM_Utils_PDF_Utils::html2pdf($message, $filenameWithContactId, TRUE);
if ($this->zip) {
$this->zip->addFromString($filenameWithContactId, $contents);
if ($this->currentBatch && $this->zip) {
$this->zip->addFromString($filenameWithContactId, $pdfContents);
}
$file = $this->createActivity($contactId, $message, $pdfContents, $fileNameWithoutContactId);
$output->setParameter('filename', $file['name']);
$output->setParameter('url', $file['url']);
$output->setParameter('path', $file['path']);
}
/**
* @param $contactId
* @param $message
* @param $pdfContents
* @param $filename
* @return array
* Returns the file array
*/
protected function createActivity($contactId, $message, $pdfContents, $filename) {
$activityTypeId = $this->getPdfLetterActivityTypeId();
$activityParams = array(
'activity_type_id' => \CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Print PDF Letter'),
'activity_type_id' => $activityTypeId,
'activity_date_time' => date('YmdHis'),
'details' => $message,
'target_contact_id' => $contactId,
......@@ -66,16 +70,77 @@ class CreatePdf extends AbstractAction {
$attachment = civicrm_api3('Attachment', 'create', array(
'entity_table' => 'civicrm_activity',
'entity_id' => $result['id'],
'name' => $fileNameWithoutContactId,
'name' => $filename,
'mime_type' => 'application/pdf',
'content' => $contents,
'content' => $pdfContents,
));
$file = reset($attachment['values']);
return reset($attachment['values']);
}
$output->setParameter('filename', $file['name']);
$output->setParameter('url', $file['url']);
$output->setParameter('path', $file['path']);
/**
* @return array
* @throws \CiviCRM_API3_Exception
*/
protected function getPdfLetterActivityTypeId() {
if (!$this->pdfLetterActivityType) {
$activityTypeName = $this->configuration->getParameter('activity_type_id');
$this->pdfLetterActivityType = civicrm_api3('OptionValue', 'getvalue', [
'option_group_id' => 'activity_type',
'name' => $activityTypeName,
'return' => 'value'
]);
}
return $this->pdfLetterActivityType;
}
/**
* Returns a processed message. Meaning that all tokens are replaced with their value.
* This message could then be used to generate the PDF.
*
* @param $contactId
* @param $message
* @return string
*/
protected function getProcessedMessage($contactId, $message) {
$tokenCategories = self::getTokenCategories();
//time being hack to strip ' '
//from particular letter line, CRM-6798
\CRM_Contact_Form_Task_PDFLetterCommon::formatMessage($message);
$messageToken = \CRM_Utils_Token::getTokens($message);
$returnProperties = array();
if (isset($messageToken['contact'])) {
foreach ($messageToken['contact'] as $key => $value) {
$returnProperties[$value] = 1;
}
}
$params = array('contact_id' => $contactId);
list($contact) = \CRM_Utils_Token::getTokenDetails($params,
$returnProperties,
false,
false,
NULL,
$messageToken,
null
);
if (civicrm_error($contact)) {
return false;
}
$tokenHtml = \CRM_Utils_Token::replaceContactTokens($message, $contact[$contactId], TRUE, $messageToken);
$tokenHtml = \CRM_Utils_Token::replaceHookTokens($tokenHtml, $contact[$contactId], $tokenCategories, TRUE);
if (defined('CIVICRM_MAIL_SMARTY') && CIVICRM_MAIL_SMARTY) {
$smarty = \CRM_Core_Smarty::singleton();
// also add the contact tokens to the template
$smarty->assign_by_ref('contact', $contact);
$tokenHtml = $smarty->fetch("string:$tokenHtml");
}
return $tokenHtml;
}
/**
......@@ -84,15 +149,14 @@ class CreatePdf extends AbstractAction {
* @param $batchName
*/
public function initializeBatch($batchName) {
// Child classes could override this function
// E.g. create a directory
$this->createSubDir($batchName);
$subdir = $this->createSubDir();
$outputName = \CRM_Core_Config::singleton()->templateCompileDir . $subdir.'/'.$batchName.'.zip';
$this->zip = new \ZipArchive();
if ($this->zip->open($outputName, \ZipArchive::CREATE) !== TRUE) {
$this->zip = null;
$outputMode = $this->configuration->getParameter('batch_output_mode');
if ($outputMode == 'zip') {
$subdir = Files::createRestrictedDirectory('createpdf');
$outputName = \CRM_Core_Config::singleton()->templateCompileDir . $subdir . '/' . $batchName . '.zip';
$this->zip = new \ZipArchive();
if ($this->zip->open($outputName, \ZipArchive::CREATE) !== TRUE) {
$this->zip = NULL;
}
}
$this->currentBatch = $batchName;
......@@ -108,32 +172,44 @@ class CreatePdf extends AbstractAction {
public function finishBatch($batchName, $isLastBatch=false) {
// Child classes could override this function
// E.g. merge files in a directorys
$subdir = $this->createSubDir();
$downloadName = $this->configuration->getParameter('filename').'.zip';
if ($this->zip) {
$this->zip->close();
if ($isLastBatch) {
$downloadUrl = \CRM_Utils_System::url('civicrm/actionprovider/downloadfile', [
'filename' => $batchName . '.zip',
'subdir' => $subdir,
'downloadname' => $downloadName
]);
\CRM_Core_Session::setStatus(E::ts('<a href="%1">Download document(s)<a/>', [1 => $downloadUrl]), E::ts('Created PDF'), 'success');
$subdir = Files::createRestrictedDirectory('createpdf');
$downloadName = $this->configuration->getParameter('filename').'.zip';
$this->createDownloadStatusMessage($batchName.'.zip', $subdir, $downloadName);
}
} else {
$subdir = Files::createRestrictedDirectory('createpdf');
$basePath = \CRM_Core_Config::singleton()->templateCompileDir . $subdir;
$htmlFile = $basePath .'/'.$batchName.'.html';
if (count($this->messages)) {
$this->addPagesToHtmlFile($this->messages, $htmlFile);
}
if ($isLastBatch) {
$pdfFile = $basePath .'/'.$batchName.'.pdf';
$this->convertHtmlToPdf($htmlFile, $pdfFile);
$downloadName = $this->configuration->getParameter('filename').'.pdf';
$this->createDownloadStatusMessage($batchName.'.pdf', $subdir, $downloadName);
}
}
}
protected function createSubDir() {
$subDir = 'action_provider';
$basePath = \CRM_Core_Config::singleton()->templateCompileDir . $subDir;
\CRM_Utils_File::createDir($basePath);
\CRM_Utils_File::restrictAccess($basePath.'/');
$subDir .= '/createpdf';
$basePath = \CRM_Core_Config::singleton()->templateCompileDir . $subDir;
\CRM_Utils_File::createDir($basePath);
\CRM_Utils_File::restrictAccess($basePath.'/');
return $subDir;
/**
* Creates a status message with a download link.
*
* @param $filename
* @param $subdir
* @param $downloadName
*/
protected function createDownloadStatusMessage($filename, $subdir, $downloadName) {
$downloadUrl = \CRM_Utils_System::url('civicrm/actionprovider/downloadfile', [
'filename' => $filename,
'subdir' => $subdir,
'downloadname' => $downloadName
]);
\CRM_Core_Session::setStatus(E::ts('<a href="%1">Download document(s)<a/>', [1 => $downloadUrl]), E::ts('Created PDF'), 'success');
}
/**
......@@ -149,8 +225,19 @@ class CreatePdf extends AbstractAction {
}
public function getConfigurationSpecification() {
$filename = new Specification('filename', 'String', E::ts('Filename'), true, E::ts('document'));
$filename->setDescription(E::ts('Without the extension .pdf or .zip'));
$batch_output_mode = new Specification('batch_output_mode', 'String', E::ts("Batch output mode"), true, 'pdf', null, array(
'zip' => E::ts('All files in one zip'),
'pdf' => E::ts('Merge all files into one PDF'),
));
$batch_output_mode->setDescription(E::ts('When this action is executed in batch mode, meaning that it generates more than one pdf, in which way do you want to retrieve the generated PDFs'));
$activity = new OptionGroupByNameSpecification('activity_type_id', 'activity_type', E::ts('PDF Letter Activity'), true, 'Print PDF Letter');
return new SpecificationBag(array(
new Specification('filename', 'String', E::ts('Filename'), true, E::ts('document')),
$filename,
$batch_output_mode,
$activity
));
}
......@@ -162,5 +249,179 @@ class CreatePdf extends AbstractAction {
));
}
/**
* Returns a help text for this action.
*
* The help text is shown to the administrator who is configuring the action.
* Override this function in a child class if your action has a help text.
*
* @return string|false
*/
public function getHelpText() {
return E::ts("
This action generates PDF files for the contacts. <br />
When this action is used in a batch you can define how you want to download the
generated PDFs: either in one zip file or in one pdf. <br />
<br />
The input for this action is a contact ID and the message. You can use the <em>Find Message Template by name</em> action
to retrieve the message.
");
}
/**
* Get the categories required for rendering tokens.
*
* @return array
*/
protected static function getTokenCategories() {
if (!isset(\Civi::$statics[__CLASS__]['token_categories'])) {
$tokens = array();
\CRM_Utils_Hook::tokens($tokens);
\Civi::$statics[__CLASS__]['token_categories'] = array_keys($tokens);
}
return \Civi::$statics[__CLASS__]['token_categories'];
}
/**
* Initialize an HTML file. The HTML file is later on converted to a PDF.
*
* Function taken from CRM_Utils_PDF_Utils::html2pdf
*
* @return string
*/
protected function initializeHtmlFile() {
$format = \CRM_Core_BAO_PdfFormat::getDefaultValues();
$metric = \CRM_Core_BAO_PdfFormat::getValue('metric', $format);
$t = \CRM_Core_BAO_PdfFormat::getValue('margin_top', $format);
$r = \CRM_Core_BAO_PdfFormat::getValue('margin_right', $format);
$b = \CRM_Core_BAO_PdfFormat::getValue('margin_bottom', $format);
$l = \CRM_Core_BAO_PdfFormat::getValue('margin_left', $format);
// Add a special region for the HTML header of PDF files:
$pdfHeaderRegion = \CRM_Core_Region::instance('export-document-header', FALSE);
$htmlHeader = ($pdfHeaderRegion) ? $pdfHeaderRegion->render('', FALSE) : '';
$html = "
<html>
<head>
<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>
<style>@page { margin: {$t}{$metric} {$r}{$metric} {$b}{$metric} {$l}{$metric}; }</style>
<style type=\"text/css\">@import url(" . \CRM_Core_Config::singleton()->userFrameworkResourceURL . "css/print.css);</style>
{$htmlHeader}
</head>
<body>
<div id=\"crm-container\">\n";
return $html;
}
/**
* Convert an array of pages to html and append it to an already existing html file
*
* Function taken from CRM_Utils_PDF_Utils::html2pdf
*
* @param $pages
* @param $html_file
*/
protected function addPagesToHtmlFile($pages, $html_file) {
$html = "";
if (!file_exists($html_file)) {
$html = $this->initializeHtmlFile();
} else {
// Append a line break to the file before adding the pages.
$html = "\n<div style=\"page-break-after: always\"></div>\n";
}
// Strip <html>, <header>, and <body> tags from each page
$htmlElementstoStrip = [
'@<head[^>]*?>.*?</head>@siu',
'@<script[^>]*?>.*?</script>@siu',
'@<body>@siu',
'@</body>@siu',
'@<html[^>]*?>@siu',
'@</html>@siu',
'@<!DOCTYPE[^>]*?>@siu',
];
$htmlElementsInstead = ['', '', '', '', '', ''];
foreach ($pages as & $page) {
$page = preg_replace($htmlElementstoStrip,
$htmlElementsInstead,
$page
);
}
// Glue the pages together
$html .= implode("\n<div style=\"page-break-after: always\"></div>\n", $pages);
file_put_contents($html_file, $html, FILE_APPEND);
}
protected function convertHtmlToPdf($html_file, $output_file) {
$html = "</div></body></html>";
file_put_contents($html_file, $html, FILE_APPEND);
$format = \CRM_Core_BAO_PdfFormat::getDefaultValues();
$paperSize = \CRM_Core_BAO_PaperSize::getByName($format['paper_size']);
$paper_width = \CRM_Utils_PDF_Utils::convertMetric($paperSize['width'], $paperSize['metric'], 'pt');
$paper_height = \CRM_Utils_PDF_Utils::convertMetric($paperSize['height'], $paperSize['metric'], 'pt');
// dompdf requires dimensions in points
$paper_size = array(0, 0, $paper_width, $paper_height);
$orientation = \CRM_Core_BAO_PdfFormat::getValue('orientation', $format);
$metric = \CRM_Core_BAO_PdfFormat::getValue('metric', $format);
$t = \CRM_Core_BAO_PdfFormat::getValue('margin_top', $format);
$r = \CRM_Core_BAO_PdfFormat::getValue('margin_right', $format);
$b = \CRM_Core_BAO_PdfFormat::getValue('margin_bottom', $format);
$l = \CRM_Core_BAO_PdfFormat::getValue('margin_left', $format);
$margins = array($metric, $t, $r, $b, $l);
if (\CRM_Core_Config::singleton()->wkhtmltopdfPath) {
$this->_html2pdf_wkhtmltopdf($paper_size, $orientation, $margins, $html_file, $output_file);
}
else {
$this->_html2pdf_dompdf($paper_size, $orientation, $html_file, $output_file);
}
unlink($html_file);
}
/**
* @param $paper_size
* @param $orientation
* @param $margins
* @param $html_file
* @param string $fileName
*/
protected function _html2pdf_wkhtmltopdf($paper_size, $orientation, $margins, $html_file, $fileName) {
require_once 'packages/snappy/src/autoload.php';
$config = \CRM_Core_Config::singleton();
$snappy = new \Knp\Snappy\Pdf($config->wkhtmltopdfPath);
$snappy->setOption("page-width", $paper_size[2] . "pt");
$snappy->setOption("page-height", $paper_size[3] . "pt");
$snappy->setOption("orientation", $orientation);
$snappy->setOption("margin-top", $margins[1] . $margins[0]);
$snappy->setOption("margin-right", $margins[2] . $margins[0]);
$snappy->setOption("margin-bottom", $margins[3] . $margins[0]);
$snappy->setOption("margin-left", $margins[4] . $margins[0]);
$pdf = $snappy->generate($html_file, $fileName);
}
/**
* @param $paper_size
* @param $orientation
* @param $html_file
* @param string $fileName
*
* @return string
*/
protected function _html2pdf_dompdf($paper_size, $orientation, $html_file, $fileName) {
// CRM-12165 - Remote file support required for image handling.
$options = new \Dompdf\Options();
$options->set('isRemoteEnabled', TRUE);
$options->set('chroot', dirname($html_file));
$dompdf = new \Dompdf\Dompdf($options);
$dompdf->set_paper($paper_size, $orientation);
$dompdf->loadHtmlFile($html_file);
$dompdf->render();
file_put_contents($fileName, $dompdf->output());
}
}
\ No newline at end of file
......@@ -6,27 +6,27 @@
namespace Civi\ActionProvider\Utils;
class FileWriter {
class Files {
/**
* Write to a file in a restricted directory. E.g. a directory which is not accesible by
* a url.
* Creates a directory under templates_c/action_provider
* And adds a .htaccess to this directory so files could not be downloaded directly from the server.
*
* @param $contents
* @param $filename
* @param $subDir
* @param string $dir
* The subdirectory without / on the end. E.g. createpdf or createpdf/batch1233
* @return string
* The full file path.
*/
public static function writeFile($contents, $filename, $subDir) {
$basePath = \CRM_Core_Config::singleton()->templateCompileDir . $subDir;
\CRM_Utils_File::createDir($basePath);
\CRM_Utils_File::restrictAccess($basePath.'/');
$fullFilePath = $basePath.'/'. $filename;
public static function createRestrictedDirectory($dir) {
$subDir = 'action_provider/'.$dir;
$fullPath = \CRM_Core_Config::singleton()->templateCompileDir . $subDir;
if (is_dir($fullPath)) {
\CRM_Utils_File::restrictAccess($fullPath.'/');
return $subDir;
}
file_put_contents($fullFilePath, $contents);
return $fullFilePath;
\CRM_Utils_File::createDir($fullPath);
\CRM_Utils_File::restrictAccess($fullPath.'/');
return $subDir;
}
}
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment