X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=src%2FModel%2FPhoto.php;h=30e666898777c5b4a9c245d5a31b611b9373b932;hb=2647514603852fe5fb9f47f0bf153dd20c124ce6;hp=0f03b54b0dbac9677dfaca7ee2cb48a6211f5e76;hpb=7e3196776bbc92a181bcc160f68b623fe8868ea1;p=friendica.git diff --git a/src/Model/Photo.php b/src/Model/Photo.php index 0f03b54b0d..30e6668987 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) { @@ -200,7 +205,21 @@ class Photo $backendRef = $photo['backend-ref'] ?? ''; $data = $backendClass->get($backendRef); } + 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; } @@ -226,13 +245,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), ""); @@ -245,6 +268,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 @@ -294,7 +344,7 @@ class Photo $storage = DI::storage(); } - if ($storage === null) { + if (empty($storage)) { $data = $Image->asString(); } else { $backend_ref = $storage->put($Image->asString(), $backend_ref); @@ -305,6 +355,7 @@ class Photo "contact-id" => $cid, "guid" => $guid, "resource-id" => $rid, + "hash" => md5($Image->asString()), "created" => $created, "edited" => DateTimeFormat::utcNow(), "filename" => basename($filename), @@ -349,15 +400,20 @@ class Photo public static function delete(array $conditions, array $options = []) { // get photo to delete data info - $photos = self::selectToArray(['backend-class', 'backend-ref'], $conditions); + $photos = DBA::select('photo', ['id', 'backend-class', 'backend-ref'], $conditions); - foreach($photos as $photo) { + while ($photo = DBA::fetch($photos)) { $backend_class = DI::storageManager()->getByName($photo['backend-class'] ?? ''); - if ($backend_class !== null) { - $backend_class->delete($photo["backend-ref"] ?? ''); + if (!empty($backend_class)) { + if ($backend_class->delete($photo["backend-ref"] ?? '')) { + // Delete the photos after they had been deleted successfully + DBA::delete("photo", ['id' => $photo['id']]); + } } } + DBA::close($photos); + return DBA::delete("photo", $conditions, $options); } @@ -382,7 +438,7 @@ class Photo foreach($photos as $photo) { $backend_class = DI::storageManager()->getByName($photo['backend-class'] ?? ''); - if ($backend_class !== null) { + if (!empty($backend_class)) { $fields["backend-ref"] = $backend_class->put($img->asString(), $photo['backend-ref']); } else { $fields["data"] = $img->asString(); @@ -440,6 +496,29 @@ class Photo if ($Image->isValid()) { $Image->scaleToSquare(300); + $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) { @@ -679,18 +758,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 * @@ -708,30 +804,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]; } /** @@ -743,13 +842,12 @@ class Photo */ public static function isLocal($name) { - $guid = self::getGUID($name); - - if (empty($guid)) { + $data = self::getResourceData($name); + if (empty($data)) { return false; } - return DBA::exists('photo', ['resource-id' => $guid]); + return DBA::exists('photo', ['resource-id' => $data['guid'], 'scale' => $data['scale']]); } /** @@ -771,4 +869,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; + } }