diff --git a/e107_handlers/vendor/hybridauth/hybridauth/src/Provider/QQ.php b/e107_handlers/vendor/hybridauth/hybridauth/src/Provider/QQ.php
new file mode 100644
index 000000000..c0107c77b
--- /dev/null
+++ b/e107_handlers/vendor/hybridauth/hybridauth/src/Provider/QQ.php
@@ -0,0 +1,134 @@
+tokenRefreshParameters = [
+ 'grant_type' => 'refresh_token',
+ 'client_id' => $this->clientId,
+ 'client_secret' => $this->clientSecret,
+ 'refresh_token' => $this->getStoredData('refresh_token'),
+ ];
+
+ $this->apiRequestParameters = [
+ 'access_token' => $this->getStoredData('access_token')
+ ];
+
+ $this->apiRequestHeaders = [];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function validateAccessTokenExchange($response)
+ {
+ $collection = parent::validateAccessTokenExchange($response);
+
+ $resp = $this->apiRequest($this->accessTokenInfoUrl);
+ $resp = key($resp);
+
+ $len = strlen($resp);
+ $res = substr($resp, 10, $len - 14);
+
+ $response = (new Data\Parser())->parse($res);
+
+ if (!isset($response->openid)) {
+ throw new UnexpectedApiResponseException('Provider API returned an unexpected response.');
+ }
+
+ $this->storeData('openid', $response->openid);
+
+ return $collection;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getUserProfile()
+ {
+ $openid = $this->getStoredData('openid');
+
+ $userRequestParameters = [
+ 'oauth_consumer_key' => $this->clientId,
+ 'openid' => $openid,
+ 'format' => 'json'
+ ];
+
+ $response = $this->apiRequest($this->accessUserInfo, 'GET', $userRequestParameters);
+
+ $data = new Data\Collection($response);
+
+ if ($data->get('ret') < 0) {
+ throw new UnexpectedApiResponseException('Provider API returned an error: ' . $data->get('msg'));
+ }
+
+ $userProfile = new Profile();
+
+ $userProfile->identifier = $openid;
+ $userProfile->displayName = $data->get('nickname');
+ $userProfile->photoURL = $data->get('figureurl_2');
+ $userProfile->gender = $data->get('gender');
+ $userProfile->region = $data->get('province');
+ $userProfile->city = $data->get('city');
+
+ return $userProfile;
+ }
+}
diff --git a/e107_handlers/vendor/hybridauth/hybridauth/src/Provider/Slack.php b/e107_handlers/vendor/hybridauth/hybridauth/src/Provider/Slack.php
new file mode 100644
index 000000000..4d6c34e24
--- /dev/null
+++ b/e107_handlers/vendor/hybridauth/hybridauth/src/Provider/Slack.php
@@ -0,0 +1,101 @@
+apiRequest('api/users.identity');
+
+ $data = new Data\Collection($response);
+
+ if (!$data->exists('ok') || !$data->get('ok')) {
+ throw new UnexpectedApiResponseException('Provider API returned an unexpected response.');
+ }
+
+ $userProfile = new User\Profile();
+
+ $userProfile->identifier = $data->filter('user')->get('id');
+ $userProfile->displayName = $data->filter('user')->get('name');
+ $userProfile->email = $data->filter('user')->get('email');
+ $userProfile->photoURL = $this->findLargestImage($data);
+
+ return $userProfile;
+ }
+
+ /**
+ * Returns the url of the image with the highest resolution in the user
+ * object.
+ *
+ * Slack sends multiple image urls with different resolutions. As they make
+ * no guarantees which resolutions will be included we have to search all
+ * image_*
properties for the one with the highest resolution.
+ * The resolution is attached to the property name such as
+ * image_32
or image_192
.
+ *
+ * @param Data\Collection $data response object as returned by
+ * api/users.identity
+ *
+ * @return string|null the value of the image_*
property with
+ * the highest resolution.
+ */
+ private function findLargestImage(Data\Collection $data)
+ {
+ $maxSize = 0;
+ foreach ($data->filter('user')->properties() as $property) {
+ if (preg_match('/^image_(\d+)$/', $property, $matches) === 1) {
+ $availableSize = (int)$matches[1];
+ if ($maxSize < $availableSize) {
+ $maxSize = $availableSize;
+ }
+ }
+ }
+ if ($maxSize > 0) {
+ return $data->filter('user')->get('image_' . $maxSize);
+ }
+ return null;
+ }
+}
diff --git a/e107_handlers/vendor/hybridauth/hybridauth/src/Provider/Strava.php b/e107_handlers/vendor/hybridauth/hybridauth/src/Provider/Strava.php
new file mode 100644
index 000000000..336a7bc9f
--- /dev/null
+++ b/e107_handlers/vendor/hybridauth/hybridauth/src/Provider/Strava.php
@@ -0,0 +1,72 @@
+apiRequest('athlete');
+
+ $data = new Data\Collection($response);
+
+ if (! $data->exists('id')) {
+ throw new UnexpectedApiResponseException('Provider API returned an unexpected response.');
+ }
+
+ $userProfile = new User\Profile();
+
+ $userProfile->identifier = $data->get('id');
+ $userProfile->firstName = $data->get('firstname');
+ $userProfile->lastName = $data->get('lastname');
+ $userProfile->gender = $data->get('sex');
+ $userProfile->country = $data->get('country');
+ $userProfile->city = $data->get('city');
+ $userProfile->email = $data->get('email');
+
+ $userProfile->displayName = $userProfile->displayName ?: $data->get('username');
+
+ return $userProfile;
+ }
+}