From: Adam Magness Date: Wed, 29 Nov 2017 12:52:27 +0000 (-0500) Subject: Photo to src X-Git-Url: https://git.mxchange.org/?a=commitdiff_plain;h=54827e7feda8253b0f1055c81f6fd76b7c8ea3f5;p=friendica.git Photo to src Move Photo to Friendica\Object namespace and replace require_once statments with use statements. --- diff --git a/include/Photo.php b/include/Photo.php deleted file mode 100644 index 761abc770c..0000000000 --- a/include/Photo.php +++ /dev/null @@ -1,1069 +0,0 @@ - 'jpg', - 'image/png' => 'png', - 'image/gif' => 'gif' - ); - } else { - $t = array(); - $t['image/jpeg'] ='jpg'; - if (imagetypes() & IMG_PNG) { - $t['image/png'] = 'png'; - } - } - - return $t; - } - - public function __construct($data, $type=null) { - $this->imagick = class_exists('Imagick'); - $this->types = static::supportedTypes(); - if (!array_key_exists($type, $this->types)){ - $type='image/jpeg'; - } - $this->type = $type; - - if ($this->is_imagick() && $this->load_data($data)) { - return true; - } else { - // Failed to load with Imagick, fallback - $this->imagick = false; - } - return $this->load_data($data); - } - - public function __destruct() { - if ($this->image) { - if ($this->is_imagick()) { - $this->image->clear(); - $this->image->destroy(); - return; - } - if (is_resource($this->image)) { - imagedestroy($this->image); - } - } - } - - public function is_imagick() { - return $this->imagick; - } - - /** - * @brief Maps Mime types to Imagick formats - * @return arr With with image formats (mime type as key) - */ - public function get_FormatsMap() { - $m = array( - 'image/jpeg' => 'JPG', - 'image/png' => 'PNG', - 'image/gif' => 'GIF' - ); - return $m; - } - - private function load_data($data) { - if ($this->is_imagick()) { - $this->image = new Imagick(); - try { - $this->image->readImageBlob($data); - } catch (Exception $e) { - // Imagick couldn't use the data - return false; - } - - /* - * Setup the image to the format it will be saved to - */ - $map = $this->get_FormatsMap(); - $format = $map[$type]; - $this->image->setFormat($format); - - // Always coalesce, if it is not a multi-frame image it won't hurt anyway - $this->image = $this->image->coalesceImages(); - - /* - * setup the compression here, so we'll do it only once - */ - switch($this->getType()){ - case "image/png": - $quality = Config::get('system', 'png_quality'); - if ((! $quality) || ($quality > 9)) { - $quality = PNG_QUALITY; - } - /* - * From http://www.imagemagick.org/script/command-line-options.php#quality: - * - * 'For the MNG and PNG image formats, the quality value sets - * the zlib compression level (quality / 10) and filter-type (quality % 10). - * The default PNG "quality" is 75, which means compression level 7 with adaptive PNG filtering, - * unless the image has a color map, in which case it means compression level 7 with no PNG filtering' - */ - $quality = $quality * 10; - $this->image->setCompressionQuality($quality); - break; - case "image/jpeg": - $quality = Config::get('system', 'jpeg_quality'); - if ((! $quality) || ($quality > 100)) { - $quality = JPEG_QUALITY; - } - $this->image->setCompressionQuality($quality); - } - - // The 'width' and 'height' properties are only used by non-Imagick routines. - $this->width = $this->image->getImageWidth(); - $this->height = $this->image->getImageHeight(); - $this->valid = true; - - return true; - } - - $this->valid = false; - $this->image = @imagecreatefromstring($data); - if ($this->image !== false) { - $this->width = imagesx($this->image); - $this->height = imagesy($this->image); - $this->valid = true; - imagealphablending($this->image, false); - imagesavealpha($this->image, true); - - return true; - } - - return false; - } - - public function is_valid() { - if ($this->is_imagick()) { - return ($this->image !== false); - } - return $this->valid; - } - - public function getWidth() { - if (!$this->is_valid()) { - return false; - } - - if ($this->is_imagick()) { - return $this->image->getImageWidth(); - } - return $this->width; - } - - public function getHeight() { - if (!$this->is_valid()) { - return false; - } - - if ($this->is_imagick()) { - return $this->image->getImageHeight(); - } - return $this->height; - } - - public function getImage() { - if (!$this->is_valid()) { - return false; - } - - if ($this->is_imagick()) { - /* Clean it */ - $this->image = $this->image->deconstructImages(); - return $this->image; - } - return $this->image; - } - - public function getType() { - if (!$this->is_valid()) { - return false; - } - - return $this->type; - } - - public function getExt() { - if (!$this->is_valid()) { - return false; - } - - return $this->types[$this->getType()]; - } - - public function scaleImage($max) { - if (!$this->is_valid()) { - return false; - } - - $width = $this->getWidth(); - $height = $this->getHeight(); - - $dest_width = $dest_height = 0; - - if ((! $width)|| (! $height)) { - return false; - } - - if ($width > $max && $height > $max) { - - // very tall image (greater than 16:9) - // constrain the width - let the height float. - - if ((($height * 9) / 16) > $width) { - $dest_width = $max; - $dest_height = intval(($height * $max) / $width); - } elseif ($width > $height) { - // else constrain both dimensions - $dest_width = $max; - $dest_height = intval(($height * $max) / $width); - } else { - $dest_width = intval(($width * $max) / $height); - $dest_height = $max; - } - } else { - if ($width > $max) { - $dest_width = $max; - $dest_height = intval(($height * $max) / $width); - } else { - if ($height > $max) { - - // very tall image (greater than 16:9) - // but width is OK - don't do anything - - if ((($height * 9) / 16) > $width) { - $dest_width = $width; - $dest_height = $height; - } else { - $dest_width = intval(($width * $max) / $height); - $dest_height = $max; - } - } else { - $dest_width = $width; - $dest_height = $height; - } - } - } - - - if ($this->is_imagick()) { - /* - * If it is not animated, there will be only one iteration here, - * so don't bother checking - */ - // Don't forget to go back to the first frame - $this->image->setFirstIterator(); - do { - - // FIXME - implement horizantal bias for scaling as in followin GD functions - // to allow very tall images to be constrained only horizontally. - - $this->image->scaleImage($dest_width, $dest_height); - } while ($this->image->nextImage()); - - // These may not be necessary any more - $this->width = $this->image->getImageWidth(); - $this->height = $this->image->getImageHeight(); - - return; - } - - - $dest = imagecreatetruecolor($dest_width, $dest_height); - imagealphablending($dest, false); - imagesavealpha($dest, true); - if ($this->type=='image/png') { - imagefill($dest, 0, 0, imagecolorallocatealpha($dest, 0, 0, 0, 127)); // fill with alpha - } - imagecopyresampled($dest, $this->image, 0, 0, 0, 0, $dest_width, $dest_height, $width, $height); - if ($this->image) { - imagedestroy($this->image); - } - $this->image = $dest; - $this->width = imagesx($this->image); - $this->height = imagesy($this->image); - } - - public function rotate($degrees) { - if (!$this->is_valid()) { - return false; - } - - if ($this->is_imagick()) { - $this->image->setFirstIterator(); - do { - $this->image->rotateImage(new ImagickPixel(), -$degrees); // ImageMagick rotates in the opposite direction of imagerotate() - } while ($this->image->nextImage()); - return; - } - - // if script dies at this point check memory_limit setting in php.ini - $this->image = imagerotate($this->image,$degrees,0); - $this->width = imagesx($this->image); - $this->height = imagesy($this->image); - } - - public function flip($horiz = true, $vert = false) { - if (!$this->is_valid()) { - return false; - } - - if ($this->is_imagick()) { - $this->image->setFirstIterator(); - do { - if ($horiz) { - $this->image->flipImage(); - } - if ($vert) { - $this->image->flopImage(); - } - } while ($this->image->nextImage()); - return; - } - - $w = imagesx($this->image); - $h = imagesy($this->image); - $flipped = imagecreate($w, $h); - if ($horiz) { - for ($x = 0; $x < $w; $x++) { - imagecopy($flipped, $this->image, $x, 0, $w - $x - 1, 0, 1, $h); - } - } - if ($vert) { - for ($y = 0; $y < $h; $y++) { - imagecopy($flipped, $this->image, 0, $y, 0, $h - $y - 1, $w, 1); - } - } - $this->image = $flipped; - } - - public function orient($filename) { - if ($this->is_imagick()) { - // based off comment on http://php.net/manual/en/imagick.getimageorientation.php - $orientation = $this->image->getImageOrientation(); - switch ($orientation) { - case imagick::ORIENTATION_BOTTOMRIGHT: - $this->image->rotateimage("#000", 180); - break; - case imagick::ORIENTATION_RIGHTTOP: - $this->image->rotateimage("#000", 90); - break; - case imagick::ORIENTATION_LEFTBOTTOM: - $this->image->rotateimage("#000", -90); - break; - } - - $this->image->setImageOrientation(imagick::ORIENTATION_TOPLEFT); - return true; - } - // based off comment on http://php.net/manual/en/function.imagerotate.php - - if (!$this->is_valid()) { - return false; - } - - if ((!function_exists('exif_read_data')) || ($this->getType() !== 'image/jpeg')) { - return; - } - - $exif = @exif_read_data($filename,null,true); - if (!$exif) { - return; - } - - $ort = $exif['IFD0']['Orientation']; - - switch($ort) - { - case 1: // nothing - break; - - case 2: // horizontal flip - $this->flip(); - break; - - case 3: // 180 rotate left - $this->rotate(180); - break; - - case 4: // vertical flip - $this->flip(false, true); - break; - - case 5: // vertical flip + 90 rotate right - $this->flip(false, true); - $this->rotate(-90); - break; - - case 6: // 90 rotate right - $this->rotate(-90); - break; - - case 7: // horizontal flip + 90 rotate right - $this->flip(); - $this->rotate(-90); - break; - - case 8: // 90 rotate left - $this->rotate(90); - break; - } - - // logger('exif: ' . print_r($exif,true)); - return $exif; - - } - - - - public function scaleImageUp($min) { - if (!$this->is_valid()) { - return false; - } - - - $width = $this->getWidth(); - $height = $this->getHeight(); - - $dest_width = $dest_height = 0; - - if ((!$width)|| (!$height)) { - return false; - } - - if ($width < $min && $height < $min) { - if ($width > $height) { - $dest_width = $min; - $dest_height = intval(($height * $min) / $width); - } else { - $dest_width = intval(($width * $min) / $height); - $dest_height = $min; - } - } else { - if ($width < $min) { - $dest_width = $min; - $dest_height = intval(($height * $min) / $width); - } else { - if ($height < $min) { - $dest_width = intval(($width * $min) / $height); - $dest_height = $min; - } else { - $dest_width = $width; - $dest_height = $height; - } - } - } - - if ($this->is_imagick()) { - return $this->scaleImage($dest_width, $dest_height); - } - - $dest = imagecreatetruecolor($dest_width, $dest_height); - imagealphablending($dest, false); - imagesavealpha($dest, true); - if ($this->type=='image/png') { - imagefill($dest, 0, 0, imagecolorallocatealpha($dest, 0, 0, 0, 127)); // fill with alpha - } - imagecopyresampled($dest, $this->image, 0, 0, 0, 0, $dest_width, $dest_height, $width, $height); - if ($this->image) { - imagedestroy($this->image); - } - $this->image = $dest; - $this->width = imagesx($this->image); - $this->height = imagesy($this->image); - } - - - - public function scaleImageSquare($dim) { - if (!$this->is_valid()) { - return false; - } - - if ($this->is_imagick()) { - $this->image->setFirstIterator(); - do { - $this->image->scaleImage($dim, $dim); - } while ($this->image->nextImage()); - return; - } - - $dest = imagecreatetruecolor($dim, $dim); - imagealphablending($dest, false); - imagesavealpha($dest, true); - if ($this->type=='image/png') { - imagefill($dest, 0, 0, imagecolorallocatealpha($dest, 0, 0, 0, 127)); // fill with alpha - } - imagecopyresampled($dest, $this->image, 0, 0, 0, 0, $dim, $dim, $this->width, $this->height); - if ($this->image) { - imagedestroy($this->image); - } - $this->image = $dest; - $this->width = imagesx($this->image); - $this->height = imagesy($this->image); - } - - - public function cropImage($max, $x, $y, $w, $h) { - if (!$this->is_valid()) { - return false; - } - - if ($this->is_imagick()) { - $this->image->setFirstIterator(); - do { - $this->image->cropImage($w, $h, $x, $y); - /* - * We need to remove the canva, - * or the image is not resized to the crop: - * http://php.net/manual/en/imagick.cropimage.php#97232 - */ - $this->image->setImagePage(0, 0, 0, 0); - } while ($this->image->nextImage()); - return $this->scaleImage($max); - } - - $dest = imagecreatetruecolor($max, $max); - imagealphablending($dest, false); - imagesavealpha($dest, true); - if ($this->type=='image/png') { - imagefill($dest, 0, 0, imagecolorallocatealpha($dest, 0, 0, 0, 127)); // fill with alpha - } - imagecopyresampled($dest, $this->image, 0, 0, $x, $y, $max, $max, $w, $h); - if ($this->image) { - imagedestroy($this->image); - } - $this->image = $dest; - $this->width = imagesx($this->image); - $this->height = imagesy($this->image); - } - - public function saveImage($path) { - if (!$this->is_valid()) { - return false; - } - - $string = $this->imageString(); - - $a = get_app(); - - $stamp1 = microtime(true); - file_put_contents($path, $string); - $a->save_timestamp($stamp1, "file"); - } - - public function imageString() { - if (!$this->is_valid()) { - return false; - } - - if ($this->is_imagick()) { - /* Clean it */ - $this->image = $this->image->deconstructImages(); - $string = $this->image->getImagesBlob(); - return $string; - } - - $quality = false; - - ob_start(); - - // Enable interlacing - imageinterlace($this->image, true); - - switch($this->getType()){ - case "image/png": - $quality = Config::get('system', 'png_quality'); - if ((!$quality) || ($quality > 9)) { - $quality = PNG_QUALITY; - } - imagepng($this->image, null, $quality); - break; - case "image/jpeg": - $quality = Config::get('system', 'jpeg_quality'); - if ((!$quality) || ($quality > 100)) { - $quality = JPEG_QUALITY; - } - imagejpeg($this->image, null, $quality); - } - $string = ob_get_contents(); - ob_end_clean(); - - return $string; - } - - - - public function store($uid, $cid, $rid, $filename, $album, $scale, $profile = 0, $allow_cid = '', $allow_gid = '', $deny_cid = '', $deny_gid = '', $desc = '') { - - $r = dba::select('photo', array('guid'), array("`resource-id` = ? AND `guid` != ?", $rid, ''), array('limit' => 1)); - if (DBM::is_result($r)) { - $guid = $r['guid']; - } else { - $guid = get_guid(); - } - - $x = dba::select('photo', array('id'), array('resource-id' => $rid, 'uid' => $uid, 'contact-id' => $cid, 'scale' => $scale), array('limit' => 1)); - - $fields = array('uid' => $uid, 'contact-id' => $cid, 'guid' => $guid, 'resource-id' => $rid, 'created' => datetime_convert(), 'edited' => datetime_convert(), - 'filename' => basename($filename), 'type' => $this->getType(), 'album' => $album, 'height' => $this->getHeight(), 'width' => $this->getWidth(), - 'datasize' => strlen($this->imageString()), 'data' => $this->imageString(), 'scale' => $scale, 'profile' => $profile, - 'allow_cid' => $allow_cid, 'allow_gid' => $allow_gid, 'deny_cid' => $deny_cid, 'deny_gid' => $deny_gid, 'desc' => $desc); - - if (DBM::is_result($x)) { - $r = dba::update('photo', $fields, array('id' => $x['id'])); - } else { - $r = dba::insert('photo', $fields); - } - - return $r; - } -} - - -/** - * Guess image mimetype from filename or from Content-Type header - * - * @arg $filename string Image filename - * @arg $fromcurl boolean Check Content-Type header from curl request - */ -function guess_image_type($filename, $fromcurl=false) { - logger('Photo: guess_image_type: '.$filename . ($fromcurl?' from curl headers':''), LOGGER_DEBUG); - $type = null; - if ($fromcurl) { - $a = get_app(); - $headers=array(); - $h = explode("\n",$a->get_curl_headers()); - foreach ($h as $l) { - list($k,$v) = array_map("trim", explode(":", trim($l), 2)); - $headers[$k] = $v; - } - if (array_key_exists('Content-Type', $headers)) - $type = $headers['Content-Type']; - } - if (is_null($type)){ - // Guessing from extension? Isn't that... dangerous? - if (class_exists('Imagick') && file_exists($filename) && is_readable($filename)) { - /** - * Well, this not much better, - * but at least it comes from the data inside the image, - * we won't be tricked by a manipulated extension - */ - $image = new Imagick($filename); - $type = $image->getImageMimeType(); - $image->setInterlaceScheme(Imagick::INTERLACE_PLANE); - } else { - $ext = pathinfo($filename, PATHINFO_EXTENSION); - $types = Photo::supportedTypes(); - $type = "image/jpeg"; - foreach ($types as $m => $e){ - if ($ext == $e) { - $type = $m; - } - } - } - } - logger('Photo: guess_image_type: type='.$type, LOGGER_DEBUG); - return $type; - -} - -/** - * @brief Updates the avatar links in a contact only if needed - * - * @param string $avatar Link to avatar picture - * @param int $uid User id of contact owner - * @param int $cid Contact id - * @param bool $force force picture update - * - * @return array Returns array of the different avatar sizes - */ -function update_contact_avatar($avatar, $uid, $cid, $force = false) { - $r = q("SELECT `avatar`, `photo`, `thumb`, `micro`, `nurl` FROM `contact` WHERE `id` = %d LIMIT 1", intval($cid)); - if (!DBM::is_result($r)) { - return false; - } else { - $data = array($r[0]["photo"], $r[0]["thumb"], $r[0]["micro"]); - } - - if (($r[0]["avatar"] != $avatar) || $force) { - $photos = import_profile_photo($avatar, $uid, $cid, true); - - if ($photos) { - q("UPDATE `contact` SET `avatar` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s', `avatar-date` = '%s' WHERE `id` = %d", - dbesc($avatar), dbesc($photos[0]), dbesc($photos[1]), dbesc($photos[2]), - dbesc(datetime_convert()), intval($cid)); - - // Update the public contact (contact id = 0) - if ($uid != 0) { - $pcontact = dba::select('contact', array('id'), array('nurl' => $r[0]['nurl']), array('limit' => 1)); - if (DBM::is_result($pcontact)) { - update_contact_avatar($avatar, 0, $pcontact['id'], $force); - } - } - - return $photos; - } - } - - return $data; -} - -function import_profile_photo($photo, $uid, $cid, $quit_on_error = false) { - - $r = q("SELECT `resource-id` FROM `photo` WHERE `uid` = %d AND `contact-id` = %d AND `scale` = 4 AND `album` = 'Contact Photos' LIMIT 1", - intval($uid), - intval($cid) - ); - if (DBM::is_result($r) && strlen($r[0]['resource-id'])) { - $hash = $r[0]['resource-id']; - } else { - $hash = photo_new_resource(); - } - - $photo_failure = false; - - $filename = basename($photo); - $img_str = fetch_url($photo, true); - - if ($quit_on_error && ($img_str == "")) { - return false; - } - - $type = guess_image_type($photo, true); - $img = new Photo($img_str, $type); - if ($img->is_valid()) { - - $img->scaleImageSquare(175); - - $r = $img->store($uid, $cid, $hash, $filename, 'Contact Photos', 4); - - if ($r === false) - $photo_failure = true; - - $img->scaleImage(80); - - $r = $img->store($uid, $cid, $hash, $filename, 'Contact Photos', 5); - - if ($r === false) - $photo_failure = true; - - $img->scaleImage(48); - - $r = $img->store($uid, $cid, $hash, $filename, 'Contact Photos', 6); - - if ($r === false) { - $photo_failure = true; - } - - $suffix = '?ts='.time(); - - $photo = System::baseUrl() . '/photo/' . $hash . '-4.' . $img->getExt() . $suffix; - $thumb = System::baseUrl() . '/photo/' . $hash . '-5.' . $img->getExt() . $suffix; - $micro = System::baseUrl() . '/photo/' . $hash . '-6.' . $img->getExt() . $suffix; - - // Remove the cached photo - $a = get_app(); - $basepath = $a->get_basepath(); - - if (is_dir($basepath."/photo")) { - $filename = $basepath.'/photo/'.$hash.'-4.'.$img->getExt(); - if (file_exists($filename)) { - unlink($filename); - } - $filename = $basepath.'/photo/'.$hash.'-5.'.$img->getExt(); - if (file_exists($filename)) { - unlink($filename); - } - $filename = $basepath.'/photo/'.$hash.'-6.'.$img->getExt(); - if (file_exists($filename)) { - unlink($filename); - } - } - } else { - $photo_failure = true; - } - - if ($photo_failure && $quit_on_error) { - return false; - } - - if ($photo_failure) { - $photo = System::baseUrl() . '/images/person-175.jpg'; - $thumb = System::baseUrl() . '/images/person-80.jpg'; - $micro = System::baseUrl() . '/images/person-48.jpg'; - } - - return(array($photo,$thumb,$micro)); - -} - -function get_photo_info($url) { - $data = array(); - - $data = Cache::get($url); - - if (is_null($data) || !$data || !is_array($data)) { - $img_str = fetch_url($url, true, $redirects, 4); - $filesize = strlen($img_str); - - if (function_exists("getimagesizefromstring")) { - $data = getimagesizefromstring($img_str); - } else { - $tempfile = tempnam(get_temppath(), "cache"); - - $a = get_app(); - $stamp1 = microtime(true); - file_put_contents($tempfile, $img_str); - $a->save_timestamp($stamp1, "file"); - - $data = getimagesize($tempfile); - unlink($tempfile); - } - - if ($data) { - $data["size"] = $filesize; - } - - Cache::set($url, $data); - } - - return $data; -} - -function scale_image($width, $height, $max) { - - $dest_width = $dest_height = 0; - - if ((!$width) || (!$height)) { - return false; - } - - if ($width > $max && $height > $max) { - - // very tall image (greater than 16:9) - // constrain the width - let the height float. - - if ((($height * 9) / 16) > $width) { - $dest_width = $max; - $dest_height = intval(($height * $max) / $width); - } elseif ($width > $height) { - // else constrain both dimensions - $dest_width = $max; - $dest_height = intval(($height * $max) / $width); - } else { - $dest_width = intval(($width * $max) / $height); - $dest_height = $max; - } - } else { - if ($width > $max) { - $dest_width = $max; - $dest_height = intval(($height * $max) / $width); - } else { - if ($height > $max) { - - // very tall image (greater than 16:9) - // but width is OK - don't do anything - - if ((($height * 9) / 16) > $width) { - $dest_width = $width; - $dest_height = $height; - } else { - $dest_width = intval(($width * $max) / $height); - $dest_height = $max; - } - } else { - $dest_width = $width; - $dest_height = $height; - } - } - } - return array("width" => $dest_width, "height" => $dest_height); -} - -function store_photo(App $a, $uid, $imagedata = "", $url = "") { - $r = q("SELECT `user`.`nickname`, `user`.`page-flags`, `contact`.`id` FROM `user` INNER JOIN `contact` on `user`.`uid` = `contact`.`uid` - WHERE `user`.`uid` = %d AND `user`.`blocked` = 0 AND `contact`.`self` = 1 LIMIT 1", - intval($uid)); - - if (!DBM::is_result($r)) { - logger("Can't detect user data for uid ".$uid, LOGGER_DEBUG); - return(array()); - } - - $page_owner_nick = $r[0]['nickname']; - - /// @TODO - /// $default_cid = $r[0]['id']; - /// $community_page = (($r[0]['page-flags'] == PAGE_COMMUNITY) ? true : false); - - if ((strlen($imagedata) == 0) && ($url == "")) { - logger("No image data and no url provided", LOGGER_DEBUG); - return(array()); - } elseif (strlen($imagedata) == 0) { - logger("Uploading picture from ".$url, LOGGER_DEBUG); - - $stamp1 = microtime(true); - $imagedata = @file_get_contents($url); - $a->save_timestamp($stamp1, "file"); - } - - $maximagesize = Config::get('system', 'maximagesize'); - - if (($maximagesize) && (strlen($imagedata) > $maximagesize)) { - logger("Image exceeds size limit of ".$maximagesize, LOGGER_DEBUG); - return(array()); - } - - $tempfile = tempnam(get_temppath(), "cache"); - - $stamp1 = microtime(true); - file_put_contents($tempfile, $imagedata); - $a->save_timestamp($stamp1, "file"); - - $data = getimagesize($tempfile); - - if (!isset($data["mime"])) { - unlink($tempfile); - logger("File is no picture", LOGGER_DEBUG); - return(array()); - } - - $ph = new Photo($imagedata, $data["mime"]); - - if (!$ph->is_valid()) { - unlink($tempfile); - logger("Picture is no valid picture", LOGGER_DEBUG); - return(array()); - } - - $ph->orient($tempfile); - unlink($tempfile); - - $max_length = Config::get('system', 'max_image_length'); - if (! $max_length) { - $max_length = MAX_IMAGE_LENGTH; - } - if ($max_length > 0) { - $ph->scaleImage($max_length); - } - - $width = $ph->getWidth(); - $height = $ph->getHeight(); - - $hash = photo_new_resource(); - - $smallest = 0; - - // Pictures are always public by now - //$defperm = '<'.$default_cid.'>'; - $defperm = ""; - $visitor = 0; - - $r = $ph->store($uid, $visitor, $hash, $tempfile, t('Wall Photos'), 0, 0, $defperm); - - if (!$r) { - logger("Picture couldn't be stored", LOGGER_DEBUG); - return(array()); - } - - $image = array("page" => System::baseUrl().'/photos/'.$page_owner_nick.'/image/'.$hash, - "full" => System::baseUrl()."/photo/{$hash}-0.".$ph->getExt()); - - if ($width > 800 || $height > 800) { - $image["large"] = System::baseUrl()."/photo/{$hash}-0.".$ph->getExt(); - } - - if ($width > 640 || $height > 640) { - $ph->scaleImage(640); - $r = $ph->store($uid, $visitor, $hash, $tempfile, t('Wall Photos'), 1, 0, $defperm); - if ($r) { - $image["medium"] = System::baseUrl()."/photo/{$hash}-1.".$ph->getExt(); - } - } - - if ($width > 320 || $height > 320) { - $ph->scaleImage(320); - $r = $ph->store($uid, $visitor, $hash, $tempfile, t('Wall Photos'), 2, 0, $defperm); - if ($r) { - $image["small"] = System::baseUrl()."/photo/{$hash}-2.".$ph->getExt(); - } - } - - if ($width > 160 && $height > 160) { - $x = 0; - $y = 0; - - $min = $ph->getWidth(); - if ($min > 160) { - $x = ($min - 160) / 2; - } - - if ($ph->getHeight() < $min) { - $min = $ph->getHeight(); - if ($min > 160) { - $y = ($min - 160) / 2; - } - } - - $min = 160; - $ph->cropImage(160, $x, $y, $min, $min); - - $r = $ph->store($uid, $visitor, $hash, $tempfile, t('Wall Photos'), 3, 0, $defperm); - if ($r) { - $image["thumb"] = System::baseUrl()."/photo/{$hash}-3.".$ph->getExt(); - } - } - - // Set the full image as preview image. This will be overwritten, if the picture is larger than 640. - $image["preview"] = $image["full"]; - - // Deactivated, since that would result in a cropped preview, if the picture wasn't larger than 320 - //if (isset($image["thumb"])) - // $image["preview"] = $image["thumb"]; - - // Unsure, if this should be activated or deactivated - //if (isset($image["small"])) - // $image["preview"] = $image["small"]; - - if (isset($image["medium"])) { - $image["preview"] = $image["medium"]; - } - - return($image); -} diff --git a/include/api.php b/include/api.php index e0dc413c2a..9f1ad44e5f 100644 --- a/include/api.php +++ b/include/api.php @@ -23,6 +23,7 @@ use Friendica\Network\HTTPException\NotImplementedException; use Friendica\Network\HTTPException\UnauthorizedException; use Friendica\Network\HTTPException\TooManyRequestsException; use Friendica\Object\Contact; +use Friendica\Object\Photo; use Friendica\Protocol\Diaspora; use Friendica\Util\XML; @@ -32,7 +33,6 @@ require_once 'include/conversation.php'; require_once 'include/oauth.php'; require_once 'include/html2plain.php'; require_once 'mod/share.php'; -require_once 'include/Photo.php'; require_once 'mod/item.php'; require_once 'include/security.php'; require_once 'include/contact_selectors.php'; diff --git a/include/follow.php b/include/follow.php index 7e8b25d797..54ea9981fa 100644 --- a/include/follow.php +++ b/include/follow.php @@ -8,13 +8,13 @@ use Friendica\Core\System; use Friendica\Core\Worker; use Friendica\Database\DBM; use Friendica\Network\Probe; +use Friendica\Object\Photo; use Friendica\Protocol\Diaspora; use Friendica\Protocol\OStatus; use Friendica\Protocol\PortableContact; require_once 'include/group.php'; require_once 'include/salmon.php'; -require_once 'include/Photo.php'; function update_contact($id) { /* diff --git a/include/items.php b/include/items.php index cffa127288..938925cb99 100644 --- a/include/items.php +++ b/include/items.php @@ -1,9 +1,7 @@ +/** + * @file mod/proxy.php + * @brief Based upon "Privacy Image Cache" by Tobias Hößl + */ use Friendica\App; use Friendica\Core\Config; use Friendica\Core\System; use Friendica\Database\DBM; +use Friendica\Object\Photo; define('PROXY_DEFAULT_TIME', 86400); // 1 Day @@ -15,7 +19,6 @@ define('PROXY_SIZE_MEDIUM', 'medium'); define('PROXY_SIZE_LARGE', 'large'); require_once 'include/security.php'; -require_once 'include/Photo.php'; function proxy_init(App $a) { // Pictures are stored in one of the following ways: diff --git a/mod/wall_upload.php b/mod/wall_upload.php index d6cb0c2e1a..5b5598a46e 100644 --- a/mod/wall_upload.php +++ b/mod/wall_upload.php @@ -13,8 +13,7 @@ use Friendica\App; use Friendica\Core\System; use Friendica\Core\Config; use Friendica\Database\DBM; - -require_once 'include/Photo.php'; +use Friendica\Object\Photo; function wall_upload_post(App $a, $desktopmode = true) { diff --git a/src/Model/GlobalContact.php b/src/Model/GlobalContact.php index be8b28260d..f0ad8faa1e 100644 --- a/src/Model/GlobalContact.php +++ b/src/Model/GlobalContact.php @@ -10,6 +10,7 @@ use Friendica\Core\System; use Friendica\Core\Worker; use Friendica\Database\DBM; use Friendica\Network\Probe; +use Friendica\Object\Photo; use Friendica\Object\Profile; use Friendica\Protocol\PortableContact; use dba; @@ -18,7 +19,6 @@ use Exception; require_once 'include/datetime.php'; require_once 'include/network.php'; require_once 'include/html2bbcode.php'; -require_once 'include/Photo.php'; /** * @brief This class handles GlobalContact related functions diff --git a/src/Object/Contact.php b/src/Object/Contact.php index d7e7853621..9f81b98c48 100644 --- a/src/Object/Contact.php +++ b/src/Object/Contact.php @@ -13,6 +13,7 @@ use Friendica\Core\System; use Friendica\Core\Worker; use Friendica\Database\DBM; use Friendica\Network\Probe; +use Friendica\Object\Photo; use Friendica\Protocol\Diaspora; use Friendica\Protocol\DFRN; use Friendica\Protocol\OStatus; @@ -643,8 +644,6 @@ class Contact extends BaseObject } } - require_once 'include/Photo.php'; - update_contact_avatar($data["photo"], $uid, $contact_id); $contact = dba::select('contact', array('url', 'nurl', 'addr', 'alias', 'name', 'nick', 'keywords', 'location', 'about', 'avatar-date'), array('id' => $contact_id), array('limit' => 1)); diff --git a/src/Object/Photo.php b/src/Object/Photo.php new file mode 100644 index 0000000000..b87a4b6e7f --- /dev/null +++ b/src/Object/Photo.php @@ -0,0 +1,1072 @@ + 'jpg', + 'image/png' => 'png', + 'image/gif' => 'gif' + ); + } else { + $t = array(); + $t['image/jpeg'] ='jpg'; + if (imagetypes() & IMG_PNG) { + $t['image/png'] = 'png'; + } + } + + return $t; + } + + public function __construct($data, $type=null) + { + $this->imagick = class_exists('Imagick'); + $this->types = static::supportedTypes(); + if (!array_key_exists($type, $this->types)){ + $type='image/jpeg'; + } + $this->type = $type; + + if ($this->is_imagick() && $this->load_data($data)) { + return true; + } else { + // Failed to load with Imagick, fallback + $this->imagick = false; + } + return $this->load_data($data); + } + + public function __destruct() { + if ($this->image) { + if ($this->is_imagick()) { + $this->image->clear(); + $this->image->destroy(); + return; + } + if (is_resource($this->image)) { + imagedestroy($this->image); + } + } + } + + public function is_imagick() + { + return $this->imagick; + } + + /** + * @brief Maps Mime types to Imagick formats + * @return arr With with image formats (mime type as key) + */ + public function get_FormatsMap() { + $m = array( + 'image/jpeg' => 'JPG', + 'image/png' => 'PNG', + 'image/gif' => 'GIF' + ); + return $m; + } + + private function load_data($data) { + if ($this->is_imagick()) { + $this->image = new Imagick(); + try { + $this->image->readImageBlob($data); + } catch (Exception $e) { + // Imagick couldn't use the data + return false; + } + + /* + * Setup the image to the format it will be saved to + */ + $map = $this->get_FormatsMap(); + $format = $map[$type]; + $this->image->setFormat($format); + + // Always coalesce, if it is not a multi-frame image it won't hurt anyway + $this->image = $this->image->coalesceImages(); + + /* + * setup the compression here, so we'll do it only once + */ + switch($this->getType()){ + case "image/png": + $quality = Config::get('system', 'png_quality'); + if ((! $quality) || ($quality > 9)) { + $quality = PNG_QUALITY; + } + /* + * From http://www.imagemagick.org/script/command-line-options.php#quality: + * + * 'For the MNG and PNG image formats, the quality value sets + * the zlib compression level (quality / 10) and filter-type (quality % 10). + * The default PNG "quality" is 75, which means compression level 7 with adaptive PNG filtering, + * unless the image has a color map, in which case it means compression level 7 with no PNG filtering' + */ + $quality = $quality * 10; + $this->image->setCompressionQuality($quality); + break; + case "image/jpeg": + $quality = Config::get('system', 'jpeg_quality'); + if ((! $quality) || ($quality > 100)) { + $quality = JPEG_QUALITY; + } + $this->image->setCompressionQuality($quality); + } + + // The 'width' and 'height' properties are only used by non-Imagick routines. + $this->width = $this->image->getImageWidth(); + $this->height = $this->image->getImageHeight(); + $this->valid = true; + + return true; + } + + $this->valid = false; + $this->image = @imagecreatefromstring($data); + if ($this->image !== false) { + $this->width = imagesx($this->image); + $this->height = imagesy($this->image); + $this->valid = true; + imagealphablending($this->image, false); + imagesavealpha($this->image, true); + + return true; + } + + return false; + } + + public function is_valid() { + if ($this->is_imagick()) { + return ($this->image !== false); + } + return $this->valid; + } + + public function getWidth() { + if (!$this->is_valid()) { + return false; + } + + if ($this->is_imagick()) { + return $this->image->getImageWidth(); + } + return $this->width; + } + + public function getHeight() { + if (!$this->is_valid()) { + return false; + } + + if ($this->is_imagick()) { + return $this->image->getImageHeight(); + } + return $this->height; + } + + public function getImage() { + if (!$this->is_valid()) { + return false; + } + + if ($this->is_imagick()) { + /* Clean it */ + $this->image = $this->image->deconstructImages(); + return $this->image; + } + return $this->image; + } + + public function getType() { + if (!$this->is_valid()) { + return false; + } + + return $this->type; + } + + public function getExt() { + if (!$this->is_valid()) { + return false; + } + + return $this->types[$this->getType()]; + } + + public function scaleImage($max) { + if (!$this->is_valid()) { + return false; + } + + $width = $this->getWidth(); + $height = $this->getHeight(); + + $dest_width = $dest_height = 0; + + if ((! $width)|| (! $height)) { + return false; + } + + if ($width > $max && $height > $max) { + + // very tall image (greater than 16:9) + // constrain the width - let the height float. + + if ((($height * 9) / 16) > $width) { + $dest_width = $max; + $dest_height = intval(($height * $max) / $width); + } elseif ($width > $height) { + // else constrain both dimensions + $dest_width = $max; + $dest_height = intval(($height * $max) / $width); + } else { + $dest_width = intval(($width * $max) / $height); + $dest_height = $max; + } + } else { + if ($width > $max) { + $dest_width = $max; + $dest_height = intval(($height * $max) / $width); + } else { + if ($height > $max) { + + // very tall image (greater than 16:9) + // but width is OK - don't do anything + + if ((($height * 9) / 16) > $width) { + $dest_width = $width; + $dest_height = $height; + } else { + $dest_width = intval(($width * $max) / $height); + $dest_height = $max; + } + } else { + $dest_width = $width; + $dest_height = $height; + } + } + } + + + if ($this->is_imagick()) { + /* + * If it is not animated, there will be only one iteration here, + * so don't bother checking + */ + // Don't forget to go back to the first frame + $this->image->setFirstIterator(); + do { + + // FIXME - implement horizantal bias for scaling as in followin GD functions + // to allow very tall images to be constrained only horizontally. + + $this->image->scaleImage($dest_width, $dest_height); + } while ($this->image->nextImage()); + + // These may not be necessary any more + $this->width = $this->image->getImageWidth(); + $this->height = $this->image->getImageHeight(); + + return; + } + + + $dest = imagecreatetruecolor($dest_width, $dest_height); + imagealphablending($dest, false); + imagesavealpha($dest, true); + if ($this->type=='image/png') { + imagefill($dest, 0, 0, imagecolorallocatealpha($dest, 0, 0, 0, 127)); // fill with alpha + } + imagecopyresampled($dest, $this->image, 0, 0, 0, 0, $dest_width, $dest_height, $width, $height); + if ($this->image) { + imagedestroy($this->image); + } + $this->image = $dest; + $this->width = imagesx($this->image); + $this->height = imagesy($this->image); + } + + public function rotate($degrees) { + if (!$this->is_valid()) { + return false; + } + + if ($this->is_imagick()) { + $this->image->setFirstIterator(); + do { + $this->image->rotateImage(new ImagickPixel(), -$degrees); // ImageMagick rotates in the opposite direction of imagerotate() + } while ($this->image->nextImage()); + return; + } + + // if script dies at this point check memory_limit setting in php.ini + $this->image = imagerotate($this->image,$degrees,0); + $this->width = imagesx($this->image); + $this->height = imagesy($this->image); + } + + public function flip($horiz = true, $vert = false) { + if (!$this->is_valid()) { + return false; + } + + if ($this->is_imagick()) { + $this->image->setFirstIterator(); + do { + if ($horiz) { + $this->image->flipImage(); + } + if ($vert) { + $this->image->flopImage(); + } + } while ($this->image->nextImage()); + return; + } + + $w = imagesx($this->image); + $h = imagesy($this->image); + $flipped = imagecreate($w, $h); + if ($horiz) { + for ($x = 0; $x < $w; $x++) { + imagecopy($flipped, $this->image, $x, 0, $w - $x - 1, 0, 1, $h); + } + } + if ($vert) { + for ($y = 0; $y < $h; $y++) { + imagecopy($flipped, $this->image, 0, $y, 0, $h - $y - 1, $w, 1); + } + } + $this->image = $flipped; + } + + public function orient($filename) { + if ($this->is_imagick()) { + // based off comment on http://php.net/manual/en/imagick.getimageorientation.php + $orientation = $this->image->getImageOrientation(); + switch ($orientation) { + case imagick::ORIENTATION_BOTTOMRIGHT: + $this->image->rotateimage("#000", 180); + break; + case imagick::ORIENTATION_RIGHTTOP: + $this->image->rotateimage("#000", 90); + break; + case imagick::ORIENTATION_LEFTBOTTOM: + $this->image->rotateimage("#000", -90); + break; + } + + $this->image->setImageOrientation(imagick::ORIENTATION_TOPLEFT); + return true; + } + // based off comment on http://php.net/manual/en/function.imagerotate.php + + if (!$this->is_valid()) { + return false; + } + + if ((!function_exists('exif_read_data')) || ($this->getType() !== 'image/jpeg')) { + return; + } + + $exif = @exif_read_data($filename,null,true); + if (!$exif) { + return; + } + + $ort = $exif['IFD0']['Orientation']; + + switch($ort) + { + case 1: // nothing + break; + + case 2: // horizontal flip + $this->flip(); + break; + + case 3: // 180 rotate left + $this->rotate(180); + break; + + case 4: // vertical flip + $this->flip(false, true); + break; + + case 5: // vertical flip + 90 rotate right + $this->flip(false, true); + $this->rotate(-90); + break; + + case 6: // 90 rotate right + $this->rotate(-90); + break; + + case 7: // horizontal flip + 90 rotate right + $this->flip(); + $this->rotate(-90); + break; + + case 8: // 90 rotate left + $this->rotate(90); + break; + } + + // logger('exif: ' . print_r($exif,true)); + return $exif; + + } + + + + public function scaleImageUp($min) { + if (!$this->is_valid()) { + return false; + } + + + $width = $this->getWidth(); + $height = $this->getHeight(); + + $dest_width = $dest_height = 0; + + if ((!$width)|| (!$height)) { + return false; + } + + if ($width < $min && $height < $min) { + if ($width > $height) { + $dest_width = $min; + $dest_height = intval(($height * $min) / $width); + } else { + $dest_width = intval(($width * $min) / $height); + $dest_height = $min; + } + } else { + if ($width < $min) { + $dest_width = $min; + $dest_height = intval(($height * $min) / $width); + } else { + if ($height < $min) { + $dest_width = intval(($width * $min) / $height); + $dest_height = $min; + } else { + $dest_width = $width; + $dest_height = $height; + } + } + } + + if ($this->is_imagick()) { + return $this->scaleImage($dest_width, $dest_height); + } + + $dest = imagecreatetruecolor($dest_width, $dest_height); + imagealphablending($dest, false); + imagesavealpha($dest, true); + if ($this->type=='image/png') { + imagefill($dest, 0, 0, imagecolorallocatealpha($dest, 0, 0, 0, 127)); // fill with alpha + } + imagecopyresampled($dest, $this->image, 0, 0, 0, 0, $dest_width, $dest_height, $width, $height); + if ($this->image) { + imagedestroy($this->image); + } + $this->image = $dest; + $this->width = imagesx($this->image); + $this->height = imagesy($this->image); + } + + + + public function scaleImageSquare($dim) { + if (!$this->is_valid()) { + return false; + } + + if ($this->is_imagick()) { + $this->image->setFirstIterator(); + do { + $this->image->scaleImage($dim, $dim); + } while ($this->image->nextImage()); + return; + } + + $dest = imagecreatetruecolor($dim, $dim); + imagealphablending($dest, false); + imagesavealpha($dest, true); + if ($this->type=='image/png') { + imagefill($dest, 0, 0, imagecolorallocatealpha($dest, 0, 0, 0, 127)); // fill with alpha + } + imagecopyresampled($dest, $this->image, 0, 0, 0, 0, $dim, $dim, $this->width, $this->height); + if ($this->image) { + imagedestroy($this->image); + } + $this->image = $dest; + $this->width = imagesx($this->image); + $this->height = imagesy($this->image); + } + + + public function cropImage($max, $x, $y, $w, $h) { + if (!$this->is_valid()) { + return false; + } + + if ($this->is_imagick()) { + $this->image->setFirstIterator(); + do { + $this->image->cropImage($w, $h, $x, $y); + /* + * We need to remove the canva, + * or the image is not resized to the crop: + * http://php.net/manual/en/imagick.cropimage.php#97232 + */ + $this->image->setImagePage(0, 0, 0, 0); + } while ($this->image->nextImage()); + return $this->scaleImage($max); + } + + $dest = imagecreatetruecolor($max, $max); + imagealphablending($dest, false); + imagesavealpha($dest, true); + if ($this->type=='image/png') { + imagefill($dest, 0, 0, imagecolorallocatealpha($dest, 0, 0, 0, 127)); // fill with alpha + } + imagecopyresampled($dest, $this->image, 0, 0, $x, $y, $max, $max, $w, $h); + if ($this->image) { + imagedestroy($this->image); + } + $this->image = $dest; + $this->width = imagesx($this->image); + $this->height = imagesy($this->image); + } + + public function saveImage($path) { + if (!$this->is_valid()) { + return false; + } + + $string = $this->imageString(); + + $a = get_app(); + + $stamp1 = microtime(true); + file_put_contents($path, $string); + $a->save_timestamp($stamp1, "file"); + } + + public function imageString() { + if (!$this->is_valid()) { + return false; + } + + if ($this->is_imagick()) { + /* Clean it */ + $this->image = $this->image->deconstructImages(); + $string = $this->image->getImagesBlob(); + return $string; + } + + $quality = false; + + ob_start(); + + // Enable interlacing + imageinterlace($this->image, true); + + switch($this->getType()){ + case "image/png": + $quality = Config::get('system', 'png_quality'); + if ((!$quality) || ($quality > 9)) { + $quality = PNG_QUALITY; + } + imagepng($this->image, null, $quality); + break; + case "image/jpeg": + $quality = Config::get('system', 'jpeg_quality'); + if ((!$quality) || ($quality > 100)) { + $quality = JPEG_QUALITY; + } + imagejpeg($this->image, null, $quality); + } + $string = ob_get_contents(); + ob_end_clean(); + + return $string; + } + + + + public function store($uid, $cid, $rid, $filename, $album, $scale, $profile = 0, $allow_cid = '', $allow_gid = '', $deny_cid = '', $deny_gid = '', $desc = '') { + + $r = dba::select('photo', array('guid'), array("`resource-id` = ? AND `guid` != ?", $rid, ''), array('limit' => 1)); + if (DBM::is_result($r)) { + $guid = $r['guid']; + } else { + $guid = get_guid(); + } + + $x = dba::select('photo', array('id'), array('resource-id' => $rid, 'uid' => $uid, 'contact-id' => $cid, 'scale' => $scale), array('limit' => 1)); + + $fields = array('uid' => $uid, 'contact-id' => $cid, 'guid' => $guid, 'resource-id' => $rid, 'created' => datetime_convert(), 'edited' => datetime_convert(), + 'filename' => basename($filename), 'type' => $this->getType(), 'album' => $album, 'height' => $this->getHeight(), 'width' => $this->getWidth(), + 'datasize' => strlen($this->imageString()), 'data' => $this->imageString(), 'scale' => $scale, 'profile' => $profile, + 'allow_cid' => $allow_cid, 'allow_gid' => $allow_gid, 'deny_cid' => $deny_cid, 'deny_gid' => $deny_gid, 'desc' => $desc); + + if (DBM::is_result($x)) { + $r = dba::update('photo', $fields, array('id' => $x['id'])); + } else { + $r = dba::insert('photo', $fields); + } + + return $r; + } +} + + +/** + * Guess image mimetype from filename or from Content-Type header + * + * @arg $filename string Image filename + * @arg $fromcurl boolean Check Content-Type header from curl request + */ +function guess_image_type($filename, $fromcurl=false) { + logger('Photo: guess_image_type: '.$filename . ($fromcurl?' from curl headers':''), LOGGER_DEBUG); + $type = null; + if ($fromcurl) { + $a = get_app(); + $headers=array(); + $h = explode("\n",$a->get_curl_headers()); + foreach ($h as $l) { + list($k,$v) = array_map("trim", explode(":", trim($l), 2)); + $headers[$k] = $v; + } + if (array_key_exists('Content-Type', $headers)) + $type = $headers['Content-Type']; + } + if (is_null($type)){ + // Guessing from extension? Isn't that... dangerous? + if (class_exists('Imagick') && file_exists($filename) && is_readable($filename)) { + /** + * Well, this not much better, + * but at least it comes from the data inside the image, + * we won't be tricked by a manipulated extension + */ + $image = new Imagick($filename); + $type = $image->getImageMimeType(); + $image->setInterlaceScheme(Imagick::INTERLACE_PLANE); + } else { + $ext = pathinfo($filename, PATHINFO_EXTENSION); + $types = Photo::supportedTypes(); + $type = "image/jpeg"; + foreach ($types as $m => $e){ + if ($ext == $e) { + $type = $m; + } + } + } + } + logger('Photo: guess_image_type: type='.$type, LOGGER_DEBUG); + return $type; + +} + +/** + * @brief Updates the avatar links in a contact only if needed + * + * @param string $avatar Link to avatar picture + * @param int $uid User id of contact owner + * @param int $cid Contact id + * @param bool $force force picture update + * + * @return array Returns array of the different avatar sizes + */ +function update_contact_avatar($avatar, $uid, $cid, $force = false) { + $r = q("SELECT `avatar`, `photo`, `thumb`, `micro`, `nurl` FROM `contact` WHERE `id` = %d LIMIT 1", intval($cid)); + if (!DBM::is_result($r)) { + return false; + } else { + $data = array($r[0]["photo"], $r[0]["thumb"], $r[0]["micro"]); + } + + if (($r[0]["avatar"] != $avatar) || $force) { + $photos = import_profile_photo($avatar, $uid, $cid, true); + + if ($photos) { + q("UPDATE `contact` SET `avatar` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s', `avatar-date` = '%s' WHERE `id` = %d", + dbesc($avatar), dbesc($photos[0]), dbesc($photos[1]), dbesc($photos[2]), + dbesc(datetime_convert()), intval($cid)); + + // Update the public contact (contact id = 0) + if ($uid != 0) { + $pcontact = dba::select('contact', array('id'), array('nurl' => $r[0]['nurl']), array('limit' => 1)); + if (DBM::is_result($pcontact)) { + update_contact_avatar($avatar, 0, $pcontact['id'], $force); + } + } + + return $photos; + } + } + + return $data; +} + +function import_profile_photo($photo, $uid, $cid, $quit_on_error = false) { + + $r = q("SELECT `resource-id` FROM `photo` WHERE `uid` = %d AND `contact-id` = %d AND `scale` = 4 AND `album` = 'Contact Photos' LIMIT 1", + intval($uid), + intval($cid) + ); + if (DBM::is_result($r) && strlen($r[0]['resource-id'])) { + $hash = $r[0]['resource-id']; + } else { + $hash = photo_new_resource(); + } + + $photo_failure = false; + + $filename = basename($photo); + $img_str = fetch_url($photo, true); + + if ($quit_on_error && ($img_str == "")) { + return false; + } + + $type = guess_image_type($photo, true); + $img = new Photo($img_str, $type); + if ($img->is_valid()) { + + $img->scaleImageSquare(175); + + $r = $img->store($uid, $cid, $hash, $filename, 'Contact Photos', 4); + + if ($r === false) + $photo_failure = true; + + $img->scaleImage(80); + + $r = $img->store($uid, $cid, $hash, $filename, 'Contact Photos', 5); + + if ($r === false) + $photo_failure = true; + + $img->scaleImage(48); + + $r = $img->store($uid, $cid, $hash, $filename, 'Contact Photos', 6); + + if ($r === false) { + $photo_failure = true; + } + + $suffix = '?ts='.time(); + + $photo = System::baseUrl() . '/photo/' . $hash . '-4.' . $img->getExt() . $suffix; + $thumb = System::baseUrl() . '/photo/' . $hash . '-5.' . $img->getExt() . $suffix; + $micro = System::baseUrl() . '/photo/' . $hash . '-6.' . $img->getExt() . $suffix; + + // Remove the cached photo + $a = get_app(); + $basepath = $a->get_basepath(); + + if (is_dir($basepath."/photo")) { + $filename = $basepath.'/photo/'.$hash.'-4.'.$img->getExt(); + if (file_exists($filename)) { + unlink($filename); + } + $filename = $basepath.'/photo/'.$hash.'-5.'.$img->getExt(); + if (file_exists($filename)) { + unlink($filename); + } + $filename = $basepath.'/photo/'.$hash.'-6.'.$img->getExt(); + if (file_exists($filename)) { + unlink($filename); + } + } + } else { + $photo_failure = true; + } + + if ($photo_failure && $quit_on_error) { + return false; + } + + if ($photo_failure) { + $photo = System::baseUrl() . '/images/person-175.jpg'; + $thumb = System::baseUrl() . '/images/person-80.jpg'; + $micro = System::baseUrl() . '/images/person-48.jpg'; + } + + return(array($photo,$thumb,$micro)); + +} + +function get_photo_info($url) { + $data = array(); + + $data = Cache::get($url); + + if (is_null($data) || !$data || !is_array($data)) { + $img_str = fetch_url($url, true, $redirects, 4); + $filesize = strlen($img_str); + + if (function_exists("getimagesizefromstring")) { + $data = getimagesizefromstring($img_str); + } else { + $tempfile = tempnam(get_temppath(), "cache"); + + $a = get_app(); + $stamp1 = microtime(true); + file_put_contents($tempfile, $img_str); + $a->save_timestamp($stamp1, "file"); + + $data = getimagesize($tempfile); + unlink($tempfile); + } + + if ($data) { + $data["size"] = $filesize; + } + + Cache::set($url, $data); + } + + return $data; +} + +function scale_image($width, $height, $max) { + + $dest_width = $dest_height = 0; + + if ((!$width) || (!$height)) { + return false; + } + + if ($width > $max && $height > $max) { + + // very tall image (greater than 16:9) + // constrain the width - let the height float. + + if ((($height * 9) / 16) > $width) { + $dest_width = $max; + $dest_height = intval(($height * $max) / $width); + } elseif ($width > $height) { + // else constrain both dimensions + $dest_width = $max; + $dest_height = intval(($height * $max) / $width); + } else { + $dest_width = intval(($width * $max) / $height); + $dest_height = $max; + } + } else { + if ($width > $max) { + $dest_width = $max; + $dest_height = intval(($height * $max) / $width); + } else { + if ($height > $max) { + + // very tall image (greater than 16:9) + // but width is OK - don't do anything + + if ((($height * 9) / 16) > $width) { + $dest_width = $width; + $dest_height = $height; + } else { + $dest_width = intval(($width * $max) / $height); + $dest_height = $max; + } + } else { + $dest_width = $width; + $dest_height = $height; + } + } + } + return array("width" => $dest_width, "height" => $dest_height); +} + +function store_photo(App $a, $uid, $imagedata = "", $url = "") { + $r = q("SELECT `user`.`nickname`, `user`.`page-flags`, `contact`.`id` FROM `user` INNER JOIN `contact` on `user`.`uid` = `contact`.`uid` + WHERE `user`.`uid` = %d AND `user`.`blocked` = 0 AND `contact`.`self` = 1 LIMIT 1", + intval($uid)); + + if (!DBM::is_result($r)) { + logger("Can't detect user data for uid ".$uid, LOGGER_DEBUG); + return(array()); + } + + $page_owner_nick = $r[0]['nickname']; + + /// @TODO + /// $default_cid = $r[0]['id']; + /// $community_page = (($r[0]['page-flags'] == PAGE_COMMUNITY) ? true : false); + + if ((strlen($imagedata) == 0) && ($url == "")) { + logger("No image data and no url provided", LOGGER_DEBUG); + return(array()); + } elseif (strlen($imagedata) == 0) { + logger("Uploading picture from ".$url, LOGGER_DEBUG); + + $stamp1 = microtime(true); + $imagedata = @file_get_contents($url); + $a->save_timestamp($stamp1, "file"); + } + + $maximagesize = Config::get('system', 'maximagesize'); + + if (($maximagesize) && (strlen($imagedata) > $maximagesize)) { + logger("Image exceeds size limit of ".$maximagesize, LOGGER_DEBUG); + return(array()); + } + + $tempfile = tempnam(get_temppath(), "cache"); + + $stamp1 = microtime(true); + file_put_contents($tempfile, $imagedata); + $a->save_timestamp($stamp1, "file"); + + $data = getimagesize($tempfile); + + if (!isset($data["mime"])) { + unlink($tempfile); + logger("File is no picture", LOGGER_DEBUG); + return(array()); + } + + $ph = new Photo($imagedata, $data["mime"]); + + if (!$ph->is_valid()) { + unlink($tempfile); + logger("Picture is no valid picture", LOGGER_DEBUG); + return(array()); + } + + $ph->orient($tempfile); + unlink($tempfile); + + $max_length = Config::get('system', 'max_image_length'); + if (! $max_length) { + $max_length = MAX_IMAGE_LENGTH; + } + if ($max_length > 0) { + $ph->scaleImage($max_length); + } + + $width = $ph->getWidth(); + $height = $ph->getHeight(); + + $hash = photo_new_resource(); + + $smallest = 0; + + // Pictures are always public by now + //$defperm = '<'.$default_cid.'>'; + $defperm = ""; + $visitor = 0; + + $r = $ph->store($uid, $visitor, $hash, $tempfile, t('Wall Photos'), 0, 0, $defperm); + + if (!$r) { + logger("Picture couldn't be stored", LOGGER_DEBUG); + return(array()); + } + + $image = array("page" => System::baseUrl().'/photos/'.$page_owner_nick.'/image/'.$hash, + "full" => System::baseUrl()."/photo/{$hash}-0.".$ph->getExt()); + + if ($width > 800 || $height > 800) { + $image["large"] = System::baseUrl()."/photo/{$hash}-0.".$ph->getExt(); + } + + if ($width > 640 || $height > 640) { + $ph->scaleImage(640); + $r = $ph->store($uid, $visitor, $hash, $tempfile, t('Wall Photos'), 1, 0, $defperm); + if ($r) { + $image["medium"] = System::baseUrl()."/photo/{$hash}-1.".$ph->getExt(); + } + } + + if ($width > 320 || $height > 320) { + $ph->scaleImage(320); + $r = $ph->store($uid, $visitor, $hash, $tempfile, t('Wall Photos'), 2, 0, $defperm); + if ($r) { + $image["small"] = System::baseUrl()."/photo/{$hash}-2.".$ph->getExt(); + } + } + + if ($width > 160 && $height > 160) { + $x = 0; + $y = 0; + + $min = $ph->getWidth(); + if ($min > 160) { + $x = ($min - 160) / 2; + } + + if ($ph->getHeight() < $min) { + $min = $ph->getHeight(); + if ($min > 160) { + $y = ($min - 160) / 2; + } + } + + $min = 160; + $ph->cropImage(160, $x, $y, $min, $min); + + $r = $ph->store($uid, $visitor, $hash, $tempfile, t('Wall Photos'), 3, 0, $defperm); + if ($r) { + $image["thumb"] = System::baseUrl()."/photo/{$hash}-3.".$ph->getExt(); + } + } + + // Set the full image as preview image. This will be overwritten, if the picture is larger than 640. + $image["preview"] = $image["full"]; + + // Deactivated, since that would result in a cropped preview, if the picture wasn't larger than 320 + //if (isset($image["thumb"])) + // $image["preview"] = $image["thumb"]; + + // Unsure, if this should be activated or deactivated + //if (isset($image["small"])) + // $image["preview"] = $image["small"]; + + if (isset($image["medium"])) { + $image["preview"] = $image["medium"]; + } + + return($image); +} diff --git a/src/ParseUrl.php b/src/ParseUrl.php index 0183fdb3f5..c5c23d7d9c 100644 --- a/src/ParseUrl.php +++ b/src/ParseUrl.php @@ -6,6 +6,7 @@ namespace Friendica; use Friendica\Core\Config; +use Friendica\Object\Photo; use Friendica\Util\XML; use dba; @@ -13,7 +14,6 @@ use DomXPath; use DOMDocument; require_once "include/network.php"; -require_once "include/Photo.php"; require_once "include/oembed.php"; /** diff --git a/src/Protocol/Diaspora.php b/src/Protocol/Diaspora.php index 4d91f8b1c0..2ffaac642a 100644 --- a/src/Protocol/Diaspora.php +++ b/src/Protocol/Diaspora.php @@ -19,6 +19,7 @@ use Friendica\Database\DBM; use Friendica\Model\GlobalContact; use Friendica\Network\Probe; use Friendica\Object\Contact; +use Friendica\Object\Photo; use Friendica\Object\Profile; use Friendica\Util\XML; @@ -27,7 +28,6 @@ use SimpleXMLElement; require_once 'include/items.php'; require_once 'include/bb2diaspora.php'; -require_once 'include/Photo.php'; require_once 'include/group.php'; require_once 'include/datetime.php'; require_once 'include/queue_fn.php'; diff --git a/src/Protocol/OStatus.php b/src/Protocol/OStatus.php index ed762084c2..206d188f5c 100644 --- a/src/Protocol/OStatus.php +++ b/src/Protocol/OStatus.php @@ -12,6 +12,7 @@ use Friendica\Database\DBM; use Friendica\Model\GlobalContact; use Friendica\Network\Probe; use Friendica\Object\Contact; +use Friendica\Object\Photo; use Friendica\Util\Lock; use Friendica\Util\XML; use dba; @@ -24,7 +25,6 @@ require_once 'include/bbcode.php'; require_once 'include/items.php'; require_once 'mod/share.php'; require_once 'include/enotify.php'; -require_once 'include/Photo.php'; require_once 'include/follow.php'; require_once 'include/api.php'; require_once 'mod/proxy.php'; diff --git a/src/Protocol/PortableContact.php b/src/Protocol/PortableContact.php index 59b3ad4325..feb4dfdc8d 100644 --- a/src/Protocol/PortableContact.php +++ b/src/Protocol/PortableContact.php @@ -14,6 +14,7 @@ use Friendica\Core\Worker; use Friendica\Database\DBM; use Friendica\Model\GlobalContact; use Friendica\Network\Probe; +use Friendica\Object\Photo; use Friendica\Object\Profile; use dba; use DOMDocument; @@ -23,7 +24,6 @@ use Exception; require_once 'include/datetime.php'; require_once 'include/network.php'; require_once 'include/html2bbcode.php'; -require_once 'include/Photo.php'; class PortableContact { diff --git a/update.php b/update.php index 21074c1d87..f228bf7351 100644 --- a/update.php +++ b/update.php @@ -6,6 +6,7 @@ use Friendica\Core\Config; use Friendica\Core\PConfig; use Friendica\Core\Worker; use Friendica\Database\DBM; +use Friendica\Object\Photo; /** * @@ -146,8 +147,8 @@ function update_1013() { AFTER `object` , ADD `target` TEXT NOT NULL AFTER `target-type`"); } -function update_1014() { - require_once('include/Photo.php'); +function update_1014() +{ q("ALTER TABLE `contact` ADD `micro` TEXT NOT NULL AFTER `thumb` "); $r = q("SELECT * FROM `photo` WHERE `scale` = 4"); if (DBM::is_result($r)) { diff --git a/view/theme/frio/theme.php b/view/theme/frio/theme.php index 35e40aa965..0a6b48e4eb 100644 --- a/view/theme/frio/theme.php +++ b/view/theme/frio/theme.php @@ -12,6 +12,7 @@ use Friendica\Core\Config; use Friendica\Core\PConfig; use Friendica\Core\System; use Friendica\Database\DBM; +use Friendica\Object\Photo; $frio = "view/theme/frio"; @@ -79,9 +80,8 @@ function frio_uninstall() { * @param App $a Unused but required by hook definition * @param array $body_info The item and its html output */ -function frio_item_photo_links(App $a, &$body_info) { - require_once('include/Photo.php'); - +function frio_item_photo_links(App $a, &$body_info) +{ $phototypes = Photo::supportedTypes(); $occurence = 1; $p = bb_find_open_close($body_info['html'], ""); diff --git a/view/theme/frost/theme.php b/view/theme/frost/theme.php index 5023aeb9ba..5d5162cd32 100644 --- a/view/theme/frost/theme.php +++ b/view/theme/frost/theme.php @@ -11,6 +11,7 @@ use Friendica\App; use Friendica\Core\System; +use Friendica\Object\Photo; function frost_init(App $a) { $a->videowidth = 400; @@ -45,8 +46,8 @@ function frost_uninstall() { logger("uninstalled theme frost"); } -function frost_item_photo_links(App $a, &$body_info) { - require_once('include/Photo.php'); +function frost_item_photo_links(App $a, &$body_info) +{ $phototypes = Photo::supportedTypes(); $occurence = 1;