X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=src%2FModel%2FPhoto.php;h=a38743d9d6adbfa5b3d10f13faedabd7b40db9a5;hb=eb035771f118e1f289f2ebbbb8a474aa2765c81e;hp=a10711db2a1c58c044f8e089214828dda00d876d;hpb=2973ed6448f56dd807df3ec0d20d095226d14b65;p=friendica.git diff --git a/src/Model/Photo.php b/src/Model/Photo.php index a10711db2a..a38743d9d6 100644 --- a/src/Model/Photo.php +++ b/src/Model/Photo.php @@ -1,6 +1,6 @@ getByName($photo['backend-class'] ?? ''); - if ($backendClass === null) { + if (empty($backendClass)) { // legacy data storage in "data" column $i = self::selectFirst(['data'], ['id' => $photo['id']]); if ($i === false) { @@ -195,9 +206,28 @@ class Photo $data = $i['data']; } else { $backendRef = $photo['backend-ref'] ?? ''; - $data = $backendClass->get($backendRef); + try { + $data = $backendClass->get($backendRef); + } catch (ReferenceStorageException $referenceStorageException) { + DI::logger()->debug('No data found for photo', ['photo' => $photo, 'exception' => $referenceStorageException]); + return null; + } } + return $data; + } + /** + * Get Image object for given row id. null if row id does not exist + * + * @param array $photo Photo data. Needs at least 'id', 'type', 'backend-class', 'backend-ref' + * + * @return \Friendica\Object\Image + * @throws \Friendica\Network\HTTPException\InternalServerErrorException + * @throws \ImagickException + */ + public static function getImageForPhoto(array $photo) + { + $data = self::getImageDataForPhoto($photo); if (empty($data)) { return null; } @@ -223,13 +253,17 @@ class Photo * Construct a photo array for a system resource image * * @param string $filename Image file name relative to code root - * @param string $mimetype Image mime type. Defaults to "image/jpeg" + * @param string $mimetype Image mime type. Is guessed by file name when empty. * * @return array * @throws \Exception */ - public static function createPhotoForSystemResource($filename, $mimetype = "image/jpeg") + public static function createPhotoForSystemResource($filename, $mimetype = '') { + if (empty($mimetype)) { + $mimetype = Images::guessTypeByExtension($filename); + } + $fields = self::getFields(); $values = array_fill(0, count($fields), ""); @@ -242,6 +276,33 @@ class Photo return $photo; } + /** + * Construct a photo array for an external resource image + * + * @param string $url Image URL + * @param int $uid User ID of the requesting person + * @param string $mimetype Image mime type. Is guessed by file name when empty. + * + * @return array + * @throws \Exception + */ + public static function createPhotoForExternalResource($url, $uid = 0, $mimetype = '') + { + if (empty($mimetype)) { + $mimetype = Images::guessTypeByExtension($url); + } + + $fields = self::getFields(); + $values = array_fill(0, count($fields), ""); + + $photo = array_combine($fields, $values); + $photo['backend-class'] = ExternalResource::NAME; + $photo['backend-ref'] = json_encode(['url' => $url, 'uid' => $uid]); + $photo['type'] = $mimetype; + $photo['cacheable'] = true; + + return $photo; + } /** * store photo metadata in db and binary in default backend @@ -286,12 +347,12 @@ class Photo if (DBA::isResult($existing_photo)) { $backend_ref = (string)$existing_photo["backend-ref"]; - $storage = DI::storageManager()->getByName($existing_photo["backend-class"] ?? ''); + $storage = DI::storageManager()->getSelectableStorageByName($existing_photo["backend-class"] ?? ''); } else { $storage = DI::storage(); } - if ($storage === null) { + if (empty($storage)) { $data = $Image->asString(); } else { $backend_ref = $storage->put($Image->asString(), $backend_ref); @@ -302,6 +363,7 @@ class Photo "contact-id" => $cid, "guid" => $guid, "resource-id" => $rid, + "hash" => md5($Image->asString()), "created" => $created, "edited" => DateTimeFormat::utcNow(), "filename" => basename($filename), @@ -346,15 +408,23 @@ class Photo public static function delete(array $conditions, array $options = []) { // get photo to delete data info - $photos = self::selectToArray(['backend-class', 'backend-ref'], $conditions); - - foreach($photos as $photo) { - $backend_class = DI::storageManager()->getByName($photo['backend-class'] ?? ''); - if ($backend_class !== null) { - $backend_class->delete($photo["backend-ref"] ?? ''); + $photos = DBA::select('photo', ['id', 'backend-class', 'backend-ref'], $conditions); + + while ($photo = DBA::fetch($photos)) { + $backend_class = DI::storageManager()->getSelectableStorageByName($photo['backend-class'] ?? ''); + if (!empty($backend_class)) { + try { + $backend_class->delete($item['backend-ref'] ?? ''); + // Delete the photos after they had been deleted successfully + DBA::delete("photo", ['id' => $photo['id']]); + } catch (ReferenceStorageException $referenceStorageException) { + DI::logger()->debug('phot doesn\'t exist.', ['conditions' => $conditions, 'exception' => $referenceStorageException]); + } } } + DBA::close($photos); + return DBA::delete("photo", $conditions, $options); } @@ -378,8 +448,8 @@ class Photo $photos = self::selectToArray(['backend-class', 'backend-ref'], $conditions); foreach($photos as $photo) { - $backend_class = DI::storageManager()->getByName($photo['backend-class'] ?? ''); - if ($backend_class !== null) { + $backend_class = DI::storageManager()->getSelectableStorageByName($photo['backend-class'] ?? ''); + if (!empty($backend_class)) { $fields["backend-ref"] = $backend_class->put($img->asString(), $photo['backend-ref']); } else { $fields["data"] = $img->asString(); @@ -408,7 +478,7 @@ class Photo $micro = ""; $photo = DBA::selectFirst( - "photo", ["resource-id"], ["uid" => $uid, "contact-id" => $cid, "scale" => 4, "album" => "Contact Photos"] + "photo", ["resource-id"], ["uid" => $uid, "contact-id" => $cid, "scale" => 4, "album" => self::CONTACT_PHOTOS] ); if (!empty($photo['resource-id'])) { $resource_id = $photo["resource-id"]; @@ -420,7 +490,7 @@ class Photo $filename = basename($image_url); if (!empty($image_url)) { - $ret = DI::httpRequest()->curl($image_url, true); + $ret = DI::httpRequest()->get($image_url); $img_str = $ret->getBody(); $type = $ret->getContentType(); } else { @@ -437,7 +507,30 @@ class Photo if ($Image->isValid()) { $Image->scaleToSquare(300); - $r = self::store($Image, $uid, $cid, $resource_id, $filename, "Contact Photos", 4); + $filesize = strlen($Image->asString()); + $maximagesize = DI::config()->get('system', 'maximagesize'); + if (!empty($maximagesize) && ($filesize > $maximagesize)) { + Logger::info('Avatar exceeds image limit', ['uid' => $uid, 'cid' => $cid, 'maximagesize' => $maximagesize, 'size' => $filesize, 'type' => $Image->getType()]); + if ($Image->getType() == 'image/gif') { + $Image->toStatic(); + $Image = new Image($Image->asString(), 'image/png'); + + $filesize = strlen($Image->asString()); + Logger::info('Converted gif to a static png', ['uid' => $uid, 'cid' => $cid, 'size' => $filesize, 'type' => $Image->getType()]); + } + if ($filesize > $maximagesize) { + foreach ([160, 80] as $pixels) { + if ($filesize > $maximagesize) { + Logger::info('Resize', ['uid' => $uid, 'cid' => $cid, 'size' => $filesize, 'max' => $maximagesize, 'pixels' => $pixels, 'type' => $Image->getType()]); + $Image->scaleDown($pixels); + $filesize = strlen($Image->asString()); + } + } + } + Logger::info('Avatar is resized', ['uid' => $uid, 'cid' => $cid, 'size' => $filesize, 'type' => $Image->getType()]); + } + + $r = self::store($Image, $uid, $cid, $resource_id, $filename, self::CONTACT_PHOTOS, 4); if ($r === false) { $photo_failure = true; @@ -445,7 +538,7 @@ class Photo $Image->scaleDown(80); - $r = self::store($Image, $uid, $cid, $resource_id, $filename, "Contact Photos", 5); + $r = self::store($Image, $uid, $cid, $resource_id, $filename, self::CONTACT_PHOTOS, 5); if ($r === false) { $photo_failure = true; @@ -453,7 +546,7 @@ class Photo $Image->scaleDown(48); - $r = self::store($Image, $uid, $cid, $resource_id, $filename, "Contact Photos", 6); + $r = self::store($Image, $uid, $cid, $resource_id, $filename, self::CONTACT_PHOTOS, 6); if ($r === false) { $photo_failure = true; @@ -492,9 +585,10 @@ class Photo } if ($photo_failure) { - $image_url = DI::baseUrl() . "/images/person-300.jpg"; - $thumb = DI::baseUrl() . "/images/person-80.jpg"; - $micro = DI::baseUrl() . "/images/person-48.jpg"; + $contact = Contact::getById($cid) ?: []; + $image_url = Contact::getDefaultAvatar($contact, Proxy::SIZE_SMALL); + $thumb = Contact::getDefaultAvatar($contact, Proxy::SIZE_THUMB); + $micro = Contact::getDefaultAvatar($contact, Proxy::SIZE_MICRO); } return [$image_url, $thumb, $micro]; @@ -561,8 +655,8 @@ class Photo WHERE `uid` = %d AND `album` != '%s' AND `album` != '%s' $sql_extra GROUP BY `album` ORDER BY `created` DESC", intval($uid), - DBA::escape("Contact Photos"), - DBA::escape(DI::l10n()->t("Contact Photos")) + DBA::escape(self::CONTACT_PHOTOS), + DBA::escape(DI::l10n()->t(self::CONTACT_PHOTOS)) ); } else { // This query doesn't do the count and is much faster @@ -570,8 +664,8 @@ class Photo FROM `photo` USE INDEX (`uid_album_scale_created`) WHERE `uid` = %d AND `album` != '%s' AND `album` != '%s' $sql_extra", intval($uid), - DBA::escape("Contact Photos"), - DBA::escape(DI::l10n()->t("Contact Photos")) + DBA::escape(self::CONTACT_PHOTOS), + DBA::escape(DI::l10n()->t(self::CONTACT_PHOTOS)) ); } DI::cache()->set($key, $albums, Duration::DAY); @@ -675,18 +769,35 @@ class Photo * Then set the permissions to public. */ - $fields = ['allow_cid' => $str_contact_allow, 'allow_gid' => $str_group_allow, - 'deny_cid' => $str_contact_deny, 'deny_gid' => $str_group_deny, - 'accessible' => DI::pConfig()->get($uid, 'system', 'accessible-photos', false)]; - - $condition = ['resource-id' => $image_rid, 'uid' => $uid]; - Logger::info('Set permissions', ['condition' => $condition, 'permissions' => $fields]); - Photo::update($fields, $condition); + self::setPermissionForRessource($image_rid, $uid, $str_contact_allow, $str_group_allow, $str_contact_deny, $str_group_deny); } return true; } + /** + * Add permissions to photo ressource + * @todo mix with previous photo permissions + * + * @param string $image_rid + * @param integer $uid + * @param string $str_contact_allow + * @param string $str_group_allow + * @param string $str_contact_deny + * @param string $str_group_deny + * @return void + */ + public static function setPermissionForRessource(string $image_rid, int $uid, string $str_contact_allow, string $str_group_allow, string $str_contact_deny, string $str_group_deny) + { + $fields = ['allow_cid' => $str_contact_allow, 'allow_gid' => $str_group_allow, + 'deny_cid' => $str_contact_deny, 'deny_gid' => $str_group_deny, + 'accessible' => DI::pConfig()->get($uid, 'system', 'accessible-photos', false)]; + + $condition = ['resource-id' => $image_rid, 'uid' => $uid]; + Logger::info('Set permissions', ['condition' => $condition, 'permissions' => $fields]); + Photo::update($fields, $condition); + } + /** * Strips known picture extensions from picture links * @@ -704,30 +815,33 @@ class Photo } /** - * Returns the GUID from picture links + * Fetch the guid and scale from picture links * * @param string $name Picture link - * @return string GUID - * @throws \Exception + * @return array */ - public static function getGUID($name) + public static function getResourceData(string $name):array { $base = DI::baseUrl()->get(); $guid = str_replace([Strings::normaliseLink($base), '/photo/'], '', Strings::normaliseLink($name)); + if (parse_url($guid, PHP_URL_SCHEME)) { + return []; + } + $guid = self::stripExtension($guid); if (substr($guid, -2, 1) != "-") { - return ''; + return []; } $scale = intval(substr($guid, -1, 1)); if (!is_numeric($scale)) { - return ''; + return []; } $guid = substr($guid, 0, -2); - return $guid; + return ['guid' => $guid, 'scale' => $scale]; } /** @@ -739,13 +853,27 @@ class Photo */ public static function isLocal($name) { - $guid = self::getGUID($name); + return (bool)self::getIdForName($name); + } - if (empty($guid)) { - return false; + /** + * Return the id of a local photo + * + * @param string $name Picture link + * @return int + */ + public static function getIdForName($name) + { + $data = self::getResourceData($name); + if (empty($data)) { + return 0; } - return DBA::exists('photo', ['resource-id' => $guid]); + $photo = DBA::selectFirst('photo', ['id'], ['resource-id' => $data['guid'], 'scale' => $data['scale']]); + if (!empty($photo['id'])) { + return $photo['id']; + } + return 0; } /** @@ -767,4 +895,167 @@ class Photo return DBA::exists('photo', ['resource-id' => $guid]); } + + /** + * + * @param int $uid User ID + * @param array $files uploaded file array + * @return array photo record + */ + public static function upload(int $uid, array $files) + { + Logger::info('starting new upload'); + + $user = User::getOwnerDataById($uid); + if (empty($user)) { + Logger::notice('User not found', ['uid' => $uid]); + return []; + } + + if (empty($files)) { + Logger::notice('Empty upload file'); + return []; + } + + if (!empty($files['tmp_name'])) { + if (is_array($files['tmp_name'])) { + $src = $files['tmp_name'][0]; + } else { + $src = $files['tmp_name']; + } + } else { + $src = ''; + } + + if (!empty($files['name'])) { + if (is_array($files['name'])) { + $filename = basename($files['name'][0]); + } else { + $filename = basename($files['name']); + } + } else { + $filename = ''; + } + + if (!empty($files['size'])) { + if (is_array($files['size'])) { + $filesize = intval($files['size'][0]); + } else { + $filesize = intval($files['size']); + } + } else { + $filesize = 0; + } + + if (!empty($files['type'])) { + if (is_array($files['type'])) { + $filetype = $files['type'][0]; + } else { + $filetype = $files['type']; + } + } else { + $filetype = ''; + } + + if (empty($src)) { + Logger::notice('No source file name', ['uid' => $uid, 'files' => $files]); + return []; + } + + $filetype = Images::getMimeTypeBySource($src, $filename, $filetype); + + Logger::info('File upload', ['src' => $src, 'filename' => $filename, 'size' => $filesize, 'type' => $filetype]); + + $imagedata = @file_get_contents($src); + $Image = new Image($imagedata, $filetype); + if (!$Image->isValid()) { + Logger::notice('Image is unvalid', ['uid' => $uid, 'files' => $files]); + return []; + } + + $Image->orient($src); + @unlink($src); + + $max_length = DI::config()->get('system', 'max_image_length'); + if (!$max_length) { + $max_length = MAX_IMAGE_LENGTH; + } + if ($max_length > 0) { + $Image->scaleDown($max_length); + $filesize = strlen($Image->asString()); + Logger::info('File upload: Scaling picture to new size', ['max-length' => $max_length]); + } + + $width = $Image->getWidth(); + $height = $Image->getHeight(); + + $maximagesize = DI::config()->get('system', 'maximagesize'); + + if (!empty($maximagesize) && ($filesize > $maximagesize)) { + // Scale down to multiples of 640 until the maximum size isn't exceeded anymore + foreach ([5120, 2560, 1280, 640] as $pixels) { + if (($filesize > $maximagesize) && (max($width, $height) > $pixels)) { + Logger::info('Resize', ['size' => $filesize, 'width' => $width, 'height' => $height, 'max' => $maximagesize, 'pixels' => $pixels]); + $Image->scaleDown($pixels); + $filesize = strlen($Image->asString()); + $width = $Image->getWidth(); + $height = $Image->getHeight(); + } + } + if ($filesize > $maximagesize) { + @unlink($src); + Logger::notice('Image size is too big', ['size' => $filesize, 'max' => $maximagesize]); + return []; + } + } + + $resource_id = Photo::newResource(); + $album = DI::l10n()->t('Wall Photos'); + $defperm = '<' . $user['id'] . '>'; + + $smallest = 0; + + $r = Photo::store($Image, $user['uid'], 0, $resource_id, $filename, $album, 0, 0, $defperm); + if (!$r) { + Logger::notice('Photo could not be stored'); + return []; + } + + if ($width > 640 || $height > 640) { + $Image->scaleDown(640); + $r = Photo::store($Image, $user['uid'], 0, $resource_id, $filename, $album, 1, 0, $defperm); + if ($r) { + $smallest = 1; + } + } + + if ($width > 320 || $height > 320) { + $Image->scaleDown(320); + $r = Photo::store($Image, $user['uid'], 0, $resource_id, $filename, $album, 2, 0, $defperm); + if ($r && ($smallest == 0)) { + $smallest = 2; + } + } + + $condition = ['resource-id' => $resource_id]; + $photo = self::selectFirst(['id', 'datasize', 'width', 'height', 'type'], $condition, ['order' => ['width' => true]]); + if (empty($photo)) { + Logger::notice('Photo not found', ['condition' => $condition]); + return []; + } + + $picture = []; + + $picture['id'] = $photo['id']; + $picture['size'] = $photo['datasize']; + $picture['width'] = $photo['width']; + $picture['height'] = $photo['height']; + $picture['type'] = $photo['type']; + $picture['albumpage'] = DI::baseUrl() . '/photos/' . $user['nickname'] . '/image/' . $resource_id; + $picture['picture'] = DI::baseUrl() . '/photo/{$resource_id}-0.' . $Image->getExt(); + $picture['preview'] = DI::baseUrl() . '/photo/{$resource_id}-{$smallest}.' . $Image->getExt(); + + Logger::info('upload done', ['picture' => $picture]); + return $picture; + } }