vorlage_kurzbz, $oe_kurzbz, $version); * $doc->setFilename($filename); * $doc->addDataXML($data); * $doc->addImage($imagepath, $imagename, $imagecontenttype); * $doc->create($outputformat); * $doc->output(true); * $doc->close(); * * New: * $xml_data = $this->documentexportlib->getDataXML($data); * $images = [[ * 'path' => $imagepath, * 'name' => $imagename, * 'contenttype' => $imagecontenttype * ]]; * $this->documentexportlib->showContent( * $filename, * $vorlage, * $xml_data, * $oe_kurzbz, * $version, * $outputformat, * null, * null, * $images * ); */ class DocumentExportLib { private $unoconv_version; /** * Constructor */ public function __construct() { // Gets CI instance $this->ci =& get_instance(); // Load Phrases $this->ci->load->library('PhrasesLib', ['document_export', null], 'documentExportPhrases'); // Which document converter has to be used if (defined('DOCSBOX_ENABLED') && DOCSBOX_ENABLED === true) { // Use docsbox!! } else { exec('unoconv --version', $ret_arr); if(isset($ret_arr[0])) { $hlp = explode(' ', $ret_arr[0]); if(isset($hlp[1])) { $this->unoconv_version = $hlp[1]; } else show_error($this->ci->documentExportPhrases->t("document_export", "error_unoconv_version")); } else show_error($this->ci->documentExportPhrases->t("document_export", "error_unoconv")); } } /** * Laedt die XML Daten fuer die XSL Transformation anhand eines Arrays * * @param array $data Array mit Daten * @param string $root Bezeichnung des Root Nodes * * @return DOMDocument */ public function getDataArray($data, $root) { $xml_data = new DOMDocument(); $xml_data->loadXML($this->convertArrayToXML($data, $root)); return $xml_data; } /** * XML Daten fuer die XSL Transformation * * @param string $xml * * @return DOMDocument */ public function getDataXML($xml) { $xml_data = new DOMDocument(); $xml_data->loadXML($xml); return $xml_data; } /** * URL zu XML Datei die fuer XSLTransformation verwendet werden soll * * @param string $xml URL to XML * @param string $params GET parameter * * @return stdClass */ public function getDataURL($xml, $params) { $xml_found = false; $aktive_addons = array_filter(array_map('trim', explode(";", ACTIVE_ADDONS))); foreach($aktive_addons as $addon) { $xmlfile = DOC_ROOT . 'addons/' . $addon . '/rdf/' . $xml; if (file_exists($xmlfile)) { $xml_found = true; $xml_url = XML_ROOT . '../addons/' . $addon . '/rdf/' . $xml . '?' . $params; break; } } if (!$xml_found) $xml_url = XML_ROOT . $xml . '?' . $params; // Load the XML source $xml_data = new DOMDocument; if (!$xml_data->load($xml_url)) return error($this->ci->documentExportPhrases->t("document_export", "error_xml_load", [ "url" => $xml_url, "xml" => $xml, "params" => $params ])); return success($xml_data); } /** * Adds a XML Tag for signatur to the document * * @param DomDocument $xml_data * * @return void */ protected function addSignToData($xml_data) { $signblock = $xml_data->createElement("signed", "true"); $xml_data->documentElement->appendChild($signblock); } /** * Adds a XML Tag for archive to the document * * @param DomDocument $xml_data * * @return void */ public function addArchiveToData($xml_data) { $archiv = $xml_data->createElement("archivierbar", "true"); $xml_data->documentElement->appendChild($archiv); } /** * Get the contents of a Document * * @param stdClass $vorlage A db entry from tbl_vorlage * @param DomDocument $xml_data * @param string $oe_kurzbz * @param integer|null $version (optional) * @param string $outputformat (optional) * @param string $sign_user (optional) Must be a valid uid * @param string $sign_profile (optional) Signatureprofile for signing * @param array $images (optional) Each element should have a property path, name & contenttype which are all strings * * @return stdClass */ public function getContent( $vorlage, $xml_data, $oe_kurzbz, $version = null, $outputformat = null, $sign_user = null, $sign_profile = null, $images = [] ) { $source_folder = getcwd(); $temp_folder = sys_get_temp_dir() . '/fhcunoconv-' . uniqid(); $outputformat = $this->getDefaultOutputFormat($outputformat, $vorlage->mimetype); $result = $this->createAndSignContent( $temp_folder, $outputformat, $vorlage, $oe_kurzbz, $version, $xml_data, $images, $sign_user, $sign_profile ); if (isError($result)) { $this->close($temp_folder, $source_folder); return $result; } $temp_filename = getData($result); $fsize = filesize($temp_filename); $handle = fopen($temp_filename, 'r'); if (!$handle) return error($this->ci->documentExportPhrases->t("document_export", "error_file_load")); $result = fread($handle, $fsize); fclose($handle); $this->close($temp_folder, $source_folder); return success($result); } /** * Sets the headers and displays the Document. * On failure the exit() function will be called * * @param string $filename * @param stdClass $vorlage A db entry from tbl_vorlage * @param DomDocument $xml_data * @param string $oe_kurzbz * @param integer|null $version (optional) * @param string $outputformat (optional) * @param string $sign_user (optional) Must be a valid uid * @param string $sign_profile (optional) Signatureprofile for signing * @param array $images (optional) Each element should have a property path, name & contenttype which are all strings * * @return void */ public function showContent( $filename, $vorlage, $xml_data, $oe_kurzbz, $version = null, $outputformat = null, $sign_user = null, $sign_profile = null, $images = [] ) { $source_folder = getcwd(); $temp_folder = sys_get_temp_dir() . '/fhcunoconv-' . uniqid(); $outputformat = $this->getDefaultOutputFormat($outputformat, $vorlage->mimetype); $result = $this->createAndSignContent( $temp_folder, $outputformat, $vorlage, $oe_kurzbz, $version, $xml_data, $images, $sign_user, $sign_profile ); if (isError($result)) { $this->close($temp_folder, $source_folder); exit(getError($result)); } $temp_filename = getData($result); $fsize = filesize($temp_filename); $handle = fopen($temp_filename, 'r'); if (!$handle) { $this->close($temp_folder, $source_folder); exit($this->ci->documentExportPhrases->t("document_export", "error_file_load")); } if (headers_sent()) { $this->close($temp_folder, $source_folder); exit($this->ci->documentExportPhrases->t("document_export", "error_headers")); } switch ($outputformat) { case 'pdf': header('Content-type: application/pdf'); header('Content-Disposition: attachment; filename="' . $filename . '.pdf"'); header('Content-Length: ' . $fsize); break; case 'doc': header('Content-type: application/vnd.ms-word'); header('Content-Disposition: attachment; filename="' . $filename . '.doc"'); header('Content-Length: ' . $fsize); break; case 'odt': header('Content-type: application/vnd.oasis.opendocument.text'); header('Content-Disposition: attachment; filename="' . $filename . '.odt"'); header('Content-Length: ' . $fsize); break; default: $this->close($temp_folder, $source_folder); exit($this->ci->documentExportPhrases->t("document_export", "error_outputformat_missing")); } while (!feof($handle)) { echo fread($handle, 8192); } fclose($handle); $this->close($temp_folder, $source_folder); } /** * Helper function for getContent and showContent. * Creates the temp folder and calls create and sign functions. * * @param string $temp_folder * @param string $outputformat * @param stdClass $vorlage * @param string $oe_kurzbz * @param integer $version * @param DomDocument $xml_data * @param array $images Each element should have a property path, name and contenttype which are all strings * @param string $sign_user Must be a valid uid * @param string $sign_profile Signatureprofile for signing * * @return stdClass */ protected function createAndSignContent( $temp_folder, $outputformat, $vorlage, $oe_kurzbz, $version, $xml_data, $images, $sign_user, $sign_profile ) { mkdir($temp_folder); chdir($temp_folder); $this->ci->load->model('system/Vorlagestudiengang_model', 'VorlagestudiengangModel'); $result = $this->ci->VorlagestudiengangModel->getCurrent($vorlage->vorlage_kurzbz, $oe_kurzbz, $version); if (isError($result)) return $result; if (!hasData($result)) return error($this->ci->documentExportPhrases->t("document_export", "error_template_missing")); $vorlage_stg = current(getData($result)); foreach ($vorlage_stg as $k => $v) $vorlage->$k = $v; if ($sign_user) { $this->addSignToData($xml_data); } $result = $this->create($temp_folder, $outputformat, $vorlage, $xml_data, $images); if (isError($result)) return $result; $temp_filename = getData($result); if ($sign_user) { $result = $this->sign($temp_folder, $temp_filename, $outputformat, $sign_user, $sign_profile); if (isError($result)) return $result; $temp_filename = getData($result); } return success($temp_filename); } /** * Helper function for createAndSignContent. * Creates the files in the temp folder. * * @param string $temp_folder * @param string $outputformat * @param stdClass $vorlage * @param DomDocument $xml_data * @param array $images Each element should have a property path, name and contenttype which are all strings * * @return stdClass */ protected function create($temp_folder, $outputformat, $vorlage, $xml_data, $images) { $content_xsl = new DOMDocument(); if (!$content_xsl->loadXML($vorlage->text)) return error($this->ci->documentExportPhrases->t("document_export", "error_xsl_load")); $proc = new XSLTProcessor(); $proc->importStyleSheet($content_xsl); $contentbuffer = $proc->transformToXml($xml_data); file_put_contents($temp_folder . '/content.xml', $contentbuffer); if ($xml_data->firstChild->tagName == 'error') return error($xml_data->firstChild->textContent); $styles_xsl = null; // styles.xml erstellen if ($vorlage->style) { $styles_xsl = new DOMDocument(); if (!$styles_xsl->loadXML($vorlage->style)) return error($this->ci->documentExportPhrases->t("document_export", "error_styles_load")); $style_proc = new XSLTProcessor(); $style_proc->importStyleSheet($styles_xsl); $stylesbuffer = $style_proc->transformToXml($xml_data); file_put_contents($temp_folder . '/styles.xml', $stylesbuffer); } // Template holen $vorlage_found = false; $vorlage_filename = $vorlage->vorlage_kurzbz . ($vorlage->mimetype == 'application/vnd.oasis.opendocument.spreadsheet' ? '.ods' : '.odt'); $aktive_addons = array_filter(array_map('trim', explode(";", ACTIVE_ADDONS))); foreach($aktive_addons as $addon) { $zipfile = DOC_ROOT . 'addons/' . $addon . '/system/vorlage_zip/' . $vorlage_filename; if (file_exists($zipfile)) { $vorlage_found = true; break; } } if (!$vorlage_found) $zipfile = DOC_ROOT . 'system/vorlage_zip/' . $vorlage_filename; $tempname_zip = $temp_folder . '/out.zip'; if (!copy($zipfile, $tempname_zip)) return error($this->ci->documentExportPhrases->t("document_export", "error_file_copy")); exec("zip $tempname_zip content.xml"); if (!is_null($styles_xsl)) exec("zip $tempname_zip styles.xml"); // bilder hinzufuegen if (count($images) > 0) { // Unterordner fuer die Bilder erstellen mkdir('Pictures'); // Manifest Datei holen exec('unzip ' . $tempname_zip . ' META-INF/manifest.xml'); // Bild zur Manifest Datei hinzufuegen $manifest = file_get_contents('META-INF/manifest.xml'); $manifest_xml = new DOMDocument; if (!$manifest_xml->loadXML($manifest)) return error($this->ci->documentExportPhrases->t("document_export", "error_manifest")); //root-node holen $root = $manifest_xml->getElementsByTagName('manifest')->item(0); foreach ($images as $bild) { copy($bild['path'], 'Pictures/' . $bild['name']); //Neues Element unterhalb des Root Nodes anlegen $node = $manifest_xml->createElement("manifest:file-entry"); $node->setAttribute("manifest:full-path", 'Pictures/' . $bild['name']); $node->setAttribute("manifest:media-type", $bild['contenttype']); $root->appendChild($node); } $out = $manifest_xml->saveXML(); //geaenderte Manifest Datei speichern und wieder ins Zip packen file_put_contents('META-INF/manifest.xml', $out); exec('zip ' . $tempname_zip . ' META-INF/*'); // Bilder zum ZIP-File hinzufuegen exec('zip ' . $tempname_zip . ' Pictures/*'); } clearstatcache(); switch ($outputformat) { case 'pdf': case 'doc': $ret = 0; $temp_filename = $temp_folder . '/out.' . $outputformat; if (defined('DOCSBOX_ENABLED') && DOCSBOX_ENABLED === true) { // Use docsbox $this->ci->load->library("DocsboxLib"); $docboxlib = get_class($this->ci->docboxlib); $ret = $docboxlib::convert($tempname_zip, $temp_filename, $outputformat); } else { // Use unoconv // Unoconv Version 0.6 hat eine Bug wodurch die Berechtigungen des PDF/Doc nicht korrekt gesetzt // werden. Deshalb wird dies hier speziell behandelt. // Die 2. Variante hat den Vorteil dass hier eine bessere Fehlerbehandlung moeglich ist if ($this->unoconv_version == '0.6') $command = 'unoconv -e IsSkipEmptyPages=false -f ' . $outputformat . ' %2$s > %1$s'; else $command = 'unoconv -e IsSkipEmptyPages=false -f ' . $outputformat . ' --output %s %s 2>&1'; $command = sprintf($command, $temp_filename, $tempname_zip); exec($command, $out, $ret); } if ($ret) return error($this->ci->documentExportPhrases->t("document_export", "error_conv_timeout")); break; case 'odt': default: $temp_filename = $tempname_zip; } return success($temp_filename); } /** * Helper function for createAndSignContent. * Signs the main file in the temp folder. * * @param string $temp_folder * @param string $temp_filename * @param string $outputformat * @param string $user Must be a valid uid * @param string $profile Signatureprofile for signing * * @return stdClass */ protected function sign($temp_folder, $temp_filename, $outputformat, $user, $profile) { if ($outputformat != 'pdf') return error($this->ci->documentExportPhrases->t("document_export", "error_sign_pdf")); // Load the File $file_data = file_get_contents($temp_filename); $data = new stdClass(); $data->document = base64_encode($file_data); // Signatur Profil if (!is_null($profile)) $data->profile = $profile; else $data->profile = SIGNATUR_DEFAULT_PROFILE; // Username des Endusers der die Signatur angefordert hat $data->user = $user; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, SIGNATUR_URL . '/' . SIGNATUR_SIGN_API); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 7); curl_setopt($ch, CURLOPT_USERAGENT, "FH-Complete"); // SSL Zertifikatsprüfung deaktivieren // Besser ist es das Zertifikat am Server zu installieren! //curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); //curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); $data_string = json_encode($data, JSON_FORCE_OBJECT); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST"); curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Content-Type: application/json', 'Content-Length:' . mb_strlen($data_string), 'Authorization: Basic ' . base64_encode(SIGNATUR_USER . ":" . SIGNATUR_PASSWORD) ]); $result = curl_exec($ch); if (curl_errno($ch)) { curl_close($ch); return error($this->ci->documentExportPhrases->t("document_export", "error_sign_timeout")); } curl_close($ch); $resultdata = json_decode($result); // If it is success if (isset($resultdata->error) && $resultdata->error == 0) { $signed_filename = $temp_folder . '/signed.pdf'; file_put_contents($signed_filename, base64_decode($resultdata->retval)); return success($signed_filename); } // otherwise if it is an error return error($resultdata->retval ?? $this->ci->documentExportPhrases->t("global", "unknown_error", ["error" => $result])); } /** * Deletes all files in the $temp_folder and changes back to the source_folder * * @param string $temp_folder * @param string $source_folder * * @return void */ protected function close($temp_folder, $source_folder) { $files = glob($temp_folder . '/*'); // get all file names foreach ($files as $file) if (is_file($file)) unlink($file); chdir($source_folder); rmdir($temp_folder); } /** * Convert an array to XML * * @param array $data * @param string $root * @param SimpleXMLElement $xml_data * * @return string|boolean */ private function convertArrayToXML($data, $root = null, $xml_data = null) { $_xml_data = $xml_data; if ($_xml_data === null) $_xml_data = new SimpleXMLElement($root !== null ? '<' . $root . ' />' : ''); foreach ($data as $key => $value) { if (is_array($value)) { if (is_numeric($key)) { $key = 'item' . $key; // dealing with <0/>.. issues $this->convertArrayToXML($value, null, $_xml_data); } else { $subnode = $_xml_data->addChild($key); $this->convertArrayToXML($value, null, $subnode); } } else { // Remove UTF8 Control Characters (breaking XML) $value = preg_replace('/[\x00-\x1F\x7F]/u', '', $value); $_xml_data->addChild((string)$key, htmlspecialchars("$value")); } } return $_xml_data->asXML(); } /** * Get default outputformat from mimetype if its not set * * @param string $outputformat * @param string $mimetype * * @return string */ private function getDefaultOutputFormat($outputformat, $mimetype) { if ($outputformat) return $outputformat; if ($mimetype == 'application/vnd.oasis.opendocument.spreadsheet') return 'ods'; if ($mimetype == 'application/vnd.oasis.opendocument.text') return 'odt'; return 'pdf'; } }