xref: /plugin/mediathumbnails/thumb_engines.php (revision e19533e13ade8665caee6886acf0de2dbdc8883d)
1<?php
2/**
3 * DokuWiki Plugin mediathumbnails (thumb_engine class and subclasses)
4 *
5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
6 * @author  Thomas Schäfer <thomas.schaefer@itschert.net>
7 */
8
9abstract class thumb_engine {
10
11	protected ?thumbnail $thumbnail = null;
12	protected bool $state_failed = false;
13
14	public function __construct(thumbnail $thumbnail) {
15		$this->thumbnail = $thumbnail;
16	}
17
18	protected function getSourceFilepath() {
19		return $this->thumbnail->getSourceFilepath();
20	}
21
22	protected function getTargetFilepath() {
23		return $this->thumbnail->getFilepath();
24	}
25
26	protected function getTargetMaxDimension() {
27		return $this->thumbnail->getMaxDimension();
28	}
29
30	public function act(): bool {
31		if ($this->act_internal()) {
32			// 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).
33			$sourceFilePath = $this->getSourceFilepath();
34			$targetFilePath = $this->getTargetFilepath();
35			if (filemtime($sourceFilePath) !== filemtime($targetFilePath)) {
36				touch($sourceFilePath, filemtime($targetFilePath));
37			}
38			return true;
39		}
40		return false;
41	}
42
43	// Checks if a thumbnail file for the current file version has already been created
44	protected function thumb_needs_update(): bool {
45		return !file_exists($this->getTargetFilepath()) || filemtime($this->getTargetFilepath()) !== filemtime($this->getSourceFilepath());
46	}
47
48	public function has_failed(): bool {
49		return $this->state_failed;
50	}
51
52	public abstract function act_internal(): bool;
53
54	public abstract function getFileSuffix(): string;
55}
56
57class thumb_pdf_engine extends thumb_engine {
58
59	public function getFileSuffix(): string {
60		return "jpg";
61	}
62
63	public function act_internal(): bool {
64		if ($this->thumb_needs_update()) {
65			//if file does not exist
66			if (!file_exists($this->getSourceFilepath())) {
67				return false;
68			}
69			$im = new imagick($this->getSourceFilepath()."[0]");
70            // the following line was in the original code. Issue #6 (https://github.com/ternite/dokuwiki-plugin-mediathumbnails/issues/6)
71            // indicated there might be problems with colors, so I uncommented the line (TS, 2025-10-11)
72			//$im->setImageColorspace(255);
73			$im->setResolution(300, 300);
74			$im->setCompressionQuality(95);
75			$im->setImageFormat('jpeg');
76			//$im->resizeImage($this->getTargetMaxDimension(),0,imagick::FILTER_LANCZOS,0.9);
77			//$im->thumbnailImage($this->getTargetMaxDimension(),$this->getTargetMaxDimension(),true,false);
78			$im->writeImage($this->getTargetFilepath());
79			$im->clear();
80			$im->destroy();
81
82			// unfortunately, resizeImage or thumbnailImage leads to a black thumbnail in my setup, so I reopen the file and resize it now.
83			$im = new imagick($this->getTargetFilepath());
84			$im->thumbnailImage($this->getTargetMaxDimension(),$this->getTargetMaxDimension(),true,false);
85			$im->writeImage($this->getTargetFilepath());
86			$im->clear();
87			$im->destroy();
88
89			return true;
90		} else {
91			return true;
92		}
93	}
94}
95
96class thumb_img_engine extends thumb_engine {
97
98	public function getFileSuffix(): string {
99		return getFileSuffix($this->getSourceFilepath());
100	}
101
102	public function act_internal(): bool {
103		if ($this->thumb_needs_update()) {
104			$im = new imagick( $this->getSourceFilepath() );
105			$im->thumbnailImage($this->getTargetMaxDimension(),$this->getTargetMaxDimension(),true,false);
106			$im->writeImage($this->getTargetFilepath());
107			$im->clear();
108			$im->destroy();
109
110			return true;
111		} else {
112			return true;
113		}
114	}
115}
116
117class thumb_zip_engine extends thumb_engine {
118
119	private array $thumb_paths;
120	private $file_suffix = "";
121
122	public function __construct(thumbnail $thumbnail, array $thumb_paths) {
123		parent::__construct($thumbnail);
124		$this->thumb_paths = $thumb_paths;
125	}
126
127	public function getFileSuffix(): string {
128		return $this->file_suffix;
129	}
130
131	public function act_internal(): bool {
132
133		$zip = new ZipArchive;
134		if ($zip->open($this->getSourceFilepath()) !== true) {
135			// file is no zip or cannot be opened
136			return false;
137		}
138
139		// The media file exists and acts as a zip file!
140
141		// Check all possible paths (configured in configuration key 'thumb_paths') if there is a file available - if there are multiple files, the first one found is used.
142		$thumbnail_found = false;
143		foreach($this->thumb_paths as $thumbnail_path) {
144			$this->file_suffix = substr(strrchr($thumbnail_path,'.'),1);
145
146			if ($zip->locateName($thumbnail_path) !== false) {
147
148				if (!$this->thumb_needs_update()) {
149					return true;
150				}
151
152				// Get the thumbnail file!
153				$fp = $zip->getStream($thumbnail_path);
154				if(!$fp) {
155					return false;
156				}
157
158				$thumbnaildata = '';
159				while (!feof($fp)) {
160					$thumbnaildata .= fread($fp, 8192);
161				}
162
163				fclose($fp);
164
165				// Write thumbnail file to media folder
166				file_put_contents($this->getTargetFilepath(), $thumbnaildata);
167
168				$thumbnail_found = true;
169				return true;
170			}
171		}
172
173		// if we reach this point, no thumbnail file was found within the zip file
174		if (!$thumbnail_found) {
175			$this->state_failed = true;
176			msg("plugin mediathumbnails: No thumbnail found inside zip file " . $this->getSourceFilepath());
177		}
178
179		return false;
180	}
181}