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