*/ // must be run within Dokuwiki use dokuwiki\File\MediaResolver; use dokuwiki\Parsing\Parser; use dokuwiki\plugin\struct\meta\AccessTable; use dokuwiki\plugin\struct\meta\Schema; use dokuwiki\plugin\struct\meta\Search; use dokuwiki\plugin\struct\meta\StructException; use dokuwiki\plugin\struct\meta\Value; use dokuwiki\plugin\struct\types\Wiki; use splitbrain\PHPArchive\FileInfo; use splitbrain\PHPArchive\Zip; if(!defined('DOKU_INC')) die(); class helper_plugin_structodt extends DokuWiki_Plugin { /** * Generate temporary file name with full path in temporary directory * * @param string $ext * @return string */ public function tmpFileName($ext='') { global $conf; $name = $conf['tmpdir'] . '/structodt/' . uniqid(); if ($ext) { $name .= ".$ext"; } return $name; } /** * Render ODT file from template * * @param $template * @param $schemas * @param $pid * * @return string|bool * @throws \splitbrain\PHPArchive\ArchiveIOException * @throws \splitbrain\PHPArchive\FileInfoException * @throws Exception */ public function renderODT($template, $row) { $template_file = mediaFN($template); $tmp_dir = $this->tmpFileName() . '/'; if (!mkdir($tmp_dir, 0777, true)) { throw new \Exception("could not create tmp dir - bad permissions?", -1); } $template_zip = new Zip(); $template_zip->open($template_file); $template_zip->extract($tmp_dir); //do replacements $files = array('content.xml', 'styles.xml'); foreach ($files as $file) { $content_file = $tmp_dir . $file; $content = file_get_contents($content_file); if ($content === false) { $this->rmdir_recursive($tmp_dir); throw new \Exception("Cannot open: $content_file"); } $content = $this->replace($content, $row); file_put_contents($content_file, $content); } $tmp_file = $this->tmpFileName('odt'); $tmp_zip = new Zip(); $tmp_zip->create($tmp_file); foreach($this->readdir_recursive($tmp_dir) as $file) { $fileInfo = FileInfo::fromPath($file); $fileInfo->strip(substr($tmp_dir, 1)); $tmp_zip->addFile($file, $fileInfo); } $tmp_zip->close(); //remove temp dir $this->rmdir_recursive($tmp_dir); return $tmp_file; } /** * Render PDF file from template * * @param $template * @param $schemas * @param $pid * * @return string|bool * @throws \splitbrain\PHPArchive\ArchiveIOException * @throws \splitbrain\PHPArchive\FileInfoException * @throws Exception */ public function renderPDF($template, $row) { $tmp_file = $this->renderODT($template, $row); $wd = dirname($tmp_file); $bn = basename($tmp_file); $cmd = "cd $wd && HOME=$wd unoconv -f pdf $bn 2>&1"; exec($cmd, $output, $result_code); @unlink($tmp_file); // remove odt file if ($result_code != 0) { throw new \Exception("PDF conversion error($result_code): " . implode('
', $output), -1); } //change extension to pdf $tmp_file = substr($tmp_file, 0, -3) . 'pdf'; return $tmp_file; } public function concatenate($rendered_pages) { $tmp_file = $this->tmpFileName('pdf'); $cmd = "gs -q -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -sOutputFile=$tmp_file "; // Add each pdf file to the end of the command foreach($rendered_pages as $file) { $cmd .= $file.' '; } $cmd .= '2>&1'; exec($cmd, $output, $result_code); if ($result_code != 0) { throw new \Exception("PDF concatenation error($result_code): " . implode('
', $output), -1); } return $tmp_file; } /** * Send ODT file using range request * * @param $tmp_file string path of sending file * @param $filename string name of sending file * $param $ext odt or pdf */ public function sendFile($tmp_file, $filename, $ext='odt') { $mime = "application/$ext"; header("Content-Type: $mime"); header("Content-Disposition: attachment; filename=\"$filename.$ext\";"); http_sendfile($tmp_file); $fp = @fopen($tmp_file, "rb"); if($fp) { //we have to remove file before exit define('SIMPLE_TEST', true); http_rangeRequest($fp, filesize($tmp_file), $mime); } else { header("HTTP/1.0 500 Internal Server Error"); print "Could not read file - bad permissions?"; } } /** * Read directory recursively * * @param string $path * @return array of file full paths */ public function readdir_recursive($path) { $directory = new \RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS); $iterator = new \RecursiveIteratorIterator($directory); $files = array(); foreach ($iterator as $info) { if ($info->isFile()) { $files[] = $info->getPathname(); } } return $files; } /** * Remove director recursively * * @param $path */ public function rmdir_recursive($path) { $directory = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS); $iterator = new RecursiveIteratorIterator($directory, RecursiveIteratorIterator::CHILD_FIRST); foreach($iterator as $file) { if ($file->isDir()){ @rmdir($file->getRealPath()); } else { @unlink($file->getRealPath()); } } rmdir($path); } /** * @param $schemas * @param Schema $first_schema * @return Search */ public function getSearch($schemas, &$first_schema) { $search = new Search(); if (!empty($schemas)) foreach ($schemas as $schema) { $search->addSchema($schema[0], $schema[1]); } $search->addColumn('*'); $first_schema = $search->getSchemas()[0]; $search->addColumn('%rowid%'); $search->addColumn('%pageid%'); $search->addColumn('%title%'); $search->addColumn('%lastupdate%'); $search->addColumn('%lasteditor%'); return $search; } /** * Get rows data, optionally filtered by pid * * @param string|array $schemas * @param Schema $first_schema * @return Value[][] */ public function getRows($schemas, &$first_schema, $filters=array()) { $search = $this->getSearch($schemas, $first_schema); foreach ($filters as $filter) { $colname = $filter[0]; $value = $filter[2]; $comp = $filter[1]; $op = $filter[3]; $search->addFilter($colname, $value, $comp, $op); } $result = $search->execute(); return $result; } /** * Get single row by pid * * @param $schemas * @param $pid * @return Value[]|null */ public function getRow($table, $pid, $rev, $rid) { // second value of schema array is alias. ignore it for now $schema = [$table, '']; $search = $this->getSearch([$schema], $ignore); if (AccessTable::isTypePage($pid, $rev)) { $search->addFilter('%pageid%', $pid, '='); } elseif (AccessTable::isTypeSerial($pid, $rev)) { $search->addFilter('%pageid%', $pid, '='); $search->addFilter('%rowid%', $rid, '='); } else { $search->addFilter('%rowid%', $rid, '='); } $result = $search->execute(); return $result[0]; } /** * Perform $content replacements basing on $row Values * * @param string $content * @param Value[] $row * @return string */ public function replace($content, $row) { /** @var Value $value */ foreach ($row as $value) { $label = $value->getColumn()->getLabel(); $pattern = '/@@' . preg_quote($label) . '(?:\[(\d+)\])?@@/'; $content = preg_replace_callback($pattern, function($matches) use ($value) { $dvalue = $value->getDisplayValue(); if (isset($matches[1])) { $index = (int)$matches[1]; if (!is_array($dvalue)) { $dvalue = array_map('trim', explode('|', $dvalue)); } if (isset($dvalue[$index])) { return $dvalue[$index]; } return 'Array: index out of bound'; } elseif (is_array($dvalue)) { return implode(',', $dvalue); } elseif ($value->getColumn()->getType() instanceof Wiki) { //import parser classes and mode definitions require_once DOKU_INC . 'inc/parser/parser.php'; // Create the parser and handler $Parser = new Parser(new Doku_Handler()); // add default modes $std_modes = array('linebreak', 'eol'); foreach($std_modes as $m){ $class = 'dokuwiki\\Parsing\\ParserMode\\'.ucfirst($m); $obj = new $class(); $modes[] = array( 'sort' => $obj->getSort(), 'mode' => $m, 'obj' => $obj ); } //add modes to parser foreach($modes as $mode){ $Parser->addMode($mode['mode'],$mode['obj']); } // Do the parsing $instructions = $Parser->parse($dvalue); $Renderer = p_get_renderer('structodt'); // Loop through the instructions foreach ($instructions as $instruction) { // Execute the callback against the Renderer if(method_exists($Renderer, $instruction[0])){ call_user_func_array(array(&$Renderer, $instruction[0]), $instruction[1] ? $instruction[1] : array()); } } return $Renderer->doc; } return $dvalue; }, $content); } return $content; } /** * @param $row * @param string $template * @return string * @throws Exception */ public function rowTemplate($row, $template) { return preg_replace_callback('/\$(.*?)\$/', function ($matches) use ($row) { $possibleValueTypes = array('getValue', 'getCompareValue', 'getDisplayValue', 'getRawValue'); $explode = explode('.', $matches[1], 2); $label = $explode[0]; $valueType = 'getDisplayValue'; if (isset($explode[1]) && in_array($explode[1], $possibleValueTypes)) { $valueType = $explode[1]; } foreach ($row as $value) { $column = $value->getColumn(); if ($column->getLabel() == $label) { return call_user_func(array($value, $valueType)); } } return ''; }, $template); } }