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