diff options
-rw-r--r-- | include/photo/photo_driver.php | 513 |
1 files changed, 271 insertions, 242 deletions
diff --git a/include/photo/photo_driver.php b/include/photo/photo_driver.php index 5c8ed9bdc..718bf875b 100644 --- a/include/photo/photo_driver.php +++ b/include/photo/photo_driver.php @@ -1,18 +1,30 @@ -<?php /** @file */ +<?php +/** + * @brief Return a photo_driver object. + * + * Use this factory when manipulating images. + * + * Return a photo driver object implementing ImageMagick or GD. + * + * @param string $data Image data + * @param string $type + * @return null|photo_driver + * NULL if unsupported image type or failure, otherwise photo driver object + */ function photo_factory($data, $type = null) { $ph = null; + $m = null; - - $unsupported_types = array( + $unsupported_types = [ 'image/bmp', 'image/vnd.microsoft.icon', 'image/tiff', - 'image/svg+xml' - ); + 'image/svg+xml', + ]; - if($type && in_array(strtolower($type),$unsupported_types)) { - logger('photo_factory: unsupported image type'); + if($type && in_array(strtolower($type), $unsupported_types)) { + logger('Unsupported image type ' . $type); return null; } @@ -21,29 +33,30 @@ function photo_factory($data, $type = null) { if(class_exists('Imagick') && !$ignore_imagick) { $v = Imagick::getVersion(); preg_match('/ImageMagick ([0-9]+\.[0-9]+\.[0-9]+)/', $v['versionString'], $m); - if(version_compare($m[1],'6.6.7') >= 0) { + if(version_compare($m[1], '6.6.7') >= 0) { require_once('include/photo/photo_imagick.php'); - $ph = new photo_imagick($data,$type); - } - else { + $ph = new photo_imagick($data, $type); + } else { // earlier imagick versions have issues with scaling png's // don't log this because it will just fill the logfile. - // leave this note here so those who are looking for why + // leave this note here so those who are looking for why // we aren't using imagick can find it } } if(! $ph) { require_once('include/photo/photo_gd.php'); - $ph = new photo_gd($data,$type); + $ph = new photo_gd($data, $type); } return $ph; } - - +/** + * @brief Abstract photo driver class. + * + */ abstract class photo_driver { protected $image; @@ -79,12 +92,12 @@ abstract class photo_driver { public function __construct($data, $type='') { $this->types = $this->supportedTypes(); - if (! array_key_exists($type,$this->types)){ - $type='image/jpeg'; + if (! array_key_exists($type, $this->types)){ + $type = 'image/jpeg'; } $this->type = $type; $this->valid = false; - $this->load($data,$type); + $this->load($data, $type); } public function __destruct() { @@ -99,12 +112,14 @@ abstract class photo_driver { public function getWidth() { if(!$this->is_valid()) return FALSE; + return $this->width; } public function getHeight() { if(!$this->is_valid()) return FALSE; + return $this->height; } @@ -112,6 +127,7 @@ abstract class photo_driver { public function saveImage($path) { if(!$this->is_valid()) return FALSE; + return (file_put_contents($path, $this->imageString()) ? true : false); } @@ -131,12 +147,14 @@ abstract class photo_driver { } /** - * @brief scale image - * int $max maximum pixel size in either dimension - * boolean $float_height - if true allow height to float to any length on tall images, - * constraining only the width + * @brief Scale image to max pixel size in either dimension. + * + * @param int $max maximum pixel size in either dimension + * @param boolean $float_height (optional) + * if true allow height to float to any length on tall images, constraining + * only the width + * @return boolean|void false on failure, otherwise void */ - public function scaleImage($max, $float_height = true) { if(!$this->is_valid()) return FALSE; @@ -203,7 +221,6 @@ abstract class photo_driver { if(!$this->is_valid()) return FALSE; - $width = $this->width; $height = $this->height; @@ -241,33 +258,39 @@ abstract class photo_driver { $this->doScaleImage($dest_width,$dest_height); } - + /** + * @brief Scales image to a square. + * + * @param int $dim Pixel of squre image + * @return boolean|void false on failure, otherwise void + */ public function scaleImageSquare($dim) { if(!$this->is_valid()) return FALSE; - $this->doScaleImage($dim,$dim); - } + $this->doScaleImage($dim, $dim); + } /** - * @brief reads exif data from filename + * @brief reads exif data from filename. + * + * @param string $filename + * @return boolean|array */ - public function exif($filename) { - - if((! function_exists('exif_read_data')) + if((! function_exists('exif_read_data')) || (! in_array($this->getType(), [ 'image/jpeg' , 'image/tiff'] ))) { return false; } /* * PHP 7.2 allows you to use a stream resource, which should reduce/avoid - * memory exhaustion on large images. + * memory exhaustion on large images. */ - if(version_compare(PHP_VERSION,'7.2.0') >= 0) { - $f = @fopen($filename,'rb'); + if(version_compare(PHP_VERSION, '7.2.0') >= 0) { + $f = @fopen($filename, 'rb'); } else { $f = $filename; @@ -281,21 +304,23 @@ abstract class photo_driver { } /** - * @brief orients current image based on exif orientation information + * @brief Orients current image based on exif orientation information. + * + * @param array $exif + * @return boolean true if oriented, otherwise false */ - public function orient($exif) { if(! ($this->is_valid() && $exif)) { return false; } - $ort = ((array_key_exists('IFD0',$exif)) ? $exif['IFD0']['Orientation'] : $exif['Orientation']); + $ort = ((array_key_exists('IFD0', $exif)) ? $exif['IFD0']['Orientation'] : $exif['Orientation']); if(! $ort) { return false; } - + switch($ort) { case 1: // nothing break; @@ -319,7 +344,7 @@ abstract class photo_driver { $this->flip(); $this->rotate(-90); break; - case 8: // 90 rotate left + case 8: // 90 rotate left $this->rotate(90); break; default: @@ -329,15 +354,21 @@ abstract class photo_driver { return true; } - + /** + * @brief Save photo to database. + * + * @param array $arr + * @param boolean $skipcheck (optional) default false + * @return boolean|array + */ public function save($arr, $skipcheck = false) { if(! ($skipcheck || $this->is_valid())) { - logger('attempt to store invalid photo.'); + logger('Attempt to store invalid photo.'); return false; } - $p = array(); + $p = []; $p['aid'] = ((intval($arr['aid'])) ? intval($arr['aid']) : 0); $p['uid'] = ((intval($arr['uid'])) ? intval($arr['uid']) : 0); @@ -351,11 +382,11 @@ abstract class photo_driver { $p['allow_gid'] = (($arr['allow_gid']) ? $arr['allow_gid'] : ''); $p['deny_cid'] = (($arr['deny_cid']) ? $arr['deny_cid'] : ''); $p['deny_gid'] = (($arr['deny_gid']) ? $arr['deny_gid'] : ''); - $p['edited'] = (($arr['edited']) ? $arr['edited'] : datetime_convert()); + $p['edited'] = (($arr['edited']) ? $arr['edited'] : datetime_convert()); $p['title'] = (($arr['title']) ? $arr['title'] : ''); $p['description'] = (($arr['description']) ? $arr['description'] : ''); $p['photo_usage'] = intval($arr['photo_usage']); - $p['os_storage'] = intval($arr['os_storage']); + $p['os_storage'] = intval($arr['os_storage']); $p['os_path'] = $arr['os_path']; $p['os_syspath'] = ((array_key_exists('os_syspath',$arr)) ? $arr['os_syspath'] : ''); $p['display_path'] = (($arr['display_path']) ? $arr['display_path'] : ''); @@ -364,7 +395,7 @@ abstract class photo_driver { $p['expires'] = (($arr['expires']) ? $arr['expires'] : gmdate('Y-m-d H:i:s', time() + get_config('system','photo_cache_time', 86400))); if(! intval($p['imgscale'])) - logger('save: ' . print_r($arr,true), LOGGER_DATA); + logger('save: ' . print_r($arr, true), LOGGER_DATA); $x = q("select id, created from photo where resource_id = '%s' and uid = %d and xchan = '%s' and imgscale = %d limit 1", dbesc($p['resource_id']), @@ -372,7 +403,7 @@ abstract class photo_driver { dbesc($p['xchan']), intval($p['imgscale']) ); - + if($x) { $p['created'] = (($x['created']) ? $x['created'] : $p['edited']); $r = q("UPDATE photo set @@ -388,7 +419,7 @@ abstract class photo_driver { height = %d, width = %d, content = '%s', - os_storage = %d, + os_storage = %d, filesize = %d, imgscale = %d, photo_usage = %d, @@ -463,33 +494,36 @@ abstract class photo_driver { dbescdate($p['expires']) ); } - logger('photo save ' . $p['imgscale'] . ' returned ' . intval($r)); + logger('Photo save ' . $p['imgscale'] . ' returned ' . intval($r)); + return $r; } } - - /** - * Guess image mimetype from filename or from Content-Type header + * @brief Guess image mimetype from filename or from Content-Type header. * - * @arg $filename string Image filename - * @arg $headers string Headers to check for Content-Type (from curl request) + * @param string $filename + * Image filename + * @param string $headers (optional) + * Headers to check for Content-Type (from curl request) + * @return null|string Guessed mimetype */ - function guess_image_type($filename, $headers = '') { // logger('Photo: guess_image_type: '.$filename . ($headers?' from curl headers':''), LOGGER_DEBUG); $type = null; - if ($headers) { - $hdrs=array(); - $h = explode("\n",$headers); + $m = null; + + if($headers) { + $hdrs = []; + $h = explode("\n", $headers); foreach ($h as $l) { - list($k,$v) = array_map("trim", explode(":", trim($l), 2)); + list($k,$v) = array_map('trim', explode(':', trim($l), 2)); $hdrs[strtolower($k)] = $v; } - logger('Curl headers: '.var_export($hdrs, true), LOGGER_DEBUG); - if (array_key_exists('content-type', $hdrs)) { + logger('Curl headers: ' .var_export($hdrs, true), LOGGER_DEBUG); + if(array_key_exists('content-type', $hdrs)) { $ph = photo_factory(''); $types = $ph->supportedTypes(); @@ -498,13 +532,13 @@ function guess_image_type($filename, $headers = '') { } } - if (is_null($type)){ + if(is_null($type)){ $ignore_imagick = get_config('system', 'ignore_imagick'); // Guessing from extension? Isn't that... dangerous? if(class_exists('Imagick') && file_exists($filename) && is_readable($filename) && !$ignore_imagick) { $v = Imagick::getVersion(); preg_match('/ImageMagick ([0-9]+\.[0-9]+\.[0-9]+)/', $v['versionString'], $m); - if(version_compare($m[1],'6.6.7') >= 0) { + if(version_compare($m[1], '6.6.7') >= 0) { /** * Well, this not much better, * but at least it comes from the data inside the image, @@ -516,7 +550,7 @@ function guess_image_type($filename, $headers = '') { else { // earlier imagick versions have issues with scaling png's // don't log this because it will just fill the logfile. - // leave this note here so those who are looking for why + // leave this note here so those who are looking for why // we aren't using imagick can find it } } @@ -551,33 +585,36 @@ function guess_image_type($filename, $headers = '') { } logger('Photo: guess_image_type: filename = ' . $filename . ' type = ' . $type, LOGGER_DEBUG); - return $type; + return $type; } - -function delete_thing_photo($url,$ob_hash) { +/** + * @brief Delete thing photo from database. + * + * @param string $url + * @param string $ob_hash + * @return void + */ +function delete_thing_photo($url, $ob_hash) { $hash = basename($url); - $hash = substr($hash,0,strpos($hash,'-')); + $hash = substr($hash, 0, strpos($hash, '-')); - // hashes should be 32 bytes. + // hashes should be 32 bytes. if((! $ob_hash) || (strlen($hash) < 16)) - return; + return; - $r = q("delete from photo where xchan = '%s' and photo_usage = %d and resource_id = '%s'", + q("delete from photo where xchan = '%s' and photo_usage = %d and resource_id = '%s'", dbesc($ob_hash), intval(PHOTO_THING), dbesc($hash) ); - } - - /** - * @brief fetches an photo from external site and prepares its miniatures. + * @brief Fetches a photo from external site and prepares its miniatures. * * @param string $photo * external URL to fetch base image @@ -596,223 +633,215 @@ function delete_thing_photo($url,$ob_hash) { * * \e boolean \b 4 => TRUE if fetch failure * * \e string \b 5 => modification date */ +function import_xchan_photo($photo, $xchan, $thing = false, $force = false) { + $modified = ''; + $o = null; + + $flags = (($thing) ? PHOTO_THING : PHOTO_XCHAN); + $album = (($thing) ? 'Things' : 'Contact Photos'); + + logger('Updating channel photo from ' . $photo . ' for ' . $xchan, LOGGER_DEBUG); + + if($thing) { + $hash = photo_new_resource(); + } else { + $r = q("select resource_id, edited, mimetype from photo where xchan = '%s' and photo_usage = %d and imgscale = 4 limit 1", dbesc($xchan), intval(PHOTO_XCHAN)); + if($r) { + $hash = $r[0]['resource_id']; + $modified = $r[0]['edited']; + $type = $r[0]['mimetype']; + } else { + $hash = photo_new_resource(); + } + } -function import_xchan_photo($photo,$xchan,$thing = false,$force = false) { - - $modified = ''; - - $flags = (($thing) ? PHOTO_THING : PHOTO_XCHAN); - $album = (($thing) ? 'Things' : 'Contact Photos'); - - logger('import_xchan_photo: updating channel photo from ' . $photo . ' for ' . $xchan, LOGGER_DEBUG); - - if($thing) { - $hash = photo_new_resource(); - } - else { - $r = q("select resource_id, edited, mimetype from photo where xchan = '%s' and photo_usage = %d and imgscale = 4 limit 1", - dbesc($xchan), - intval(PHOTO_XCHAN) - ); - if($r) { - $hash = $r[0]['resource_id']; - $modified = $r[0]['edited']; - $type = $r[0]['mimetype']; - } - else { - $hash = photo_new_resource(); - } - } - - $photo_failure = false; - $img_str = ''; - - if($photo) { - $filename = basename($photo); - - if($force || $modified == '') { - $result = z_fetch_url($photo,true); - } - else { - $h = array('headers' => array("If-Modified-Since: " . gmdate("D, d M Y H:i:s", strtotime($modified . "Z")) . " GMT")); - $result = z_fetch_url($photo,true,0,$h); - } - - if($result['success']) { - $img_str = $result['body']; - $type = guess_image_type($photo, $result['header']); - $modified = gmdate('Y-m-d H:i:s', (preg_match('/last-modified: (.+) \S+/i', $result['header'], $o) ? strtotime($o[1] . 'Z') : time())); - if(is_null($type)) - $photo_failure = true; - } - elseif($result['return_code'] == 304) { - $photo = z_root() . '/photo/' . $hash . '-4'; - $thumb = z_root() . '/photo/' . $hash . '-5'; - $micro = z_root() . '/photo/' . $hash . '-6'; - } - else { - $photo_failure = true; - } - - } - else - $photo_failure = true; - - if(! $photo_failure && $result['return_code'] != 304) { - $img = photo_factory($img_str, $type); - if($img->is_valid()) { - $width = $img->getWidth(); - $height = $img->getHeight(); - - if($width && $height) { - if(($width / $height) > 1.2) { - // crop out the sides - $margin = $width - $height; - $img->cropImage(300,($margin / 2),0,$height,$height); - } - elseif(($height / $width) > 1.2) { - // crop out the bottom - $margin = $height - $width; - $img->cropImage(300,0,0,$width,$width); - - } - else { - $img->scaleImageSquare(300); - } - - } - else - $photo_failure = true; - - $p = array( - 'xchan' => $xchan, - 'resource_id' => $hash, - 'filename' => basename($photo), - 'album' => $album, - 'photo_usage' => $flags, - 'imgscale' => 4, - 'edited' => $modified - ); - - $r = $img->save($p); - - if($r === false) - $photo_failure = true; - - $img->scaleImage(80); - $p['imgscale'] = 5; - - $r = $img->save($p); - - if($r === false) - $photo_failure = true; - - $img->scaleImage(48); - $p['imgscale'] = 6; - - $r = $img->save($p); - - if($r === false) - $photo_failure = true; - - $photo = z_root() . '/photo/' . $hash . '-4'; - $thumb = z_root() . '/photo/' . $hash . '-5'; - $micro = z_root() . '/photo/' . $hash . '-6'; - } - else { - logger('import_xchan_photo: invalid image from ' . $photo); - $photo_failure = true; - } - } - if($photo_failure) { - $default = get_default_profile_photo(); - $photo = z_root() . '/' . $default; - $thumb = z_root() . '/' . get_default_profile_photo(80); - $micro = z_root() . '/' . get_default_profile_photo(48); - $type = 'image/png'; - $modified = gmdate('Y-m-d H:i:s', filemtime($default)); - } - - logger('HTTP code: ' . $result['return_code'] . '; modified: ' . $modified . '; failure: ' . ($photo_failure ? 'yes' : 'no') . '; URL: ' . $photo, LOGGER_DEBUG); - return(array($photo,$thumb,$micro,$type,$photo_failure,$modified)); - -} - -function import_channel_photo_from_url($photo,$aid,$uid) { + $photo_failure = false; + $img_str = ''; if($photo) { - $filename = basename($photo); - $result = z_fetch_url($photo,true); + if($force || $modified == '') { + $result = z_fetch_url($photo, true); + } else { + $h = [ + 'headers' => [ + 'If-Modified-Since: ' . gmdate('D, d M Y H:i:s', strtotime($modified . 'Z')) . ' GMT' + ] + ]; + $result = z_fetch_url($photo, true, 0, $h); + } if($result['success']) { $img_str = $result['body']; $type = guess_image_type($photo, $result['header']); + $modified = gmdate('Y-m-d H:i:s', (preg_match('/last-modified: (.+) \S+/i', $result['header'], $o) ? strtotime($o[1] . 'Z') : time())); + if(is_null($type)) + $photo_failure = true; + } elseif($result['return_code'] == 304) { + $photo = z_root() . '/photo/' . $hash . '-4'; + $thumb = z_root() . '/photo/' . $hash . '-5'; + $micro = z_root() . '/photo/' . $hash . '-6'; + } else { + $photo_failure = true; + } + } else { + $photo_failure = true; + } - if(is_null($type)) - $photo_failure = true; + if(!$photo_failure && $result['return_code'] != 304) { + $img = photo_factory($img_str, $type); + if($img->is_valid()) { + $width = $img->getWidth(); + $height = $img->getHeight(); + + if($width && $height) { + if(($width / $height) > 1.2) { + // crop out the sides + $margin = $width - $height; + $img->cropImage(300, ($margin / 2), 0, $height, $height); + } elseif(($height / $width) > 1.2) { + // crop out the bottom + $margin = $height - $width; + $img->cropImage(300, 0, 0, $width, $width); + } else { + $img->scaleImageSquare(300); + } + } else { + $photo_failure = true; + } + + $p = [ + 'xchan' => $xchan, + 'resource_id' => $hash, + 'filename' => basename($photo), + 'album' => $album, + 'photo_usage' => $flags, + 'imgscale' => 4, + 'edited' => $modified, + ]; + + $r = $img->save($p); + if($r === false) + $photo_failure = true; + + $img->scaleImage(80); + $p['imgscale'] = 5; + $r = $img->save($p); + if($r === false) + $photo_failure = true; + + $img->scaleImage(48); + $p['imgscale'] = 6; + $r = $img->save($p); + if($r === false) + $photo_failure = true; + + $photo = z_root() . '/photo/' . $hash . '-4'; + $thumb = z_root() . '/photo/' . $hash . '-5'; + $micro = z_root() . '/photo/' . $hash . '-6'; + } else { + logger('Invalid image from ' . $photo); + $photo_failure = true; } } - else { - $photo_failure = true; + if($photo_failure) { + $default = get_default_profile_photo(); + $photo = z_root() . '/' . $default; + $thumb = z_root() . '/' . get_default_profile_photo(80); + $micro = z_root() . '/' . get_default_profile_photo(48); + $type = 'image/png'; + $modified = gmdate('Y-m-d H:i:s', filemtime($default)); } - import_channel_photo($img_str,$type,$aid,$uid); + logger('HTTP code: ' . $result['return_code'] . '; modified: ' . $modified + . '; failure: ' . ($photo_failure ? 'yes' : 'no') . '; URL: ' . $photo, LOGGER_DEBUG); - return $type; + return(array($photo,$thumb,$micro,$type,$photo_failure,$modified)); } +/** + * @brief Import channel photo from a URL. + * + * @param string $photo URL to a photo + * @param int $aid + * @param int $uid channel_id + * @return null|string Guessed image mimetype or null. + */ +function import_channel_photo_from_url($photo, $aid, $uid) { + $type = null; -function import_channel_photo($photo,$type,$aid,$uid) { + if($photo) { + $result = z_fetch_url($photo, true); + + if($result['success']) { + $img_str = $result['body']; + $type = guess_image_type($photo, $result['header']); - logger('import_channel_photo: importing channel photo for ' . $uid, LOGGER_DEBUG); + import_channel_photo($img_str, $type, $aid, $uid); + } + } - $hash = photo_new_resource(); + return $type; +} - $photo_failure = false; +/** + * @brief Import a channel photo and prepare its miniatures. + * + * @param string $photo Image data + * @param string $type + * @param int $aid + * @param int $uid channel_id + * @return boolean|string false on failure, otherwise resource_id of photo + */ +function import_channel_photo($photo, $type, $aid, $uid) { + logger('Importing channel photo for ' . $uid, LOGGER_DEBUG); + $photo_failure = false; + $hash = photo_new_resource(); $filename = $hash; $img = photo_factory($photo, $type); if($img->is_valid()) { + // config array for image save method + $p = [ + 'aid' => $aid, + 'uid' => $uid, + 'resource_id' => $hash, + 'filename' => $filename, + 'album' => t('Profile Photos'), + 'photo_usage' => PHOTO_PROFILE, + 'imgscale' => 4, + ]; + + // photo size $img->scaleImageSquare(300); - - $p = array('aid' => $aid, 'uid' => $uid, 'resource_id' => $hash, 'filename' => $filename, 'album' => t('Profile Photos'), 'photo_usage' => PHOTO_PROFILE, 'imgscale' => 4); - $r = $img->save($p); - if($r === false) $photo_failure = true; + // thumb size $img->scaleImage(80); $p['imgscale'] = 5; - $r = $img->save($p); - if($r === false) $photo_failure = true; + // micro size $img->scaleImage(48); $p['imgscale'] = 6; - $r = $img->save($p); - if($r === false) $photo_failure = true; - } - else { - logger('import_channel_photo: invalid image.'); + } else { + logger('Invalid image.'); $photo_failure = true; } - //return(($photo_failure)? false : true); - if($photo_failure) return false; else return $hash; - } |