app = Factory::getApplication();
$this->doc = $this->app->getDocument();
$this->input = $this->app->input;
$this->user = $this->app->getIdentity();
$this->userId = $this->user->get('id');
$this->pdfHelper = new \visFormsPdfHelper($this->input, $this->user);
// payload
$this->pages = array();
HTMLHelper::_('jquery.framework');
}
protected function initialize() {
$this->id = $this->input->get('id', '0', 'INT');
$this->fid = $this->input->get('fid', '0', 'INT');
$this->pdfOutputMethod = $this->app->getUserState('pdf.out', 'I');
$this->pdfOutputName = 'visforms.pdf';
// sql data selection
$this->did = $this->input->get('did', '0', 'INT');
$this->data = $this->input->get('data', '', 'STRING');
$this->published = $this->input->get('published', '');
$this->start = $this->input->get('start', '');
$this->limit = $this->input->get('limit', '');
$this->sort = $this->input->get('fullordering', '', 'raw');
}
abstract protected function initializeModels();
protected function initializePayLoad() {
// get pdf template: user state template before database loaded template
$userStateData = $this->app->getUserState('pdf.preview.data');
if(isset($userStateData)) {
$this->docTemplate = $userStateData->document;
$this->hdrTemplate = $userStateData->header;
$this->ftrTemplate = $userStateData->footer;
// only once
$this->app->setUserState('pdf.preview.data', null);
}
else {
$this->docTemplate = $this->pdfItem->doc_template;
$this->hdrTemplate = $this->pdfItem->hdr_template;
$this->ftrTemplate = $this->pdfItem->ftr_template;
}
// set class members
$this->pdfOutputName = $this->formItem->name . '.pdf';
$this->settings = $this->pdfItem->settings;
$this->pdfPage = $this->pdfItem->page;
$this->pdfImage = $this->pdfItem->image;
$this->pdfDocument = $this->pdfItem->document;
$this->pdfStatements = $this->pdfItem->statements;
// Replace placeholder in header Template
$this->processHeaderFooter('hdrTemplate');
// Replace placeholder in footer Template
$this->processHeaderFooter('ftrTemplate');
}
protected function initializeTCPDF() {
// overwrite joomla content type
$this->doc = Factory::getApplication()->getDocument();
$this->doc->setMimeEncoding('application/pdf');
// local abbreviations
$settings = $this->settings;
// create new PDF document
$orientation = 'v' == $this->pdfPage['orientation'] ? 'P' : 'L';
$format = $this->pdfPage['format'];
$pdf = new \visTCPDF($orientation, VISFORMS_PDF_UNIT, $format, true, 'UTF-8', false);
// set document information
$pdf->SetCreator(VISFORMS_PDF_CREATOR);
$pdf->SetAuthor($this->pdfDocument['author']);
$pdf->SetTitle($this->pdfItem->title);
$pdf->SetSubject($this->pdfDocument['subject']);
$pdf->SetKeywords($this->pdfDocument['keywords']);
$pdf->SetCompression(1 == $this->pdfDocument['compressed']);
// set header data
$pdf->setHeaderTemplate($this->hdrTemplate);
$pdf->setShowHeader($this->pdfPage['show_header']);
// set footer data
$pdf->setFooterTemplate($this->ftrTemplate);
$pdf->setShowFooter($this->pdfPage['show_footer']);
// set background image data
$pdf->setBackgroundImage($this->pdfImage['folder'] . '/' . $this->pdfImage['file']);
$pdf->setShowBackgroundImage(1 == $this->pdfImage['show']);
// check and set font areas
$fontArea = 'font_family_header';
if( !isset($settings[$fontArea]) || '' === $settings[$fontArea] ) {
$settings[$fontArea] = $settings['font_family'];
}
$pdf->setHeaderFont(Array($settings[$fontArea], '', $settings['font_size_header']/10));
$fontArea = 'font_family_footer';
if( !isset($settings[$fontArea]) || '' === $settings[$fontArea] ) {
$settings[$fontArea] = $settings['font_family'];
}
$pdf->setFooterFont(Array($settings[$fontArea], '', $settings['font_size_footer']/10));
// set default monospaced font
$pdf->SetDefaultMonospacedFont(VISFORMS_PDF_FONT_MONOSPACED);
// set margins
$pdf->SetMargins($settings['margin_left'], $settings['margin_top'], $settings['margin_right']);
$pdf->SetHeaderMargin($settings['margin_header']);
$pdf->SetFooterMargin($settings['margin_footer']);
// set auto page breaks
$pdf->SetAutoPageBreak(TRUE, $settings['margin_bottom']);
// set image scale factor
$pdf->setImageScale(VISFORMS_PDF_IMAGE_SCALE_RATIO);
// set some language-dependent strings (optional)
if (@file_exists(dirname(__FILE__).'/lang/eng.php')) {
global $l;
require_once(dirname(__FILE__).'/lang/eng.php');
$pdf->setLanguageArray($l);
}
// ---------------------------------------------------------
// set default font sub setting mode
$pdf->setFontSubsetting(true);
// Set font
// dejavusans is a UTF-8 Unicode font, if you only need to
// print standard ASCII chars, you can use core fonts like
// helvetica or times to reduce file size.
$pdf->SetFont($settings['font_family'], '', $settings['font_size']/10, '', true);
// set local to class member
$this->tcpdf = $pdf;
}
// overwrites
public function display($tpl = null) {
$this->initialize();
$this->initializeModels();
$this->initializePayLoad();
$this->initializeTCPDF();
$this->setOutputFileName();
$errors = $this->get('Errors');
if (!empty($errors) && is_array($errors)) {
Log::add(implode('
', $errors), Log::WARNING, 'jerror');
return false;
}
try {
$this->processTemplate();
}
catch (\Exception $ex) {
echo $ex->getMessage();
}
// This method has several options, check the source code documentation for more information.
// if we get pdf as string, we need to return the string
if ($this->pdfOutputMethod == 'S') {
return $this->tcpdf->Output('', $this->pdfOutputMethod);
}
// otherwise document is send directly to browser
else {
$this->tcpdf->Output($this->pdfOutputName, $this->pdfOutputMethod);
}
}
protected function processHeaderFooter($part) {
if (empty($this->$part) || empty($this->dataItems)) {
return;
}
try {
$text = $this->processItem($this->dataItems[0], $this->$part);
$this->$part = $text;
}
catch (\RuntimeException $e) {
return;
}
}
protected function processTemplate() {
// this function only replaces placeholder where a replace value (even an empty one) is given in the "data" list
// local abbreviations
$tcpdf = $this->tcpdf;
$pagePerEntry = $this->pdfItem->page['page_per_entry'];
// $form = $this->formItem;
$counter = 0;
$page = array();
$this->addManuallPageBreaks();
// todo: make sure the string handling is resource friendly (like string += string killers)
// for each data entry
if (empty($this->dataItems)) {
$text = Text::_('COM_VISFORMS_RECORDSETS_FOUND') . ' 0';
$tcpdf->AddPage();
$tcpdf->writeHTMLCell(0, 0, '', '', $text, 0, 1, 0, true, '', true);
}
else {
//test if we have a template with class = loop
$hasLoop = $this->checkForLoop($this->docTemplate);
if ($hasLoop) {
$text = $this->processLoopTemplate();
if (1 == $pagePerEntry) {
// immediately write this data part starting a new page
$tcpdf->AddPage();
$tcpdf->writeHTMLCell(0, 0, '', '', $text, 0, 1, 0, true, '', true);
}
else {
// entry after entry
$page[] = $text;
// todo: this would leave us without any space between end and start of new data part
}
++$counter;
}
else {
foreach ($this->dataItems as $item) {
$text = $this->processItem($item, $this->docTemplate);
if (1 == $pagePerEntry) {
// immediately write this data part starting a new page
$tcpdf->AddPage();
$tcpdf->writeHTMLCell(0, 0, '', '', $text, 0, 1, 0, true, '', true);
}
else {
// entry after entry
$page[] = $text;
// todo: this would leave us without any space between end and start of new data part
}
++$counter;
}
}
if (0 == $pagePerEntry) {
// we could although implode with a p-Element in order to get some space '
' $tcpdf->AddPage(); $tcpdf->writeHTMLCell(0, 0, '', '', implode(' ', $page), 0, 1, 0, true, '', true); } } } private function processItem ($item, $text) { foreach ($item as $key => $value) { // if key is a field, replace db field name with field name // process value with option free placeholderEntry if (str_starts_with($key, 'F')) { $field = $this->pdfHelper->findFieldFromDbFieldName($key, $this->dataFields); if (empty($field)) { continue; } $placeholder = \VisformsPlaceholderEntry::getInstance('', $value, $field->typefield, $field); $value = $placeholder->getReplaceValue(); $this->pdfHelper->addItem('item.' .$field->name, $value); } else { $this->pdfHelper->addItem('item.' .$key, $value); } } // process embedded nodes having set class=sql $text = $this->processHTMLSQL($text); // replace all visforms placeholder(${}): clean means: clean up and remove all placeholders, that are not yet repleced $text = $this->pdfHelper->getReplacedText($item, $text, $this->formItem, $this->dataFields); // process framework objects $text = $this->pdfHelper->processFrameworkObjects($text); // process all parameter $text = $this->pdfHelper->processParameter($text); return $text; } // miscellaneous protected function buildWhere() { $where = ''; if (0 < $this->did) { // data id if set goes first $where .= 'id=' . $this->did; } else if ('' != $this->data) { // sql query parameter is set $index = array_search($this->data, array_column($this->pdfData, 'name')); if (false !== $index) { $where .= $this->pdfData[$index]['sql']; } } return $this->pdfHelper->processFrameworkObjects($where); } protected function prepareDataFields () { if (empty($this->dataFields)) { return; } $i = 0; foreach ($this->dataFields as $field) { if (empty($field->defaultvalue)) { $i++; continue; } foreach ($field->defaultvalue as $dvName => $dvValue) { $dvName = str_replace('f_' . $field->typefield . '_', '', $dvName); $field->$dvName = $dvValue; } $this->dataFields[$i] = $field; $i++; } } protected function prepareSqlDataFields($fields) { if (empty($fields)) { return $fields; } $i = 0; foreach ($fields as $field) { if (empty($field->defaultvalue)) { $i++; continue; } foreach ($field->defaultvalue as $dvName => $dvValue) { $dvName = str_replace('f_' . $field->typefield . '_', '', $dvName); $field->$dvName = $dvValue; } $fields[$i] = $field; $i++; } return $fields; } private function checkForLoop($text): bool { if (empty($text)) { return false; } $html = str_get_html($text); $nodes = $html->find('[class="loop"]'); if (!empty($nodes)) { return true; } return false; } private function processLoopTemplate() { $html = str_get_html($this->docTemplate); $nodes = $html->find('[class="loop"]'); foreach ($nodes as $template) { // remove class attribute, so that the loop node is not processed twice $template->class = null; $outerText = $template->outertext; $template->outertext = ''; foreach ($this->dataItems as $index => $item) { $text = $this->processItem($item, $outerText); if (0 == $index) { // replace the template $template->outertext = $text; } else { // append after replaced template $template->outertext .= $text; } } } $html->save(); // process embedded nodes having set class=sql $text = $this->processHTMLSQL($html); // replace all visforms placeholder(${}): clean means: clean up and remove all placeholders, that are not yet repleced $text = $this->pdfHelper->getReplacedText($item, $text, $this->formItem, $this->dataFields); // process framework objects $text = $this->pdfHelper->processFrameworkObjects($text); // process all parameter $text = $this->pdfHelper->processParameter($text); return $text; } private function processHTMLSQL($text) { if (empty($text)) { return ''; } // nested sql nodes NOT supported // placeholder inside sql nodes use db field name as returned by the sql statement (i.e. field alias is possible) as placeholder string $html = str_get_html($text); $nodes = $html->find('[class="sql"]'); foreach ($nodes as $template) { // remove class attribute, so that the sql node is not processed twice $template->class = null; $name = $template->getAttribute('id'); if (empty($name)) { // todo: add issue handling continue; } // remove id attribute $template->id = null; if (is_numeric($index = array_search($name, array_column($this->pdfStatements, 'name')))) { $statement = $this->pdfStatements[$index]; } else { // todo: add issue handling continue; } $outerText = $template->outertext; $template->outertext = ''; try { $sql = $this->pdfHelper->processParameter($statement['sql']); $sqlHelper = new \VisformsSql($sql); if ('free' == $statement['type']) { // build free custom sql statement $items = $sqlHelper->getItemsFromSQL(); if (empty($items)) { $items = array(); } // we expect item to be a list of database field names and vaules. Placeholder would be ${fieldname}. // at some point we will probably run into problems with identical field names.... foreach ($items as $index => $item) { $part = $outerText; $dArray = json_decode(json_encode($item), true); // store results from sql statement in pdf helper parameter array for later reuse // format of array kel: sql statement id . sql select field or alias name foreach ($item as $key => $value) { $this->pdfHelper->addItem($name . '.' . $key, $value); } $placeholders = new \VisformsPlaceholder($part); while ($placeholders->hasNext()) { $placeholders->getNext(); $pName = $placeholders->getPlaceholderPart('name'); // only replace placeholder which are part of the item if (!empty($pName) && array_key_exists($pName, $dArray)) { $placeholders->replace($dArray[$pName]); } } $text = $placeholders->getText(); if (0 == $index) { // replace the template $template->outertext = $text; } else { // append after replaced template $template->outertext .= $text; } } } else if ($sqlHelper->checkIsSelectSqlOnly()) { // get data model $model = new VisdataspdfModel(array('id' => $statement['id'], 'where' => $sql)); $items = $model->getItems(); if (empty($items)) { $items = array(); } $fields = $model->getDatafields(); $fields = $this->prepareSqlDataFields($fields); $formModel = new VisformModel(array('ignore_request' => true)); $form = $formModel->getItem($statement['id']); // for all items foreach ($items as $index => $item) { // store results from sql statement in pdf helper parameter array for later reuse // format of array kel: sql statement id . sql select field or alias name foreach ($item as $key => $value) { // if key is a field, replace db field name with field name // process value with option free placeholderEntry if (str_starts_with($key, 'F')) { $field = $this->pdfHelper->findFieldFromDbFieldName($key, $this->dataFields); if (empty($field)) { continue; } $placeholder = \VisformsPlaceholderEntry::getInstance('', $value, $field->typefield, $field); $value = $placeholder->getReplaceValue(); $this->pdfHelper->addItem($name . '.' . $field->name, $value); } else { $this->pdfHelper->addItem($name . '.' . $key, $value); } } $text = $this->pdfHelper->getReplacedText($item, $outerText, $form, $fields); if (0 == $index) { // replace the template $template->outertext = $text; } else { // append after replaced template $template->outertext .= $text; } } } } catch (\Exception $e) { $test = true; } } $html->save(); return $html; } private function addManuallPageBreaks() { if (empty($this->docTemplate)) { return; } $html = str_get_html($this->docTemplate); // test, that we have a node with [class="AddPage"] $nodes = $html->find('[class="AddPage"]'); foreach ($nodes as $node) { $pageBreak = '