1bbf77476Sternite<?php 2bbf77476Sternite/** 3bbf77476Sternite * DokuWiki Plugin mediathumbnails (Syntax Component) 4bbf77476Sternite * 5bbf77476Sternite * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 6bbf77476Sternite * @author Thomas Schäfer <thomas.schaefer@itschert.net> 7bbf77476Sternite */ 8bbf77476Sternite 9bbf77476Sternite// must be run within Dokuwiki 10bbf77476Sterniteif (!defined('DOKU_INC')) { 11bbf77476Sternite die(); 12bbf77476Sternite} 13bbf77476Sternite 14bbf77476Sterniteclass syntax_plugin_mediathumbnails extends DokuWiki_Syntax_Plugin { 15bbf77476Sternite 16bbf77476Sternite /** 17bbf77476Sternite * @return string Syntax mode type 18bbf77476Sternite */ 19bbf77476Sternite public function getType() 20bbf77476Sternite { 21bbf77476Sternite return 'substition'; 22bbf77476Sternite } 23bbf77476Sternite 24bbf77476Sternite /** 25bbf77476Sternite * @return string Paragraph type 26bbf77476Sternite */ 27bbf77476Sternite public function getPType() 28bbf77476Sternite { 29bbf77476Sternite return 'normal'; 30bbf77476Sternite } 31bbf77476Sternite 32bbf77476Sternite /** 33bbf77476Sternite * @return int Sort order - Low numbers go before high numbers 34bbf77476Sternite */ 35bbf77476Sternite public function getSort() 36bbf77476Sternite { 37bbf77476Sternite return 1; 38bbf77476Sternite } 39bbf77476Sternite 40bbf77476Sternite /** 41bbf77476Sternite * Connect lookup pattern to lexer. 42bbf77476Sternite * 43bbf77476Sternite * @param string $mode Parser mode 44bbf77476Sternite */ 45bbf77476Sternite public function connectTo($mode) 46bbf77476Sternite { 47bbf77476Sternite $this->Lexer->addSpecialPattern("{{thumbnail>.+?}}", $mode, substr(get_class($this), 7)); 48bbf77476Sternite } 49bbf77476Sternite 50bbf77476Sternite /** 51bbf77476Sternite * Handle matches of the mediathumbnails syntax 52bbf77476Sternite * 53bbf77476Sternite * @param string $match The match of the syntax 54bbf77476Sternite * @param int $state The state of the handler 55bbf77476Sternite * @param int $pos The position in the document 56bbf77476Sternite * @param Doku_Handler $handler The handler 57bbf77476Sternite * 58bbf77476Sternite * @return array Data for the renderer 59bbf77476Sternite */ 60bbf77476Sternite public function handle($match, $state, $pos, Doku_Handler $handler) 61bbf77476Sternite { 62da77d72dSternite // Locate the given media file and check if it can be opened as zip 6332095c04Sternite $mediapath_file = substr($match, 12, -2); //strip markup 64bbf77476Sternite 65a2549de1Sternite $thumb = new thumbnail($mediapath_file,$this); 66a2549de1Sternite if ($thumb->create()) { 67a2549de1Sternite return array($mediapath_file,$thumb->getMediapath()); 68da77d72dSternite } 69bbf77476Sternite 70da77d72dSternite return array($mediapath_file); 71bbf77476Sternite } 72bbf77476Sternite 73bbf77476Sternite /** 74bbf77476Sternite * Render xhtml output or metadata 75bbf77476Sternite * 76bbf77476Sternite * @param string $mode Renderer mode (supported modes: xhtml) 77bbf77476Sternite * @param Doku_Renderer $renderer The renderer 78bbf77476Sternite * @param array $data The data from the handler() function 79bbf77476Sternite * 80bbf77476Sternite * @return bool If rendering was successful. 81bbf77476Sternite */ 82bbf77476Sternite public function render($mode, Doku_Renderer $renderer, $data) 83bbf77476Sternite { 84967904f1Sternite list ($mediapath_file, $mediapath_thumbnail) = $data; 85bbf77476Sternite 86bbf77476Sternite if ($mode == 'xhtml') { 87bbf77476Sternite 88da77d72dSternite // check if a thumbnail file was found 89da77d72dSternite if (!$mediapath_thumbnail) { 902f64379eSternite if ($this->getConf('show_missing_thumb_error')) { 91da77d72dSternite $renderer->doc .= trim($this->getConf('no_thumb_error_message')) . " " . $mediapath_file; 92da77d72dSternite return true; 93da77d72dSternite } else { 94da77d72dSternite return false; 95da77d72dSternite } 96da77d72dSternite } 97da77d72dSternite 98bbf77476Sternite $src = ml($mediapath_thumbnail,array()); 99bbf77476Sternite 100bbf77476Sternite $i = array(); 101*591afb04Sternite $i['width'] = $this->getConf('thumb_max_dimension'); //TODO: ausrichtung herausrechnen! 1023ac8d5b9Sternite //$i['height'] = ''; 1033ac8d5b9Sternite $i['title'] = $mediapath_file; 104bbf77476Sternite $i['class'] = 'tn'; 105bbf77476Sternite $iatt = buildAttributes($i); 106bbf77476Sternite 1076bc8b42dSternite $renderer->doc .= ($this->getConf('link_to_media_file') ? '<a href="/lib/exe/fetch.php?media=' . $mediapath_file . '">' : '') . 10832095c04Sternite '<img src="'.$src.'" '.$iatt.' />' . 1096bc8b42dSternite ($this->getConf('link_to_media_file') ? '</a>' : ''); 110bbf77476Sternite return true; 111bbf77476Sternite 112bbf77476Sternite } elseif ($mode == 'odt') { 113bbf77476Sternite 114bbf77476Sternite // TODO: yet to implement 115bbf77476Sternite $renderer->cdata(""); 116bbf77476Sternite return true; 117bbf77476Sternite 118bbf77476Sternite } 119bbf77476Sternite 120bbf77476Sternite return false; 121bbf77476Sternite } 122bbf77476Sternite} 123bbf77476Sternite 12429e328d9Sternitefunction getFileSuffix(string $file) { 12529e328d9Sternite return substr(strrchr($file,'.'),1); 12629e328d9Sternite} 12729e328d9Sternite 128a2549de1Sterniteclass thumbnail { 129a2549de1Sternite 130a2549de1Sternite private $source_filepath; 131a2549de1Sternite private $source_mediapath; 132a2549de1Sternite private ?thumb_engine $thumb_engine = null; 13329e328d9Sternite private $formats; 134*591afb04Sternite private int $max_dimension; 135a2549de1Sternite 136a2549de1Sternite public function __construct(string $source_filepath, DokuWiki_Syntax_Plugin $plugin, bool $ismediapath = true) { 137a2549de1Sternite 138a2549de1Sternite if ($ismediapath) { 139a2549de1Sternite $this->source_mediapath = $source_filepath; 140a2549de1Sternite $this->source_filepath = mediaFN($source_filepath); 141a2549de1Sternite } else { 142a2549de1Sternite $this->source_mediapath = false; 143a2549de1Sternite $this->source_filepath = $source_filepath; 144a2549de1Sternite } 145a2549de1Sternite 146*591afb04Sternite $this->max_dimension = $plugin->getConf('thumb_max_dimension'); 147*591afb04Sternite 14829e328d9Sternite // determine file formats supported by ImageMagick 14929e328d9Sternite $this->formats = \Imagick::queryformats(); 15029e328d9Sternite 151a2549de1Sternite // Now attach the correct thumb_engine for the file type of the source file 152a2549de1Sternite //TODO: check for extension "fileinfo", then check for MIME type: if (mime_content_type($filepath_local_file) == "application/pdf") { 15329e328d9Sternite $sourceFileSuffix = getFileSuffix($this->source_filepath); 15429e328d9Sternite if ($sourceFileSuffix == "pdf") { 15529e328d9Sternite // file suffix is pdf, so assume it's a PDF file 156*591afb04Sternite $this->thumb_engine = new thumb_pdf_engine($this); 15729e328d9Sternite } else if (in_array(strtoupper($sourceFileSuffix), $this->formats)) { 15829e328d9Sternite // file suffix is in support list of ImageMagick 159*591afb04Sternite $this->thumb_engine = new thumb_img_engine($this); 160a2549de1Sternite } else { 16129e328d9Sternite // last resort: check if the source file is a ZIP file and look for thumbnails, therein 162*591afb04Sternite $this->thumb_engine = new thumb_zip_engine($this,$plugin->getConf('thumb_paths')); 163a2549de1Sternite } 164a2549de1Sternite } 165a2549de1Sternite 166*591afb04Sternite public function getMaxDimension() { 167*591afb04Sternite return $this->max_dimension; 168*591afb04Sternite } 169*591afb04Sternite 170a2549de1Sternite public function create() { 171a2549de1Sternite if (!$this->thumb_engine) { 172a2549de1Sternite return false; 173a2549de1Sternite } 174a2549de1Sternite 175a2549de1Sternite return $this->thumb_engine->act(); 176a2549de1Sternite } 177a2549de1Sternite 178a2549de1Sternite public function getSourceFilepath() { 179a2549de1Sternite return $this->source_filepath; 180a2549de1Sternite } 181a2549de1Sternite 182a2549de1Sternite protected function getFilename() { 183a2549de1Sternite 184*591afb04Sternite return basename($this->source_filepath) . ".thumb".$this->max_dimension.".".$this->thumb_engine->getFileSuffix(); 185a2549de1Sternite } 186a2549de1Sternite 187a2549de1Sternite public function getFilepath() { 188a2549de1Sternite return dirname($this->source_filepath) . DIRECTORY_SEPARATOR . $this->getFilename(); 189a2549de1Sternite } 190a2549de1Sternite 191a2549de1Sternite public function getMediapath() { 192a2549de1Sternite if ($this->source_mediapath !== false) { 193a2549de1Sternite return substr($this->source_mediapath,0,strrpos($this->source_mediapath,':')) . ":" . $this->getFilename(); 194a2549de1Sternite } else { 195a2549de1Sternite return false; 196a2549de1Sternite } 197a2549de1Sternite } 198a2549de1Sternite 199a2549de1Sternite public function getTimestamp() { 200a2549de1Sternite return file_exists($this->getFilepath()) ? filemtime($this->getFilepath()) : false; 201a2549de1Sternite } 202a2549de1Sternite} 203a2549de1Sternite 204a2549de1Sterniteabstract class thumb_engine { 205a2549de1Sternite 206a2549de1Sternite private ?thumbnail $thumbnail = null; 207a2549de1Sternite 208*591afb04Sternite public function __construct(thumbnail $thumbnail) { 209a2549de1Sternite $this->thumbnail = $thumbnail; 210a2549de1Sternite } 211a2549de1Sternite 212a2549de1Sternite protected function getSourceFilepath() { 213a2549de1Sternite return $this->thumbnail->getSourceFilepath(); 214a2549de1Sternite } 215a2549de1Sternite 216a2549de1Sternite protected function getTargetFilepath() { 217a2549de1Sternite return $this->thumbnail->getFilepath(); 218a2549de1Sternite } 219a2549de1Sternite 220*591afb04Sternite protected function getTargetMaxDimension() { 221*591afb04Sternite return $this->thumbnail->getMaxDimension(); 222a2549de1Sternite } 223a2549de1Sternite 224a2549de1Sternite public function act() { 225a2549de1Sternite if ($this->act_internal()) { 226a2549de1Sternite // Set timestamp to the source file's timestamp (this is used to check in later passes if the file already exists in the correct version). 227e80c5674Sternite if (filemtime($this->getSourceFilepath()) !== filemtime($this->getTargetFilepath())) { 228a2549de1Sternite touch($this->getTargetFilepath(), filemtime($this->getSourceFilepath())); 229e80c5674Sternite } 230a2549de1Sternite return true; 231a2549de1Sternite } 232a2549de1Sternite return false; 233a2549de1Sternite } 234a2549de1Sternite 235e80c5674Sternite // Checks if a thumbnail file for the current file version has already been created 236e80c5674Sternite protected function thumb_needs_update() { 2374ae20bfcSternite return !file_exists($this->getTargetFilepath()) || filemtime($this->getTargetFilepath()) !== filemtime($this->getSourceFilepath()); 238e80c5674Sternite } 239e80c5674Sternite 240a2549de1Sternite public abstract function act_internal(); 241a2549de1Sternite 242a2549de1Sternite public abstract function getFileSuffix(); 243a2549de1Sternite} 244a2549de1Sternite 245a2549de1Sterniteclass thumb_pdf_engine extends thumb_engine { 246a2549de1Sternite 247a2549de1Sternite public function getFileSuffix() { 248a2549de1Sternite return "jpg"; 249a2549de1Sternite } 250a2549de1Sternite 251a2549de1Sternite public function act_internal() { 252e80c5674Sternite if ($this->thumb_needs_update()) { 253a2549de1Sternite $im = new imagick($this->getSourceFilepath()."[0]"); 254a2549de1Sternite $im->setImageColorspace(255); 255a2549de1Sternite $im->setResolution(300, 300); 256a2549de1Sternite $im->setCompressionQuality(95); 257a2549de1Sternite $im->setImageFormat('jpeg'); 258*591afb04Sternite //$im->resizeImage($this->getTargetMaxDimension(),0,imagick::FILTER_LANCZOS,0.9); 259*591afb04Sternite //$im->thumbnailImage($this->getTargetMaxDimension(),$this->getTargetMaxDimension(),true,false); 260*591afb04Sternite $im->writeImage($this->getTargetFilepath()); 261*591afb04Sternite $im->clear(); 262*591afb04Sternite $im->destroy(); 263*591afb04Sternite 264*591afb04Sternite // unfortunately, resizeImage or thumbnailImage leads to a black thumbnail in my setup, so I reopen the file and resize it now. 265*591afb04Sternite $im = new imagick($this->getTargetFilepath()); 266*591afb04Sternite $im->thumbnailImage($this->getTargetMaxDimension(),$this->getTargetMaxDimension(),true,false); 267a2549de1Sternite $im->writeImage($this->getTargetFilepath()); 268a2549de1Sternite $im->clear(); 269a2549de1Sternite $im->destroy(); 270a2549de1Sternite 271a2549de1Sternite return true; 272e80c5674Sternite } else { 273e80c5674Sternite return true; 274e80c5674Sternite } 275a2549de1Sternite } 276a2549de1Sternite} 277a2549de1Sternite 2784ae20bfcSterniteclass thumb_img_engine extends thumb_engine { 2794ae20bfcSternite 2804ae20bfcSternite public function getFileSuffix() { 28129e328d9Sternite return getFileSuffix($this->getSourceFilepath()); 2824ae20bfcSternite } 2834ae20bfcSternite 2844ae20bfcSternite public function act_internal() { 2854ae20bfcSternite if ($this->thumb_needs_update()) { 2864ae20bfcSternite $im = new imagick( $this->getSourceFilepath() ); 287*591afb04Sternite $im->thumbnailImage($this->getTargetMaxDimension(),$this->getTargetMaxDimension(),true,false); 2884ae20bfcSternite $im->writeImage($this->getTargetFilepath()); 2894ae20bfcSternite $im->clear(); 2904ae20bfcSternite $im->destroy(); 2914ae20bfcSternite 2924ae20bfcSternite return true; 2934ae20bfcSternite } else { 2944ae20bfcSternite return true; 2954ae20bfcSternite } 2964ae20bfcSternite } 2974ae20bfcSternite} 2984ae20bfcSternite 299a2549de1Sterniteclass thumb_zip_engine extends thumb_engine { 300a2549de1Sternite 301a2549de1Sternite private array $thumb_paths; 302a2549de1Sternite private $file_suffix = ""; 303a2549de1Sternite 304*591afb04Sternite public function __construct(thumbnail $thumbnail, array $thumb_paths) { 305*591afb04Sternite parent::__construct($thumbnail); 306a2549de1Sternite $this->thumb_paths = $thumb_paths; 307a2549de1Sternite } 308a2549de1Sternite 309a2549de1Sternite public function getFileSuffix() { 310a2549de1Sternite return $this->file_suffix; 311a2549de1Sternite } 312a2549de1Sternite 313a2549de1Sternite public function act_internal() { 314e80c5674Sternite 315a2549de1Sternite $zip = new ZipArchive; 316a2549de1Sternite if ($zip->open($this->getSourceFilepath()) !== true) { 317a2549de1Sternite // file is no zip or cannot be opened 318a2549de1Sternite return false; 319a2549de1Sternite } 320a2549de1Sternite 321a2549de1Sternite // The media file exists and acts as a zip file! 322a2549de1Sternite 323a2549de1Sternite // Check all possible paths (configured in configuration key 'thumb_paths') if there is a file available 324a2549de1Sternite foreach($this->thumb_paths as $thumbnail_path) { 325a2549de1Sternite $this->file_suffix = substr(strrchr($thumbnail_path,'.'),1); 326a2549de1Sternite 327a2549de1Sternite if ($zip->locateName($thumbnail_path) !== false) { 328a2549de1Sternite 329e80c5674Sternite if (!$this->thumb_needs_update()) { 330a2549de1Sternite return true; 331a2549de1Sternite } 332a2549de1Sternite 333a2549de1Sternite // Get the thumbnail file! 334a2549de1Sternite $fp = $zip->getStream($thumbnail_path); 335a2549de1Sternite if(!$fp) { 336a2549de1Sternite return false; 337a2549de1Sternite } 338a2549de1Sternite 339a2549de1Sternite $thumbnaildata = ''; 340a2549de1Sternite while (!feof($fp)) { 341a2549de1Sternite $thumbnaildata .= fread($fp, 8192); 342a2549de1Sternite } 343a2549de1Sternite 344a2549de1Sternite fclose($fp); 345a2549de1Sternite 346a2549de1Sternite // Write thumbnail file to media folder 347a2549de1Sternite file_put_contents($this->getTargetFilepath(), $thumbnaildata); 348a2549de1Sternite 349a2549de1Sternite return true; 350a2549de1Sternite } 351a2549de1Sternite } 352a2549de1Sternite 353a2549de1Sternite return true; 354a2549de1Sternite } 355a2549de1Sternite}