xref: /plugin/mediathumbnails/syntax.php (revision 29e328d90cd5f1ecb4112d5013d40185c50429fa)
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();
101b8b45234Sternite			$i['width']    = $this->getConf('thumb_width');
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
124*29e328d9Sternitefunction getFileSuffix(string $file) {
125*29e328d9Sternite	return substr(strrchr($file,'.'),1);
126*29e328d9Sternite}
127*29e328d9Sternite
128a2549de1Sterniteclass thumbnail {
129a2549de1Sternite
130a2549de1Sternite	private $source_filepath;
131a2549de1Sternite	private $source_mediapath;
132a2549de1Sternite	private ?thumb_engine $thumb_engine = null;
133*29e328d9Sternite	private $formats;
134a2549de1Sternite
135a2549de1Sternite	public function __construct(string $source_filepath, DokuWiki_Syntax_Plugin $plugin, bool $ismediapath = true) {
136a2549de1Sternite
137a2549de1Sternite		if ($ismediapath) {
138a2549de1Sternite			$this->source_mediapath = $source_filepath;
139a2549de1Sternite			$this->source_filepath = mediaFN($source_filepath);
140a2549de1Sternite		} else {
141a2549de1Sternite			$this->source_mediapath = false;
142a2549de1Sternite			$this->source_filepath = $source_filepath;
143a2549de1Sternite		}
144a2549de1Sternite
145*29e328d9Sternite		// determine file formats supported by ImageMagick
146*29e328d9Sternite		$this->formats = \Imagick::queryformats();
147*29e328d9Sternite
148a2549de1Sternite		// Now attach the correct thumb_engine for the file type of the source file
149a2549de1Sternite		//TODO: check for extension "fileinfo", then check for MIME type: if (mime_content_type($filepath_local_file) == "application/pdf") {
150*29e328d9Sternite		$sourceFileSuffix = getFileSuffix($this->source_filepath);
151*29e328d9Sternite		if ($sourceFileSuffix == "pdf") {
152*29e328d9Sternite			// file suffix is pdf, so assume it's a PDF file
153a2549de1Sternite			$this->thumb_engine = new thumb_pdf_engine($this,$plugin->getConf('thumb_width'));
154*29e328d9Sternite		} else if (in_array(strtoupper($sourceFileSuffix), $this->formats)) {
155*29e328d9Sternite			// file suffix is in support list of ImageMagick
1564ae20bfcSternite			$this->thumb_engine = new thumb_img_engine($this,$plugin->getConf('thumb_width'));
157a2549de1Sternite		} else {
158*29e328d9Sternite			// last resort: check if the source file is a ZIP file and look for thumbnails, therein
159a2549de1Sternite			$this->thumb_engine = new thumb_zip_engine($this,$plugin->getConf('thumb_width'),$plugin->getConf('thumb_paths'));
160a2549de1Sternite		}
161a2549de1Sternite	}
162a2549de1Sternite
163a2549de1Sternite	public function create() {
164a2549de1Sternite		if (!$this->thumb_engine) {
165a2549de1Sternite			return false;
166a2549de1Sternite		}
167a2549de1Sternite
168a2549de1Sternite		return $this->thumb_engine->act();
169a2549de1Sternite	}
170a2549de1Sternite
171a2549de1Sternite	public function getSourceFilepath() {
172a2549de1Sternite		return $this->source_filepath;
173a2549de1Sternite	}
174a2549de1Sternite
175a2549de1Sternite	protected function getFilename() {
176a2549de1Sternite
177a2549de1Sternite		return basename($this->source_filepath) . ".thumb.".$this->thumb_engine->getFileSuffix();
178a2549de1Sternite	}
179a2549de1Sternite
180a2549de1Sternite	public function getFilepath() {
181a2549de1Sternite		return dirname($this->source_filepath) . DIRECTORY_SEPARATOR . $this->getFilename();
182a2549de1Sternite	}
183a2549de1Sternite
184a2549de1Sternite	public function getMediapath() {
185a2549de1Sternite		if ($this->source_mediapath !== false) {
186a2549de1Sternite			return substr($this->source_mediapath,0,strrpos($this->source_mediapath,':')) . ":" . $this->getFilename();
187a2549de1Sternite		} else {
188a2549de1Sternite			return false;
189a2549de1Sternite		}
190a2549de1Sternite	}
191a2549de1Sternite
192a2549de1Sternite	public function getTimestamp() {
193a2549de1Sternite		return file_exists($this->getFilepath()) ? filemtime($this->getFilepath()) : false;
194a2549de1Sternite	}
195a2549de1Sternite}
196a2549de1Sternite
197a2549de1Sterniteabstract class thumb_engine {
198a2549de1Sternite
199a2549de1Sternite	private ?thumbnail $thumbnail = null;
200a2549de1Sternite	private int $width;
201a2549de1Sternite
202a2549de1Sternite	public function __construct(thumbnail $thumbnail, int $width) {
203a2549de1Sternite		$this->thumbnail = $thumbnail;
204a2549de1Sternite		$this->width = $width;
205a2549de1Sternite	}
206a2549de1Sternite
207a2549de1Sternite	protected function getSourceFilepath() {
208a2549de1Sternite		return $this->thumbnail->getSourceFilepath();
209a2549de1Sternite	}
210a2549de1Sternite
211a2549de1Sternite	protected function getTargetFilepath() {
212a2549de1Sternite		return $this->thumbnail->getFilepath();
213a2549de1Sternite	}
214a2549de1Sternite
215a2549de1Sternite	protected function getTargetWidth() {
216a2549de1Sternite		return $this->width;
217a2549de1Sternite	}
218a2549de1Sternite
219a2549de1Sternite	public function act() {
220a2549de1Sternite		if ($this->act_internal()) {
221a2549de1Sternite			// 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).
222e80c5674Sternite			if (filemtime($this->getSourceFilepath()) !== filemtime($this->getTargetFilepath())) {
223a2549de1Sternite				touch($this->getTargetFilepath(), filemtime($this->getSourceFilepath()));
224e80c5674Sternite			}
225a2549de1Sternite			return true;
226a2549de1Sternite		}
227a2549de1Sternite		return false;
228a2549de1Sternite	}
229a2549de1Sternite
230e80c5674Sternite	// Checks if a thumbnail file for the current file version has already been created
231e80c5674Sternite	protected function thumb_needs_update() {
2324ae20bfcSternite		return !file_exists($this->getTargetFilepath()) || filemtime($this->getTargetFilepath()) !== filemtime($this->getSourceFilepath());
233e80c5674Sternite	}
234e80c5674Sternite
235a2549de1Sternite	public abstract function act_internal();
236a2549de1Sternite
237a2549de1Sternite	public abstract function getFileSuffix();
238a2549de1Sternite}
239a2549de1Sternite
240a2549de1Sterniteclass thumb_pdf_engine extends thumb_engine {
241a2549de1Sternite
242a2549de1Sternite	public function getFileSuffix() {
243a2549de1Sternite		return "jpg";
244a2549de1Sternite	}
245a2549de1Sternite
246a2549de1Sternite	public function act_internal() {
247e80c5674Sternite		if ($this->thumb_needs_update()) {
248a2549de1Sternite			$im = new imagick( $this->getSourceFilepath()."[0]" );
249a2549de1Sternite			$im->setImageColorspace(255);
250a2549de1Sternite			$im->setResolution(300, 300);
251a2549de1Sternite			$im->setCompressionQuality(95);
252a2549de1Sternite			$im->setImageFormat('jpeg');
253a2549de1Sternite			//$im->resizeImage(substr($this->getConf('thumb_width'),-2),0,imagick::FILTER_LANCZOS,0.9);
254a2549de1Sternite			$im->writeImage($this->getTargetFilepath());
255a2549de1Sternite			$im->clear();
256a2549de1Sternite			$im->destroy();
257a2549de1Sternite
258a2549de1Sternite			return true;
259e80c5674Sternite		} else {
260e80c5674Sternite			return true;
261e80c5674Sternite		}
262a2549de1Sternite	}
263a2549de1Sternite}
264a2549de1Sternite
2654ae20bfcSterniteclass thumb_img_engine extends thumb_engine {
2664ae20bfcSternite
2674ae20bfcSternite	public function getFileSuffix() {
268*29e328d9Sternite		return getFileSuffix($this->getSourceFilepath());
2694ae20bfcSternite	}
2704ae20bfcSternite
2714ae20bfcSternite	public function act_internal() {
2724ae20bfcSternite		if ($this->thumb_needs_update()) {
2734ae20bfcSternite			$im = new imagick( $this->getSourceFilepath() );
2744ae20bfcSternite			//TODO: consider height?
2754ae20bfcSternite			$im->thumbnailImage($this->getTargetWidth(),$this->getTargetWidth(),true,false);
2764ae20bfcSternite			$im->writeImage($this->getTargetFilepath());
2774ae20bfcSternite			$im->clear();
2784ae20bfcSternite			$im->destroy();
2794ae20bfcSternite
2804ae20bfcSternite			return true;
2814ae20bfcSternite		} else {
2824ae20bfcSternite			return true;
2834ae20bfcSternite		}
2844ae20bfcSternite	}
2854ae20bfcSternite}
2864ae20bfcSternite
287a2549de1Sterniteclass thumb_zip_engine extends thumb_engine {
288a2549de1Sternite
289a2549de1Sternite	private array $thumb_paths;
290a2549de1Sternite	private $file_suffix = "";
291a2549de1Sternite
292a2549de1Sternite	public function __construct(thumbnail $thumbnail, int $width, array $thumb_paths) {
293a2549de1Sternite		parent::__construct($thumbnail,$width);
294a2549de1Sternite		$this->thumb_paths = $thumb_paths;
295a2549de1Sternite	}
296a2549de1Sternite
297a2549de1Sternite	public function getFileSuffix() {
298a2549de1Sternite		return $this->file_suffix;
299a2549de1Sternite	}
300a2549de1Sternite
301a2549de1Sternite	public function act_internal() {
302e80c5674Sternite
303a2549de1Sternite		$zip = new ZipArchive;
304a2549de1Sternite		if ($zip->open($this->getSourceFilepath()) !== true) {
305a2549de1Sternite			// file is no zip or cannot be opened
306a2549de1Sternite			return false;
307a2549de1Sternite		}
308a2549de1Sternite
309a2549de1Sternite		// The media file exists and acts as a zip file!
310a2549de1Sternite
311a2549de1Sternite		// Check all possible paths (configured in configuration key 'thumb_paths') if there is a file available
312a2549de1Sternite		foreach($this->thumb_paths as $thumbnail_path) {
313a2549de1Sternite			$this->file_suffix = substr(strrchr($thumbnail_path,'.'),1);
314a2549de1Sternite
315a2549de1Sternite			if ($zip->locateName($thumbnail_path) !== false) {
316a2549de1Sternite
317e80c5674Sternite				if (!$this->thumb_needs_update()) {
318a2549de1Sternite					return true;
319a2549de1Sternite				}
320a2549de1Sternite
321a2549de1Sternite				// Get the thumbnail file!
322a2549de1Sternite				$fp = $zip->getStream($thumbnail_path);
323a2549de1Sternite				if(!$fp) {
324a2549de1Sternite					return false;
325a2549de1Sternite				}
326a2549de1Sternite
327a2549de1Sternite				$thumbnaildata = '';
328a2549de1Sternite				while (!feof($fp)) {
329a2549de1Sternite					$thumbnaildata .= fread($fp, 8192);
330a2549de1Sternite				}
331a2549de1Sternite
332a2549de1Sternite				fclose($fp);
333a2549de1Sternite
334a2549de1Sternite				// Write thumbnail file to media folder
335a2549de1Sternite				file_put_contents($this->getTargetFilepath(), $thumbnaildata);
336a2549de1Sternite
337a2549de1Sternite				return true;
338a2549de1Sternite			}
339a2549de1Sternite		}
340a2549de1Sternite
341a2549de1Sternite		return true;
342a2549de1Sternite	}
343a2549de1Sternite}