1<?php 2/** 3 * DokuWiki Plugin struct (Helper Component) 4 * 5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 6 * @author Szymon Olewniczak <it@rid.pl> 7 */ 8 9// must be run within Dokuwiki 10use dokuwiki\Parsing\Parser; 11use dokuwiki\plugin\struct\meta\AccessTable; 12use dokuwiki\plugin\struct\meta\Schema; 13use dokuwiki\plugin\struct\meta\Search; 14use dokuwiki\plugin\struct\meta\StructException; 15use dokuwiki\plugin\struct\meta\Value; 16use dokuwiki\plugin\struct\types\Wiki; 17use splitbrain\PHPArchive\FileInfo; 18use splitbrain\PHPArchive\Zip; 19 20if(!defined('DOKU_INC')) die(); 21 22class helper_plugin_structodt extends DokuWiki_Plugin { 23 /** 24 * Generate temporary file name with full path in temporary directory 25 * 26 * @param string $ext 27 * @return string 28 */ 29 public function tmpFileName($ext='') { 30 global $conf; 31 $name = $conf['tmpdir'] . '/structodt/' . uniqid(); 32 if ($ext) { 33 $name .= ".$ext"; 34 } 35 return $name; 36 } 37 38 /** 39 * Render ODT file from template 40 * 41 * @param $template 42 * @param $schemas 43 * @param $pid 44 * 45 * @return string|bool 46 * @throws \splitbrain\PHPArchive\ArchiveIOException 47 * @throws \splitbrain\PHPArchive\FileInfoException 48 */ 49 public function renderODT($template, $row) { 50 $template_file = mediaFN($template); 51 $tmp_dir = $this->tmpFileName() . '/'; 52 if (!mkdir($tmp_dir, 0777, true)) { 53 msg("could not create tmp dir - bad permissions?", -1); 54 return false; 55 } 56 57 $template_zip = new Zip(); 58 $template_zip->open($template_file); 59 $template_zip->extract($tmp_dir); 60 61 //do replacements 62 $files = array('content.xml', 'styles.xml'); 63 foreach ($files as $file) { 64 $content_file = $tmp_dir . $file; 65 $content = file_get_contents($content_file); 66 if ($content === false) { 67 msg("Cannot open: $content_file", -1); 68 $this->rmdir_recursive($tmp_dir); 69 return false; 70 } 71 72 $content = $this->replace($content, $row); 73 file_put_contents($content_file, $content); 74 } 75 76 77 $tmp_file = $this->tmpFileName('odt'); 78 79 $tmp_zip = new Zip(); 80 $tmp_zip->create($tmp_file); 81 foreach($this->readdir_recursive($tmp_dir) as $file) { 82 $fileInfo = FileInfo::fromPath($file); 83 $fileInfo->strip(substr($tmp_dir, 1)); 84 $tmp_zip->addFile($file, $fileInfo); 85 } 86 $tmp_zip->close(); 87 88 //remove temp dir 89 $this->rmdir_recursive($tmp_dir); 90 91 return $tmp_file; 92 } 93 94 /** 95 * Render PDF file from template 96 * 97 * @param $template 98 * @param $schemas 99 * @param $pid 100 * 101 * @return string|bool 102 * @throws \splitbrain\PHPArchive\ArchiveIOException 103 * @throws \splitbrain\PHPArchive\FileInfoException 104 */ 105 public function renderPDF($template, $row) { 106 $tmp_file = $this->renderODT($template, $row); 107 if (!$tmp_file) return false; 108 109 $wd = dirname($tmp_file); 110 $bn = basename($tmp_file); 111 $cmd = "cd $wd && HOME=$wd unoconv -f pdf $bn 2>&1"; 112 exec($cmd, $output, $return_var); 113 unlink($tmp_file); 114 if ($return_var != 0) { 115 msg("PDF conversion error($return_var): " . implode('<br>', $output), -1); 116 return false; 117 } 118 //change extension to pdf 119 $tmp_file = substr($tmp_file, 0, -3) . 'pdf'; 120 121 return $tmp_file; 122 } 123 124 /** 125 * Send ODT file using range request 126 * 127 * @param $tmp_file string path of sending file 128 * @param $filename string name of sending file 129 * $param $ext odt or pdf 130 */ 131 public function sendFile($tmp_file, $filename, $ext='odt') { 132 $mime = "application/$ext"; 133 header("Content-Type: $mime"); 134 header("Content-Disposition: attachment; filename=\"$filename.$ext\";"); 135 136 http_sendfile($tmp_file); 137 138 $fp = @fopen($tmp_file, "rb"); 139 if($fp) { 140 //we have to remove file before exit 141 define('SIMPLE_TEST', true); 142 http_rangeRequest($fp, filesize($tmp_file), $mime); 143 } else { 144 header("HTTP/1.0 500 Internal Server Error"); 145 print "Could not read file - bad permissions?"; 146 } 147 } 148 149 /** 150 * Read directory recursively 151 * 152 * @param string $path 153 * @return array of file full paths 154 */ 155 public function readdir_recursive($path) { 156 $directory = new \RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS); 157 $iterator = new \RecursiveIteratorIterator($directory); 158 $files = array(); 159 foreach ($iterator as $info) { 160 if ($info->isFile()) { 161 $files[] = $info->getPathname(); 162 } 163 } 164 165 return $files; 166 } 167 168 /** 169 * Remove director recursively 170 * 171 * @param $path 172 */ 173 public function rmdir_recursive($path) { 174 $directory = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS); 175 $iterator = new RecursiveIteratorIterator($directory, 176 RecursiveIteratorIterator::CHILD_FIRST); 177 foreach($iterator as $file) { 178 if ($file->isDir()){ 179 rmdir($file->getRealPath()); 180 } else { 181 unlink($file->getRealPath()); 182 } 183 } 184 rmdir($path); 185 } 186 187 /** 188 * @param $schemas 189 * @param Schema $first_schema 190 * @return Search 191 */ 192 public function getSearch($schemas, &$first_schema) { 193 $search = new Search(); 194 if (!empty($schemas)) foreach ($schemas as $schema) { 195 $search->addSchema($schema[0], $schema[1]); 196 } 197 $search->addColumn('*'); 198 $first_schema = $search->getSchemas()[0]; 199 200 if ($first_schema->isLookup()) { 201 $search->addColumn('%rowid%'); 202 } else { 203 $search->addColumn('%pageid%'); 204 $search->addColumn('%title%'); 205 $search->addColumn('%lastupdate%'); 206 $search->addColumn('%lasteditor%'); 207 } 208 209 return $search; 210 } 211 212 /** 213 * Get rows data, optionally filtered by pid 214 * 215 * @param string|array $schemas 216 * @param Schema $first_schema 217 * @return Value[][] 218 */ 219 public function getRows($schemas, &$first_schema, $filters=array()) 220 { 221 $search = $this->getSearch($schemas, $first_schema); 222 foreach ($filters as $filter) { 223 $colname = $filter[0]; 224 $value = $filter[2]; 225 $comp = $filter[1]; 226 $op = $filter[3]; 227 $search->addFilter($colname, $value, $comp, $op); 228 } 229 $result = $search->execute(); 230 $pids = $search->getPids(); 231 return array_combine($pids, $result); 232 } 233 234 /** 235 * Get single row by pid 236 * 237 * @param $schemas 238 * @param $pid 239 * @return Value[]|null 240 */ 241 public function getRow($table, $pid, $rev, $rid) { 242 try { 243 if (AccessTable::isTypePage($pid, $rev)) { 244 $schemadata = AccessTable::getPageAccess($table, $pid); 245 } elseif (AccessTable::isTypeSerial($pid, $rev)) { 246 $schemadata = AccessTable::getSerialAccess($table, $pid, $rid); 247 } else { 248 $schemadata = AccessTable::getGlobalAccess($table, $rid); 249 } 250 return $schemadata->getData(); 251 } catch (StructException $ignore) { 252 return null; 253 } 254 } 255 256 /** 257 * Perform $content replacements basing on $row Values 258 * 259 * @param string $content 260 * @param Value[] $row 261 * @return string 262 */ 263 public function replace($content, $row) { 264 /** @var Value $value */ 265 foreach ($row as $value) { 266 $label = $value->getColumn()->getLabel(); 267 $pattern = '/@@' . preg_quote($label) . '(?:\[(\d+)\])?@@/'; 268 $content = preg_replace_callback($pattern, function($matches) use ($value) { 269 $dvalue = $value->getDisplayValue(); 270 if (isset($matches[1])) { 271 $index = (int)$matches[1]; 272 if (!is_array($dvalue)) { 273 $dvalue = array_map('trim', explode('|', $dvalue)); 274 } 275 if (isset($dvalue[$index])) { 276 return $dvalue[$index]; 277 } 278 return 'Array: index out of bound'; 279 } elseif (is_array($dvalue)) { 280 return implode(',', $dvalue); 281 } elseif ($value->getColumn()->getType() instanceof Wiki) { 282 //import parser classes and mode definitions 283 require_once DOKU_INC . 'inc/parser/parser.php'; 284 285 // Create the parser and handler 286 $Parser = new Parser(new Doku_Handler()); 287 288 // add default modes 289 $std_modes = array('linebreak', 'eol'); 290 291 foreach($std_modes as $m){ 292 $class = 'dokuwiki\\Parsing\\ParserMode\\'.ucfirst($m); 293 $obj = new $class(); 294 $modes[] = array( 295 'sort' => $obj->getSort(), 296 'mode' => $m, 297 'obj' => $obj 298 ); 299 } 300 301 //add modes to parser 302 foreach($modes as $mode){ 303 $Parser->addMode($mode['mode'],$mode['obj']); 304 } 305 // Do the parsing 306 $instructions = $Parser->parse($dvalue); 307 $Renderer = p_get_renderer('structodt'); 308 309 // Loop through the instructions 310 foreach ($instructions as $instruction) { 311 // Execute the callback against the Renderer 312 if(method_exists($Renderer, $instruction[0])){ 313 call_user_func_array(array(&$Renderer, $instruction[0]), $instruction[1] ? $instruction[1] : array()); 314 } 315 } 316 317 return $Renderer->doc; 318 } 319 return $dvalue; 320 }, $content); 321 } 322 323 return $content; 324 } 325 326 /** 327 * @param $row 328 * @param string $template 329 * @return string 330 */ 331 public function rowTemplate($row, $template) { 332 global $ID; 333 334 //do media file substitutions 335 $media = preg_replace_callback('/\$(.*?)\$/', function ($matches) use ($row) { 336 $possibleValueTypes = array('getValue', 'getCompareValue', 'getDisplayValue', 'getRawValue'); 337 list($label, $valueType) = explode('.', $matches[1], 2); 338 if (!$valueType || !in_array($valueType, $possibleValueTypes)) { 339 $valueType = 'getDisplayValue'; 340 } 341 foreach ($row as $value) { 342 $column = $value->getColumn(); 343 if ($column->getLabel() == $label) { 344 return call_user_func(array($value, $valueType)); 345 } 346 } 347 return ''; 348 }, $template); 349 350 resolve_mediaid(getNS($ID), $media, $exists); 351 if (!$exists) { 352 msg("<strong>structodt</strong>: template file($media) doesn't exist", -1); 353 } 354 return $media; 355 } 356}