PluginProbe
Document Gallery / 1.4.2
Document Gallery v1.4.2
trunk 0.8 0.8.5 1.0 1.0.1 1.0.2 1.0.3 1.0.4 1.1 1.2 1.2.1 1.3 1.3.1 1.4 1.4.1 1.4.2 1.4.3 2.0 2.0.1 2.0.10 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 All 94 releases
document-gallery / util / class-thumber.php

class-thumber.php in Document Gallery 1.4.2, at util/class-thumber.php

469 lines 16.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Thumber wraps the functionality required to
5 * generate thumbnails for arbitrary documents.
6 *
7 * @author drossiter
8 */
9 class DG_Thumber {
10 /**
11 * Blocks instantiation. All functionality is static.
12 */
13 private function __construct() {
14
15 }
16
17 /**
18 * @param type $ID Document ID
19 * @return string URL to the thumbnail.
20 */
21 public static function getThumbnail($ID) {
22 if (!($url = wp_get_attachment_thumb_url($ID))
23 && !($url = self::getGhostscriptThumbnail($ID))
24 && !($url = self::getGoogleDriveThumbnail($ID))) {
25 $url = self::getDefaultThumbnail($ID);
26 }
27
28 return $url;
29 }
30
31 public static function getAudioVideoThumbnail($ID) {
32 global $wp_version;
33
34 // can only pull images from videos/audio after 3.6
35 if(version_compare($wp_version, '3.6', '<')) return false;
36
37 $attachment = get_post($ID);
38 $doc_path = get_attached_file($ID);
39 $doc_url = wp_get_attachment_url($ID);
40
41 if (preg_match('#^video/#', get_post_mime_type($attachment))) {
42 $metadata = wp_read_video_metadata($doc_path);
43 }
44 elseif (preg_match('#^audio/#', get_post_mime_type($attachment))) {
45 $metadata = wp_read_audio_metadata($doc_path);
46 }
47 else {
48 return false;
49 }
50
51 if (!empty($metadata['image']['data'])) {
52 $ext = '.jpg';
53 switch ($metadata['image']['mime']) {
54 case 'image/gif':
55 $ext = '.gif';
56 break;
57 case 'image/png':
58 $ext = '.png';
59 break;
60 }
61
62 self::deleteExistingThumb($ID);
63
64 $dirname = dirname($doc_path);
65 $basename = basename($doc_path);
66 $thumb_name = self::getUniqueThumbName(
67 $dirname,substr($basename, 0, strrpos($basename, '.')), $ext);
68 $basename = str_replace('.', '-', $basename) . '-thumb' . $ext;
69 $uploaded = wp_upload_bits($basename, '', $metadata['image']['data']);
70 if (false === $uploaded['error']) {
71 $attachment = array(
72 'post_mime_type' => $metadata['image']['mime'],
73 'post_type' => 'attachment',
74 'post_content' => '',
75 );
76 $sub_attachment_id = wp_insert_attachment($attachment, $uploaded['file']);
77 $attach_data = wp_generate_attachment_metadata($sub_attachment_id, $uploaded['file']);
78 wp_update_attachment_metadata($sub_attachment_id, $attach_data);
79 update_post_meta($attachment_id, '_thumbnail_id', $sub_attachment_id);
80 }
81 }
82 }
83
84 /**
85 * Get thumbnail for document with given ID using Ghostscript
86 * .
87 * @param string $ID The attachment ID to retrieve thumbnail from.
88 * @param int $pg The page number to make thumbnail of -- index starts at 1.
89 * @return bool|string False on failure, URL to thumb on success.
90 */
91 public static function getGhostscriptThumbnail($ID, $pg = 1) {
92 static $gs = false;
93 static $exts = array('pdf');
94
95 if (!self::isGhostscriptAvailable())
96 return false;
97
98 if (!$gs) {
99 // I don't understand why anyone would run a Windows server...
100 $gs = (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' ? 'gswin32c' : 'gs')
101 . ' -sDEVICE=png16m -dFirstPage=%d -dLastPage=%d -dBATCH'
102 . ' -dNOPAUSE -dPDFFitPage -sOutputFile=%s %s';
103 }
104
105 $doc_path = get_attached_file($ID);
106 $doc_url = wp_get_attachment_url($ID);
107 $dirname = dirname($doc_path);
108 $basename = basename($doc_path);
109
110 // check whether filetype supported by Ghostscript
111 if (!in_array(strtolower(self::getExt($basename)), $exts))
112 return false;
113
114 self::deleteExistingThumb($ID);
115
116 $thumb_name = self::getUniqueThumbName(
117 $dirname,substr($basename, 0, strrpos($basename, '.')));
118 $thumb_path = $dirname . DIRECTORY_SEPARATOR . $thumb_name;
119
120 exec(sprintf($gs, $pg, $pg, $thumb_path, $doc_path), $out, $ret);
121
122 if ($ret != 0) {
123 error_log('DG: Ghostscript failed: ' . $out);
124 @unlink($thumb_path);
125 return false;
126 }
127
128 self::fixNewThumb($thumb_path);
129
130 // store reference to new thumbnail
131 wp_update_attachment_metadata($ID, array('thumb' => $thumb_name));
132
133 // return URL pointing to new thumbnail
134 return str_replace($basename, $thumb_name, $doc_url);
135 }
136
137 /**
138 * Get thumbnail for document with given ID from Google Drive Viewer.
139 *
140 * @param string $ID The attachment ID to retrieve thumbnail from.
141 * @param int $pg The page number to make thumbnail of -- index starts at 1.
142 * @return bool|string False on failure, URL to thumb on success.
143 */
144 public static function getGoogleDriveThumbnail($ID, $pg = 1) {
145 // User agent for Lynx 2.8.7rel.2 -- Why? Because I can.
146 static $user_agent = "Lynx/2.8.7rel.2 libwww-FM/2.14 SSL-MM/1.4.1 OpenSSL/1.0.0a";
147 static $timeout = 90;
148 static $exts = array(
149 'tiff', 'bmp', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx',
150 'pdf', 'pages', 'ai', 'psd', 'dxf', 'svg', 'eps', 'ps', 'ttf'
151 );
152
153 $google_viewer = "https://docs.google.com/viewer?url=%s&a=bi&pagenumber=%d&w=%d";
154 $doc_path = get_attached_file($ID);
155 $doc_url = wp_get_attachment_url($ID);
156 $basename = basename($doc_path);
157 $dirname = dirname($doc_path);
158
159 // check whether filetype supported by Google Drive Viewer
160 if (!in_array(strtolower(self::getExt($basename)), $exts))
161 return false;
162
163 self::deleteExistingThumb($ID);
164
165 $thumb_name = self::getUniqueThumbName(
166 $dirname,substr($basename, 0, strrpos($basename, '.')));
167 $thumb_path = $dirname . DIRECTORY_SEPARATOR . $thumb_name;
168
169 // args for use in HTTP request
170 $args = array(
171 'timeout' => $timeout, // these requests can take a LONG time
172 'redirection' => 5,
173 'httpversion' => '1.0',
174 'user-agent' => self::$user_agent,
175 'blocking' => true,
176 'headers' => array(),
177 'cookies' => array(),
178 'body' => null,
179 'compress' => false,
180 'decompress' => true,
181 'sslverify' => true,
182 'stream' => true,
183 'filename' => $thumb_path
184 );
185
186 // prevent PHP timeout before HTTP completes
187 set_time_limit($timeout);
188
189 $google_viewer = sprintf($google_viewer, urlencode($doc_url), (int) $pg, 150);
190
191 // get thumbnail from Google Drive Viewer & check for error on return
192 $response = wp_remote_get($google_viewer, $args);
193
194 if (is_wp_error($response) || $response['response']['code'] != 200) {
195 error_log('DG: Failed to retrieve thumbnail from Google: ' .
196 (is_wp_error($response)
197 ? $response->get_error_message()
198 : $response['response']['message']));
199
200 @unlink($thumb_path);
201 return false;
202 }
203
204 self::fixNewThumb($thumb_path);
205
206 // store reference to new thumbnail
207 wp_update_attachment_metadata($ID, array('thumb' => $thumb_name));
208
209 // return URL pointing to new thumbnail
210 return str_replace($basename, $thumb_name, $doc_url);
211 }
212
213 /**
214 * Get thumbnail for document with given ID from default images.
215 *
216 * @param string $ID The attachment ID to retrieve thumbnail from.
217 * @return string URL to thumb on success.
218 */
219 public static function getDefaultThumbnail($ID) {
220 $icon_url = DG_URL . 'icons/';
221
222 $url = wp_get_attachment_url($ID);
223 $ext = self::getExt(basename($url));
224
225 // handle images
226 if (wp_attachment_is_image($ID) &&
227 ($icon = wp_get_attachment_image_src($ID, 'thumbnail', false))) {
228 $icon = $icon[0];
229 }
230 // default extension icon
231 elseif ($name = self::getDefaultIcon($ext)) {
232 $icon = $icon_url . $name;
233 }
234 // fallback to standard WP icons
235 elseif ($icon = wp_get_attachment_image_src($ID, null, true)) {
236 $icon = $icon[0];
237 }
238 // everything failed. This is bad...
239 else {
240 $icon = $icon_url . 'missing.png';
241 }
242
243 return $icon;
244 }
245
246 /**
247 * @param string $dirname Directory where the document is located.
248 * @param string $basename Filename of document minue the extension.
249 * @param string $ext The extension of the image to be created.
250 * @return string Name unique within the directory given, derived from the basename given.
251 */
252 private static function getUniqueThumbName($dirname, $basename, $ext = '.png') {
253 return wp_unique_filename($dirname, str_replace('.', '-', $basename) . '-thumb' . $ext);
254 }
255
256 /**
257 * Removes the existing thumbnail for the attachment
258 * with $ID, if such a thumbnail exists.
259 *
260 * Substantially borrowed from wp_delete_attachment() in wp-includes/post.php
261 *
262 * @filter wp_delete_file Filter the path of the file to delete.
263 * @global type $wpdb
264 * @param type $ID
265 */
266 private static function deleteExistingThumb($ID) {
267 global $wpdb;
268
269 $meta = wp_get_attachment_metadata($ID);
270
271 if (empty($meta['thumb']))
272 return;
273
274 $doc_path = get_attached_file($ID);
275
276 // Don't delete the thumb if another attachment uses it
277 $SQL = 'SELECT meta_id '
278 . "FROM $wpdb->postmeta "
279 . 'WHERE meta_key = \'_wp_attachment_metadata\' '
280 . 'AND meta_value LIKE %s '
281 . 'AND post_id <> %d';
282
283 if (!$wpdb->get_row($wpdb->prepare($SQL, '%' . $meta['thumb'] . '%', $ID))) {
284 $thumb_path = str_replace(basename($doc_path), $meta['thumb'], $doc_path);
285 // This filter is documented in wp-admin/custom-header.php
286 $thumb_path = apply_filters('wp_delete_file', $thumb_path);
287 @unlink($thumb_path);
288 }
289 }
290
291 /**
292 * Cleans up dimensions and permissions for a newly-created thumbnail.
293 * @param type $thumb_path
294 * @return type
295 */
296 private static function fixNewThumb($thumb_path) {
297 // ensure perms are correct
298 $stat = stat(dirname($thumb_path));
299 $perms = $stat['mode'] & 0000666;
300 @chmod($thumb_path, $perms);
301
302 // scae to no larger than 150x150px
303 $img = wp_get_image_editor($thumb_path);
304
305 if (is_wp_error($img)) {
306 error_log('DG: Failed to get image editor. Can\'t resize thumbnail.');
307 return;
308 }
309
310 $img->resize(150, 150, false);
311 $img->save($thumb_path);
312 }
313
314 /**
315 * Checks whether we may call gs through exec().
316 * @staticvar bool $available
317 * @return bool
318 */
319 private static function isGhostscriptAvailable() {
320 static $available;
321
322 if (!isset($available)) {
323 $is_win = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN';
324 $gs = $is_win ? 'gswin32c' : 'gs';
325
326 $exec = exec($is_win ? "where $gs" : "which $gs");
327 $available = self::isExecAvailable() && !empty($exec);
328
329 if (WP_DEBUG && $available) {
330 error_log("DG: Found the $gs executable.");
331 }
332 else if (WP_DEBUG) {
333 error_log("DG: Didn\'t find the $gs executable.");
334 }
335 }
336
337 return $available;
338 }
339
340 /**
341 * Checks whether exec() may be used.
342 * Source: http://stackoverflow.com/a/12980534/866618
343 * @staticvar bool $available
344 * @return bool
345 */
346 private static function isExecAvailable() {
347 static $available;
348
349 if (!isset($available)) {
350 $available = true;
351
352 if (ini_get('safe_mode')) {
353 $available = false;
354 }
355 else {
356 $d = ini_get('disable_functions');
357 $s = ini_get('suhosin.executor.func.blacklist');
358 if ("$d$s") {
359 $array = preg_split('/,\s*/', "$d,$s");
360 if (in_array('exec', $array)) {
361 $available = false;
362 }
363 }
364 }
365
366 if (WP_DEBUG) {
367 error_log('DG: exec() ' . ($available ? 'is' : 'isn\'t') . ' available.');
368 }
369 }
370
371 return $available;
372 }
373
374 /**
375 * Formerly achieved with wp_check_filetype(), but it was only returning
376 * valid results if the active user had permission to upload the given filetype.
377 *
378 * @param type $filename Name of the file to get extension from.
379 * @return str|bool Returns the file extension on success, false on failure.
380 */
381 private static function getExt($filename) {
382 foreach (wp_get_mime_types() as $ext_preg => $unused) {
383 $ext_preg = '!\.(' . $ext_preg . ')$!i';
384 if (preg_match($ext_preg, $filename, $ext_matches)) {
385 return $ext_matches[1];
386 }
387 }
388
389 return false;
390 }
391
392 /**
393 * Returns the name of the image to represent the filetype given.
394 * @staticvar array $exts
395 * @param type $filename
396 * @return boolean
397 */
398 private static function getDefaultIcon($ext) {
399 // Maps file ext to default image name.
400 static $exts = array(
401 // Most Common First
402 'pdf' => 'pdf.png',
403
404 // MS Office
405 'doc|docx|docm|dotx|dotm' => 'msdoc.png',
406 'ppt|pot|pps|pptx|pptm|ppsx|ppsm|potx|potm|ppam|sldx|sldm' => 'msppt.png',
407 'xla|xls|xlt|xlw|xlsx|xlsm|xlsb|xltx|xltm|xlam' => 'msxls.png',
408 'mdb' => 'msaccess.png',
409
410 // iWork
411 'key' => 'key.png',
412 'numbers' => 'numbers.png',
413 'pages' => 'pages.png',
414
415 // Images
416 'jpg|jpeg|jpe|gif|png|bmp|tif|tiff|ico' => 'image.png',
417
418 // Video formats
419 'asf|asx|wmv|wmx|wm|avi|divx|flv|mov' => 'video.png',
420 'qt|mpeg|mpg|mpe|mp4|m4v|ogv|webm|mkv' => 'video.png',
421
422 // Audio formats
423 'mp3|m4a|m4b|ra|ram|wav|ogg|oga|wma|wax|mka' => 'audio.png',
424 'midi|mid' => 'midi.png',
425
426 // Text formats
427 'txt|tsv|csv' => 'text.png',
428 'rtx' => 'rtx.png',
429 'rtf' => 'rtf.png',
430 'ics' => 'ics.png',
431 'wp|wpd' => 'wordperfect.png',
432
433 // Programming
434 'html|htm' => 'html.png',
435 'css' => 'css.png',
436 'js' => 'javascript.png',
437 'class' => 'java.png',
438 'asc' => 'asc.png',
439 'c' => 'c.png',
440 'cc|cpp' => 'cpp.png',
441 'h' => 'h.png',
442
443 // Msc application formats
444 'zip|tar|gzip|gz|bz2|tgz|7z|rar' => 'compressed.png',
445 'exe' => 'exec.png',
446 'swf' => 'shockwave.png',
447
448 // OpenDocument formats
449 'odt' => 'opendocument-text.png',
450 'odp' => 'opendocument-presentation.png',
451 'ods' => 'opendocument-spreadsheet.png',
452 'odg' => 'opendocument-graphics.png',
453 'odb' => 'opendocument-database.png',
454 'odf' => 'opendocument-formula.png'
455 );
456
457 foreach ($exts as $ext_preg => $icon) {
458 $ext_preg = '!(' . $ext_preg . ')$!i';
459 if (preg_match($ext_preg, $ext)) {
460 return $icon;
461 }
462 }
463
464 return false;
465 }
466 }
467
468 ?>
469