From 296ebf7c7cbe44d297625e29c681b3a8ffb3dc3c Mon Sep 17 00:00:00 2001 From: eleith Date: Sun, 1 Nov 2020 03:32:54 +0000 Subject: [PATCH 1/4] 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 + .../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 084df4e7b..76945ad4d 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 000000000..00a96cb01 --- /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 40d937e6e..8e9f3e897 100644 --- a/lib/Service/Social/CompositeSocialProvider.php +++ b/lib/Service/Social/CompositeSocialProvider.php @@ -37,17 +37,19 @@ public function __construct(InstagramProvider $instagramProvider, 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 @@ public function getSupportedNetworks() : array { 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 73f85792c..44f46ae1f 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 @@ public function getImageUrl(string $profileUrl):?string { } } 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 770ac45ae..2aa27f830 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 @@ public function __construct(IClientService $httpClient) { * * @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 @@ public function cleanupId(string $candidate):string { } /** - * 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 000000000..d735dd5bb --- /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 1415f4d09..a21fe32bb 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 b7687e715..2cd3bf523 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 @@ public function __construct(IClientService $httpClient) { * * @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 @@ protected function getFromJson(string $url, string $desired) : ?string { $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 74b68e96d..2c6cbd19c 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 @@ public function getImageUrl(string $profileUrl):?string { 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 a830b945b..e1c1d61ef 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 @@ public function __construct() { * * @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 @@ public function cleanupId(string $candidate):?string { } /** - * 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 953eb1a09..ed249a0bb 100644 --- a/lib/Service/Social/TwitterProvider.php +++ b/lib/Service/Social/TwitterProvider.php @@ -26,14 +26,54 @@ 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 @@ public function __construct(IClientService $httpClient) { * * @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 @@ public function cleanupId(string $candidate):string { } /** - * 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 @@ protected function getFromHtml(string $url, string $desired) : ?string { } } 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 739d1fd2f..ce695607a 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 @@ public function getImageUrl(string $profileUrl):?string { } // 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 03484c99e..af07c998f 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 @@ public function __construct( /** * 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 @@ protected function registerAddressbooks($userId, IManager $manager) { * @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 @@ public function updateContact(string $addressbookId, string $contactId, string $ // 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 b13e94111..92b1befb7 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)) From da321c14516862c1216f57060d1d609704f61c9d Mon Sep 17 00:00:00 2001 From: leith abdulla Date: Sun, 1 Nov 2020 19:31:58 -0800 Subject: [PATCH 2/4] linting, unit tests, comment fixes Signed-off-by: leith abdulla --- .../Social/CompositeSocialProvider.php | 5 +- lib/Service/Social/DiasporaProvider.php | 84 ++++---- lib/Service/Social/FacebookProvider.php | 14 +- lib/Service/Social/GravatarProvider.php | 6 +- lib/Service/Social/InstagramProvider.php | 14 +- lib/Service/Social/MastodonProvider.php | 18 +- lib/Service/Social/TumblrProvider.php | 14 +- lib/Service/Social/TwitterProvider.php | 14 +- lib/Service/Social/XingProvider.php | 16 +- lib/Service/SocialApiService.php | 12 +- tests/unit/Service/SocialApiServiceTest.php | 184 ++++++++++++++---- 11 files changed, 238 insertions(+), 143 deletions(-) diff --git a/lib/Service/Social/CompositeSocialProvider.php b/lib/Service/Social/CompositeSocialProvider.php index 8e9f3e897..751f5cc68 100644 --- a/lib/Service/Social/CompositeSocialProvider.php +++ b/lib/Service/Social/CompositeSocialProvider.php @@ -65,10 +65,9 @@ public function getSupportedNetworks() : array { /** * generate download url for a social entry * - * @param array contact all social data from the contact * @param String network the choice which network to use * - * @returns ISocialProvider if provider of 'network' is found, otherwise null + * @return ISocialProvider if provider of 'network' is found, otherwise null */ public function getSocialConnector(string $network) : ?ISocialProvider { $connector = null; @@ -82,8 +81,6 @@ public function getSocialConnector(string $network) : ?ISocialProvider { /** * 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 { diff --git a/lib/Service/Social/DiasporaProvider.php b/lib/Service/Social/DiasporaProvider.php index 44f46ae1f..312690292 100644 --- a/lib/Service/Social/DiasporaProvider.php +++ b/lib/Service/Social/DiasporaProvider.php @@ -33,8 +33,8 @@ class DiasporaProvider implements ISocialProvider { /** @var bool */ private $looping; - /** @var string */ - public $name = "diaspora"; + /** @var string */ + public $name = "diaspora"; public function __construct(IClientService $httpClient) { $this->httpClient = $httpClient->NewClient(); @@ -49,17 +49,17 @@ public function __construct(IClientService $httpClient) { * @return bool */ 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; + $socialprofiles = $contact['X-SOCIALPROFILE']; + $supports = false; + if (isset($socialprofiles)) { + foreach ($socialprofiles as $profile) { + if ($profile['type'] == $this->name) { + $supports = true; + break; + } + } + } + return $supports; } /** @@ -67,23 +67,23 @@ public function supportsContact(array $contact):bool { * * @param {array} contact information * - * @return array + * @return array */ public function getImageUrls(array $contact):array { - $profileIds = $this->getProfileIds($contact); - $urls = array(); + $profileIds = $this->getProfileIds($contact); + $urls = []; - foreach($profileIds as $profileId) { - $url = $this->getImageUrl($profileId); - if (isset($url)) { - $urls[] = $url; - } - } + foreach ($profileIds as $profileId) { + $url = $this->getImageUrl($profileId); + if (isset($url)) { + $urls[] = $url; + } + } - return $urls; + return $urls; } - /** + /** * Returns the profile-picture url * * @param {string} profileId the profile-id @@ -113,29 +113,29 @@ protected function getImageUrl(string $profileUrl):?string { } } - /** - * Returns all possible profile ids for contact + /** + * Returns all possible profile ids for contact * * @param {array} contact information * - * @return array + * @return array */ - protected function getProfileIds($contact):array { - $socialprofiles = $contact['X-SOCIALPROFILE']; - $profileIds = array(); + protected function getProfileIds($contact):array { + $socialprofiles = $contact['X-SOCIALPROFILE']; + $profileIds = []; - 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; - } + 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 diff --git a/lib/Service/Social/FacebookProvider.php b/lib/Service/Social/FacebookProvider.php index 2aa27f830..0202e6d09 100644 --- a/lib/Service/Social/FacebookProvider.php +++ b/lib/Service/Social/FacebookProvider.php @@ -47,8 +47,8 @@ public function __construct(IClientService $httpClient) { public function supportsContact(array $contact):bool { $socialprofiles = $contact['X-SOCIALPROFILE']; $supports = false; - if(isset($socialprofiles)) { - foreach($socialprofiles as $profile) { + if (isset($socialprofiles)) { + foreach ($socialprofiles as $profile) { if (strtolower($profile['type']) == $this->name) { $supports = true; break; @@ -67,8 +67,8 @@ public function supportsContact(array $contact):bool { */ public function getImageUrls(array $contact):array { $profileIds = $this->getProfileIds($contact); - $urls = array(); - foreach($profileIds as $profileId) { + $urls = []; + foreach ($profileIds as $profileId) { $recipe = 'https://graph.facebook.com/{socialId}/picture?width=720'; $connector = str_replace("{socialId}", $profileId, $recipe); $urls[] = $connector; @@ -100,9 +100,9 @@ protected function cleanupId(string $candidate):string { */ protected function getProfileIds($contact):array { $socialprofiles = $contact['X-SOCIALPROFILE']; - $profileIds = array(); - if(isset($socialprofiles)) { - foreach($socialprofiles as $profile) { + $profileIds = []; + if (isset($socialprofiles)) { + foreach ($socialprofiles as $profile) { if (strtolower($profile['type']) == $this->name) { $profileIds[] = $this->cleanupId($profile['value']); } diff --git a/lib/Service/Social/GravatarProvider.php b/lib/Service/Social/GravatarProvider.php index d735dd5bb..07b0f83ef 100644 --- a/lib/Service/Social/GravatarProvider.php +++ b/lib/Service/Social/GravatarProvider.php @@ -54,8 +54,8 @@ public function supportsContact(array $contact):bool { */ public function getImageUrls(array $contact):array { $emails = $this->getProfileIds($contact); - $urls = array(); - foreach($emails as $email) { + $urls = []; + 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); @@ -76,6 +76,6 @@ protected function getProfileIds(array $contact):array { if (isset($emails)) { return $emails; } - return array(); + return []; } } diff --git a/lib/Service/Social/InstagramProvider.php b/lib/Service/Social/InstagramProvider.php index 2cd3bf523..f0c593c71 100644 --- a/lib/Service/Social/InstagramProvider.php +++ b/lib/Service/Social/InstagramProvider.php @@ -47,8 +47,8 @@ public function __construct(IClientService $httpClient) { public function supportsContact(array $contact):bool { $socialprofiles = $contact['X-SOCIALPROFILE']; $supports = false; - if(isset($socialprofiles)) { - foreach($socialprofiles as $profile) { + if (isset($socialprofiles)) { + foreach ($socialprofiles as $profile) { if (strtolower($profile['type']) == $this->name) { $supports = true; break; @@ -67,8 +67,8 @@ public function supportsContact(array $contact):bool { */ public function getImageUrls(array $contact):array { $profileIds = $this->getProfileIds($contact); - $urls = array(); - foreach($profileIds as $profileId) { + $urls = []; + 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'); @@ -98,9 +98,9 @@ protected function cleanupId(string $candidate):string { */ protected function getProfileIds($contact):array { $socialprofiles = $contact['X-SOCIALPROFILE']; - $profileIds = array(); - if(isset($socialprofiles)) { - foreach($socialprofiles as $profile) { + $profileIds = []; + if (isset($socialprofiles)) { + foreach ($socialprofiles as $profile) { if (strtolower($profile['type']) == $this->name) { $profileIds[] = $this->cleanupId($profile['value']); } diff --git a/lib/Service/Social/MastodonProvider.php b/lib/Service/Social/MastodonProvider.php index 2c6cbd19c..b1bb5097a 100644 --- a/lib/Service/Social/MastodonProvider.php +++ b/lib/Service/Social/MastodonProvider.php @@ -30,7 +30,7 @@ class MastodonProvider implements ISocialProvider { /** @var IClientService */ private $httpClient; - /** @var string */ + /** @var string */ public $name = "mastodon"; public function __construct(IClientService $httpClient) { @@ -47,8 +47,8 @@ public function __construct(IClientService $httpClient) { public function supportsContact(array $contact):bool { $socialprofiles = $contact['X-SOCIALPROFILE']; $supports = false; - if(isset($socialprofiles)) { - foreach($socialprofiles as $profile) { + if (isset($socialprofiles)) { + foreach ($socialprofiles as $profile) { if (strtolower($profile['type']) == $this->name) { $supports = true; break; @@ -67,9 +67,9 @@ public function supportsContact(array $contact):bool { */ public function getImageUrls(array $contact):array { $profileIds = $this->getProfileIds($contact); - $urls = array(); + $urls = []; - foreach($profileIds as $profileId) { + foreach ($profileIds as $profileId) { $url = $this->getImageUrl($profileId); if (isset($url)) { $urls[] = $url; @@ -110,12 +110,12 @@ public function getImageUrl(string $profileUrl):?string { */ protected function getProfileIds($contact):array { $socialprofiles = $contact['X-SOCIALPROFILE']; - $profileIds = array(); - if(isset($socialprofiles)) { - foreach($socialprofiles as $profile) { + $profileIds = []; + if (isset($socialprofiles)) { + foreach ($socialprofiles as $profile) { if (strtolower($profile['type']) == $this->name) { $profileId = $this->cleanupId($profile['value']); - if(isset($profileId)) { + if (isset($profileId)) { $profileIds[] = $profileId; } } diff --git a/lib/Service/Social/TumblrProvider.php b/lib/Service/Social/TumblrProvider.php index e1c1d61ef..c0d8ffcad 100644 --- a/lib/Service/Social/TumblrProvider.php +++ b/lib/Service/Social/TumblrProvider.php @@ -40,8 +40,8 @@ public function __construct() { public function supportsContact(array $contact):bool { $socialprofiles = $contact['X-SOCIALPROFILE']; $supports = false; - if(isset($socialprofiles)) { - foreach($socialprofiles as $profile) { + if (isset($socialprofiles)) { + foreach ($socialprofiles as $profile) { if (strtolower($profile['type']) == $this->name) { $supports = true; break; @@ -60,8 +60,8 @@ public function supportsContact(array $contact):bool { */ public function getImageUrls(array $contact):array { $profileIds = $this->getProfileIds($contact); - $urls = array(); - foreach($profileIds as $profileId) { + $urls = []; + foreach ($profileIds as $profileId) { $recipe = 'https://api.tumblr.com/v2/blog/{socialId}/avatar/512'; $connector = str_replace("{socialId}", $profileId, $recipe); $urls[] = $connector; @@ -94,9 +94,9 @@ protected function cleanupId(string $candidate):?string { */ protected function getProfileIds($contact):array { $socialprofiles = $contact['X-SOCIALPROFILE']; - $profileIds = array(); - if(isset($socialprofiles)) { - foreach($socialprofiles as $profile) { + $profileIds = []; + if (isset($socialprofiles)) { + foreach ($socialprofiles as $profile) { if (strtolower($profile['type']) == $this->name) { $profileIds[] = $this->cleanupId($profile['value']); } diff --git a/lib/Service/Social/TwitterProvider.php b/lib/Service/Social/TwitterProvider.php index ed249a0bb..08aab855b 100644 --- a/lib/Service/Social/TwitterProvider.php +++ b/lib/Service/Social/TwitterProvider.php @@ -45,8 +45,8 @@ public function __construct(IClientService $httpClient) { */ public function supportsContact(array $contact):bool { $socialprofiles = $contact['X-SOCIALPROFILE']; - if(isset($socialprofiles)) { - foreach($socialprofiles as $profile) { + if (isset($socialprofiles)) { + foreach ($socialprofiles as $profile) { if (strtolower($profile['type']) == $this->name) { return true; } @@ -64,8 +64,8 @@ public function supportsContact(array $contact):bool { */ public function getImageUrls(array $contact):array { $profileIds = $this->getProfileIds($contact); - $urls = array(); - foreach($profileIds as $profileId) { + $urls = []; + foreach ($profileIds as $profileId) { $recipe = 'https://mobile.twitter.com/{socialId}'; $connector = str_replace("{socialId}", $profileId, $recipe); $connector = $this->getFromHtml($connector, '_normal'); @@ -98,9 +98,9 @@ protected function cleanupId(string $candidate):string { */ protected function getProfileIds($contact):array { $socialprofiles = $contact['X-SOCIALPROFILE']; - $profileIds = array(); - if(isset($socialprofiles)) { - foreach($socialprofiles as $profile) { + $profileIds = []; + if (isset($socialprofiles)) { + foreach ($socialprofiles as $profile) { if (strtolower($profile['type']) == $this->name) { $profileIds[] = $this->cleanupId($profile['value']); } diff --git a/lib/Service/Social/XingProvider.php b/lib/Service/Social/XingProvider.php index ce695607a..c0a816d98 100644 --- a/lib/Service/Social/XingProvider.php +++ b/lib/Service/Social/XingProvider.php @@ -48,8 +48,8 @@ public function __construct(IClientService $httpClient) { public function supportsContact(array $contact):bool { $socialprofiles = $contact['X-SOCIALPROFILE']; $supports = false; - if(isset($socialprofiles)) { - foreach($socialprofiles as $profile) { + if (isset($socialprofiles)) { + foreach ($socialprofiles as $profile) { if (strtolower($profile['type']) == $this->name) { $supports = true; break; @@ -68,9 +68,9 @@ public function supportsContact(array $contact):bool { */ public function getImageUrls(array $contact):array { $profileIds = $this->getProfileIds($contact); - $urls = array(); + $urls = []; - foreach($profileIds as $profileId) { + foreach ($profileIds as $profileId) { $url = $this->getImageUrl($profileId); if (isset($url)) { $urls[] = $url; @@ -131,12 +131,12 @@ protected function cleanupId(string $candidate):?string { */ protected function getProfileIds($contact):array { $socialprofiles = $contact['X-SOCIALPROFILE']; - $profileIds = array(); - if(isset($socialprofiles)) { - foreach($socialprofiles as $profile) { + $profileIds = []; + if (isset($socialprofiles)) { + foreach ($socialprofiles as $profile) { if (strtolower($profile['type']) == $this->name) { $profileId = $this->cleanupId($profile['value']); - if(isset($profileId)) { + if (isset($profileId)) { $profileIds[] = $profileId; } } diff --git a/lib/Service/SocialApiService.php b/lib/Service/SocialApiService.php index af07c998f..446a9ef69 100644 --- a/lib/Service/SocialApiService.php +++ b/lib/Service/SocialApiService.php @@ -167,10 +167,10 @@ protected function registerAddressbooks($userId, IManager $manager) { * * @returns {JSONResponse} an empty JSONResponse with respective http status code */ - public function updateContact(string $addressbookId, string $contactId, string $network) : JSONResponse { + public function updateContact(string $addressbookId, string $contactId, ?string $network) : JSONResponse { $socialdata = null; $imageType = null; - $urls = array(); + $urls = []; $allConnectors = $this->socialProvider->getSocialConnectors(); try { @@ -191,7 +191,7 @@ public function updateContact(string $addressbookId, string $contactId, string $ $allConnectors = [$this->socialProvider->getSocialConnector($network)]; } - $connectors = array_filter($allConnectors, function($connector) use($contact) { + $connectors = array_filter($allConnectors, function ($connector) use ($contact) { return $connector->supportsContact($contact); }); @@ -199,7 +199,7 @@ public function updateContact(string $addressbookId, string $contactId, string $ return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED); } - foreach($connectors as $connector) { + foreach ($connectors as $connector) { $urls = array_merge($connector->getImageUrls($contact), $urls); } @@ -207,7 +207,7 @@ public function updateContact(string $addressbookId, string $contactId, string $ return new JSONResponse([], Http::STATUS_BAD_REQUEST); } - foreach($urls as $url) { + foreach ($urls as $url) { try { $httpResult = $this->clientService->NewClient()->get($url); $socialdata = $httpResult->getBody(); @@ -215,7 +215,7 @@ public function updateContact(string $addressbookId, string $contactId, string $ if (isset($socialdata) && isset($imageType)) { break; } - } catch(\Exception $e) { + } catch (\Exception $e) { } } diff --git a/tests/unit/Service/SocialApiServiceTest.php b/tests/unit/Service/SocialApiServiceTest.php index 327bf1610..e7f4c8e3f 100644 --- a/tests/unit/Service/SocialApiServiceTest.php +++ b/tests/unit/Service/SocialApiServiceTest.php @@ -25,6 +25,7 @@ namespace OCA\Contacts\Service; use OCA\Contacts\Service\Social\CompositeSocialProvider; +use OCA\Contacts\Service\Social\ISocialProvider; use OCP\AppFramework\Http; use OCP\Http\Client\IClient; @@ -45,7 +46,7 @@ class SocialApiServiceTest extends TestCase { private $service; - /** @var CompositeSocialProvider */ + /** @var CompositeSocialProvider|MockObject */ private $socialProvider; /** @var IManager|MockObject */ private $manager; @@ -53,22 +54,56 @@ class SocialApiServiceTest extends TestCase { private $config; /** @var IClientService|MockObject */ private $clientService; - /** @var IL10N|MockObject */ + /** @var IL10N|MockObject */ private $l10n; - /** @var IURLGenerator|MockObject */ + /** @var IURLGenerator|MockObject */ private $urlGen; /** @var CardDavBackend|MockObject */ private $davBackend; /** @var ITimeFactory|MockObject */ private $timeFactory; - public function socialProfileProvider() { + public function allSocialProfileProviders() { + $body = "the body"; + $imageType = "jpg"; + $contact = [ + 'URI' => '3225c0d5-1bd2-43e5-a08c-4e65eaa406b0', + 'VERSION' => '4.0' + ]; + $connector = $this->createMock(ISocialProvider::class); + $connector->method('supportsContact')->willReturn(true); + $connector->method('getImageUrls')->willReturn(["url1"]); + + $connectorNoSupport = $this->createMock(ISocialProvider::class); + $connectorNoSupport->method('supportsContact')->willReturn(false); + + $connectorNoUrl = $this->createMock(ISocialProvider::class); + $connectorNoUrl->method('supportsContact')->willReturn(true); + $connectorNoUrl->method('getImageUrls')->willReturn([]); + + $addressbookEmpty = $this->createMock(IAddressBook::class); + $addressbookEmpty + ->method('getUri') + ->willReturn('contacts'); + $addressbookEmpty + ->method('search') + ->willReturn(null); + + $addressbook = $this->createMock(IAddressBook::class); + $addressbook + ->method('getUri') + ->willReturn('contacts'); + $addressbook + ->method('search') + ->willReturn([$contact]); + return [ - 'no social profiles set' => [null, 'someConnector', 'someResult', Http::STATUS_PRECONDITION_FAILED], - 'valid social profile' => [[['type' => 'someNetwork', 'value' => 'someId']], 'someConnector', 'someResult', Http::STATUS_OK], - 'bad formatted profile id' => [[['type' => 'someNetwork', 'value' => 'someId']], null, 'someResult', Http::STATUS_BAD_REQUEST], - 'not existing profile id' => [[['type' => 'someNetwork', 'value' => 'someId']], 'someConnector', '', Http::STATUS_NOT_FOUND], - 'unchanged data' => [[['type' => 'someNetwork', 'value' => 'someId']], 'someConnector', 'thePhoto', Http::STATUS_NOT_MODIFIED], + 'no address book found' => [null, [], "", "", Http::STATUS_BAD_REQUEST], + 'no contact found' => [[$addressbookEmpty], [], "", "", Http::STATUS_PRECONDITION_FAILED], + 'no supporting contacts found' => [[$addressbook], [$connectorNoSupport], "", "", Http::STATUS_PRECONDITION_FAILED], + 'no url found' => [[$addressbook], [$connectorNoUrl], "", "", Http::STATUS_BAD_REQUEST], + 'no image found' => [[$addressbook], [$connector], "", "", Http::STATUS_NOT_FOUND], + 'image found' => [[$addressbook], [$connector], $body, $imageType, Http::STATUS_OK] ]; } @@ -114,15 +149,54 @@ public function testDeactivatedSocial() { } /** - * @dataProvider socialProfileProvider + * @dataProvider allSocialProfileProviders */ - public function testUpdateContact($social, $connector, $httpResult, $expected) { + public function testUpdateContactWithoutNetwork($addressbooks, $providers, $body, $imageType, $status) { + $this->manager + ->method('getUserAddressBooks') + ->willReturn($addressbooks); + + $this->socialProvider + ->method('getSocialConnectors') + ->willReturn($providers); + + $response = $this->createMock(IResponse::class); + $response + ->method('getBody') + ->willReturn($body); + $response + ->method('getHeader') + ->willReturn($imageType); + $client = $this->createMock(IClient::class); + $client + ->method('get') + ->willReturn($response); + $this->clientService + ->method('NewClient') + ->willReturn($client); + + $result = $this->service + ->updateContact( + 'contacts', + '3225c0d5-1bd2-43e5-a08c-4e65eaa406b0', + null); + $this->assertEquals($status, $result->getStatus()); + } + + public function testUpdateContactWithNetwork() { + $network = "mastodon"; + $body = "the body"; + $imageType = "jpg"; + $addressBookId = "contacts"; + $contactId = "3225c0d5-1bd2-43e5-a08c-4e65eaa406b0"; $contact = [ - 'URI' => '3225c0d5-1bd2-43e5-a08c-4e65eaa406b0', - 'VERSION' => '4.0', - 'PHOTO' => "data:" . $httpResult . ";base64," . base64_encode('thePhoto'), - 'X-SOCIALPROFILE' => $social, + 'URI' => $contactId, + 'VERSION' => '4.0' ]; + $provider = $this->createMock(ISocialProvider::class); + $provider->method('supportsContact')->willReturn(true); + $provider->method('getImageUrls')->willReturn(["url1"]); + $addressbook = $this->createMock(IAddressBook::class); $addressbook ->method('getUri') @@ -135,17 +209,21 @@ public function testUpdateContact($social, $connector, $httpResult, $expected) { ->method('getUserAddressBooks') ->willReturn([$addressbook]); + $this->socialProvider + ->method('getSocialConnectors') + ->willReturn([$provider]); + $this->socialProvider ->method('getSocialConnector') - ->willReturn($connector); + ->willReturn($provider); $response = $this->createMock(IResponse::class); $response ->method('getBody') - ->willReturn($httpResult); + ->willReturn($body); $response ->method('getHeader') - ->willReturn($httpResult); + ->willReturn($imageType); $client = $this->createMock(IClient::class); $client ->method('get') @@ -154,9 +232,24 @@ public function testUpdateContact($social, $connector, $httpResult, $expected) { ->method('NewClient') ->willReturn($client); - $result = $this->service->updateContact('contacts', '3225c0d5-1bd2-43e5-a08c-4e65eaa406b0', 'theSocialNetwork'); + $changes = [ + 'URI' => $contact['URI'], + 'VERSION' => $contact['VERSION'], + 'PHOTO' => "data:".$imageType.";base64," . base64_encode($body) + ]; + + $this->socialProvider + ->expects($this->once())->method("getSocialConnector")->with($network); + $provider->expects($this->once())->method("supportsContact")->with($contact); + $addressbook->expects($this->once())->method("createOrUpdate")->with($changes, $addressBookId); - $this->assertEquals($expected, $result->getStatus()); + $result = $this->service + ->updateContact( + $addressBookId, + $contactId, + $network); + + $this->assertEquals(Http::STATUS_OK, $result->getStatus()); } protected function setupAddressbooks() { @@ -219,15 +312,33 @@ protected function setupAddressbooks() { ->method('getUserAddressBooks') ->willReturn([$addressbook1, $addressbook2]); - $socialConnectorMap = [ - [$validContact1['X-SOCIALPROFILE'], 'any', 'validConnector'], - [$validContact2['X-SOCIALPROFILE'], 'any', 'validConnector'], - [$invalidContact['X-SOCIALPROFILE'], 'any', 'invalidConnector'], - [$emptyContact['X-SOCIALPROFILE'], 'any', 'emptyConnector'], + $providerSupportsMap = [ + [$validContact1, true], + [$emptyContact, false], + [$invalidContact, false], + [$validContact2, true] ]; + + $providerUrlMap = [ + [$validContact1, ["url1"]], + [$emptyContact, []], + [$invalidContact, []], + [$validContact2, ["url1"]] + ]; + + $provider = $this->createMock(ISocialProvider::class); + $provider->method('getImageUrls') + ->will($this->returnValueMap($providerUrlMap)); + $provider->method('supportsContact') + ->will($this->returnValueMap($providerSupportsMap)); + + $this->socialProvider + ->method('getSocialConnectors') + ->willReturn([$provider]); + $this->socialProvider ->method('getSocialConnector') - ->will($this->returnValueMap($socialConnectorMap)); + ->willReturn($provider); $validResponse = $this->createMock(IResponse::class); $validResponse @@ -236,29 +347,16 @@ protected function setupAddressbooks() { $validResponse ->method('getHeader') ->willReturn('someHeader'); - $invalidResponse = $this->createMock(IResponse::class); - $invalidResponse - ->method('getBody') - ->willReturn(''); - $invalidResponse - ->method('getHeader') - ->willReturn(''); - $clientResponseMap = [ - ['validConnector', [], $validResponse], - ['invalidConnector', [], $invalidResponse], - ['emptyConnector', [], $invalidResponse], - ]; $client = $this->createMock(IClient::class); $client ->method('get') - ->will($this->returnValueMap($clientResponseMap)); + ->willReturn($validResponse); $this->clientService ->method('NewClient') ->willReturn($client); } - /** * @dataProvider updateAddressbookProvider */ @@ -287,9 +385,9 @@ public function testUpdateAddressbooks($syncAllowedByAdmin, $bgSyncEnabledByUser $this->assertArrayHasKey('checked', $report[0]); $this->assertContains('Valid Contact Two', $report[0]['checked']); $this->assertArrayHasKey('failed', $report[0]); - $this->assertArrayHasKey('404', $report[0]['failed']); - $this->assertContains('Invalid Contact', $report[0]['failed']['404']); - $this->assertNotContains('Empty Contact', $report[0]['failed']['404']); + $this->assertArrayHasKey('412', $report[0]['failed']); + $this->assertContains('Invalid Contact', $report[0]['failed']['412']); + $this->assertContains('Empty Contact', $report[0]['failed']['412']); } } From ca1c40a8ae2b388f5e923e70c9ac2c3af176e71d Mon Sep 17 00:00:00 2001 From: leith abdulla Date: Wed, 4 Nov 2020 11:04:41 -0800 Subject: [PATCH 3/4] unit tests for all providers this commit adds unit tests for all providers while also reducing some redundancy in looking up social fields minor feedback was addressed as well as some minor bugs fixed Signed-off-by: leith abdulla --- lib/Service/Social/DiasporaProvider.php | 15 +- lib/Service/Social/FacebookProvider.php | 35 ++-- lib/Service/Social/GravatarProvider.php | 32 +-- lib/Service/Social/InstagramProvider.php | 41 ++-- lib/Service/Social/MastodonProvider.php | 13 +- lib/Service/Social/TumblrProvider.php | 13 +- lib/Service/Social/TwitterProvider.php | 11 +- lib/Service/Social/XingProvider.php | 14 +- .../Service/Social/DiasporaProviderTest.php | 182 ++++++++++++++++++ .../Service/Social/FacebookProviderTest.php | 156 +++++++++++++++ .../Service/Social/GravatarProviderTest.php | 92 +++++++++ .../Service/Social/InstagramProviderTest.php | 158 +++++++++++++++ .../Service/Social/MastodonProviderTest.php | 154 +++++++++++++++ .../Service/Social/TumblrProviderTest.php | 100 ++++++++++ .../Service/Social/TwitterProviderTest.php | 154 +++++++++++++++ .../unit/Service/Social/XingProviderTest.php | 156 +++++++++++++++ 16 files changed, 1216 insertions(+), 110 deletions(-) create mode 100644 tests/unit/Service/Social/DiasporaProviderTest.php create mode 100644 tests/unit/Service/Social/FacebookProviderTest.php create mode 100644 tests/unit/Service/Social/GravatarProviderTest.php create mode 100644 tests/unit/Service/Social/InstagramProviderTest.php create mode 100644 tests/unit/Service/Social/MastodonProviderTest.php create mode 100644 tests/unit/Service/Social/TumblrProviderTest.php create mode 100644 tests/unit/Service/Social/TwitterProviderTest.php create mode 100644 tests/unit/Service/Social/XingProviderTest.php diff --git a/lib/Service/Social/DiasporaProvider.php b/lib/Service/Social/DiasporaProvider.php index 312690292..c0af278f6 100644 --- a/lib/Service/Social/DiasporaProvider.php +++ b/lib/Service/Social/DiasporaProvider.php @@ -49,17 +49,8 @@ public function __construct(IClientService $httpClient) { * @return bool */ 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; + $socialprofiles = $this->getProfileIds($contact); + return isset($socialprofiles) && count($socialprofiles) > 0; } /** @@ -150,7 +141,7 @@ protected function cleanupId(string $candidate):?string { $user_server = explode('@', $candidate); $candidate = 'https://' . array_pop($user_server) . '/public/' . array_pop($user_server) . '.atom'; } - } catch (Exception $e) { + } catch (\Exception $e) { $candidate = null; } return $candidate; diff --git a/lib/Service/Social/FacebookProvider.php b/lib/Service/Social/FacebookProvider.php index 0202e6d09..e5d34397a 100644 --- a/lib/Service/Social/FacebookProvider.php +++ b/lib/Service/Social/FacebookProvider.php @@ -45,17 +45,8 @@ public function __construct(IClientService $httpClient) { * @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; + $socialprofiles = $this->getProfiles($contact); + return isset($socialprofiles) && count($socialprofiles) > 0; } /** @@ -98,16 +89,32 @@ protected function cleanupId(string $candidate):string { * * @return array of string profile ids */ - protected function getProfileIds($contact):array { + protected function getProfiles(array $contact):array { $socialprofiles = $contact['X-SOCIALPROFILE']; - $profileIds = []; + $profiles = []; if (isset($socialprofiles)) { foreach ($socialprofiles as $profile) { if (strtolower($profile['type']) == $this->name) { - $profileIds[] = $this->cleanupId($profile['value']); + $profiles[] = $profile['value']; } } } + return $profiles; + } + + /** + * Returns all possible profile ids for contact + * + * @param {array} contact information + * + * @return array of string profile ids + */ + protected function getProfileIds(array $contact):array { + $profiles = $this->getProfiles($contact); + $profileIds = []; + foreach ($profiles as $profile) { + $profileIds[] = $this->cleanupId($profile); + } return $profileIds; } diff --git a/lib/Service/Social/GravatarProvider.php b/lib/Service/Social/GravatarProvider.php index 07b0f83ef..4b9d9a842 100644 --- a/lib/Service/Social/GravatarProvider.php +++ b/lib/Service/Social/GravatarProvider.php @@ -23,14 +23,11 @@ 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(); + public function __construct() { } /** @@ -53,29 +50,16 @@ public function supportsContact(array $contact):bool { * @return array */ public function getImageUrls(array $contact):array { - $emails = $this->getProfileIds($contact); $urls = []; - 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; + 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 []; + return $urls; } } diff --git a/lib/Service/Social/InstagramProvider.php b/lib/Service/Social/InstagramProvider.php index f0c593c71..a43d12c3d 100644 --- a/lib/Service/Social/InstagramProvider.php +++ b/lib/Service/Social/InstagramProvider.php @@ -45,17 +45,8 @@ public function __construct(IClientService $httpClient) { * @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; + $socialprofiles = $this->getProfiles($contact); + return isset($socialprofiles) && count($socialprofiles) > 0; } /** @@ -88,24 +79,40 @@ protected function cleanupId(string $candidate):string { $candidate = preg_replace('/^' . preg_quote('x-apple:', '/') . '/', '', $candidate); return basename($candidate); } - + /** - * Returns all possible profile ids for contact + * Returns all possible profile urls for contact * * @param {array} contact information * - * @return array of string profile ids + * @return array of string profile urls */ - protected function getProfileIds($contact):array { + protected function getProfiles($contact):array { $socialprofiles = $contact['X-SOCIALPROFILE']; - $profileIds = []; + $profiles = []; if (isset($socialprofiles)) { foreach ($socialprofiles as $profile) { if (strtolower($profile['type']) == $this->name) { - $profileIds[] = $this->cleanupId($profile['value']); + $profiles[] = $profile['value']; } } } + return $profiles; + } + + /** + * Returns all possible profile ids for contact + * + * @param {array} contact information + * + * @return array of string profile ids + */ + protected function getProfileIds($contact):array { + $socialprofiles = $this->getProfiles($contact); + $profileIds = []; + foreach ($socialprofiles as $profile) { + $profileIds[] = $this->cleanupId($profile); + } return $profileIds; } diff --git a/lib/Service/Social/MastodonProvider.php b/lib/Service/Social/MastodonProvider.php index b1bb5097a..a212433c7 100644 --- a/lib/Service/Social/MastodonProvider.php +++ b/lib/Service/Social/MastodonProvider.php @@ -45,17 +45,8 @@ public function __construct(IClientService $httpClient) { * @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; + $profiles = $this->getProfileIds($contact); + return isset($profiles) && count($profiles) > 0; } /** diff --git a/lib/Service/Social/TumblrProvider.php b/lib/Service/Social/TumblrProvider.php index c0d8ffcad..cd3d577f7 100644 --- a/lib/Service/Social/TumblrProvider.php +++ b/lib/Service/Social/TumblrProvider.php @@ -38,17 +38,8 @@ public function __construct() { * @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; + $socialprofiles = $this->getProfileIds($contact); + return isset($socialprofiles) && count($socialprofiles) > 0; } /** diff --git a/lib/Service/Social/TwitterProvider.php b/lib/Service/Social/TwitterProvider.php index 08aab855b..18cd0d467 100644 --- a/lib/Service/Social/TwitterProvider.php +++ b/lib/Service/Social/TwitterProvider.php @@ -44,15 +44,8 @@ public function __construct(IClientService $httpClient) { * @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; + $socialprofiles = $this->getProfileIds($contact); + return isset($socialprofiles) && count($socialprofiles) > 0; } /** diff --git a/lib/Service/Social/XingProvider.php b/lib/Service/Social/XingProvider.php index c0a816d98..a752d3c60 100644 --- a/lib/Service/Social/XingProvider.php +++ b/lib/Service/Social/XingProvider.php @@ -35,7 +35,6 @@ class XingProvider implements ISocialProvider { public function __construct(IClientService $httpClient) { $this->httpClient = $httpClient->NewClient(); - $this->looping = false; } /** @@ -46,17 +45,8 @@ public function __construct(IClientService $httpClient) { * @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; + $socialprofiles = $this->getProfileIds($contact); + return isset($socialprofiles) && count($socialprofiles) > 0; } /** diff --git a/tests/unit/Service/Social/DiasporaProviderTest.php b/tests/unit/Service/Social/DiasporaProviderTest.php new file mode 100644 index 000000000..e4b066c1c --- /dev/null +++ b/tests/unit/Service/Social/DiasporaProviderTest.php @@ -0,0 +1,182 @@ + + * + * @author Matthias Heinisch + * + * @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\IClient; +use OCP\Http\Client\IResponse; +use OCP\Http\Client\IClientService; +use ChristophWurst\Nextcloud\Testing\TestCase; +use PHPUnit\Framework\MockObject\MockObject; + +class DiasporaProviderTest extends TestCase { + private $provider; + + /** @var IClientService|MockObject */ + private $clientService; + + /** @var IClient|MockObject */ + private $client; + + /** @var IResponse|MockObject */ + private $response; + + protected function setUp(): void { + parent::setUp(); + $this->clientService = $this->createMock(IClientService::class); + $this->response = $this->createMock(IResponse::class); + $this->client = $this->createMock(IClient::class); + + $this->clientService + ->method('NewClient') + ->willReturn($this->client); + + $this->provider = new DiasporaProvider( + $this->clientService + ); + } + + public function dataProviderSupportsContact() { + $contactWithSocial = [ + 'X-SOCIALPROFILE' => [ + ["value" => "one", "type" => "diaspora"], + ["value" => "two", "type" => "diaspora"] + ] + ]; + + $contactWithoutSocial = [ + 'X-SOCIALPROFILE' => [ + ["value" => "one", "type" => "social2"], + ["value" => "two", "type" => "social1"] + ] + ]; + + return [ + 'contact with diaspora fields' => [$contactWithSocial, true], + 'contact without diaspora fields' => [$contactWithoutSocial, false] + ]; + } + + /** + * @dataProvider dataProviderSupportsContact + */ + public function testSupportsContact($contact, $expected) { + $result = $this->provider->supportsContact($contact); + $this->assertEquals($expected, $result); + } + + public function dataProviderGetImageUrls() { + $contactWithSocial = [ + 'X-SOCIALPROFILE' => [ + ["value" => "one@two", "type" => "diaspora"], + ["value" => "two@three", "type" => "diaspora"] + ] + ]; + $contactWithSocialUrls = [ + "https://two/public/one.atom", + "https://three/public/two.atom" + ]; + $contactWithSocialHtml = array_map(function ($url) { + return "".$url."-small-avatar.jpg"; + }, $contactWithSocialUrls); + $contactWithSocialImg = array_map(function ($url) { + return $url."-large-avatar.jpg"; + }, $contactWithSocialUrls); + + $contactWithoutSocial = [ + 'X-SOCIALPROFILE' => [ + ["value" => "one", "type" => "social2"], + ["value" => "two", "type" => "social1"] + ] + ]; + $contactWithoutSocialUrls = []; + $contactWithoutSocialHtml = []; + $contactWithoutSocialImg = []; + + return [ + 'contact with diaspora fields' => [ + $contactWithSocial, + $contactWithSocialUrls, + $contactWithSocialHtml, + $contactWithSocialImg + ], + 'contact without diaspora fields' => [ + $contactWithoutSocial, + $contactWithoutSocialUrls, + $contactWithoutSocialHtml, + $contactWithoutSocialImg + ] + ]; + } + + /** + * @dataProvider dataProviderGetImageUrls + */ + public function testGetImageUrls($contact, $urls, $htmls, $imgs) { + if (count($urls)) { + $this->response + ->method('getBody') + ->willReturnOnConsecutiveCalls(...$htmls); + + $urlArgs = array_map(function ($url) { + return [$url]; + }, $urls); + + $this->client + ->expects($this->exactly(count($urls))) + ->method('get') + ->withConsecutive(...$urlArgs) + ->willReturn($this->response); + } + + $result = $this->provider->getImageUrls($contact); + $this->assertEquals($imgs, $result); + } + + public function testGetImageUrlLoop() { + $contact = [ + 'X-SOCIALPROFILE' => [ + ["value" => "one@two", "type" => "diaspora"], + ] + ]; + $url1 = "https://two/public/one.atom"; + $url2 = "https://four/public/three.atom"; + $html1 = ''; + $html2 = "".$url2."-small-avatar.jpg"; + $img = $url2."-large-avatar.jpg"; + + $this->response + ->method('getBody') + ->willReturnOnConsecutiveCalls($html1, $html2); + + $this->client + ->expects($this->exactly(2)) + ->method('get') + ->withConsecutive([$url1], [$url2]) + ->willReturn($this->response); + + $result = $this->provider->getImageUrls($contact); + $this->assertEquals([$img], $result); + } +} diff --git a/tests/unit/Service/Social/FacebookProviderTest.php b/tests/unit/Service/Social/FacebookProviderTest.php new file mode 100644 index 000000000..b2e3d8e0b --- /dev/null +++ b/tests/unit/Service/Social/FacebookProviderTest.php @@ -0,0 +1,156 @@ + + * + * @author Matthias Heinisch + * + * @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\IClient; +use OCP\Http\Client\IResponse; +use OCP\Http\Client\IClientService; +use ChristophWurst\Nextcloud\Testing\TestCase; +use PHPUnit\Framework\MockObject\MockObject; + +class FacebookProviderTest extends TestCase { + private $provider; + + /** @var IClientService|MockObject */ + private $clientService; + + /** @var IClient|MockObject */ + private $client; + + /** @var IResponse|MockObject */ + private $response; + + protected function setUp(): void { + parent::setUp(); + $this->clientService = $this->createMock(IClientService::class); + $this->response = $this->createMock(IResponse::class); + $this->client = $this->createMock(IClient::class); + + $this->clientService + ->method('NewClient') + ->willReturn($this->client); + + $this->provider = new FacebookProvider( + $this->clientService + ); + } + + public function dataProviderSupportsContact() { + $contactWithSocial = [ + 'X-SOCIALPROFILE' => [ + ["value" => "123124123", "type" => "facebook"], + ["value" => "23426523423", "type" => "facebook"] + ] + ]; + + $contactWithoutSocial = [ + 'X-SOCIALPROFILE' => [ + ["value" => "one", "type" => "social2"], + ["value" => "two", "type" => "social1"] + ] + ]; + + return [ + 'contact with facebook fields' => [$contactWithSocial, true], + 'contact without facebook fields' => [$contactWithoutSocial, false] + ]; + } + + /** + * @dataProvider dataProviderSupportsContact + */ + public function testSupportsContact($contact, $expected) { + $result = $this->provider->supportsContact($contact); + $this->assertEquals($expected, $result); + } + + public function dataProviderGetImageUrls() { + $contactWithSocial = [ + 'X-SOCIALPROFILE' => [ + ["value" => "123456", "type" => "facebook"], + ["value" => "7891011", "type" => "facebook"] + ] + ]; + $contactWithSocialUrls = [ + "https://graph.facebook.com/123456/picture?width=720", + "https://graph.facebook.com/7891011/picture?width=720", + ]; + + $contactWithoutSocial = [ + 'X-SOCIALPROFILE' => [ + ["value" => "one", "type" => "social2"], + ["value" => "two", "type" => "social1"] + ] + ]; + $contactWithoutSocialUrls = []; + + return [ + 'contact with facebook fields' => [ + $contactWithSocial, + $contactWithSocialUrls + ], + 'contact without facebook fields' => [ + $contactWithoutSocial, + $contactWithoutSocialUrls + ] + ]; + } + + /** + * @dataProvider dataProviderGetImageUrls + */ + public function testGetImageUrls($contact, $urls) { + $result = $this->provider->getImageUrls($contact); + $this->assertEquals($urls, $result); + } + + public function testGetImageUrlLookup() { + $contact = [ + 'X-SOCIALPROFILE' => [ + ["value" => "username1", "type" => "facebook"], + ] + ]; + $url1 = "https://facebook.com/username1"; + $url2 = "https://graph.facebook.com/1234567/picture?width=720"; + $html1 = '"entity_id":"1234567"'; + + $this->response + ->method('getBody') + ->willReturn($html1); + + $this->response + ->method('getStatusCode') + ->willReturn(200); + + $this->client + ->expects($this->once()) + ->method('get') + ->with($url1) + ->willReturn($this->response); + + $result = $this->provider->getImageUrls($contact); + $this->assertEquals([$url2], $result); + } +} diff --git a/tests/unit/Service/Social/GravatarProviderTest.php b/tests/unit/Service/Social/GravatarProviderTest.php new file mode 100644 index 000000000..8de4f5656 --- /dev/null +++ b/tests/unit/Service/Social/GravatarProviderTest.php @@ -0,0 +1,92 @@ + + * + * @author Matthias Heinisch + * + * @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 ChristophWurst\Nextcloud\Testing\TestCase; + +class GravatarProviderTest extends TestCase { + private $provider; + + protected function setUp(): void { + parent::setUp(); + + $this->provider = new GravatarProvider( + ); + } + + public function dataProviderSupportsContact() { + $contactWithEmail = [ + 'EMAIL' => [["value" => "one"], ["value" => "two"]] + ]; + + $contactWithoutEmail = [ + 'PHONE' => [["value" => "one"], ["value" => "two"]] + ]; + + return [ + 'contact with email' => [$contactWithEmail, true], + 'contact without email' => [$contactWithoutEmail, false] + ]; + } + + /** + * @dataProvider dataProviderSupportsContact + */ + public function testSupportsContact($contact, $expected) { + $result = $this->provider->supportsContact($contact); + $this->assertEquals($expected, $result); + } + + public function dataProviderGetImageUrls() { + $contactWithEmail = [ + 'EMAIL' => [["value" => "one"], ["value" => "two"]] + ]; + + $contactWithoutEmail = [ + 'PHONE' => [["value" => "one"], ["value" => "two"]] + ]; + + $urls = []; + + foreach ($contactWithEmail['EMAIL'] as $email) { + $hash = md5(strtolower(trim($email['value']))); + $recipe = 'https://www.gravatar.com/avatar/{hash}?s=720&d=404'; + $urls[] = str_replace("{hash}", $hash, $recipe); + } + + return [ + 'contact with email' => [$contactWithEmail, $urls], + 'contact without email' => [$contactWithoutEmail, []] + ]; + } + + /** + * @dataProvider dataProviderGetImageUrls + */ + public function testGetImageUrls($contact, $expected) { + $result = $this->provider->getImageUrls($contact); + $this->assertEquals($expected, $result); + } +} diff --git a/tests/unit/Service/Social/InstagramProviderTest.php b/tests/unit/Service/Social/InstagramProviderTest.php new file mode 100644 index 000000000..6fa0e8668 --- /dev/null +++ b/tests/unit/Service/Social/InstagramProviderTest.php @@ -0,0 +1,158 @@ + + * + * @author Matthias Heinisch + * + * @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\IClient; +use OCP\Http\Client\IResponse; +use OCP\Http\Client\IClientService; +use ChristophWurst\Nextcloud\Testing\TestCase; +use PHPUnit\Framework\MockObject\MockObject; + +class InstagramProviderTest extends TestCase { + private $provider; + + /** @var IClientService|MockObject */ + private $clientService; + + /** @var IClient|MockObject */ + private $client; + + /** @var IResponse|MockObject */ + private $response; + + protected function setUp(): void { + parent::setUp(); + $this->clientService = $this->createMock(IClientService::class); + $this->response = $this->createMock(IResponse::class); + $this->client = $this->createMock(IClient::class); + + $this->clientService + ->method('NewClient') + ->willReturn($this->client); + + $this->provider = new InstagramProvider( + $this->clientService + ); + } + + public function dataProviderSupportsContact() { + $contactWithSocial = [ + 'X-SOCIALPROFILE' => [ + ["value" => "username1", "type" => "instagram"], + ["value" => "username2", "type" => "instagram"] + ] + ]; + + $contactWithoutSocial = [ + 'X-SOCIALPROFILE' => [ + ["value" => "one", "type" => "social2"], + ["value" => "two", "type" => "social1"] + ] + ]; + + return [ + 'contact with instagram fields' => [$contactWithSocial, true], + 'contact without instagram fields' => [$contactWithoutSocial, false] + ]; + } + + /** + * @dataProvider dataProviderSupportsContact + */ + public function testSupportsContact($contact, $expected) { + $result = $this->provider->supportsContact($contact); + $this->assertEquals($expected, $result); + } + + public function dataProviderGetImageUrls() { + $contactWithSocial = [ + 'X-SOCIALPROFILE' => [ + ["value" => "username1", "type" => "instagram"], + ["value" => "username2", "type" => "instagram"] + ] + ]; + $contactWithSocialUrls = [ + "https://www.instagram.com/username1/?__a=1", + "https://www.instagram.com/username2/?__a=1", + ]; + $contactWithSocialJson = [ + json_encode( + ["graphql" => ["user" => ["profile_pic_url_hd" => "username1.jpg"]]] + ), + json_encode( + ["graphql" => ["user" => ["profile_pic_url_hd" => "username2.jpg"]]] + ) + ]; + $contactWithSocialImgs = [ + "username1.jpg", + "username2.jpg" + ]; + + $contactWithoutSocial = [ + 'X-SOCIALPROFILE' => [ + ["value" => "one", "type" => "social2"], + ["value" => "two", "type" => "social1"] + ] + ]; + $contactWithoutSocialUrls = []; + $contactWithoutSocialJson = []; + $contactWithoutSocialImgs = []; + + return [ + 'contact with instagram fields' => [ + $contactWithSocial, + $contactWithSocialJson, + $contactWithSocialUrls, + $contactWithSocialImgs + ], + 'contact without instagram fields' => [ + $contactWithoutSocial, + $contactWithoutSocialJson, + $contactWithoutSocialUrls, + $contactWithoutSocialImgs + ] + ]; + } + + /** + * @dataProvider dataProviderGetImageUrls + */ + public function testGetImageUrls($contact, $json, $urls, $imgs) { + if (count($urls)) { + $this->response->method("getBody")->willReturnOnConsecutiveCalls(...$json); + $this->client + ->expects($this->exactly(count($urls))) + ->method("get") + ->withConsecutive(...array_map(function ($a) { + return [$a]; + }, $urls)) + ->willReturn($this->response); + } + + + $result = $this->provider->getImageUrls($contact); + $this->assertEquals($imgs, $result); + } +} diff --git a/tests/unit/Service/Social/MastodonProviderTest.php b/tests/unit/Service/Social/MastodonProviderTest.php new file mode 100644 index 000000000..9611bf6b3 --- /dev/null +++ b/tests/unit/Service/Social/MastodonProviderTest.php @@ -0,0 +1,154 @@ + + * + * @author Matthias Heinisch + * + * @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\IClient; +use OCP\Http\Client\IResponse; +use OCP\Http\Client\IClientService; +use ChristophWurst\Nextcloud\Testing\TestCase; +use PHPUnit\Framework\MockObject\MockObject; + +class MastodonProviderTest extends TestCase { + private $provider; + + /** @var IClientService|MockObject */ + private $clientService; + + /** @var IClient|MockObject */ + private $client; + + /** @var IResponse|MockObject */ + private $response; + + protected function setUp(): void { + parent::setUp(); + $this->clientService = $this->createMock(IClientService::class); + $this->response = $this->createMock(IResponse::class); + $this->client = $this->createMock(IClient::class); + + $this->clientService + ->method('NewClient') + ->willReturn($this->client); + + $this->provider = new MastodonProvider( + $this->clientService + ); + } + + public function dataProviderSupportsContact() { + $contactWithSocial = [ + 'X-SOCIALPROFILE' => [ + ["value" => "user1@cloud1", "type" => "mastodon"], + ["value" => "user2@cloud2", "type" => "mastodon"] + ] + ]; + + $contactWithoutSocial = [ + 'X-SOCIALPROFILE' => [ + ["value" => "one", "type" => "social2"], + ["value" => "two", "type" => "social1"] + ] + ]; + + return [ + 'contact with mastodon fields' => [$contactWithSocial, true], + 'contact without mastodon fields' => [$contactWithoutSocial, false] + ]; + } + + /** + * @dataProvider dataProviderSupportsContact + */ + public function testSupportsContact($contact, $expected) { + $result = $this->provider->supportsContact($contact); + $this->assertEquals($expected, $result); + } + + public function dataProviderGetImageUrls() { + $contactWithSocial = [ + 'X-SOCIALPROFILE' => [ + ["value" => "user1@cloud1", "type" => "mastodon"], + ["value" => "user2@cloud2", "type" => "mastodon"] + ] + ]; + $contactWithSocialUrls = [ + "https://cloud1/@user1", + "https://cloud2/@user2", + ]; + $contactWithSocialHtml = [ + '', + '' + ]; + $contactWithSocialImgs = [ + "user1.jpg", + "user2.jpg" + ]; + + $contactWithoutSocial = [ + 'X-SOCIALPROFILE' => [ + ["value" => "one", "type" => "social2"], + ["value" => "two", "type" => "social1"] + ] + ]; + $contactWithoutSocialUrls = []; + $contactWithoutSocialHtml = []; + $contactWithoutSocialImgs = []; + + return [ + 'contact with mastodon fields' => [ + $contactWithSocial, + $contactWithSocialHtml, + $contactWithSocialUrls, + $contactWithSocialImgs + ], + 'contact without mastodon fields' => [ + $contactWithoutSocial, + $contactWithoutSocialHtml, + $contactWithoutSocialUrls, + $contactWithoutSocialImgs + ] + ]; + } + + /** + * @dataProvider dataProviderGetImageUrls + */ + public function testGetImageUrls($contact, $htmls, $urls, $imgs) { + if (count($urls)) { + $this->response->method("getBody")->willReturnOnConsecutiveCalls(...$htmls); + $this->client + ->expects($this->exactly(count($urls))) + ->method("get") + ->withConsecutive(...array_map(function ($a) { + return [$a]; + }, $urls)) + ->willReturn($this->response); + } + + + $result = $this->provider->getImageUrls($contact); + $this->assertEquals($imgs, $result); + } +} diff --git a/tests/unit/Service/Social/TumblrProviderTest.php b/tests/unit/Service/Social/TumblrProviderTest.php new file mode 100644 index 000000000..79e1c931f --- /dev/null +++ b/tests/unit/Service/Social/TumblrProviderTest.php @@ -0,0 +1,100 @@ + + * + * @author Matthias Heinisch + * + * @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 ChristophWurst\Nextcloud\Testing\TestCase; + +class TumblrProviderTest extends TestCase { + private $provider; + + protected function setUp(): void { + parent::setUp(); + + $this->provider = new TumblrProvider( + ); + } + + public function dataProviderSupportsContact() { + $contactWithSocial = [ + 'X-SOCIALPROFILE' => [ + ["value" => "username1", "type" => "tumblr"], + ["value" => "username2", "type" => "tumblr"] + ] + ]; + + $contactWithoutSocial = [ + 'X-SOCIALPROFILE' => [ + ["value" => "one", "type" => "social2"], + ["value" => "two", "type" => "social1"] + ] + ]; + + return [ + 'contact with email' => [$contactWithSocial, true], + 'contact without email' => [$contactWithoutSocial, false] + ]; + } + + /** + * @dataProvider dataProviderSupportsContact + */ + public function testSupportsContact($contact, $expected) { + $result = $this->provider->supportsContact($contact); + $this->assertEquals($expected, $result); + } + + public function dataProviderGetImageUrls() { + $contactWithSocial = [ + 'X-SOCIALPROFILE' => [ + ["value" => "username1", "type" => "tumblr"], + ["value" => "username2", "type" => "tumblr"] + ] + ]; + + $contactWithoutSocial = [ + 'X-SOCIALPROFILE' => [ + ["value" => "one", "type" => "social2"], + ["value" => "two", "type" => "social1"] + ] + ]; + + foreach ($contactWithSocial['X-SOCIALPROFILE'] as $profile) { + $urls[] = "https://api.tumblr.com/v2/blog/".$profile['value']."/avatar/512"; + } + + return [ + 'contact with email' => [$contactWithSocial, $urls], + 'contact without email' => [$contactWithoutSocial, []] + ]; + } + + /** + * @dataProvider dataProviderGetImageUrls + */ + public function testGetImageUrls($contact, $expected) { + $result = $this->provider->getImageUrls($contact); + $this->assertEquals($expected, $result); + } +} diff --git a/tests/unit/Service/Social/TwitterProviderTest.php b/tests/unit/Service/Social/TwitterProviderTest.php new file mode 100644 index 000000000..55515b0ff --- /dev/null +++ b/tests/unit/Service/Social/TwitterProviderTest.php @@ -0,0 +1,154 @@ + + * + * @author Matthias Heinisch + * + * @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\IClient; +use OCP\Http\Client\IResponse; +use OCP\Http\Client\IClientService; +use ChristophWurst\Nextcloud\Testing\TestCase; +use PHPUnit\Framework\MockObject\MockObject; + +class TwitterProviderTest extends TestCase { + private $provider; + + /** @var IClientService|MockObject */ + private $clientService; + + /** @var IClient|MockObject */ + private $client; + + /** @var IResponse|MockObject */ + private $response; + + protected function setUp(): void { + parent::setUp(); + $this->clientService = $this->createMock(IClientService::class); + $this->response = $this->createMock(IResponse::class); + $this->client = $this->createMock(IClient::class); + + $this->clientService + ->method('NewClient') + ->willReturn($this->client); + + $this->provider = new TwitterProvider( + $this->clientService + ); + } + + public function dataProviderSupportsContact() { + $contactWithSocial = [ + 'X-SOCIALPROFILE' => [ + ["value" => "username1", "type" => "twitter"], + ["value" => "username2", "type" => "twitter"] + ] + ]; + + $contactWithoutSocial = [ + 'X-SOCIALPROFILE' => [ + ["value" => "one", "type" => "social2"], + ["value" => "two", "type" => "social1"] + ] + ]; + + return [ + 'contact with twitter fields' => [$contactWithSocial, true], + 'contact without twitter fields' => [$contactWithoutSocial, false] + ]; + } + + /** + * @dataProvider dataProviderSupportsContact + */ + public function testSupportsContact($contact, $expected) { + $result = $this->provider->supportsContact($contact); + $this->assertEquals($expected, $result); + } + + public function dataProviderGetImageUrls() { + $contactWithSocial = [ + 'X-SOCIALPROFILE' => [ + ["value" => "https://twitter.com/username1", "type" => "twitter"], + ["value" => "https://twitter.com/@username2", "type" => "twitter"] + ] + ]; + $contactWithSocialUrls = [ + "https://mobile.twitter.com/username1", + "https://mobile.twitter.com/username2", + ]; + $contactWithSocialHtml = [ + '', + '', + ]; + $contactWithSocialImgs = [ + "username1_400x400.jpg", + "username2_400x400.jpg" + ]; + + $contactWithoutSocial = [ + 'X-SOCIALPROFILE' => [ + ["value" => "one", "type" => "social2"], + ["value" => "two", "type" => "social1"] + ] + ]; + $contactWithoutSocialUrls = []; + $contactWithoutSocialHtml = []; + $contactWithoutSocialImgs = []; + + return [ + 'contact with twitter fields' => [ + $contactWithSocial, + $contactWithSocialHtml, + $contactWithSocialUrls, + $contactWithSocialImgs + ], + 'contact without twitter fields' => [ + $contactWithoutSocial, + $contactWithoutSocialHtml, + $contactWithoutSocialUrls, + $contactWithoutSocialImgs + ] + ]; + } + + /** + * @dataProvider dataProviderGetImageUrls + */ + public function testGetImageUrls($contact, $htmls, $urls, $imgs) { + if (count($urls)) { + $this->response->method("getBody")->willReturnOnConsecutiveCalls(...$htmls); + $this->client + ->expects($this->exactly(count($urls))) + ->method("get") + ->withConsecutive(...array_map(function ($a) { + return [$a]; + }, $urls)) + ->willReturn($this->response); + } + + + $result = $this->provider->getImageUrls($contact); + $this->assertEquals($imgs, $result); + } +} diff --git a/tests/unit/Service/Social/XingProviderTest.php b/tests/unit/Service/Social/XingProviderTest.php new file mode 100644 index 000000000..cb417346d --- /dev/null +++ b/tests/unit/Service/Social/XingProviderTest.php @@ -0,0 +1,156 @@ + + * + * @author Matthias Heinisch + * + * @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\IClient; +use OCP\Http\Client\IResponse; +use OCP\Http\Client\IClientService; +use ChristophWurst\Nextcloud\Testing\TestCase; +use PHPUnit\Framework\MockObject\MockObject; + +class XingProviderTest extends TestCase { + private $provider; + + /** @var IClientService|MockObject */ + private $clientService; + + /** @var IClient|MockObject */ + private $client; + + /** @var IResponse|MockObject */ + private $response; + + protected function setUp(): void { + parent::setUp(); + $this->clientService = $this->createMock(IClientService::class); + $this->response = $this->createMock(IResponse::class); + $this->client = $this->createMock(IClient::class); + + $this->clientService + ->method('NewClient') + ->willReturn($this->client); + + $this->provider = new XingProvider( + $this->clientService + ); + } + + public function dataProviderSupportsContact() { + $contactWithSocial = [ + 'X-SOCIALPROFILE' => [ + ["value" => "username1", "type" => "xing"], + ["value" => "username2", "type" => "xing"] + ] + ]; + + $contactWithoutSocial = [ + 'X-SOCIALPROFILE' => [ + ["value" => "one", "type" => "social2"], + ["value" => "two", "type" => "social1"] + ] + ]; + + return [ + 'contact with xing fields' => [$contactWithSocial, true], + 'contact without xing fields' => [$contactWithoutSocial, false] + ]; + } + + /** + * @dataProvider dataProviderSupportsContact + */ + public function testSupportsContact($contact, $expected) { + $result = $this->provider->supportsContact($contact); + $this->assertEquals($expected, $result); + } + + public function dataProviderGetImageUrls() { + $contactWithSocial = [ + 'X-SOCIALPROFILE' => [ + ["value" => "username1", "type" => "xing"], + ["value" => "username2", "type" => "xing"] + ] + ]; + $contactWithSocialUrls = [ + "https://www.xing.com/profile/username1", + "https://www.xing.com/profile/username2" + ]; + $contactWithSocialHtml = array_map(function ($profile) { + return ''; + }, $contactWithSocial['X-SOCIALPROFILE']); + $contactWithSocialImg = array_map(function ($profile) { + return 'https://profile-images-abc'.$profile['value'].".jpg"; + }, $contactWithSocial['X-SOCIALPROFILE']); + + $contactWithoutSocial = [ + 'X-SOCIALPROFILE' => [ + ["value" => "one", "type" => "social2"], + ["value" => "two", "type" => "social1"] + ] + ]; + $contactWithoutSocialUrls = []; + $contactWithoutSocialHtml = []; + $contactWithoutSocialImg = []; + + return [ + 'contact with xing fields' => [ + $contactWithSocial, + $contactWithSocialUrls, + $contactWithSocialHtml, + $contactWithSocialImg + ], + 'contact without xing fields' => [ + $contactWithoutSocial, + $contactWithoutSocialUrls, + $contactWithoutSocialHtml, + $contactWithoutSocialImg + ] + ]; + } + + /** + * @dataProvider dataProviderGetImageUrls + */ + public function testGetImageUrls($contact, $urls, $htmls, $imgs) { + if (count($urls)) { + $this->response + ->method('getBody') + ->willReturnOnConsecutiveCalls(...$htmls); + + $urlArgs = array_map(function ($url) { + return [$url]; + }, $urls); + + $this->client + ->expects($this->exactly(count($urls))) + ->method('get') + ->withConsecutive(...$urlArgs) + ->willReturn($this->response); + } + + $result = $this->provider->getImageUrls($contact); + $this->assertEquals($imgs, $result); + } +} From ffddb704dbc5c653bd2c8b4914898f7f8a2585d6 Mon Sep 17 00:00:00 2001 From: leith abdulla Date: Wed, 4 Nov 2020 19:57:46 +0000 Subject: [PATCH 4/4] add gravatar logo use a gravatar logo instead of a wordpress logo Signed-off-by: leith abdulla --- css/icons.scss | 2 +- img/gravatar.svg | 2 +- img/license.txt | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/css/icons.scss b/css/icons.scss index 76945ad4d..853289d5a 100644 --- a/css/icons.scss +++ b/css/icons.scss @@ -41,7 +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) +@include icon-black-white('gravatar', 'contacts', 2); // “gravatar (fab)” by svgrepo.com is licensed under public domain CCO 1.0. (https://www.svgrepo.com/page/licensing) .icon-up-force-white { // using #fffffe to trick the accessibility dark theme icon invert diff --git a/img/gravatar.svg b/img/gravatar.svg index 00a96cb01..9b20de269 100644 --- a/img/gravatar.svg +++ b/img/gravatar.svg @@ -1 +1 @@ - \ No newline at end of file +Gravatar icon \ No newline at end of file diff --git a/img/license.txt b/img/license.txt index 1022843e4..a1c9cbaa4 100644 --- a/img/license.txt +++ b/img/license.txt @@ -6,3 +6,5 @@ * “twitter (fab)” by fontawesome.com is licensed under CC BY 4.0. (https://fontawesome.com/icons/twitter?style=brands) * “diaspora (fab)” by fontawesome.com is licensed under CC BY 4.0. (https://fontawesome.com/icons/diaspora?style=brands) * “xing (fab)” by fontawesome.com is licensed under CC BY 4.0. (https://fontawesome.com/icons/xing?style=brands) +* “gravatar (fab)” by svgrepo.com is licensed under public domain CCO 1.0. (https://www.svgrepo.com/page/licensing) +