From 96809b3fddbd49d5e722cb8e26859c5b02437174 Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 24 Jun 2021 07:08:38 +0000 Subject: [PATCH 1/3] API: Improved avatar handling --- src/Model/Contact.php | 2 +- src/Model/Storage/SystemResource.php | 11 ++++ src/Module/Photo.php | 78 ++++++++++++++++++---------- src/Object/Api/Mastodon/Account.php | 5 +- static/routes.config.php | 6 +-- 5 files changed, 67 insertions(+), 35 deletions(-) diff --git a/src/Model/Contact.php b/src/Model/Contact.php index f0d1c62bd9..30fa41307d 100644 --- a/src/Model/Contact.php +++ b/src/Model/Contact.php @@ -1614,7 +1614,7 @@ class Contact * * @param array $contact contact array * @param string $size Size of the avatar picture - * @return void + * @return string avatar URL */ public static function getDefaultAvatar(array $contact, string $size) { diff --git a/src/Model/Storage/SystemResource.php b/src/Model/Storage/SystemResource.php index c7699c2b79..00af522ca5 100644 --- a/src/Model/Storage/SystemResource.php +++ b/src/Model/Storage/SystemResource.php @@ -22,6 +22,7 @@ namespace Friendica\Model\Storage; use \BadMethodCallException; +use Friendica\DI; /** * System resource storage class @@ -41,6 +42,16 @@ class SystemResource implements IStorage */ public function get(string $filename) { + $parts = parse_url($filename); + if (!empty($parts['scheme']) && !empty($parts['host'])) { + $curlResult = DI::httpRequest()->get($filename); + if ($curlResult->isSuccess()) { + return $curlResult->getBody(); + } else { + return ""; + } + } + $folder = dirname($filename); if (!in_array($folder, self::VALID_FOLDERS)) { return ""; diff --git a/src/Module/Photo.php b/src/Module/Photo.php index 5faaafe40c..f193f9c6b0 100644 --- a/src/Module/Photo.php +++ b/src/Module/Photo.php @@ -44,11 +44,6 @@ class Photo extends BaseModule public static function rawContent(array $parameters = []) { $totalstamp = microtime(true); - $a = DI::app(); - // @TODO: Replace with parameter from router - if ($a->argc <= 1 || $a->argc > 4) { - throw new \Friendica\Network\HTTPException\BadRequestException(); - } if (isset($_SERVER["HTTP_IF_MODIFIED_SINCE"])) { header("HTTP/1.1 304 Not Modified"); @@ -69,30 +64,27 @@ class Photo extends BaseModule $customsize = 0; $photo = false; $scale = null; - // @TODO: Replace with parameter from router $stamp = microtime(true); - switch($a->argc) { - case 4: - $customsize = intval($a->argv[2]); - $uid = MPhoto::stripExtension($a->argv[3]); - $photo = self::getAvatar($uid, $a->argv[1]); - break; - case 3: - $uid = MPhoto::stripExtension($a->argv[2]); - $photo = self::getAvatar($uid, $a->argv[1]); - break; - case 2: - $photoid = MPhoto::stripExtension($a->argv[1]); - $scale = 0; - if (substr($photoid, -2, 1) == "-") { - $scale = intval(substr($photoid, -1, 1)); - $photoid = substr($photoid, 0, -2); - } - $photo = MPhoto::getPhoto($photoid, $scale); - if ($photo === false) { - throw new \Friendica\Network\HTTPException\NotFoundException(DI::l10n()->t('The Photo with id %s is not available.', $photoid)); - } - break; + if (!empty($parameters['customsize'])) { + $customsize = intval($parameters['customsize']); + $uid = MPhoto::stripExtension($parameters['name']); + $photo = self::getAvatar($uid, $parameters['type']); + } elseif (!empty($parameters['type'])) { + $uid = MPhoto::stripExtension($parameters['name']); + $photo = self::getAvatar($uid, $parameters['type']); + } elseif (!empty($parameters['name'])) { + $photoid = MPhoto::stripExtension($parameters['name']); + $scale = 0; + if (substr($photoid, -2, 1) == "-") { + $scale = intval(substr($photoid, -1, 1)); + $photoid = substr($photoid, 0, -2); + } + $photo = MPhoto::getPhoto($photoid, $scale); + if ($photo === false) { + throw new \Friendica\Network\HTTPException\NotFoundException(DI::l10n()->t('The Photo with id %s is not available.', $photoid)); + } + } else { + throw new \Friendica\Network\HTTPException\BadRequestException(); } $fetch = microtime(true) - $stamp; @@ -160,6 +152,36 @@ class Photo extends BaseModule private static function getAvatar($uid, $type="avatar") { switch($type) { + case "contact": + $contact = Contact::getById($uid, ['uid', 'url', 'avatar', 'photo']); + if (empty($contact)) { + return false; + } + If (($contact['uid'] != 0) && empty($contact['photo']) && empty($contact['avatar'])) { + $contact = Contact::getByURL($contact['url'], false, ['avatar', 'photo']); + } + if (!empty($contact['photo'])) { + $url = $contact['photo']; + } elseif (!empty($contact['avatar'])) { + $url = $contact['avatar']; + } else { + $url = DI::baseUrl() . Contact::DEFAULT_AVATAR_PHOTO; + } + return MPhoto::createPhotoForSystemResource($url); + case "header": + $contact = Contact::getById($uid, ['uid', 'url', 'header']); + if (empty($contact)) { + return false; + } + If (($contact['uid'] != 0) && empty($contact['header'])) { + $contact = Contact::getByURL($contact['url'], false, ['header']); + } + if (!empty($contact['header'])) { + $url = $contact['header']; + } else { + $url = DI::baseUrl() . '/images/blank.png'; + } + return MPhoto::createPhotoForSystemResource($url); case "profile": case "custom": $scale = 4; diff --git a/src/Object/Api/Mastodon/Account.php b/src/Object/Api/Mastodon/Account.php index 9b5639949d..5104fde1f1 100644 --- a/src/Object/Api/Mastodon/Account.php +++ b/src/Object/Api/Mastodon/Account.php @@ -29,7 +29,6 @@ use Friendica\Database\DBA; use Friendica\DI; use Friendica\Model\Contact; use Friendica\Util\DateTimeFormat; -use Friendica\Util\Proxy; /** * Class Account @@ -114,9 +113,9 @@ class Account extends BaseDataTransferObject $this->note = BBCode::convert($publicContact['about'], false); $this->url = $publicContact['url']; - $this->avatar = ($userContact['photo'] ?? $publicContact['photo']) ?: Proxy::proxifyUrl($userContact['avatar'] ?? $publicContact['avatar']); + $this->avatar = (($userContact['photo'] ?? '') ?: $publicContact['photo']) ?: DI::baseUrl() . '/photo/contact/'. (($userContact['id'] ?? 0) ?: $publicContact['id']); $this->avatar_static = $this->avatar; - $this->header = Proxy::proxifyUrl($userContact['header'] ?? $publicContact['header'] ?? '') ?: DI::baseUrl() . '/images/blank.png'; + $this->header = DI::baseUrl() . '/photo/header/'. (($userContact['id'] ?? 0) ?: $publicContact['id']); $this->header_static = $this->header; $this->followers_count = $apcontact['followers_count'] ?? 0; $this->following_count = $apcontact['following_count'] ?? 0; diff --git a/static/routes.config.php b/static/routes.config.php index 815d833ada..6bc17c4141 100644 --- a/static/routes.config.php +++ b/static/routes.config.php @@ -363,9 +363,9 @@ return [ '/permission/tooltip/{type}/{id:\d+}' => [Module\PermissionTooltip::class, [R::GET]], '/photo' => [ - '/{name}' => [Module\Photo::class, [R::GET]], - '/{type}/{name}' => [Module\Photo::class, [R::GET]], - '/{type}/{customize}/{name}' => [Module\Photo::class, [R::GET]], + '/{name}' => [Module\Photo::class, [R::GET]], + '/{type}/{name}' => [Module\Photo::class, [R::GET]], + '/{type}/{customsize}/{name}' => [Module\Photo::class, [R::GET]], ], '/pretheme' => [Module\ThemeDetails::class, [R::GET]], From 9276f6823bc6dcaa5f6d93af1549735c70f2f55d Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 24 Jun 2021 17:30:22 +0000 Subject: [PATCH 2/3] New class for external resources --- src/Core/StorageManager.php | 5 +- src/Model/Photo.php | 23 ++++++ src/Model/Storage/ExternalResource.php | 99 ++++++++++++++++++++++++++ src/Model/Storage/SystemResource.php | 11 --- src/Module/Photo.php | 11 ++- 5 files changed, 134 insertions(+), 15 deletions(-) create mode 100644 src/Model/Storage/ExternalResource.php diff --git a/src/Core/StorageManager.php b/src/Core/StorageManager.php index 9e35aa6a36..b8cde14bd0 100644 --- a/src/Core/StorageManager.php +++ b/src/Core/StorageManager.php @@ -126,6 +126,9 @@ class StorageManager case Storage\SystemResource::getName(): $this->backendInstances[$name] = new Storage\SystemResource(); break; + case Storage\ExternalResource::getName(): + $this->backendInstances[$name] = new Storage\ExternalResource(); + break; default: $data = [ 'name' => $name, @@ -158,7 +161,7 @@ class StorageManager public function isValidBackend(string $name = null, bool $onlyUserBackend = false) { return array_key_exists($name, $this->backends) || - (!$onlyUserBackend && $name === Storage\SystemResource::getName()); + (!$onlyUserBackend && in_array($name, [Storage\SystemResource::getName(), Storage\ExternalResource::getName()])); } /** diff --git a/src/Model/Photo.php b/src/Model/Photo.php index f79409e3f5..acc6b0d197 100644 --- a/src/Model/Photo.php +++ b/src/Model/Photo.php @@ -27,6 +27,7 @@ use Friendica\Core\System; use Friendica\Database\DBA; use Friendica\Database\DBStructure; use Friendica\DI; +use Friendica\Model\Storage\ExternalResource; use Friendica\Model\Storage\SystemResource; use Friendica\Object\Image; use Friendica\Util\DateTimeFormat; @@ -263,6 +264,28 @@ class Photo return $photo; } + /** + * Construct a photo array for an external resource image + * + * @param string $url Image URL + * @param string $mimetype Image mime type. Defaults to "image/jpeg" + * + * @return array + * @throws \Exception + */ + public static function createPhotoForExternalResource($url, $mimetype = "image/jpeg") + { + $fields = self::getFields(); + $values = array_fill(0, count($fields), ""); + + $photo = array_combine($fields, $values); + $photo['backend-class'] = ExternalResource::NAME; + $photo['backend-ref'] = $url; + $photo['type'] = $mimetype; + $photo['cacheable'] = false; + + return $photo; + } /** * store photo metadata in db and binary in default backend diff --git a/src/Model/Storage/ExternalResource.php b/src/Model/Storage/ExternalResource.php new file mode 100644 index 0000000000..e0e0a6279e --- /dev/null +++ b/src/Model/Storage/ExternalResource.php @@ -0,0 +1,99 @@ +. + * + */ + +namespace Friendica\Model\Storage; + +use \BadMethodCallException; +use Friendica\DI; + +/** + * External resource storage class + * + * This class is used to load external resources, like images. + * Is not intended to be selectable by admins as default storage class. + */ +class ExternalResource implements IStorage +{ + const NAME = 'ExternalResource'; + + /** + * @inheritDoc + */ + public function get(string $filename) + { + $parts = parse_url($filename); + if (empty($parts['scheme']) || empty($parts['host'])) { + return ""; + } + + $curlResult = DI::httpRequest()->get($filename); + if ($curlResult->isSuccess()) { + return $curlResult->getBody(); + } else { + return ""; + } + } + + /** + * @inheritDoc + */ + public function put(string $data, string $filename = '') + { + throw new BadMethodCallException(); + } + + public function delete(string $filename) + { + throw new BadMethodCallException(); + } + + /** + * @inheritDoc + */ + public function getOptions() + { + return []; + } + + /** + * @inheritDoc + */ + public function saveOptions(array $data) + { + return []; + } + + /** + * @inheritDoc + */ + public function __toString() + { + return self::NAME; + } + + /** + * @inheritDoc + */ + public static function getName() + { + return self::NAME; + } +} diff --git a/src/Model/Storage/SystemResource.php b/src/Model/Storage/SystemResource.php index 00af522ca5..c7699c2b79 100644 --- a/src/Model/Storage/SystemResource.php +++ b/src/Model/Storage/SystemResource.php @@ -22,7 +22,6 @@ namespace Friendica\Model\Storage; use \BadMethodCallException; -use Friendica\DI; /** * System resource storage class @@ -42,16 +41,6 @@ class SystemResource implements IStorage */ public function get(string $filename) { - $parts = parse_url($filename); - if (!empty($parts['scheme']) && !empty($parts['host'])) { - $curlResult = DI::httpRequest()->get($filename); - if ($curlResult->isSuccess()) { - return $curlResult->getBody(); - } else { - return ""; - } - } - $folder = dirname($filename); if (!in_array($folder, self::VALID_FOLDERS)) { return ""; diff --git a/src/Module/Photo.php b/src/Module/Photo.php index f193f9c6b0..d430fa9b02 100644 --- a/src/Module/Photo.php +++ b/src/Module/Photo.php @@ -167,7 +167,7 @@ class Photo extends BaseModule } else { $url = DI::baseUrl() . Contact::DEFAULT_AVATAR_PHOTO; } - return MPhoto::createPhotoForSystemResource($url); + return MPhoto::createPhotoForExternalResource($url); case "header": $contact = Contact::getById($uid, ['uid', 'url', 'header']); if (empty($contact)) { @@ -181,7 +181,7 @@ class Photo extends BaseModule } else { $url = DI::baseUrl() . '/images/blank.png'; } - return MPhoto::createPhotoForSystemResource($url); + return MPhoto::createPhotoForExternalResource($url); case "profile": case "custom": $scale = 4; @@ -211,7 +211,12 @@ class Photo extends BaseModule $default = Contact::getDefaultAvatar($contact, Proxy::SIZE_THUMB); } - $photo = MPhoto::createPhotoForSystemResource($default); + $parts = parse_url($default); + if (!empty($parts['scheme']) || !empty($parts['host'])) { + $photo = MPhoto::createPhotoForExternalResource($default); + } else { + $photo = MPhoto::createPhotoForSystemResource($default); + } } return $photo; } From ee25246e41206f739ad951af937de79c320a1123 Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 24 Jun 2021 17:37:50 +0000 Subject: [PATCH 3/3] Standards --- src/Model/Storage/ExternalResource.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Model/Storage/ExternalResource.php b/src/Model/Storage/ExternalResource.php index e0e0a6279e..9c57e3990b 100644 --- a/src/Model/Storage/ExternalResource.php +++ b/src/Model/Storage/ExternalResource.php @@ -21,7 +21,7 @@ namespace Friendica\Model\Storage; -use \BadMethodCallException; +use BadMethodCallException; use Friendica\DI; /**