From 296ebf7c7cbe44d297625e29c681b3a8ffb3dc3c Mon Sep 17 00:00:00 2001 From: eleith Date: Sun, 1 Nov 2020 03:32:54 +0000 Subject: add support for downloading avatars from gravatar adding support for gravatar to the current social providers/services. because gravatar relies on email instead of the x-socialprofile carddav field, this requires re-working the current abstraction for how social providers work. also in this change is improved support for when a contact card has multiple fields of the same type (as might be common with email) thus they all can be checked for profile photos if one of them doesn't result in a valid photo Signed-off-by: leith abdulla --- css/icons.scss | 1 + img/gravatar.svg | 1 + lib/Service/Social/CompositeSocialProvider.php | 59 ++++++------ lib/Service/Social/DiasporaProvider.php | 103 +++++++++++++++++---- lib/Service/Social/FacebookProvider.php | 67 ++++++++++++-- lib/Service/Social/GravatarProvider.php | 81 ++++++++++++++++ lib/Service/Social/ISocialProvider.php | 17 ++-- lib/Service/Social/InstagramProvider.php | 73 ++++++++++++--- lib/Service/Social/MastodonProvider.php | 97 ++++++++++++++++--- lib/Service/Social/TumblrProvider.php | 67 ++++++++++++-- lib/Service/Social/TwitterProvider.php | 72 +++++++++++--- lib/Service/Social/XingProvider.php | 99 ++++++++++++++++---- lib/Service/SocialApiService.php | 49 +++++++--- .../ContactDetails/ContactDetailsAvatar.vue | 4 + 14 files changed, 644 insertions(+), 146 deletions(-) create mode 100644 img/gravatar.svg create mode 100644 lib/Service/Social/GravatarProvider.php diff --git a/css/icons.scss b/css/icons.scss index 084df4e7..76945ad4 100644 --- a/css/icons.scss +++ b/css/icons.scss @@ -41,6 +41,7 @@ @include icon-black-white('twitter', 'contacts', 2); // “twitter (fab)” by fontawesome.com is licensed under CC BY 4.0. (https://fontawesome.com/icons/twitter?style=brands) @include icon-black-white('diaspora', 'contacts', 2); // “diaspora (fab)” by fontawesome.com is licensed under CC BY 4.0. (https://fontawesome.com/icons/diaspora?style=brands) @include icon-black-white('xing', 'contacts', 2); // “xing (fab)” by fontawesome.com is licensed under CC BY 4.0. (https://fontawesome.com/icons/xing?style=brands) +@include icon-black-white('gravatar', 'contacts', 2); // “wordpress (fab)” by fontawesome.com is licensed under CC BY 4.0. (https://fontawesome.com/icons/wordpress?style=brands) .icon-up-force-white { // using #fffffe to trick the accessibility dark theme icon invert diff --git a/img/gravatar.svg b/img/gravatar.svg new file mode 100644 index 00000000..00a96cb0 --- /dev/null +++ b/img/gravatar.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lib/Service/Social/CompositeSocialProvider.php b/lib/Service/Social/CompositeSocialProvider.php index 40d937e6..8e9f3e89 100644 --- a/lib/Service/Social/CompositeSocialProvider.php +++ b/lib/Service/Social/CompositeSocialProvider.php @@ -37,17 +37,19 @@ class CompositeSocialProvider { TwitterProvider $twitterProvider, TumblrProvider $tumblrProvider, DiasporaProvider $diasporaProvider, - XingProvider $xingProvider) { + XingProvider $xingProvider, + GravatarProvider $gravatarProvider) { // This determines the priority of known providers $this->providers = [ - 'instagram' => $instagramProvider, - 'mastodon' => $mastodonProvider, - 'twitter' => $twitterProvider, - 'facebook' => $facebookProvider, - 'tumblr' => $tumblrProvider, - 'diaspora' => $diasporaProvider, - 'xing' => $xingProvider, + $instagramProvider->name => $instagramProvider, + $mastodonProvider->name => $mastodonProvider, + $twitterProvider->name => $twitterProvider, + $facebookProvider->name => $facebookProvider, + $tumblrProvider->name => $tumblrProvider, + $diasporaProvider->name => $diasporaProvider, + $xingProvider->name => $xingProvider, + $gravatarProvider->name => $gravatarProvider ]; } @@ -60,40 +62,31 @@ class CompositeSocialProvider { return array_keys($this->providers); } - /** * generate download url for a social entry * - * @param array socialEntries all social data from the contact - * @param String network the choice which network to use (fallback: take first available) + * @param array contact all social data from the contact + * @param String network the choice which network to use * - * @returns String the url to the requested information or null in case of errors + * @returns ISocialProvider if provider of 'network' is found, otherwise null */ - public function getSocialConnector(array $socialEntries, string $network) : ?string { + public function getSocialConnector(string $network) : ?ISocialProvider { $connector = null; - $selection = $this->providers; // check if dedicated network selected if (isset($this->providers[$network])) { - $selection = [$network => $this->providers[$network]]; + $connector = $this->providers[$network]; } + return $connector; + } - // check selected providers in order - foreach ($selection as $type => $socialProvider) { - - // search for this network in user's profile - foreach ($socialEntries as $socialEntry) { - if (strtolower($type) === strtolower($socialEntry['type'])) { - $profileId = $socialProvider->cleanupId($socialEntry['value']); - if (!is_null($profileId)) { - $connector = $socialProvider->getImageUrl($profileId); - } - break; - } - } - if ($connector) { - break; - } - } - return ($connector); + /** + * generate download url for a social entry + * + * @param array contact all social data from the contact + * + * @return ISocialProvider[] all social providers + */ + public function getSocialConnectors() : array { + return array_values($this->providers); } } diff --git a/lib/Service/Social/DiasporaProvider.php b/lib/Service/Social/DiasporaProvider.php index 73f85792..44f46ae1 100644 --- a/lib/Service/Social/DiasporaProvider.php +++ b/lib/Service/Social/DiasporaProvider.php @@ -30,41 +30,67 @@ class DiasporaProvider implements ISocialProvider { /** @var IClientService */ private $httpClient; - /** @var boolean */ + /** @var bool */ private $looping; + /** @var string */ + public $name = "diaspora"; + public function __construct(IClientService $httpClient) { $this->httpClient = $httpClient->NewClient(); $this->looping = false; } - + /** - * Returns the profile-id + * Returns if this provider supports this contact * - * @param {string} the value from the contact's x-socialprofile + * @param {array} contact info * - * @return string + * @return bool */ - public function cleanupId(string $candidate):string { - try { - if (strpos($candidate, 'http') !== 0) { - $user_server = explode('@', $candidate); - $candidate = 'https://' . array_pop($user_server) . '/public/' . array_pop($user_server) . '.atom'; - } - } catch (Exception $e) { - $candidate = null; - } - return $candidate; + public function supportsContact(array $contact):bool { + $socialprofiles = $contact['X-SOCIALPROFILE']; + $supports = false; + if(isset($socialprofiles)) { + foreach($socialprofiles as $profile) { + if ($profile['type'] == $this->name) { + $supports = true; + break; + } + } + } + return $supports; } /** + * Returns all possible profile-picture urls + * + * @param {array} contact information + * + * @return array + */ + public function getImageUrls(array $contact):array { + $profileIds = $this->getProfileIds($contact); + $urls = array(); + + foreach($profileIds as $profileId) { + $url = $this->getImageUrl($profileId); + if (isset($url)) { + $urls[] = $url; + } + } + + return $urls; + } + + /** * Returns the profile-picture url * * @param {string} profileId the profile-id * * @return string|null */ - public function getImageUrl(string $profileUrl):?string { + protected function getImageUrl(string $profileUrl):?string { try { $result = $this->httpClient->get($profileUrl); $htmlResult = $result->getBody(); @@ -82,8 +108,51 @@ class DiasporaProvider implements ISocialProvider { } } return null; - } catch (Exception $e) { + } catch (\Exception $e) { return null; } } + + /** + * Returns all possible profile ids for contact + * + * @param {array} contact information + * + * @return array + */ + protected function getProfileIds($contact):array { + $socialprofiles = $contact['X-SOCIALPROFILE']; + $profileIds = array(); + + if(isset($socialprofiles)) { + foreach($socialprofiles as $profile) { + if (strtolower($profile['type']) == $this->name) { + $profileId = $this->cleanupId($profile['value']); + if (isset($profileId)) { + $profileIds[] = $profileId; + } + } + } + } + return $profileIds; + } + + /** + * Returns the profile-id + * + * @param {string} the value from the contact's x-socialprofile + * + * @return string + */ + protected function cleanupId(string $candidate):?string { + try { + if (strpos($candidate, 'http') !== 0) { + $user_server = explode('@', $candidate); + $candidate = 'https://' . array_pop($user_server) . '/public/' . array_pop($user_server) . '.atom'; + } + } catch (Exception $e) { + $candidate = null; + } + return $candidate; + } } diff --git a/lib/Service/Social/FacebookProvider.php b/lib/Service/Social/FacebookProvider.php index 770ac45a..2aa27f83 100644 --- a/lib/Service/Social/FacebookProvider.php +++ b/lib/Service/Social/FacebookProvider.php @@ -30,10 +30,52 @@ class FacebookProvider implements ISocialProvider { /** @var IClientService */ private $httpClient; + /** @var string */ + public $name = "facebook"; + public function __construct(IClientService $httpClient) { $this->httpClient = $httpClient->NewClient(); } - + + /** + * Returns if this provider supports this contact + * + * @param {array} contact info + * + * @return bool + */ + public function supportsContact(array $contact):bool { + $socialprofiles = $contact['X-SOCIALPROFILE']; + $supports = false; + if(isset($socialprofiles)) { + foreach($socialprofiles as $profile) { + if (strtolower($profile['type']) == $this->name) { + $supports = true; + break; + } + } + } + return $supports; + } + + /** + * Returns the profile-picture url + * + * @param {array} contact information + * + * @return array + */ + public function getImageUrls(array $contact):array { + $profileIds = $this->getProfileIds($contact); + $urls = array(); + foreach($profileIds as $profileId) { + $recipe = 'https://graph.facebook.com/{socialId}/picture?width=720'; + $connector = str_replace("{socialId}", $profileId, $recipe); + $urls[] = $connector; + } + return $urls; + } + /** * Returns the profile-id * @@ -41,7 +83,7 @@ class FacebookProvider implements ISocialProvider { * * @return string */ - public function cleanupId(string $candidate):string { + protected function cleanupId(string $candidate):string { $candidate = basename($candidate); if (!is_numeric($candidate)) { $candidate = $this->findFacebookId($candidate); @@ -50,16 +92,23 @@ class FacebookProvider implements ISocialProvider { } /** - * Returns the profile-picture url + * Returns all possible profile ids for contact * - * @param {string} profileId the profile-id + * @param {array} contact information * - * @return string + * @return array of string profile ids */ - public function getImageUrl(string $profileId):string { - $recipe = 'https://graph.facebook.com/{socialId}/picture?width=720'; - $connector = str_replace("{socialId}", $profileId, $recipe); - return $connector; + protected function getProfileIds($contact):array { + $socialprofiles = $contact['X-SOCIALPROFILE']; + $profileIds = array(); + if(isset($socialprofiles)) { + foreach($socialprofiles as $profile) { + if (strtolower($profile['type']) == $this->name) { + $profileIds[] = $this->cleanupId($profile['value']); + } + } + } + return $profileIds; } /** diff --git a/lib/Service/Social/GravatarProvider.php b/lib/Service/Social/GravatarProvider.php new file mode 100644 index 00000000..d735dd5b --- /dev/null +++ b/lib/Service/Social/GravatarProvider.php @@ -0,0 +1,81 @@ + + * + * @author leith + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\Contacts\Service\Social; + +use OCP\Http\Client\IClientService; + +class GravatarProvider implements ISocialProvider { + /** @var string */ + public $name = "gravatar"; + + public function __construct(IClientService $httpClient) { + $this->httpClient = $httpClient->NewClient(); + } + + /** + * Returns if this provider supports this contact + * + * @param {array} contact info + * + * @return bool + */ + public function supportsContact(array $contact):bool { + $emails = $contact['EMAIL']; + return isset($emails) && count($emails); + } + + /** + * Returns the profile-picture url + * + * @param {array} contact information + * + * @return array + */ + public function getImageUrls(array $contact):array { + $emails = $this->getProfileIds($contact); + $urls = array(); + foreach($emails as $email) { + $hash = md5(strtolower(trim($email['value']))); + $recipe = 'https://www.gravatar.com/avatar/{hash}?s=720&d=404'; + $connector = str_replace("{hash}", $hash, $recipe); + $urls[] = $connector; + } + return $urls; + } + + /** + * Returns all possible profile ids for contact + * + * @param {array} contact information + * + * @return array of string profile ids + */ + protected function getProfileIds(array $contact):array { + $emails = $contact['EMAIL']; + if (isset($emails)) { + return $emails; + } + return array(); + } +} diff --git a/lib/Service/Social/ISocialProvider.php b/lib/Service/Social/ISocialProvider.php index 1415f4d0..a21fe32b 100644 --- a/lib/Service/Social/ISocialProvider.php +++ b/lib/Service/Social/ISocialProvider.php @@ -24,22 +24,21 @@ namespace OCA\Contacts\Service\Social; interface ISocialProvider { - /** - * Returns the profile-id + * Returns true if provider supports the contact * - * @param {string} the value from the contact's x-socialprofile + * @param {array} contact details * - * @return string + * @return boolean */ - public function cleanupId(string $candidate):?string ; + public function supportsContact(array $contact):bool ; /** - * Returns the profile-picture url + * Returns all possible profile-picture urls * - * @param {string} profileId the profile-id + * @param {array} contact information * - * @return string|null + * @return array */ - public function getImageUrl(string $profileId):?string ; + public function getImageUrls(array $contact):array ; } diff --git a/lib/Service/Social/InstagramProvider.php b/lib/Service/Social/InstagramProvider.php index b7687e71..2cd3bf52 100644 --- a/lib/Service/Social/InstagramProvider.php +++ b/lib/Service/Social/InstagramProvider.php @@ -30,10 +30,53 @@ class InstagramProvider implements ISocialProvider { /** @var IClientService */ private $httpClient; + /** @var string */ + public $name = "instagram"; + public function __construct(IClientService $httpClient) { $this->httpClient = $httpClient->NewClient(); } - + + /** + * Returns if this provider supports this contact + * + * @param {array} contact info + * + * @return bool + */ + public function supportsContact(array $contact):bool { + $socialprofiles = $contact['X-SOCIALPROFILE']; + $supports = false; + if(isset($socialprofiles)) { + foreach($socialprofiles as $profile) { + if (strtolower($profile['type']) == $this->name) { + $supports = true; + break; + } + } + } + return $supports; + } + + /** + * Returns the profile-picture url + * + * @param {array} contact information + * + * @return array + */ + public function getImageUrls(array $contact):array { + $profileIds = $this->getProfileIds($contact); + $urls = array(); + foreach($profileIds as $profileId) { + $recipe = 'https://www.instagram.com/{socialId}/?__a=1'; + $connector = str_replace("{socialId}", $profileId, $recipe); + $connector = $this->getFromJson($connector, 'graphql->user->profile_pic_url_hd'); + $urls[] = $connector; + } + return $urls; + } + /** * Returns the profile-id * @@ -41,25 +84,31 @@ class InstagramProvider implements ISocialProvider { * * @return string */ - public function cleanupId(string $candidate):string { + protected function cleanupId(string $candidate):string { $candidate = preg_replace('/^' . preg_quote('x-apple:', '/') . '/', '', $candidate); return basename($candidate); } /** - * Returns the profile-picture url + * Returns all possible profile ids for contact * - * @param {string} profileId the profile-id + * @param {array} contact information * - * @return string|null + * @return array of string profile ids */ - public function getImageUrl(string $profileId):?string { - $recipe = 'https://www.instagram.com/{socialId}/?__a=1'; - $connector = str_replace("{socialId}", $profileId, $recipe); - $connector = $this->getFromJson($connector, 'graphql->user->profile_pic_url_hd'); - return $connector; + protected function getProfileIds($contact):array { + $socialprofiles = $contact['X-SOCIALPROFILE']; + $profileIds = array(); + if(isset($socialprofiles)) { + foreach($socialprofiles as $profile) { + if (strtolower($profile['type']) == $this->name) { + $profileIds[] = $this->cleanupId($profile['value']); + } + } + } + return $profileIds; } - + /** * extracts desired value from a json * @@ -81,7 +130,7 @@ class InstagramProvider implements ISocialProvider { $jsonResult = $jsonResult[$loc]; } return $jsonResult; - } catch (Exception $e) { + } catch (\Exception $e) { return null; } } diff --git a/lib/Service/Social/MastodonProvider.php b/lib/Service/Social/MastodonProvider.php index 74b68e96..2c6cbd19 100644 --- a/lib/Service/Social/MastodonProvider.php +++ b/lib/Service/Social/MastodonProvider.php @@ -30,34 +30,58 @@ class MastodonProvider implements ISocialProvider { /** @var IClientService */ private $httpClient; + /** @var string */ + public $name = "mastodon"; + public function __construct(IClientService $httpClient) { $this->httpClient = $httpClient->NewClient(); } - + /** - * Returns the profile-id + * Returns if this provider supports this contact * - * @param {string} the value from the contact's x-socialprofile + * @param {array} contact info * - * @return string + * @return bool */ - public function cleanupId(string $candidate):?string { - $candidate = preg_replace('/^' . preg_quote('x-apple:', '/') . '/', '', $candidate); - try { - if (strpos($candidate, 'http') !== 0) { - $user_server = explode('@', $candidate); - $candidate = 'https://' . array_pop($user_server) . '/@' . array_pop($user_server); + public function supportsContact(array $contact):bool { + $socialprofiles = $contact['X-SOCIALPROFILE']; + $supports = false; + if(isset($socialprofiles)) { + foreach($socialprofiles as $profile) { + if (strtolower($profile['type']) == $this->name) { + $supports = true; + break; + } } - } catch (Exception $e) { - $candidate = null; } - return $candidate; + return $supports; + } + + /** + * Returns all possible profile-picture urls + * + * @param {array} contact information + * + * @return array + */ + public function getImageUrls(array $contact):array { + $profileIds = $this->getProfileIds($contact); + $urls = array(); + + foreach($profileIds as $profileId) { + $url = $this->getImageUrl($profileId); + if (isset($url)) { + $urls[] = $url; + } + } + return $urls; } /** * Returns the profile-picture url * - * @param {string} profileUrl link to the profile + * @param {array} contact information * * @return string|null */ @@ -72,8 +96,51 @@ class MastodonProvider implements ISocialProvider { return $img->getAttribute("data-original"); } return null; - } catch (Exception $e) { + } catch (\Exception $e) { return null; } } + + /** + * Returns all possible profile ids for contact + * + * @param {array} contact information + * + * @return array of possible profileIds + */ + protected function getProfileIds($contact):array { + $socialprofiles = $contact['X-SOCIALPROFILE']; + $profileIds = array(); + if(isset($socialprofiles)) { + foreach($socialprofiles as $profile) { + if (strtolower($profile['type']) == $this->name) { + $profileId = $this->cleanupId($profile['value']); + if(isset($profileId)) { + $profileIds[] = $profileId; + } + } + } + } + return $profileIds; + } + + /** + * Returns the profile-id + * + * @param {string} the value from the contact's x-socialprofile + * + * @return string + */ + protected function cleanupId(string $candidate):?string { + $candidate = preg_replace('/^' . preg_quote('x-apple:', '/') . '/', '', $candidate); + try { + if (strpos($candidate, 'http') !== 0) { + $user_server = explode('@', $candidate); + $candidate = 'https://' . array_pop($user_server) . '/@' . array_pop($user_server); + } + } catch (\Exception $e) { + $candidate = null; + } + return $candidate; + } } diff --git a/lib/Service/Social/TumblrProvider.php b/lib/Service/Social/TumblrProvider.php index a830b945..e1c1d61e 100644 --- a/lib/Service/Social/TumblrProvider.php +++ b/lib/Service/Social/TumblrProvider.php @@ -24,9 +24,51 @@ namespace OCA\Contacts\Service\Social; class TumblrProvider implements ISocialProvider { + /** @var string */ + public $name = "tumblr"; + public function __construct() { } - + + /** + * Returns if this provider supports this contact + * + * @param {array} contact info + * + * @return bool + */ + public function supportsContact(array $contact):bool { + $socialprofiles = $contact['X-SOCIALPROFILE']; + $supports = false; + if(isset($socialprofiles)) { + foreach($socialprofiles as $profile) { + if (strtolower($profile['type']) == $this->name) { + $supports = true; + break; + } + } + } + return $supports; + } + + /** + * Returns the profile-picture url + * + * @param {string} profileId the profile-id + * + * @return array + */ + public function getImageUrls(array $contact):array { + $profileIds = $this->getProfileIds($contact); + $urls = array(); + foreach($profileIds as $profileId) { + $recipe = 'https://api.tumblr.com/v2/blog/{socialId}/avatar/512'; + $connector = str_replace("{socialId}", $profileId, $recipe); + $urls[] = $connector; + } + return $urls; + } + /** * Returns the profile-id * @@ -34,7 +76,7 @@ class TumblrProvider implements ISocialProvider { * * @return string */ - public function cleanupId(string $candidate):?string { + protected function cleanupId(string $candidate):?string { $candidate = preg_replace('/^' . preg_quote('x-apple:', '/') . '/', '', $candidate); $subdomain = '/(?:http[s]*\:\/\/)*(.*?)\.(?=[^\/]*\..{2,5})/i'; // subdomain if (preg_match($subdomain, $candidate, $matches)) { @@ -44,15 +86,22 @@ class TumblrProvider implements ISocialProvider { } /** - * Returns the profile-picture url + * Returns all possible profile ids for contact * - * @param {string} profileId the profile-id + * @param {array} contact information * - * @return string|null + * @return array of string profile ids */ - public function getImageUrl(string $profileId):?string { - $recipe = 'https://api.tumblr.com/v2/blog/{socialId}/avatar/512'; - $connector = str_replace("{socialId}", $profileId, $recipe); - return $connector; + protected function getProfileIds($contact):array { + $socialprofiles = $contact['X-SOCIALPROFILE']; + $profileIds = array(); + if(isset($socialprofiles)) { + foreach($socialprofiles as $profile) { + if (strtolower($profile['type']) == $this->name) { + $profileIds[] = $this->cleanupId($profile['value']); + } + } + } + return $profileIds; } } diff --git a/lib/Service/Social/TwitterProvider.php b/lib/Service/Social/TwitterProvider.php index 953eb1a0..ed249a0b 100644 --- a/lib/Service/Social/TwitterProvider.php +++ b/lib/Service/Social/TwitterProvider.php @@ -26,14 +26,54 @@ namespace OCA\Contacts\Service\Social; use OCP\Http\Client\IClientService; class TwitterProvider implements ISocialProvider { - /** @var IClientService */ private $httpClient; + /** @var string */ + public $name = "twitter"; + public function __construct(IClientService $httpClient) { $this->httpClient = $httpClient->NewClient(); } - + + /** + * Returns if this provider supports this contact + * + * @param {array} contact info + * + * @return bool + */ + public function supportsContact(array $contact):bool { + $socialprofiles = $contact['X-SOCIALPROFILE']; + if(isset($socialprofiles)) { + foreach($socialprofiles as $profile) { + if (strtolower($profile['type']) == $this->name) { + return true; + } + } + } + return false; + } + + /** + * Returns the profile-picture url + * + * @param {array} contact information + * + * @return array + */ + public function getImageUrls(array $contact):array { + $profileIds = $this->getProfileIds($contact); + $urls = array(); + foreach($profileIds as $profileId) { + $recipe = 'https://mobile.twitter.com/{socialId}'; + $connector = str_replace("{socialId}", $profileId, $recipe); + $connector = $this->getFromHtml($connector, '_normal'); + $urls[] = $connector; + } + return $urls; + } + /** * Returns the profile-id * @@ -41,7 +81,7 @@ class TwitterProvider implements ISocialProvider { * * @return string */ - public function cleanupId(string $candidate):string { + protected function cleanupId(string $candidate):string { $candidate = basename($candidate); if ($candidate[0] === '@') { $candidate = substr($candidate, 1); @@ -50,19 +90,25 @@ class TwitterProvider implements ISocialProvider { } /** - * Returns the profile-picture url + * Returns all possible profile ids for contact * - * @param {string} profileId the profile-id + * @param {array} contact information * - * @return string|null + * @return array of string profile ids */ - public function getImageUrl(string $profileId):?string { - $recipe = 'https://mobile.twitter.com/{socialId}'; - $connector = str_replace("{socialId}", $profileId, $recipe); - $connector = $this->getFromHtml($connector, '_normal'); - return $connector; + protected function getProfileIds($contact):array { + $socialprofiles = $contact['X-SOCIALPROFILE']; + $profileIds = array(); + if(isset($socialprofiles)) { + foreach($socialprofiles as $profile) { + if (strtolower($profile['type']) == $this->name) { + $profileIds[] = $this->cleanupId($profile['value']); + } + } + } + return $profileIds; } - + /** * extracts desired value from an html page * @@ -88,7 +134,7 @@ class TwitterProvider implements ISocialProvider { } } return null; - } catch (Exception $e) { + } catch (\Exception $e) { return null; } } diff --git a/lib/Service/Social/XingProvider.php b/lib/Service/Social/XingProvider.php index 739d1fd2..ce695607 100644 --- a/lib/Service/Social/XingProvider.php +++ b/lib/Service/Social/XingProvider.php @@ -30,41 +30,64 @@ class XingProvider implements ISocialProvider { /** @var IClientService */ private $httpClient; - /** @var boolean */ - private $looping; + /** @var string */ + public $name = "xing"; public function __construct(IClientService $httpClient) { $this->httpClient = $httpClient->NewClient(); $this->looping = false; } - + /** - * Returns the profile-id + * Returns if this provider supports this contact * - * @param {string} the value from the contact's x-socialprofile + * @param {array} contact info * - * @return string + * @return bool */ - public function cleanupId(string $candidate):string { - $candidate = preg_replace('/^' . preg_quote('x-apple:', '/') . '/', '', $candidate); - try { - if (strpos($candidate, 'http') !== 0) { - $candidate = 'https://www.xing.com/profile/' . $candidate; + public function supportsContact(array $contact):bool { + $socialprofiles = $contact['X-SOCIALPROFILE']; + $supports = false; + if(isset($socialprofiles)) { + foreach($socialprofiles as $profile) { + if (strtolower($profile['type']) == $this->name) { + $supports = true; + break; + } } - } catch (Exception $e) { - $candidate = null; } - return $candidate; + return $supports; + } + + /** + * Returns all possible profile-picture urls + * + * @param {array} contact information + * + * @return array + */ + public function getImageUrls(array $contact):array { + $profileIds = $this->getProfileIds($contact); + $urls = array(); + + foreach($profileIds as $profileId) { + $url = $this->getImageUrl($profileId); + if (isset($url)) { + $urls[] = $url; + } + } + + return $urls; } /** * Returns the profile-picture url * - * @param {string} profileId the profile-id + * @param {string} profile url * * @return string|null */ - public function getImageUrl(string $profileUrl):?string { + protected function getImageUrl(string $profileUrl):?string { try { $result = $this->httpClient->get($profileUrl); $htmlResult = $result->getBody(); @@ -75,8 +98,50 @@ class XingProvider implements ISocialProvider { } // keyword not found, maybe page changed? return null; - } catch (Exception $e) { + } catch (\Exception $e) { return null; } } + + /** + * Returns the profile-id + * + * @param {string} the value from the contact's x-socialprofile + * + * @return string + */ + protected function cleanupId(string $candidate):?string { + $candidate = preg_replace('/^' . preg_quote('x-apple:', '/') . '/', '', $candidate); + try { + if (strpos($candidate, 'http') !== 0) { + $candidate = 'https://www.xing.com/profile/' . $candidate; + } + } catch (\Exception $e) { + $candidate = null; + } + return $candidate; + } + + /** + * Returns all possible profile ids for contact + * + * @param {array} contact information + * + * @return string of first profile url else null + */ + protected function getProfileIds($contact):array { + $socialprofiles = $contact['X-SOCIALPROFILE']; + $profileIds = array(); + if(isset($socialprofiles)) { + foreach($socialprofiles as $profile) { + if (strtolower($profile['type']) == $this->name) { + $profileId = $this->cleanupId($profile['value']); + if(isset($profileId)) { + $profileIds[] = $profileId; + } + } + } + } + return $profileIds; + } } diff --git a/lib/Service/SocialApiService.php b/lib/Service/SocialApiService.php index 03484c99..af07c998 100644 --- a/lib/Service/SocialApiService.php +++ b/lib/Service/SocialApiService.php @@ -50,9 +50,9 @@ class SocialApiService { private $config; /** @var IClientService */ private $clientService; - /** @var IL10N */ + /** @var IL10N */ private $l10n; - /** @var IURLGenerator */ + /** @var IURLGenerator */ private $urlGen; /** @var CardDavBackend */ private $davBackend; @@ -84,7 +84,7 @@ class SocialApiService { /** * returns an array of supported social networks * - * @returns {array} array of the supported social networks + * @return {array} array of the supported social networks */ public function getSupportedNetworks() : array { $syncAllowedByAdmin = $this->config->getAppValue($this->appName, 'allowSocialSync', 'yes'); @@ -168,7 +168,10 @@ class SocialApiService { * @returns {JSONResponse} an empty JSONResponse with respective http status code */ public function updateContact(string $addressbookId, string $contactId, string $network) : JSONResponse { - $url = null; + $socialdata = null; + $imageType = null; + $urls = array(); + $allConnectors = $this->socialProvider->getSocialConnectors(); try { // get corresponding addressbook @@ -179,20 +182,42 @@ class SocialApiService { // search contact in that addressbook, get social data $contact = $addressBook->search($contactId, ['UID'], ['types' => true])[0]; - if (!isset($contact['X-SOCIALPROFILE'])) { + + if (!isset($contact)) { + return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED); + } + + if ($network) { + $allConnectors = [$this->socialProvider->getSocialConnector($network)]; + } + + $connectors = array_filter($allConnectors, function($connector) use($contact) { + return $connector->supportsContact($contact); + }); + + if (count($connectors) == 0) { return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED); } - $socialprofiles = $contact['X-SOCIALPROFILE']; - // retrieve data - $url = $this->socialProvider->getSocialConnector($socialprofiles, $network); - if (empty($url)) { + foreach($connectors as $connector) { + $urls = array_merge($connector->getImageUrls($contact), $urls); + } + + if (count($urls) == 0) { return new JSONResponse([], Http::STATUS_BAD_REQUEST); } - $httpResult = $this->clientService->NewClient()->get($url); - $socialdata = $httpResult->getBody(); - $imageType = $httpResult->getHeader('content-type'); + foreach($urls as $url) { + try { + $httpResult = $this->clientService->NewClient()->get($url); + $socialdata = $httpResult->getBody(); + $imageType = $httpResult->getHeader('content-type'); + if (isset($socialdata) && isset($imageType)) { + break; + } + } catch(\Exception $e) { + } + } if (!$socialdata || $imageType === null) { return new JSONResponse([], Http::STATUS_NOT_FOUND); diff --git a/src/components/ContactDetails/ContactDetailsAvatar.vue b/src/components/ContactDetails/ContactDetailsAvatar.vue index b13e9411..92b1befb 100644 --- a/src/components/ContactDetails/ContactDetailsAvatar.vue +++ b/src/components/ContactDetails/ContactDetailsAvatar.vue @@ -196,11 +196,15 @@ export default { return false }, supportedSocial() { + const emails = this.contact.vCard.getAllProperties('email') // get social networks set for the current contact const available = this.contact.vCard.getAllProperties('x-socialprofile') .map(a => a.jCal[1].type.toString().toLowerCase()) // get list of social networks that allow for avatar download const supported = supportedNetworks.map(v => v.toLowerCase()) + if (emails.length) { + available.push('gravatar') + } // return supported social networks which are set return supported.filter(i => available.includes(i)) .map(j => this.capitalize(j)) -- cgit v1.2.3