xref: /plugin/mediathumbnails/syntax.php (revision 591afb049951650ad9e486347d2de13d1718042e)
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}