PluginProbe
Document Gallery / 1.4.3
Document Gallery v1.4.3
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.3, at util/class-thumber.php

605 lines 21.1 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 * Wraps generation of thumbnails for various attachment filetypes.
19 *
20 * @filter dg_thumbers Allows developers to filter the Thumbers used
21 * for specific filetypes. Index is the regex to match file extensions
22 * supported and the value is anything that can be accepted by call_user_func().
23 * The function must take two parameters, 1st is the int ID of the attachment
24 * to get a thumbnail for, 2nd is the page to take a thumbnail of
25 * (may not be relevant for some filetypes).
26 *
27 * NOTE: Last thumber in array *must* match ALL extensions (i.e.: ".*")
28 *
29 * @param type $ID Document ID
30 * @return string URL to the thumbnail.
31 */
32 public static function getThumbnail($ID, $pg = 1) {
33 static $thumbers = array();
34
35 if (!$thumbers) {
36 global $wp_version;
37
38 // Audio/Video embedded images
39 if (version_compare($wp_version, '3.6', '>=')) {
40 $exts = implode('|', self::getAudioVideoExts());
41 $thumbers[$exts] = array('Thumber', 'getAudioVideoThumbnail');
42 }
43
44 // Ghostscript
45 if (self::isGhostscriptAvailable()) {
46 $exts = implode('|', self::getGhostscriptExts());
47 $thumbers[$exts] = array('Thumber', 'getGhostscriptThumbnail');
48 }
49
50 // Imagick
51 if (call_user_func(array( 'WP_Image_Editor_Imagick', 'test'))) {
52 try {
53 $exts = @Imagick::queryFormats();
54 if($exts) {
55 $exts = implode('|', $exts);
56 $thumbers[$exts] = array('Thumber', 'getImagickThumbnail');
57 }
58 }
59 catch (Exception $e) {
60
61 }
62 }
63
64 // Google Drive Viewer
65 $exts = implode('|', self::getGoogleDriveExts());
66 $thumbers[$exts] = array('Thumber', 'getGoogleDriveThumbnail');
67
68 // match everything that falls through to the end
69 $thumbers['.*'] = array('Thumber', 'getDefaultThumbnail');
70
71 // allow users to filter thumbers used
72 $thumbers = apply_filter('dg_thumbers', $thumbers);
73
74 // strip out anything that can't be called
75 $thumbers = array_filter($thumbers, 'is_callable');
76 }
77
78 // if we haven't saved a thumb, generate one
79 if(!($url = wp_get_attachment_thumb_url($ID))) {
80 $file = get_attached_file($ID);
81
82 foreach ($thumbers as $ext_preg => $thumber) {
83 $ext_preg = '!\.(\.' . $ext_preg . ')$!i';
84
85 if (preg_match($ext_preg, $file) // can handle ext
86 && ($url = call_user_func($thumber, $ID, $pg))) { // is vaild return
87 break;
88 }
89 }
90 }
91
92 return $url;
93 }
94
95 /**
96 * Uses wp_read_video_metadata() and wp_read_audio_metadata() to retrieve
97 * an embedded image to use as a thumbnail.
98 *
99 * NOTE: Caller must verify that WP version >= 3.6.
100 *
101 * @param string $ID The attachment ID to retrieve thumbnail from.
102 * @param int $pg Unused.
103 * @return bool|string False on failure, URL to thumb on success.
104 */
105 public static function getAudioVideoThumbnail($ID, $pg = 1) {
106 $attachment = get_post($ID);
107 $doc_path = get_attached_file($ID);
108
109 if (preg_match('#^video/#', get_post_mime_type($attachment))) {
110 $metadata = wp_read_video_metadata($doc_path);
111 }
112 elseif (preg_match('#^audio/#', get_post_mime_type($attachment))) {
113 $metadata = wp_read_audio_metadata($doc_path);
114 }
115
116 // unsupported mime type || no embedded image present
117 if(!isset($metadata) || empty($metadata['image']['data'])) {
118 return false;
119 }
120
121 self::deleteExistingThumb($ID);
122
123 $ext = '.jpg';
124 switch ($metadata['image']['mime']) {
125 case 'image/gif':
126 $ext = '.gif';
127 break;
128 case 'image/png':
129 $ext = '.png';
130 break;
131 }
132
133 $dirname = dirname($doc_path);
134 $basename = basename($doc_path);
135
136 $thumb_name = self::getUniqueThumbName(
137 $dirname, substr($basename, 0, strrpos($basename, '.')), $ext);
138 $thumb_path = $dirname . DIRECTORY_SEPARATOR . $thumb_name;
139
140 // TODO: This doesn't work... Need alternate to wp_upload_bits()
141 $uploaded = wp_upload_bits($thumb_path, null, $metadata['image']['data']);
142
143 if (false !== $uploaded['error']) {
144 if (WP_DEBUG) {
145 error_log("DG: Failed to save AV thumbnail: " . $uploaded['error']);
146 }
147
148 return false;
149 }
150
151 self::fixNewThumb($uploaded['file']);
152
153 // store reference to new thumbnail
154 wp_update_attachment_metadata($ID, array('thumb' => $thumb_name));
155
156 return $uploaded['url'];
157 }
158
159 /**
160 * Uses WP_Image_Editor_Imagick to generate thumbnails.
161 *
162 * NOTE: Caller must verify that Imagick is present and that the extension is supported.
163 *
164 * @param string $ID The attachment ID to retrieve thumbnail from.
165 * @param int $pg Unused.
166 * @return bool|string False on failure, URL to thumb on success.
167 */
168 public static function getImagickThumbnail($ID, $pg = 1) {
169 $doc_path = get_attached_file($ID);
170
171 $img = wp_get_image_editor($doc_path);
172 if(is_wp_error($img)) {
173 return false;
174 }
175
176 $doc_url = wp_get_attachment_url($ID);
177 $dirname = dirname($doc_path);
178 $basename = basename($doc_path);
179
180 self::deleteExistingThumb($ID);
181
182 $thumb_name = self::getUniqueThumbName(
183 $dirname, substr($basename, 0, strrpos($basename, '.')));
184 $thumb_path = $dirname . DIRECTORY_SEPARATOR . $thumb_name;
185
186 $img->resize(150, 150, false);
187 $img->save($thumb_path);
188
189 // ensure perms are correct
190 $stat = stat($dirname);
191 $perms = $stat['mode'] & 0000666;
192 @chmod($thumb_path, $perms);
193
194 // store reference to new thumbnail
195 wp_update_attachment_metadata($ID, array('thumb' => $thumb_name));
196
197 // return URL pointing to new thumbnail
198 return preg_replace('#'.preg_quote($basename).'$#', $thumb_name, $doc_url);
199 }
200
201 /**
202 * Get thumbnail for document with given ID using Ghostscript. Imagick could
203 * also handle this, but is *much* slower.
204 *
205 * NOTE: Caller must verify that exec and gs are available and that extension is supported.
206 *
207 * @param string $ID The attachment ID to retrieve thumbnail from.
208 * @param int $pg The page number to make thumbnail of -- index starts at 1.
209 * @return bool|string False on failure, URL to thumb on success.
210 */
211 public static function getGhostscriptThumbnail($ID, $pg = 1) {
212 static $gs = false;
213 if (!$gs) {
214 // I don't understand why anyone would run a Windows server...
215 $gs = (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' ? 'gswin32c' : 'gs')
216 . ' -sDEVICE=png16m -dFirstPage=%d -dLastPage=%d -dBATCH'
217 . ' -dNOPAUSE -dPDFFitPage -sOutputFile=%s %s';
218 }
219
220 $doc_path = get_attached_file($ID);
221 $doc_url = wp_get_attachment_url($ID);
222 $dirname = dirname($doc_path);
223 $basename = basename($doc_path);
224
225 self::deleteExistingThumb($ID);
226
227 $thumb_name = self::getUniqueThumbName(
228 $dirname, substr($basename, 0, strrpos($basename, '.')));
229 $thumb_path = $dirname . DIRECTORY_SEPARATOR . $thumb_name;
230
231 exec(sprintf($gs, $pg, $pg, $thumb_path, $doc_path), $out, $ret);
232
233 if ($ret != 0) {
234 error_log('DG: Ghostscript failed: ' . $out);
235 @unlink($thumb_path);
236 return false;
237 }
238
239 self::fixNewThumb($thumb_path);
240
241 // store reference to new thumbnail
242 wp_update_attachment_metadata($ID, array('thumb' => $thumb_name));
243
244 // return URL pointing to new thumbnail
245 return preg_replace('#'.preg_quote($basename).'$#', $thumb_name, $doc_url);
246 }
247
248 /**
249 * Get thumbnail for document with given ID from Google Drive Viewer.
250 *
251 * NOTE: Caller must verify that extension is supported.
252 *
253 * @param string $ID The attachment ID to retrieve thumbnail from.
254 * @param int $pg The page number to make thumbnail of -- index starts at 1.
255 * @return bool|string False on failure, URL to thumb on success.
256 */
257 public static function getGoogleDriveThumbnail($ID, $pg = 1) {
258 // User agent for Lynx 2.8.7rel.2 -- Why? Because I can.
259 static $user_agent = "Lynx/2.8.7rel.2 libwww-FM/2.14 SSL-MM/1.4.1 OpenSSL/1.0.0a";
260 static $timeout = 90;
261
262 $exts = self::getGoogleDriveExts();
263 $google_viewer = "https://docs.google.com/viewer?url=%s&a=bi&pagenumber=%d&w=%d";
264 $doc_path = get_attached_file($ID);
265 $doc_url = wp_get_attachment_url($ID);
266 $basename = basename($doc_path);
267 $dirname = dirname($doc_path);
268
269 // check whether filetype supported by Google Drive Viewer
270 if (!in_array(strtolower(self::getExt($basename)), $exts))
271 return false;
272
273 self::deleteExistingThumb($ID);
274
275 $thumb_name = self::getUniqueThumbName(
276 $dirname, substr($basename, 0, strrpos($basename, '.')));
277 $thumb_path = $dirname . DIRECTORY_SEPARATOR . $thumb_name;
278
279 // args for use in HTTP request
280 $args = array(
281 'timeout' => $timeout, // these requests can take a LONG time
282 'redirection' => 5,
283 'httpversion' => '1.0',
284 'user-agent' => self::$user_agent,
285 'blocking' => true,
286 'headers' => array(),
287 'cookies' => array(),
288 'body' => null,
289 'compress' => false,
290 'decompress' => true,
291 'sslverify' => true,
292 'stream' => true,
293 'filename' => $thumb_path
294 );
295
296 // prevent PHP timeout before HTTP completes
297 set_time_limit($timeout);
298
299 $google_viewer = sprintf($google_viewer, urlencode($doc_url), (int) $pg, 150);
300
301 // get thumbnail from Google Drive Viewer & check for error on return
302 $response = wp_remote_get($google_viewer, $args);
303
304 if (is_wp_error($response) || $response['response']['code'] != 200) {
305 error_log('DG: Failed to retrieve thumbnail from Google: ' .
306 (is_wp_error($response)
307 ? $response->get_error_message()
308 : $response['response']['message']));
309
310 @unlink($thumb_path);
311 return false;
312 }
313
314 self::fixNewThumb($thumb_path);
315
316 // store reference to new thumbnail
317 wp_update_attachment_metadata($ID, array('thumb' => $thumb_name));
318
319 // return URL pointing to new thumbnail
320 return preg_replace('#'.preg_quote($basename).'$#', $thumb_name, $doc_url);
321 }
322
323 /**
324 * Get thumbnail for document with given ID from default images.
325 *
326 * @param string $ID The attachment ID to retrieve thumbnail from.
327 * @param int $pg Unused.
328 * @return string URL to thumbnail.
329 */
330 public static function getDefaultThumbnail($ID, $pg = 1) {
331 $icon_url = DG_URL . 'icons/';
332
333 $url = wp_get_attachment_url($ID);
334 $ext = self::getExt(basename($url));
335
336 // handle images
337 if (wp_attachment_is_image($ID) &&
338 ($icon = wp_get_attachment_image_src($ID, 'thumbnail', false))) {
339 $icon = $icon[0];
340 }
341 // default extension icon
342 elseif ($name = self::getDefaultIcon($ext)) {
343 $icon = $icon_url . $name;
344 }
345 // fallback to standard WP icons
346 elseif ($icon = wp_get_attachment_image_src($ID, null, true)) {
347 $icon = $icon[0];
348 }
349 // everything failed. This is bad...
350 else {
351 $icon = $icon_url . 'missing.png';
352 }
353
354 return $icon;
355 }
356
357 /**
358 * @return array All extensions supported by Google Drive Viewer.
359 */
360 private static function getGoogleDriveExts() {
361 return array(
362 'tiff', 'bmp', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx',
363 'pdf', 'pages', 'ai', 'psd', 'dxf', 'svg', 'eps', 'ps', 'ttf'
364 );
365 }
366
367 /**
368 * @return array All extensions supported by Ghostscript.
369 */
370 private static function getGhostscriptExts() {
371 return array('pdf');
372 }
373
374 /**
375 * @return array All extensions supported by WP Audio Video Media metadata.
376 */
377 private static function getAudioVideoExts() {
378 return array_merge(wp_get_audio_extensions(), wp_get_video_extensions());
379 }
380
381 /**
382 * @param string $dirname Directory where the document is located.
383 * @param string $basename Filename of document minue the extension.
384 * @param string $ext The extension of the image to be created.
385 * @return string Name unique within the directory given, derived from the basename given.
386 */
387 private static function getUniqueThumbName($dirname, $basename, $ext = '.png') {
388 return wp_unique_filename($dirname, str_replace('.', '-', $basename) . '-thumb' . $ext);
389 }
390
391 /**
392 * Removes the existing thumbnail for the attachment
393 * with $ID, if such a thumbnail exists.
394 *
395 * Substantially borrowed from wp_delete_attachment() in wp-includes/post.php
396 *
397 * @filter wp_delete_file Filter the path of the file to delete.
398 * @global type $wpdb
399 * @param type $ID
400 */
401 private static function deleteExistingThumb($ID) {
402 global $wpdb;
403
404 $meta = wp_get_attachment_metadata($ID);
405
406 if (empty($meta['thumb']))
407 return;
408
409 $doc_path = get_attached_file($ID);
410
411 // Don't delete the thumb if another attachment uses it
412 $SQL = 'SELECT meta_id '
413 . "FROM $wpdb->postmeta "
414 . 'WHERE meta_key = \'_wp_attachment_metadata\' '
415 . 'AND meta_value LIKE %s '
416 . 'AND post_id <> %d';
417
418 if (!$wpdb->get_row($wpdb->prepare($SQL, '%' . $meta['thumb'] . '%', $ID))) {
419 $thumb_path = str_replace(basename($doc_path), $meta['thumb'], $doc_path);
420 // This filter is documented in wp-admin/custom-header.php
421 $thumb_path = apply_filters('wp_delete_file', $thumb_path);
422 @unlink($thumb_path);
423 }
424 }
425
426 /**
427 * Cleans up dimensions and permissions for a newly-created thumbnail.
428 * @param type $thumb_path
429 * @return type
430 */
431 private static function fixNewThumb($thumb_path) {
432 // ensure perms are correct
433 $stat = stat(dirname($thumb_path));
434 $perms = $stat['mode'] & 0000666;
435 @chmod($thumb_path, $perms);
436
437 // scae to no larger than 150x150px
438 $img = wp_get_image_editor($thumb_path);
439
440 if (is_wp_error($img)) {
441 error_log('DG: Failed to get image editor. Can\'t resize thumbnail.');
442 return;
443 }
444
445 $img->resize(150, 150, false);
446 $img->save($thumb_path);
447 }
448
449 /**
450 * Checks whether we may call gs through exec().
451 * @staticvar bool $available
452 * @return bool
453 */
454 private static function isGhostscriptAvailable() {
455 static $available;
456
457 if (!isset($available)) {
458 $is_win = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN';
459 $gs = $is_win ? 'gswin32c' : 'gs';
460
461 $available = self::isExecAvailable();
462 $exec = exec($is_win ? "where $gs" : "which $gs");
463 $available = $available && !empty($exec);
464
465 if (WP_DEBUG && $available) {
466 error_log("DG: Found the $gs executable.");
467 }
468 else if (WP_DEBUG) {
469 error_log("DG: Didn\'t find the $gs executable.");
470 }
471 }
472
473 return $available;
474 }
475
476 /**
477 * Checks whether exec() may be used.
478 * Source: http://stackoverflow.com/a/12980534/866618
479 * @staticvar bool $available
480 * @return bool
481 */
482 private static function isExecAvailable() {
483 static $available;
484
485 if (!isset($available)) {
486 $available = true;
487
488 if (ini_get('safe_mode')) {
489 $available = false;
490 }
491 else {
492 $d = ini_get('disable_functions');
493 $s = ini_get('suhosin.executor.func.blacklist');
494 if ("$d$s") {
495 $array = preg_split('/,\s*/', "$d,$s");
496 if (in_array('exec', $array)) {
497 $available = false;
498 }
499 }
500 }
501
502 if (WP_DEBUG) {
503 error_log('DG: exec() ' . ($available ? 'is' : 'isn\'t') . ' available.');
504 }
505 }
506
507 return $available;
508 }
509
510 /**
511 * Formerly achieved with wp_check_filetype(), but it was only returning
512 * valid results if the active user had permission to upload the given filetype.
513 *
514 * @param type $filename Name of the file to get extension from.
515 * @return str|bool Returns the file extension on success, false on failure.
516 */
517 private static function getExt($filename) {
518 foreach (wp_get_mime_types() as $ext_preg => $unused) {
519 $ext_preg = '!\.(' . $ext_preg . ')$!i';
520 if (preg_match($ext_preg, $filename, $ext_matches)) {
521 return $ext_matches[1];
522 }
523 }
524
525 return false;
526 }
527
528 /**
529 * Returns the name of the image to represent the filetype given.
530 * @staticvar array $exts
531 * @param type $filename
532 * @return boolean
533 */
534 private static function getDefaultIcon($ext) {
535 // Maps file ext to default image name.
536 static $exts = array(
537 // Most Common First
538 'pdf' => 'pdf.png',
539
540 // MS Office
541 'doc|docx|docm|dotx|dotm' => 'msdoc.png',
542 'ppt|pot|pps|pptx|pptm|ppsx|ppsm|potx|potm|ppam|sldx|sldm' => 'msppt.png',
543 'xla|xls|xlt|xlw|xlsx|xlsm|xlsb|xltx|xltm|xlam' => 'msxls.png',
544 'mdb' => 'msaccess.png',
545
546 // iWork
547 'key' => 'key.png',
548 'numbers' => 'numbers.png',
549 'pages' => 'pages.png',
550
551 // Images
552 'jpg|jpeg|jpe|gif|png|bmp|tif|tiff|ico' => 'image.png',
553
554 // Video formats
555 'asf|asx|wmv|wmx|wm|avi|divx|flv|mov' => 'video.png',
556 'qt|mpeg|mpg|mpe|mp4|m4v|ogv|webm|mkv' => 'video.png',
557
558 // Audio formats
559 'mp3|m4a|m4b|ra|ram|wav|ogg|oga|wma|wax|mka' => 'audio.png',
560 'midi|mid' => 'midi.png',
561
562 // Text formats
563 'txt|tsv|csv' => 'text.png',
564 'rtx' => 'rtx.png',
565 'rtf' => 'rtf.png',
566 'ics' => 'ics.png',
567 'wp|wpd' => 'wordperfect.png',
568
569 // Programming
570 'html|htm' => 'html.png',
571 'css' => 'css.png',
572 'js' => 'javascript.png',
573 'class' => 'java.png',
574 'asc' => 'asc.png',
575 'c' => 'c.png',
576 'cc|cpp' => 'cpp.png',
577 'h' => 'h.png',
578
579 // Msc application formats
580 'zip|tar|gzip|gz|bz2|tgz|7z|rar' => 'compressed.png',
581 'exe' => 'exec.png',
582 'swf' => 'shockwave.png',
583
584 // OpenDocument formats
585 'odt' => 'opendocument-text.png',
586 'odp' => 'opendocument-presentation.png',
587 'ods' => 'opendocument-spreadsheet.png',
588 'odg' => 'opendocument-graphics.png',
589 'odb' => 'opendocument-database.png',
590 'odf' => 'opendocument-formula.png'
591 );
592
593 foreach ($exts as $ext_preg => $icon) {
594 $ext_preg = '!(' . $ext_preg . ')$!i';
595 if (preg_match($ext_preg, $ext)) {
596 return $icon;
597 }
598 }
599
600 return false;
601 }
602 }
603
604 ?>
605