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(); $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 (strpos($key, 'F') === 0) { $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; } $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) { 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) { // 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 (strpos($key, 'F') === 0) { $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() { $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 = ' '; $node->innertext = $pageBreak . $node->innertext; $this->docTemplate = $html->save(); } } // not yet used make sure, that it uses the sql parser before executing any sql statement. private function processHTMLSQLRecursive($text, $parentItem = null) { $html = str_get_html($text); while ($template = $html->find('[class="sql"]', 0)) { $name = $template->getAttribute('id'); $template->id = null; $template->class = null; if (empty($name)) { // todo: add issue handling continue; } if (is_numeric($index = array_search($name, array_column($this->pdfStatements, 'name')))) { $statement = $this->pdfStatements[$index]; } else { // todo: add issue handling continue; } // todo process field placeholder from parent item in sql statement $sql = $this->pdfHelper->processFrameworkObjects($statement['sql']); if ('free' == $statement['type']) { // build free custom sql statement $db = Factory::getDbo(); // todo: evaluate resource friendly cursor //$query = $db->getQuery(true); //$query->select($sql); //$db->setQuery($query); //$items = $db->loadObject(); $db->setQuery($sql); //$items = $db->loadRowList(); //$items = $db->loadObject(); $items = $db->loadObjectList(); // 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.... } else { // get data model $model = new VisdataspdfModel(array('id' => $statement['id'], 'where' => $sql));//JModelLegacy::getInstance('VisdatasPdf', 'VisformsModel', array('id' => $statement['id'], 'where' => $sql)); $items = $model->getItems(); $fields = $model->getDatafields(); $fields = $this->prepareSqlDataFields($fields); $formModel = new VisformModel(array('ignore_request' => true));//JModelLegacy::getInstance('Visform', 'VisformsModel', array('ignore_request' => true)); $form = $formModel->getItem($statement['id']); // for all items } // todo make recusive // check if we have a child sql node, process this one first, // todo figure how to pass Item in order to replace selections in nested sql statement $template = $this->processHTMLSQLRecursive($template); $outerText = $template->outertext; $template->outertext = ''; if ('free' == $statement['type']) { foreach ($items as $index => $item) { $part = $outerText; $dArray = json_decode(json_encode($item), true); $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 { foreach ($items as $index => $item) { $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; } } } } $html->save(); return $html; } // xml based replacements, no longer used private function processXMLSQL($text) { $xml = simplexml_load_string(''.$text.''); $nodes = $xml->xpath("//*[@class='sql']"); foreach ($nodes as $node) { $template = $node->asXML(); $name = (string) $node['id']; if(empty($name)) { // todo: add issue handling continue; } if (is_numeric($index = array_search($name, array_column($this->pdfStatements, 'name')))) { $statement = $this->pdfStatements[$index]; } else { // todo: add issue handling continue; } $fields = array(); $fArray = array(); $sql = $this->pdfHelper->processParameter($statement['sql']); if('free' == $statement['type']) { // build free custom sql statement $db = Factory::getDbo(); // todo: evaluate resource friendly cursor //$query = $db->getQuery(true); //$query->select($sql); //$db->setQuery($query); //$items = $db->loadObject(); $db->setQuery($sql); //$items = $db->loadRowList(); //$items = $db->loadObject(); $items = $db->loadObjectList(); } else { // get data model $model = new VisdataspdfModel(array('id' => $statement['id'], 'where' => $sql));//JModelLegacy::getInstance('VisdatasPdf', 'VisformsModel', array('id' => $statement['id'], 'where' => $sql)); //$model->populateFilterState($this->sort, $this->published, '', $this->start, $this->limit); $items = $model->getItems(); $fields = $model->getDatafields(); $fArray = json_decode(json_encode($fields), true); } // find existing placeholders: case insensitive $found = preg_match_all("/\[$name:([A-Z0-9]{1}[A-Z0-9\-_]*)]/i", $template, $matches); // for all items foreach ($items as $index => $item) { // add to parameter container foreach ($item as $key => $value) { $this->pdfHelper->addItem($name . '.' . $key, $value); } $part = $template; $dArray = json_decode(json_encode($item), true); if ($found) { // for all placeholder foreach ($matches[1] as $key => $match) { $tag = $match; $search = $matches[0][$key]; if (is_numeric($fIndex = array_search($tag, array_column($fArray, 'name')))) { // F[number] $part = str_replace($search, $dArray['F' . $fields[$fIndex]->id], $part); } else if (isset($dArray[$tag])) { // explicit field name $part = str_replace($search, $dArray[$tag], $part); } } } if(0 == $index) { // replace the template $this->xmlReplaceNode($node, $part); } else { // append after replaced template $this->xmlAppendNode($part); } } } $tmp = $this->xmlRemoveRoot($xml); return $tmp; } // helpers //only used with processXMLSQL() private function xmlReplaceNode($node, $part) { $domToChange = dom_import_simplexml($node); $xmlParts = simplexml_load_string($part); $domReplace = dom_import_simplexml($xmlParts); $nodeImport = $domToChange->ownerDocument->importNode($domReplace, TRUE); $domToChange->parentNode->replaceChild($nodeImport, $domToChange); // remember in class members for subsequent append node calls $this->domToChange = $domToChange; $this->nodeReplaced = $nodeImport; } private function xmlAppendNode($part) { // get class member from first replace node call $domToChange = $this->domToChange; $nodeReplaced = $this->nodeReplaced; // load DOMs $xmlParts = simplexml_load_string($part); $domReplace = dom_import_simplexml($xmlParts); $nodeImport = $domToChange->ownerDocument->importNode($domReplace, TRUE); $parent = $nodeReplaced->parentNode; if($nodeReplaced->nextSibling === null) { return $parent>appendChild($nodeImport); } else { return $parent->insertBefore($nodeImport, $nodeReplaced->nextSibling); } } private function xmlRemoveRoot($xml) { $root = $xml->xpath('/*'); $tmp = $root[0]->asXML(); $tmp = str_replace('', '', $tmp); $tmp = str_replace('', '', $tmp); $tmp = str_replace('', '', $tmp); // todo: convert to valid xhtml, returns invalid xhtml (empty nodes) return trim($tmp); } // not used private function processListing() { // local abbreviations $tcpdf = $this->tcpdf; $docTemplate = $this->docTemplate; // get tr class header $headerTemplate = ''; $from = 0; $to = 0; $found = preg_match_all('@(?s)@', $docTemplate, $matches, PREG_OFFSET_CAPTURE); if($found) { $from = $matches[0][0][1]; foreach ($matches as $index => $match) { if(0 < $index) { $headerTemplate .= '\n'; } $headerTemplate .= $match[0][0]; $to = max($to, $match[0][1] + strlen($match[0][0])); } } // replace header template with placeholder //$docTemplate = substr_replace($docTemplate, '__header__', $from, $to - $from); // get tr class data $dataTemplate = ''; $found = preg_match_all('@(?s)@', $docTemplate, $matches, PREG_OFFSET_CAPTURE); if($found) { $from = $matches[0][0][1]; foreach ($matches as $index => $match) { if(0 < $index) { $dataTemplate .= '\n'; } $dataTemplate .= $match[0][0]; $to = max($to, $match[0][1] + strlen($match[0][0])); } } // replace data template with placeholder $docTemplate = substr_replace($docTemplate, '__data__', $from, $to - $from); // find and process placeholders $found = preg_match_all('/\[[A-Z0-9]{1}[A-Z0-9\-]*]/', $dataTemplate, $matches); $fArray = json_decode(json_encode($this->dataFields), true); $counter = 0; $page = ''; // debug looping: make it seem large data amount $debugLoopCount = 1; for($index=0; $index < $debugLoopCount; ++$index) { // todo: refactor this copy-paste code // for each data entry foreach ($this->dataItems as $data) { $text = $dataTemplate; $dArray = json_decode(json_encode($data), true); if ($found) { // for each placeholder foreach ($matches[0] as $match) { $tag = strtolower(trim($match, '\[]')); if (is_numeric($fIndex = array_search($tag, array_column($fArray, 'name')))) { $text = str_replace($match, $dArray['F' . $this->dataFields[$fIndex]->id], $text); } } } $text = str_replace('[CREATED]', $dArray['created'], $text); $page .= $text; ++$counter; } } // replace data placeholder with real data rows $page = str_replace('__data__', $page, $docTemplate); $tcpdf->AddPage(); $tcpdf->writeHTMLCell(0, 0, '', '', $page, 0, 1, 0, true, '', true); } }