null); protected $_placeholder = false; protected $_thumbQuality = 65; protected $_upsize = true; protected $_forceWebP = false; /** * Constructor - init paths * * * @return void */ public function __construct() { } /** * Initialize the class with e107 core prefs. * @param array $pref * @return null */ public function init($pref) { $this->parseRequest(); if(!empty($this->_request['noinit'])) { return null; } $this->_watermark = array( 'activate' => vartrue($pref['watermark_activate'], false), 'text' => vartrue($pref['watermark_text']), 'size' => vartrue($pref['watermark_size'], 20), 'pos' => vartrue($pref['watermark_pos'],"BR"), 'color' => vartrue($pref['watermark_color'],'fff'), 'font' => vartrue($pref['watermark_font']), 'margin' => vartrue($pref['watermark_margin'],30), 'shadowcolor' => vartrue($pref['watermark_shadowcolor'], '000000'), 'opacity' => vartrue($pref['watermark_opacity'], 20) ); $this->_thumbQuality = vartrue($pref['thumbnail_quality'],65); $this->_upsize = ((isset($this->_request['w']) && $this->_request['w'] > 110) || (isset($this->_request['aw']) && ($this->_request['aw'] > 110))); // don't resizeUp the icon images. $this->_forceWebP = empty($this->_request['type']) && !empty($pref['thumb_to_webp']) && (strpos( $_SERVER['HTTP_ACCEPT'], 'image/webp' ) !== false) ? true : false; // var_dump($this); // exit; return null; } /** * @param array $array keys: activate, text, size, pos, color, font, margin, shadowcolor, opacity. @see above. */ public function setWatermark($array) { $this->_watermark = (array) $array; } /** * Enabled/Disable debugging/testing. * @param $val */ public function setDebug($val) { $this->_debug = (bool) $val; } /** * Enable/disable image caching. * @param bool $val */ public function setCache($val) { $this->_cache = (bool) $val; } /** * @return $this */ private function parseRequest() { $e_QUERY = !empty($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : e_QUERY; if(isset($_GET['id'])) // very-basic url-tampering prevention and path cloaking { $e_QUERY = base64_decode($_GET['id']); } parse_str(str_replace('&', '&', $e_QUERY), $this->_request); if(isset($this->_request['w'])) { $this->_request['w'] = (int) $this->_request['w']; } if(isset($this->_request['h'])) { $this->_request['h'] = (int) $this->_request['h']; } return $this; } /** * Manually Set the Request parameters * @param array $array keys: w, h, ah, aw, c(rop) */ public function setRequest($array) { $this->_request = (array) $array; } /** * @return array|string|string[] */ private function getImageInfo() { $thumbnfo = pathinfo($this->_src_path); if($this->_forceWebP === true || (!empty($this->_request['type']) && $this->_request['type'] == 'webp')) { $thumbnfo['extension'] = 'webp'; } return $thumbnfo; } /** * Validate and Sanitize the Request. * @return bool true when request is okay. */ public function checkSrc() { if(!vartrue($this->_request['src'])) // display placeholder when src is missing. { $this->_placeholder = true; return true; } $tp = e107::getParser(); // convert raw to SC path $this->_request['src'] = str_replace($tp->getUrlConstants('raw'), $tp->getUrlConstants(), $this->_request['src']); // convert absolute and full url to SC URL $this->_src = $tp->createConstants($this->_request['src'], 'mix'); if(preg_match('#^(https?|ftps?|file)://#i', $this->_request['src'])) { return false; } if(!is_writable(e_CACHE_IMAGE)) { echo 'Cache folder not writeable! '; return false; } // convert to relative server path $path = $tp->replaceConstants(str_replace('..', '', $this->_src)); //should be safe enough if(is_file($path) && is_readable($path)) { $this->_src_path = $path; return true; } if($this->_debug === true || defined("e_DEBUG_THUMBNAIL")) { echo "File Not Found: ".$path; } $this->_placeholder = true; return true; } /** * @return $this|false|string|void */ public function sendImage() { if($this->_placeholder == true) { $width = ($this->_request['aw']) ? $this->_request['aw'] : $this->_request['w']; $height = ($this->_request['ah']) ? $this->_request['ah'] : $this->_request['h']; $parm = array('size' => $width."x".$height); $this->placeholder($parm); return false; } if(!$this->_src_path) // would only happen during testing. { echo "no source"; return $this; } $thumbnfo = $this->getImageInfo(); $options = $this->getRequestOptions(); $fname = e107::getParser()->thumbCacheFile($this->_src_path, $options); $cache_filename = e_CACHE_IMAGE . $fname; $this->sendCachedImage($cache_filename, $thumbnfo); // No Cached file found - proceed with image creation. $img = Image::make($this->_src_path); if(!empty($options['c'])) // Cropping: $quadrant T(op), B(ottom), C(enter), L(eft), R(right) { if(!empty($this->_request['ah'])) { $this->_request['h'] = $this->_request['ah']; } if(!empty($this->_request['aw'])) { $this->_request['w'] = $this->_request['aw']; } $key = $options['c']; $fit = array( 'T' => 'top', 'C' => 'center', 'B' => 'bottom', 'L' => 'left', 'R' => 'right' ); $position = varset($fit[$key],'top'); $img->fit(vartrue($this->_request['w'], null), vartrue($this->_request['h'], null), null, $position); } elseif(!empty($this->_request['w']) || !empty($this->_request['h'])) { $img->resize(vartrue($this->_request['w'], null), vartrue($this->_request['h'], null), function ($constraint) { $constraint->aspectRatio(); if(!$this->_upsize) { $constraint->upsize(); } }); } elseif(!empty($this->_request['ah']) || !empty($this->_request['aw'])) { $img->fit(vartrue($this->_request['aw'], null), vartrue($this->_request['ah'], null), null, 'top'); } /* todo watermark support. @see http://image.intervention.io/api/text todo also @see: http://image.intervention.io/api/insert */ $img->save($cache_filename, $this->_thumbQuality, $thumbnfo['extension']); $this->_request = array(); // reset the request. if($this->_debug === true) // return the cache file path for testing. { return $cache_filename; } $imageData = $img->encode($thumbnfo['extension'], $this->_thumbQuality); $thumbnfo['fsize'] = strlen($imageData); $this->sendHeaders($thumbnfo); echo $imageData; exit; } /* protected function sendImageOld() // for reference. - can be removed at a later date. { if($this->_placeholder == true) { $width = ($this->_request['aw']) ? $this->_request['aw'] : $this->_request['w']; $height = ($this->_request['ah']) ? $this->_request['ah'] : $this->_request['h']; $parm = array('size' => $width."x".$height); $this->placeholder($parm); return false; } if(!$this->_src_path) { echo "no source"; return $this; } $thumbnfo = pathinfo($this->_src_path); $options = $this->getRequestOptions(); $fname = e107::getParser()->thumbCacheFile($this->_src_path, $options); $cache_filename = e_CACHE_IMAGE . $fname; $this->sendCachedImage($cache_filename,$thumbnfo); require_once(e_HANDLER.'phpthumb/ThumbLib.inc.php'); if(!$thumb = PhpThumbFactory::create($this->_src_path)) { if(getperms('0')) { echo "Couldn't load thumb factory"; } return null; } $sizeUp = ((isset($this->_request['w']) && $this->_request['w'] > 110) || (isset($this->_request['aw']) && ($this->_request['aw'] > 110))); // don't resizeUp the icon images. $thumb->setOptions(array( 'correctPermissions' => true, 'resizeUp' => $sizeUp, 'jpegQuality' => $this->_thumbQuality, 'interlace' => true // improves performance )); // Image Cropping by Quadrant. if(!empty($options['c'])) // $quadrant T(op), B(ottom), C(enter), L(eft), R(right) { if(!empty($this->_request['ah'])) { $this->_request['h'] = $this->_request['ah']; } if(!empty($this->_request['aw'])) { $this->_request['w'] = $this->_request['aw']; } $thumb->adaptiveResizeQuadrant((integer) vartrue($this->_request['w'], 0), (integer) vartrue($this->_request['h'], 0), $options['c']); } if(isset($this->_request['w']) || isset($this->_request['h'])) { $thumb->resize((integer) vartrue($this->_request['w'], 0), (integer) vartrue($this->_request['h'], 0)); } elseif(!empty($this->_request['ah'])) { //Typically gives a better result with images of people than adaptiveResize(). $thumb->adaptiveResizeQuadrant((integer) vartrue($this->_request['aw'], 0), (integer) vartrue($this->_request['ah'], 0), 'T'); } else { $thumb->adaptiveResize((integer) vartrue($this->_request['aw'], 0), (integer) vartrue($this->_request['ah'], 0)); } if($this->_debug === true) { // echo "time: ".round((microtime(true) - $start),4); var_dump($thumb); return false; } // Watermark Option - See admin->MediaManager->prefs for details. / if(($this->_watermark['activate'] < $options['w'] || $this->_watermark['activate'] < $options['aw'] || $this->_watermark['activate'] < $options['h'] || $this->_watermark['activate'] < $options['ah'] ) && $this->_watermark['activate'] > 0 && $this->_watermark['font'] !='') { $tp = e107::getParser(); $this->_watermark['font'] = $tp->createConstants($this->_watermark['font'], 'mix'); $this->_watermark['font'] = realpath($tp->replaceConstants($this->_watermark['font'],'rel')); // $thumb->WatermarkText($this->_watermark); // failing due to phpThumb:: } // echo "hello"; // set cache $thumb->save($cache_filename); $this->_request = array(); // reset the request. if($this->_debug === true) // return the cache file path for testing. { return $cache_filename; } // show thumb $thumb->show(); return $this; } */ /** * When caching is enabled, send the cached image with headers and then exit the script. * @param string $cache_filename * @param array $thumbnfo * @return null */ private function sendCachedImage($cache_filename, $thumbnfo) { if(!$this->_cache || !is_file($cache_filename) || !is_readable($cache_filename) || $this->_debug) { return null; } $thumbnfo['lmodified'] = filemtime($cache_filename); $thumbnfo['md5s'] = md5_file($cache_filename); $thumbnfo['fsize'] = filesize($cache_filename); // Send required headers if($this->_debug !== true) { $this->sendHeaders($thumbnfo); } // check browser cache if (@$_SERVER['HTTP_IF_MODIFIED_SINCE'] && ($thumbnfo['lmodified'] <= strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE'])) && (isset($_SERVER['HTTP_IF_NONE_MATCH']) && trim($_SERVER['HTTP_IF_NONE_MATCH']) == $thumbnfo['md5s'])) { header('HTTP/1.1 304 Not Modified'); //$bench->end()->logResult('thumb.php', $_GET['src'].' - 304 not modified'); exit; } eShims::readfile($cache_filename); exit; } /** * @return array */ private function getRequestOptions() { $ret = array(); $ret['w'] = isset($this->_request['w']) ? intval($this->_request['w']) : false; $ret['h'] = isset($this->_request['h']) ? intval($this->_request['h']) : $ret['w']; $ret['aw'] = isset($this->_request['aw']) ? intval($this->_request['aw']) : false; $ret['ah'] = isset($this->_request['ah']) ? intval($this->_request['ah']) : $ret['aw']; $ret['c'] = isset($this->_request['c']) ? strtoupper(substr(e107::getParser()->filter($this->_request['c'], 'str'),0,1)) : false; // $ret['wm'] = isset($this->_request['wm']) ? intval($this->_request['wm']) : $ret['wm']; if($ret['c'] == 'A') // auto { $ret['c'] = 'T'; // default is 'Top'; } if(!empty($this->_request['type'])) { $ret['type'] = $this->_request['type']; } elseif($this->_forceWebP) { $ret['type'] = 'webp'; } return $ret; } /** * @param $thumbnfo * @return void */ private function sendHeaders($thumbnfo) { if(headers_sent($filename, $linenum)) { echo 'Headers already sent in '.$filename.' on line '.$linenum; exit; } if (function_exists('date_default_timezone_set')) { date_default_timezone_set('UTC'); } header('Cache-Control: must-revalidate'); if(isset($thumbnfo['lmodified'])) { header('Last-Modified: '.gmdate('D, d M Y H:i:s', $thumbnfo['lmodified']).' GMT'); } if(isset($thumbnfo['fsize'])) // extra check, if empty image will fail. { header('Content-Length: '.$thumbnfo['fsize']); } header('Content-Disposition: filename='.$thumbnfo['basename']); // important for right-click save-as. $ctype = $this->ctype($thumbnfo['extension']); if(null !== $ctype) { header('Content-Type: '.$ctype); } // Expire header - 1 year $time = time() + 365 * 86400; header('Expires: '.gmdate("D, d M Y H:i:s", $time).' GMT'); if(isset($thumbnfo['md5s'])) { header("Etag: ".$thumbnfo['md5s']); } } /** * @param $ftype * @return mixed|string|null */ private function ctype($ftype) { static $known_types = array( 'gif' => 'image/gif', 'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'png' => 'image/png', 'webp' => 'image/webp', //'bmp' => 'image/bmp', ); $ftype = strtolower($ftype); if(isset($known_types[$ftype])) { return $known_types[$ftype]; } return null; } // Display a placeholder image. /** * @param $parm * @return void|null */ private function placeholder($parm) { if($this->_debug === true) { echo "Placeholder activated"; return null; } $getsize = isset($parm['size']) ? $parm['size'] : '100x100'; header('location: https://via.placeholder.com/'.$getsize); header('Content-Length: 0'); exit(); } }