/** * Initialize the thumber class for use in thumbnail generation. * * @return DG_AbstractThumber The instance for the calling class. */ public static function init() { $class = get_called_class(); if (!isset(self::$instances[$class])) { try { self::$instances[$class] = new static(); add_action('dg_thumbers', array($class, 'thumbersFilter'), 0); } catch (Exception $e) { DG_Logger::writeLog(DG_LogLevel::Error, "Failed to construct thumber of type {$class}."); } } return isset(self::$instances[$class]) ? self::$instances[$class] : null; }
/** * Uses WP_Image_Editor_Imagick to generate thumbnails. * * @param int $ID The attachment ID to retrieve thumbnail from. * @param int $pg The page to get the thumbnail of. * * @return bool|string False on failure, URL to thumb on success. */ public function getThumbnail($ID, $pg = 1) { $doc_path = get_attached_file($ID); $img = new DG_Image_Editor_Imagick($doc_path, $pg - 1); $err = $img->load(); if (is_wp_error($err)) { DG_Logger::writeLog(DG_LogLevel::Error, __('Failed to open file in Imagick: ', 'document-gallery') . $err->get_error_message()); return false; } $temp_file = DG_Util::getTempFile(); $err = $img->save($temp_file, 'image/png'); if (is_wp_error($err)) { DG_Logger::writeLog(DG_LogLevel::Error, __('Failed to save image in Imagick: ', 'document-gallery') . $err->get_error_message()); return false; } return $temp_file; }
/** * Loads the filepath into Imagick object. */ public function load() { $ret = parent::load(); // set correct page number if (!is_null($this->pg) && !is_wp_error($ret) && is_callable(array($this->image, 'setIteratorIndex'))) { $err = __('Failed to set Imagick page number', 'document-gallery'); // setIteratorIndex() should return false on failure, but I've found // reports of it throwing an error so handling both cases. // NOTE: I've also seen it fail and return true, so we may not // log anything on failure... try { if (!$this->image->setIteratorIndex($this->pg)) { DG_Logger::writeLog(DG_LogLevel::Error, $err . '.'); } } catch (Exception $e) { DG_Logger::writeLog(DG_LogLevel::Error, $err . ': ' . $e->getMessage()); } } return $ret; }
/** * @param int $ID The attachment ID. * @param int $pg The page to thumbnail. * * @return bool Always false. Asynchronously set the thumbnail in webhook later. */ public function getThumbnail($ID, $pg = 1) { global $dg_options; include_once DG_PATH . 'inc/thumbers/thumber-co/thumber-client/client.php'; include_once DG_PATH . 'inc/thumbers/thumber-co/thumber-client/thumb-request.php'; $options = DG_Thumber::getOptions(); $url_or_path = get_attached_file($ID); if (!self::checkGeometry($options['width'], $options['height'])) { DG_Logger::writeLog(DG_LogLevel::Detail, "Skipping attachment #{$ID} as it exceeds Thumber.co subscription geometry limit."); return false; } if (!self::checkFileSize($url_or_path)) { DG_Logger::writeLog(DG_LogLevel::Detail, "Skipping attachment #{$ID} as it exceeds Thumber.co subscription file size limit."); return false; } $mime_type = get_post_mime_type($ID); if (!$dg_options['thumber-co']['direct_upload']) { $url_or_path = wp_get_attachment_url($ID); } if (!$url_or_path || !$mime_type) { return false; } $req = new ThumberThumbReq(); $req->setCallback(self::$webhook); $req->setMimeType($mime_type); $req->setNonce($ID . self::NonceSeparator . md5(microtime())); $req->setPg($pg); $req->setGeometry($options['width'] . 'x' . $options['height']); if ($dg_options['thumber-co']['direct_upload']) { $req->setDecodedData(file_get_contents($url_or_path)); } else { $req->setUrl($url_or_path); } $resp = self::$client->sendThumbRequest($req); if ($resp['http_code'] < 200 || $resp['http_code'] > 399) { DG_Logger::writeLog(DG_LogLevel::Error, 'Failed to transmit to server: ' . $resp['body']); } // always returns false -- we set the thumbnail later when webhook is hit return false; }
/** * Sanitize the given key/value pair, passing any error to $errs if given. * * @param string $key The key to reference the current value in the defaults array. * @param mixed $value The value to be sanitized. * @param string[] $errs The array of errors, which will be appended with any errors found. * * @return mixed The sanitized value, falling back to the current default value when invalid value given. */ public static function sanitizeParameter($key, $value, &$errs = null) { // all sanitize methods must be in the following form: sanitize<UpperCamelCaseKey> $funct = $key; $funct[0] = strtoupper($funct[0]); $funct = 'sanitize' . preg_replace_callback('/_([a-z])/', array(__CLASS__, 'secondCharToUpper'), $funct); $callable = array(__CLASS__, $funct); // avoid looking for method beforehand unless we're running in debug mode -- expensive call if (DG_Logger::logEnabled() && !method_exists(__CLASS__, $funct)) { DG_Logger::writeLog(DG_LogLevel::Error, __('Attempted to call invalid function: ', 'document-gallery') . implode('::', $callable), true); } // call param-specific sanitization $ret = call_user_func_array($callable, array($value, &$err)); // check for error and return default if (isset($err)) { $defaults = DG_Gallery::getOptions(); $ret = $defaults[$key]; if (!is_null($errs)) { $errs[$key] = $err; } } return $ret; }
/** * Get thumbnail for document with given ID using Ghostscript. Imagick could * also handle this, but is *much* slower. * * @param int $ID The attachment ID to retrieve thumbnail from. * @param int $pg The page number to make thumbnail of -- index starts at 1. * * @return bool|string False on failure, URL to thumb on success. */ public function getThumbnail($ID, $pg = 1) { static $gs = null; if (is_null($gs)) { $options = DG_Thumber::getOptions(); $gs = $options['gs']; if (false !== $gs) { $gs = escapeshellarg($gs) . ' -sDEVICE=png16m -dFirstPage=%1$d' . ' -dLastPage=%1$d -dBATCH -dNOPAUSE -dPDFFitPage -sOutputFile=%2$s %3$s 2>&1'; } } if (false === $gs) { return false; } $doc_path = get_attached_file($ID); $temp_path = DG_Util::getTempFile(); exec(sprintf($gs, $pg, $temp_path, $doc_path), $out, $ret); if ($ret != 0) { DG_Logger::writeLog(DG_LogLevel::Error, __('Ghostscript failed: ', 'document-gallery') . print_r($out)); @unlink($temp_path); return false; } return $temp_path; }
/** * Uses wp_read_video_metadata() and wp_read_audio_metadata() to retrieve * an embedded image to use as a thumbnail. * * @param string $ID The attachment ID to retrieve thumbnail from. * @param int $pg Unused. * * @return bool|string False on failure, URL to thumb on success. */ public function getThumbnail($ID, $pg = 1) { include_once DG_WPADMIN_PATH . 'includes/media.php'; $doc_path = get_attached_file($ID); $mime_type = get_post_mime_type($ID); if (DG_Util::startsWith($mime_type, 'video/')) { $metadata = wp_read_video_metadata($doc_path); } elseif (DG_Util::startsWith($mime_type, 'audio/')) { $metadata = wp_read_audio_metadata($doc_path); } // unsupported mime type || no embedded image present if (!isset($metadata) || empty($metadata['image']['data'])) { return false; } $ext = 'jpg'; switch ($metadata['image']['mime']) { case 'image/gif': $ext = 'gif'; break; case 'image/png': $ext = 'png'; break; } $temp_file = DG_Util::getTempFile($ext); if (!($fp = @fopen($temp_file, 'wb'))) { DG_Logger::writeLog(DG_LogLevel::Error, __('Could not open file: ', 'document-gallery') . $temp_file); return false; } if (!@fwrite($fp, $metadata['image']['data'])) { DG_Logger::writeLog(DG_LogLevel::Error, __('Could not write file: ', 'document-gallery') . $temp_file); fclose($fp); return false; } fclose($fp); return $temp_file; }
/** * @param $err string Fires on fatal error. */ protected function handleError($err) { DG_Logger::writeLog(DG_LogLevel::Error, $err); }
/** * Initializes the thumbs variable if not already initialized. */ private static function initThumbs() { if (!isset(self::$thumbs)) { DG_Logger::writeLog(DG_LogLevel::Detail, 'Populating thumbnail cache.'); global $wpdb; self::$thumbs = array(); $meta_key = self::$meta_key; $sql = "SELECT post_id, meta_id, meta_value FROM {$wpdb->postmeta} WHERE meta_key = '{$meta_key}'"; foreach ($wpdb->get_results($sql) as $row) { $key = $row->post_id; $new = new DG_Thumb($row); if (!isset(self::$thumbs[$key])) { self::$thumbs[$key] = array(); } elseif (isset(self::$thumbs[$key][$new->dimensions])) { // it is possible to end up with duplicate thumbnails -- cleanup here $old = self::$thumbs[$key][$new->dimensions]; if ($old->timestamp < $new->timestamp) { $old->delete(); } else { $new->delete(); continue; } } self::$thumbs[$key][$new->dimensions] = $new; } } }
/** * Render the Logging table. */ public static function renderLoggingSection() { $log_list = DG_Logger::readLog(); if ($log_list) { $levels = array_map(array(__CLASS__, 'getLogLabelSpan'), array_keys(DG_LogLevel::getLogLevels())); $fmt = '<tr>' . '<th scope="col" class="manage-column column-date sorted desc"><a href="javascript:void(0);">' . '<span>%s</span><span class="sorting-indicator"></span></a>' . '</th>' . '<th scope="col" class="manage-column column-level"><span>%s</span></th>' . '<th scope="col" class="manage-column column-message"><span>%s</span></th>' . '</tr>'; $thead = sprintf($fmt, __('Date', 'document-gallery'), __('Level', 'document-gallery'), __('Message', 'document-gallery')); ?> <div class="log-list-wrapper"> <div> <div class="tablenav top"> <div class="alignleft bulkactions"> <button class="action expandAll"> <?php echo __('Expand All', 'document-gallery'); ?> </button> <button class="action collapseAll"> <?php echo __('Collapse All', 'document-gallery'); ?> </button> </div> <div class="levelSelector"> <input type="checkbox" id="allLevels" name="lswitch" value="all" checked/> <label for="allLevels" class="allLevels">ALL</label> <?php foreach (array_keys(DG_LogLevel::getLogLevels()) as $k) { ?> <?php $lower = strtolower($k); $upper = strtoupper($k); ?> <input type="checkbox" id="<?php echo $lower; ?> Level" name="lswitch" value="<?php echo $lower; ?> " checked/> <label for="<?php echo $lower; ?> Level" class="<?php echo $lower; ?> Level"><?php echo $upper; ?> </label> <?php } ?> </div> </div> <table id="LogTable" class="wp-list-table widefat fixed media" cellpadding="0" cellspacing="0"> <thead> <?php echo $thead; ?> </thead> <tfoot> <?php echo $thead; ?> </tfoot> <tbody><?php for ($i = count($log_list); $i > 0; $i--) { $log_entry = $log_list[$i - 1]; $date = DocumentGallery::localDateTimeFromTimestamp($log_entry[0]); // convert attachment names to links $log_entry[2] = preg_replace('/[ ^](attachment #)(\\d+)[.,: ]/i', ' <a href="' . home_url() . '/?attachment_id=\\2" target="_blank">\\1<strong>\\2</strong></a> ', $log_entry[2]); // bold the place where log entry was submitted $log_entry[2] = preg_replace('/^(\\((?:\\w+(?:::|->))?\\w+\\)) /', '<strong>\\1</strong> ', $log_entry[2]); // italicize any function references within log entry $log_entry[2] = preg_replace('/(\\(?\\w+(?:::|->)\\w+\\)?)/m', '<i>\\1</i>', $log_entry[2]); echo '<tr><td class="date column-date" data-sort-value="' . $log_entry[0] . '"><span class="logLabel date">' . $date . '</span></td>' . '<td class="column-level">' . $levels[$log_entry[1]] . '</td>' . '<td class="column-entry">' . (empty($log_entry[3]) ? '<pre>' . $log_entry[2] . '</pre>' : '<div class="expander" title="Click to Expand"><pre>' . $log_entry[2] . '</pre><div><span class="dashicons dashicons-arrow-down-alt2"></span></div></div><div class="spoiler-body"><pre>' . $log_entry[3] . '</pre></div>') . '</td>' . '</tr>' . PHP_EOL; } ?> </tbody> </table> <div class="tablenav bottom"> <div class="alignright bulkactions"> <button class="button action clearLog" name='<?php echo DG_OPTION_NAME; ?> [clearLog]' value='true'> <?php echo __('Clear Log', 'document-gallery'); ?> </button> </div> </div> </div> </div> <?php } else { echo '<div class="noLog">' . __('There are no log entries at this time.', 'document-gallery') . '<br />' . __('For Your information:', 'document-gallery') . ' <strong><i>' . __('Logging', 'document-gallery') . '</i></strong> ' . (DG_Logger::logEnabled() ? '<span class="loggingON">' . __('is turned ON', 'document-gallery') . '!</span>' : '<span class="loggingOFF">' . __('is turned OFF', 'document-gallery') . '!</span>') . '</div>'; } }
/** * Template that handles generating a thumbnail. * * If image has already been generated through other means, $pg may be set to the system path where the * thumbnail is located. In this case, $generator will not be invoked, but *will* be kept for historical purposes. * * @param callable $generator Takes ID and pg and returns path to temp file or false. * @param int $ID ID for the attachment that we need a thumbnail for. * @param int|string $pg Page number of the attachment to get a thumbnail for or the system path to the image to be used. * * @return bool Whether generation was successful. */ private static function thumbnailGenerationHarness($generator, $ID, $pg = 1) { // handle system page in $pg variable if (is_string($pg) && !is_numeric($pg)) { $temp_path = $pg; } elseif (false === ($temp_path = call_user_func($generator, $ID, $pg))) { return false; } // get some useful stuff $doc_path = get_attached_file($ID); $doc_url = wp_get_attachment_url($ID); $dirname = dirname($doc_path); $basename = basename($doc_path); if (false === ($len = strrpos($basename, '.'))) { $len = strlen($basename); } $extless = substr($basename, 0, $len); $ext = self::getExt($temp_path); $thumb_name = self::getUniqueThumbName($dirname, $extless, $ext); $thumb_path = $dirname . DIRECTORY_SEPARATOR . $thumb_name; // scale generated image down $img = wp_get_image_editor($temp_path); if (is_wp_error($img)) { DG_Logger::writeLog(DG_LogLevel::Error, __('Failed to get image editor: ', 'document-gallery') . $img->get_error_message()); return false; } $options = self::getOptions(); $img->resize($options['width'], $options['height'], false); $err = $img->save($thumb_path); if (is_wp_error($err)) { DG_Logger::writeLog(DG_LogLevel::Error, __('Failed to save image: ', 'document-gallery') . $err->get_error_message()); return false; } // do some cleanup @unlink($temp_path); self::deleteThumbMeta($ID); // store new thumbnail in DG options $options['thumbs'][$ID] = array('timestamp' => time(), 'thumb_url' => preg_replace('#' . preg_quote($basename) . '$#', $thumb_name, $doc_url), 'thumb_path' => $thumb_path, 'thumber' => $generator); self::setOptions($options); return true; }
/** * Removes the validation option. Validation is now non-optional. * * @param mixed[][] $options The options to be modified. */ private static function threePointFour(&$options) { if (version_compare($options['meta']['version'], '3.4', '<')) { unset($options['validation']); if (!DocumentGallery::isValidOptionsStructure($options)) { DG_Logger::writeLog(DG_LogLevel::Error, 'Found invalid options structure. Reverting to default options.', false, true); $options = self::getDefaultOptions(); } } }
/** * Validates uploaded file as a semi for potential thumbnail. * * @param string $var File field name. * * @return bool|string False on failure, path to temp file on success. */ public static function validateUploadedFile($var = 'file') { // checking if any file was delivered if (!isset($_FILES[$var])) { return false; } // we gonna process only first one if (!is_array($_FILES[$var]['error'])) { $upload_err = $_FILES[$var]['error']; $upload_path = $_FILES[$var]['tmp_name']; $upload_size = $_FILES[$var]['size']; $upload_type = $_FILES[$var]['type']; $upload_name = $_FILES[$var]['name']; } else { $upload_err = $_FILES[$var]['error'][0]; $upload_path = $_FILES[$var]['tmp_name'][0]; $upload_size = $_FILES[$var]['size'][0]; $upload_type = $_FILES[$var]['type'][0]; $upload_name = $_FILES[$var]['name'][0]; } $info = getimagesize($upload_path); if ($info) { if ($info['mime'] !== $upload_type) { // in DG_Thumber::getExt() we'll define and set appropriate extension DG_Logger::writeLog(DG_LogLevel::Warning, __('File extension doesn\'t match the MIME type of the image: ', 'document-gallery') . $upload_name . ' - ' . $info['mime']); } if ($upload_size > wp_max_upload_size()) { DG_Logger::writeLog(DG_LogLevel::Warning, __('Uploaded file size exceeds the allowable limit: ', 'document-gallery') . $upload_name . ' - ' . $upload_size . 'b'); return false; } } else { DG_Logger::writeLog(DG_LogLevel::Warning, __('Uploaded file is not an image: ', 'document-gallery') . $upload_name); return false; } if ($upload_err == UPLOAD_ERR_OK && $upload_size > 0) { $temp_file = $upload_path; } else { DG_Logger::writeLog(DG_LogLevel::Error, __('Failed to get uploaded file: ', 'document-gallery') . $upload_err); return false; } return $temp_file; }
/** * Template that handles generating a thumbnail. * * If image has already been generated through other means, $pg may be set to the system path where the * thumbnail is located. In this case, $generator will not be invoked, but *will* be kept for historical purposes. * * @param DG_AbstractThumber|string $generator Takes ID and pg and returns path to temp file or false. * @param int $ID ID for the attachment that we need a thumbnail for. * @param int|string $pg Page number of the attachment to get a thumbnail for or the system path to the image to be used. * * @return DG_Thumb|bool The generated thumbnail or false on failure. */ private static function thumbnailGenerationHarness($generator, $ID, $pg = 1) { // handle system page in $pg variable if (is_string($pg) && !is_numeric($pg)) { $temp_path = $pg; } elseif (is_a($generator, 'DG_AbstractThumber')) { // delegate thumbnail generation to $generator if (false === ($temp_path = $generator->getThumbnail($ID, $pg))) { return false; } // NOTE: get string representation to be stored with thumb in DB $generator = get_class($generator); } else { DG_Logger::writeLog(DG_LogLevel::Error, 'Attempted to call thumbnailGenerationHarness with invalid generator: ' . print_r($generator, true)); return false; } // get some useful stuff $doc_path = get_attached_file($ID); $dirname = dirname($doc_path); $basename = basename($doc_path); if (false === ($len = strrpos($basename, '.'))) { $len = strlen($basename); } $extless = substr($basename, 0, $len); $ext = self::getExt($temp_path); $thumb_name = self::getUniqueThumbName($dirname, $extless, $ext); $thumb_path = "{$dirname}/{$thumb_name}"; // scale generated image down $img = wp_get_image_editor($temp_path); if (is_wp_error($img)) { DG_Logger::writeLog(DG_LogLevel::Error, __('Failed to get image editor: ', 'document-gallery') . $img->get_error_message()); return false; } $options = self::getOptions(); $img->resize($options['width'], $options['height'], false); $err = $img->save($thumb_path); if (is_wp_error($err)) { DG_Logger::writeLog(DG_LogLevel::Error, __('Failed to save image: ', 'document-gallery') . $err->get_error_message()); return false; } // do some cleanup @unlink($temp_path); // save new thumb DG_Logger::writeLog(DG_LogLevel::Detail, 'Creating thumb object.'); $upload = wp_upload_dir(); $thumb = new DG_Thumb(); $thumb->setPostId($ID); $thumb->setDimensions($options['width'] . 'x' . $options['height']); $thumb->setTimestamp(time()); $thumb->setRelativePath(substr($thumb_path, strlen($upload['basedir']) + 1)); $thumb->setGenerator($generator); $thumb->save(); return $thumb; }
/** * Checks whether the given options match the option schema. * * @param mixed[] $new The new options to be validated. * @param mixed[] $old The old options. * * @return mixed[] The options to be saved. */ public static function validateOptionsStructure($new, $old) { if (self::isValidOptionsStructure($new)) { $ret = $new; } else { $ret = $old; DG_Logger::writeLog(DG_LogLevel::Error, 'Attempted to save invalid options.' . PHP_EOL . preg_replace('/\\s+/', ' ', print_r($new, true)), true, true); } return $ret; }