1
0
mirror of https://github.com/e107inc/e107.git synced 2025-08-30 01:30:32 +02:00

HybridAuth update

This commit is contained in:
Cameron
2020-03-04 16:38:22 -08:00
parent 9a2f979551
commit a3d99f0d19
44 changed files with 383 additions and 288 deletions

17
composer.lock generated
View File

@@ -8,16 +8,16 @@
"packages": [ "packages": [
{ {
"name": "hybridauth/hybridauth", "name": "hybridauth/hybridauth",
"version": "v3.1.1", "version": "3.2.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/hybridauth/hybridauth.git", "url": "https://github.com/hybridauth/hybridauth.git",
"reference": "020be6991e7ae9f1ffaabae6586245d2a9626273" "reference": "2edf92f07b94fcc9e17ea14e2a1644b83981af7d"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/hybridauth/hybridauth/zipball/020be6991e7ae9f1ffaabae6586245d2a9626273", "url": "https://api.github.com/repos/hybridauth/hybridauth/zipball/2edf92f07b94fcc9e17ea14e2a1644b83981af7d",
"reference": "020be6991e7ae9f1ffaabae6586245d2a9626273", "reference": "2edf92f07b94fcc9e17ea14e2a1644b83981af7d",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -25,14 +25,9 @@
}, },
"require-dev": { "require-dev": {
"ext-curl": "*", "ext-curl": "*",
"phpunit/phpunit": "~4.8.35" "phpunit/phpunit": "^4.8.35 || ^6.5 || ^8"
}, },
"type": "library", "type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.0-dev"
}
},
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"Hybridauth\\": "src/" "Hybridauth\\": "src/"
@@ -61,7 +56,7 @@
"social", "social",
"twitter" "twitter"
], ],
"time": "2019-12-27T09:26:40+00:00" "time": "2020-03-04T14:32:04+00:00"
}, },
{ {
"name": "ifsnop/mysqldump-php", "name": "ifsnop/mysqldump-php",

View File

@@ -1,17 +1,17 @@
[ [
{ {
"name": "hybridauth/hybridauth", "name": "hybridauth/hybridauth",
"version": "v3.1.1", "version": "3.2.0",
"version_normalized": "3.1.1.0", "version_normalized": "3.2.0.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/hybridauth/hybridauth.git", "url": "https://github.com/hybridauth/hybridauth.git",
"reference": "020be6991e7ae9f1ffaabae6586245d2a9626273" "reference": "2edf92f07b94fcc9e17ea14e2a1644b83981af7d"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/hybridauth/hybridauth/zipball/020be6991e7ae9f1ffaabae6586245d2a9626273", "url": "https://api.github.com/repos/hybridauth/hybridauth/zipball/2edf92f07b94fcc9e17ea14e2a1644b83981af7d",
"reference": "020be6991e7ae9f1ffaabae6586245d2a9626273", "reference": "2edf92f07b94fcc9e17ea14e2a1644b83981af7d",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -19,15 +19,10 @@
}, },
"require-dev": { "require-dev": {
"ext-curl": "*", "ext-curl": "*",
"phpunit/phpunit": "~4.8.35" "phpunit/phpunit": "^4.8.35 || ^6.5 || ^8"
}, },
"time": "2019-12-27T09:26:40+00:00", "time": "2020-03-04T14:32:04+00:00",
"type": "library", "type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.0-dev"
}
},
"installation-source": "dist", "installation-source": "dist",
"autoload": { "autoload": {
"psr-4": { "psr-4": {

View File

@@ -100,14 +100,14 @@ abstract class AbstractAdapter implements AdapterInterface
$this->config = new Data\Collection($config); $this->config = new Data\Collection($config);
$this->configure();
$this->setHttpClient($httpClient); $this->setHttpClient($httpClient);
$this->setStorage($storage); $this->setStorage($storage);
$this->setLogger($logger); $this->setLogger($logger);
$this->configure();
$this->logger->debug(sprintf('Initialize %s, config: ', get_class($this)), $config); $this->logger->debug(sprintf('Initialize %s, config: ', get_class($this)), $config);
$this->initialize(); $this->initialize();

View File

@@ -135,8 +135,8 @@ final class Collection
{ {
$properties = []; $properties = [];
foreach ($this->collection as $property) { foreach ($this->collection as $key => $value) {
$properties[] = $property; $properties[] = $key;
} }
return $properties; return $properties;
@@ -151,8 +151,8 @@ final class Collection
{ {
$values = []; $values = [];
foreach ($this->collection as $property) { foreach ($this->collection as $value) {
$values[] = $this->get($property); $values[] = $value;
} }
return $values; return $values;

View File

@@ -48,6 +48,7 @@ class Exception extends \Exception implements ExceptionInterface
$obj_dump = print_r($object, true); $obj_dump = print_r($object, true);
// phpcs:ignore
$html .= sprintf('<b>' . get_class($object) . '</b> extends <b>' . get_parent_class($object) . '</b><pre>%s</pre>', $obj_dump); $html .= sprintf('<b>' . get_class($object) . '</b> extends <b>' . get_parent_class($object) . '</b><pre>%s</pre>', $obj_dump);
} }
@@ -57,6 +58,7 @@ class Exception extends \Exception implements ExceptionInterface
$html .= sprintf('<pre>%s</pre>', $session_dump); $html .= sprintf('<pre>%s</pre>', $session_dump);
// phpcs:ignore
echo sprintf("<html><head><title>%s</title><style>body{margin:0;padding:30px;font:12px/1.5 Helvetica,Arial,Verdana,sans-serif;}h1{margin:0;font-size:48px;font-weight:normal;line-height:48px;}strong{display:inline-block;width:75px;}</style></head><body>%s</body></html>", $title, $html); echo sprintf("<html><head><title>%s</title><style>body{margin:0;padding:30px;font:12px/1.5 Helvetica,Arial,Verdana,sans-serif;}h1{margin:0;font-size:48px;font-weight:normal;line-height:48px;}strong{display:inline-block;width:75px;}</style></head><body>%s</body></html>", $title, $html);
} }
} }

View File

@@ -31,6 +31,7 @@ class Curl implements HttpClientInterface
CURLOPT_MAXREDIRS => 5, CURLOPT_MAXREDIRS => 5,
CURLINFO_HEADER_OUT => true, CURLINFO_HEADER_OUT => true,
CURLOPT_ENCODING => 'identity', CURLOPT_ENCODING => 'identity',
// phpcs:ignore
CURLOPT_USERAGENT => 'HybridAuth, PHP Social Authentication Library (https://github.com/hybridauth/hybridauth)', CURLOPT_USERAGENT => 'HybridAuth, PHP Social Authentication Library (https://github.com/hybridauth/hybridauth)',
]; ];
@@ -160,9 +161,11 @@ class Curl implements HttpClientInterface
$this->responseClientInfo = curl_getinfo($curl); $this->responseClientInfo = curl_getinfo($curl);
if ($this->logger) { if ($this->logger) {
// phpcs:ignore
$this->logger->debug(sprintf('%s::request( %s, %s ), response:', get_class($this), $uri, $method), $this->getResponse()); $this->logger->debug(sprintf('%s::request( %s, %s ), response:', get_class($this), $uri, $method), $this->getResponse());
if (false === $response) { if (false === $response) {
// phpcs:ignore
$this->logger->error(sprintf('%s::request( %s, %s ), error:', get_class($this), $uri, $method), [$this->responseClientError]); $this->logger->error(sprintf('%s::request( %s, %s ), error:', get_class($this), $uri, $method), [$this->responseClientError]);
} }
} }

View File

@@ -177,9 +177,11 @@ class Guzzle implements HttpClientInterface
} }
if ($this->logger) { if ($this->logger) {
// phpcs:ignore
$this->logger->debug(sprintf('%s::request( %s, %s ), response:', get_class($this), $uri, $method), $this->getResponse()); $this->logger->debug(sprintf('%s::request( %s, %s ), response:', get_class($this), $uri, $method), $this->getResponse());
if ($this->responseClientError) { if ($this->responseClientError) {
// phpcs:ignore
$this->logger->error(sprintf('%s::request( %s, %s ), error:', get_class($this), $uri, $method), [$this->responseClientError]); $this->logger->error(sprintf('%s::request( %s, %s ), error:', get_class($this), $uri, $method), [$this->responseClientError]);
} }
} }

View File

@@ -88,12 +88,8 @@ class Util
$protocol = 'http://'; $protocol = 'http://';
if ( if (($collection->get('HTTPS') && $collection->get('HTTPS') !== 'off') ||
( $collection->get('HTTP_X_FORWARDED_PROTO') === 'https') {
$collection->get('HTTPS') && $collection->get('HTTPS') !== 'off'
) ||
$collection->get('HTTP_X_FORWARDED_PROTO') === 'https'
) {
$protocol = 'https://'; $protocol = 'https://';
} }

View File

@@ -166,6 +166,10 @@ class Hybridauth
} }
$config = $providersConfig[$name]; $config = $providersConfig[$name];
$config += [
'debug_mode' => $this->config['debug_mode'],
'debug_file' => $this->config['debug_file'],
];
if (! isset($config['callback']) && isset($this->config['callback'])) { if (! isset($config['callback']) && isset($this->config['callback'])) {
$config['callback'] = $this->config['callback']; $config['callback'] = $this->config['callback'];

View File

@@ -7,12 +7,12 @@
namespace Hybridauth\Provider; namespace Hybridauth\Provider;
use Hybridauth\Adapter\OpenID as OpenIDAdapter; use Hybridauth\Adapter\OpenID;
/** /**
* AOL OpenID provider adapter. * AOL OpenID provider adapter.
*/ */
class AOLOpenID extends OpenIDAdapter class AOLOpenID extends OpenID
{ {
/** /**
* {@inheritdoc} * {@inheritdoc}

View File

@@ -67,10 +67,9 @@ class BitBucket extends OAuth2
if (empty($userProfile->email) && strpos($this->scope, 'email') !== false) { if (empty($userProfile->email) && strpos($this->scope, 'email') !== false) {
try { try {
// user email is not mandatory so keep it quite
$userProfile = $this->requestUserEmail($userProfile); $userProfile = $this->requestUserEmail($userProfile);
} } catch (\Exception $e) {
// user email is not mandatory so keep it quite
catch (\Exception $e) {
} }
} }
@@ -83,6 +82,8 @@ class BitBucket extends OAuth2
* @param $userProfile * @param $userProfile
* *
* @return User\Profile * @return User\Profile
*
* @throws \Exception
*/ */
protected function requestUserEmail($userProfile) protected function requestUserEmail($userProfile)
{ {

View File

@@ -17,41 +17,55 @@ use Hybridauth\User;
*/ */
class Discord extends OAuth2 class Discord extends OAuth2
{ {
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public $scope = 'identify email'; public $scope = 'identify email';
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
protected $apiBaseUrl = 'https://discordapp.com/api/'; protected $apiBaseUrl = 'https://discordapp.com/api/';
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
protected $authorizeUrl = 'https://discordapp.com/api/oauth2/authorize'; protected $authorizeUrl = 'https://discordapp.com/api/oauth2/authorize';
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
protected $accessTokenUrl = 'https://discordapp.com/api/oauth2/token'; protected $accessTokenUrl = 'https://discordapp.com/api/oauth2/token';
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
protected $apiDocumentation = 'https://discordapp.com/developers/docs/topics/oauth2'; protected $apiDocumentation = 'https://discordapp.com/developers/docs/topics/oauth2';
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
protected function initialize()
{
parent::initialize();
$this->tokenRefreshParameters += [
'client_id' => $this->clientId,
'client_secret' => $this->clientSecret,
];
}
/**
* {@inheritdoc}
*/
public function getUserProfile() public function getUserProfile()
{ {
$response = $this->apiRequest('users/@me'); $response = $this->apiRequest('users/@me');
$data = new Data\Collection($response); $data = new Data\Collection($response);
if (! $data->exists('id')) { if (!$data->exists('id')) {
throw new UnexpectedApiResponseException('Provider API returned an unexpected response.'); throw new UnexpectedApiResponseException('Provider API returned an unexpected response.');
} }
@@ -63,16 +77,17 @@ class Discord extends OAuth2
$userProfile = new User\Profile(); $userProfile = new User\Profile();
$userProfile->identifier = $data->get('id'); $userProfile->identifier = $data->get('id');
$userProfile->displayName = $displayName; $userProfile->displayName = $displayName;
$userProfile->email = $data->get('email'); $userProfile->email = $data->get('email');
if ($data->get('verified')) { if ($data->get('verified')) {
$userProfile->emailVerified = $data->get('email'); $userProfile->emailVerified = $data->get('email');
} }
if ($data->get('avatar')) { if ($data->get('avatar')) {
$userProfile->photoURL = 'https://cdn.discordapp.com/avatars/' . $data->get('id') . '/' . $data->get('avatar') . '.png'; $userProfile->photoURL = 'https://cdn.discordapp.com/avatars/';
$userProfile->photoURL .= $data->get('id') . '/' . $data->get('avatar') . '.png';
} }
return $userProfile; return $userProfile;

View File

@@ -88,7 +88,21 @@ class Facebook extends OAuth2
*/ */
public function getUserProfile() public function getUserProfile()
{ {
$response = $this->apiRequest('me?fields=id,name,first_name,last_name,link,website,gender,locale,about,email,hometown,verified,birthday'); $fields = [
'id',
'name',
'first_name',
'last_name',
'link',
'website',
'gender',
'locale',
'about',
'email',
'hometown',
'birthday',
];
$response = $this->apiRequest('me?fields=' . implode(',', $fields));
$data = new Data\Collection($response); $data = new Data\Collection($response);
@@ -118,11 +132,9 @@ class Facebook extends OAuth2
$photoSize = $this->config->get('photo_size') ?: '150'; $photoSize = $this->config->get('photo_size') ?: '150';
$userProfile->photoURL = $this->apiBaseUrl . $userProfile->identifier . '/picture?width=' . $photoSize . '&height=' . $photoSize; $userProfile->photoURL = $this->apiBaseUrl . $userProfile->identifier;
$userProfile->photoURL .= '/picture?width=' . $photoSize . '&height=' . $photoSize;
// Don't use $data->get('verified') here, as Facebook will only return an email if it is validated first:
// https://developers.facebook.com/docs/graph-api/reference/v2.0/user
// "The User's primary email address listed on their profile. This field will not be returned if no valid email address is available."
$userProfile->emailVerified = $userProfile->email; $userProfile->emailVerified = $userProfile->email;
$userProfile = $this->fetchUserRegion($userProfile); $userProfile = $this->fetchUserRegion($userProfile);
@@ -236,22 +248,6 @@ class Facebook extends OAuth2
return $userContact; return $userContact;
} }
/**
* {@inheritdoc}
*
* @deprecated since August 1, 2018. Scheduled for removal before Hybridauth 3.0.0.
* See https://developers.facebook.com/docs/graph-api/changelog/breaking-changes#login-4-24 for more info.
*/
public function setUserStatus($status, $pageId = 'me')
{
@trigger_error('The ' . __METHOD__ . ' method is deprecated since August 1, 2018 and will be removed in Hybridauth 3.0.0.', E_USER_DEPRECATED);
$status = is_string($status) ? ['message' => $status] : $status;
$response = $this->apiRequest("{$pageId}/feed", 'POST', $status);
return $response;
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
@@ -261,7 +257,7 @@ class Facebook extends OAuth2
// Post on user wall. // Post on user wall.
if ($pageId === 'me') { if ($pageId === 'me') {
return $this->setUserStatus($status, $pageId); return $this->setUserStatus($status);
} }
// Retrieve writable user pages and filter by given one. // Retrieve writable user pages and filter by given one.
@@ -364,7 +360,8 @@ class Facebook extends OAuth2
$userActivity->user->profileURL = $this->getProfileUrl($userActivity->user->identifier); $userActivity->user->profileURL = $this->getProfileUrl($userActivity->user->identifier);
$userActivity->user->photoURL = $this->apiBaseUrl . $userActivity->user->identifier . '/picture?width=150&height=150'; $userActivity->user->photoURL = $this->apiBaseUrl . $userActivity->user->identifier;
$userActivity->user->photoURL .= '/picture?width=150&height=150';
} }
return $userActivity; return $userActivity;

View File

@@ -51,7 +51,10 @@ class Foursquare extends OAuth2
$apiVersion = $this->config->get('api_version') ?: '20140201'; $apiVersion = $this->config->get('api_version') ?: '20140201';
$this->apiRequestParameters = [ 'v' => $apiVersion ]; $this->apiRequestParameters = [
'oauth_token' => $this->getStoredData('access_token'),
'v' => $apiVersion,
];
} }
/** /**
@@ -84,7 +87,8 @@ class Foursquare extends OAuth2
if ($data->exists('photo')) { if ($data->exists('photo')) {
$photoSize = $this->config->get('photo_size') ?: '150x150'; $photoSize = $this->config->get('photo_size') ?: '150x150';
$userProfile->photoURL = $data->filter('photo')->get('prefix') . $photoSize . $data->filter('photo')->get('suffix'); $userProfile->photoURL = $data->filter('photo')->get('prefix');
$userProfile->photoURL .= $photoSize . $data->filter('photo')->get('suffix');
} }
return $userProfile; return $userProfile;
@@ -125,10 +129,11 @@ class Foursquare extends OAuth2
$userContact = new User\Contact(); $userContact = new User\Contact();
$userContact->identifier = $item->get('id'); $userContact->identifier = $item->get('id');
$userContact->photoURL = $item->filter('photo')->get('prefix') . $photoSize . $item->filter('photo')->get('suffix'); $userContact->photoURL = $item->filter('photo')->get('prefix');
$userContact->photoURL .= $photoSize . $item->filter('photo')->get('suffix');
$userContact->displayName = trim($item->get('firstName') . ' ' . $item->get('lastName')); $userContact->displayName = trim($item->get('firstName') . ' ' . $item->get('lastName'));
$userContact->email = $item->filter('contact')->get('email'); $userContact->email = $item->filter('contact')->get('email');
return $userContact; return $userContact;
} }

View File

@@ -17,63 +17,63 @@ use Hybridauth\User;
*/ */
class GitHub extends OAuth2 class GitHub extends OAuth2
{ {
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public $scope = 'user:email'; public $scope = 'user:email';
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
protected $apiBaseUrl = 'https://api.github.com/'; protected $apiBaseUrl = 'https://api.github.com/';
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
protected $authorizeUrl = 'https://github.com/login/oauth/authorize'; protected $authorizeUrl = 'https://github.com/login/oauth/authorize';
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
protected $accessTokenUrl = 'https://github.com/login/oauth/access_token'; protected $accessTokenUrl = 'https://github.com/login/oauth/access_token';
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
protected $apiDocumentation = 'https://developer.github.com/v3/oauth/'; protected $apiDocumentation = 'https://developer.github.com/v3/oauth/';
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function getUserProfile() public function getUserProfile()
{ {
$response = $this->apiRequest('user'); $response = $this->apiRequest('user');
$data = new Data\Collection($response); $data = new Data\Collection($response);
if (! $data->exists('id')) { if (!$data->exists('id')) {
throw new UnexpectedApiResponseException('Provider API returned an unexpected response.'); throw new UnexpectedApiResponseException('Provider API returned an unexpected response.');
} }
$userProfile = new User\Profile(); $userProfile = new User\Profile();
$userProfile->identifier = $data->get('id'); $userProfile->identifier = $data->get('id');
$userProfile->displayName = $data->get('name'); $userProfile->displayName = $data->get('name');
$userProfile->description = $data->get('bio'); $userProfile->description = $data->get('bio');
$userProfile->photoURL = $data->get('avatar_url'); $userProfile->photoURL = $data->get('avatar_url');
$userProfile->profileURL = $data->get('html_url'); $userProfile->profileURL = $data->get('html_url');
$userProfile->email = $data->get('email'); $userProfile->email = $data->get('email');
$userProfile->webSiteURL = $data->get('blog'); $userProfile->webSiteURL = $data->get('blog');
$userProfile->region = $data->get('location'); $userProfile->region = $data->get('location');
$userProfile->displayName = $userProfile->displayName ?: $data->get('login'); $userProfile->displayName = $userProfile->displayName ?: $data->get('login');
if (empty($userProfile->email) && strpos($this->scope, 'user:email') !== false) { if (empty($userProfile->email) && strpos($this->scope, 'user:email') !== false) {
try { try {
// user email is not mandatory so keep it quite.
$userProfile = $this->requestUserEmail($userProfile); $userProfile = $this->requestUserEmail($userProfile);
} } catch (\Exception $e) {
// user email is not mandatory so keep it quite
catch (\Exception $e) {
} }
} }
@@ -87,16 +87,18 @@ class GitHub extends OAuth2
* @param User\Profile $userProfile * @param User\Profile $userProfile
* *
* @return User\Profile * @return User\Profile
*
* @throws \Exception
*/ */
protected function requestUserEmail(User\Profile $userProfile) protected function requestUserEmail(User\Profile $userProfile)
{ {
$response = $this->apiRequest('user/emails'); $response = $this->apiRequest('user/emails');
foreach ($response as $idx => $item) { foreach ($response as $idx => $item) {
if (! empty($item->primary) && $item->primary == 1) { if (!empty($item->primary) && $item->primary == 1) {
$userProfile->email = $item->email; $userProfile->email = $item->email;
if (! empty($item->verified) && $item->verified == 1) { if (!empty($item->verified) && $item->verified == 1) {
$userProfile->emailVerified = $userProfile->email; $userProfile->emailVerified = $userProfile->email;
} }

View File

@@ -116,7 +116,7 @@ class Google extends OAuth2
$userProfile->language = $data->get('locale'); $userProfile->language = $data->get('locale');
$userProfile->email = $data->get('email'); $userProfile->email = $data->get('email');
$userProfile->emailVerified = ($data->get('email_verified') === true || $data->get('email_verified') === 1) ? $userProfile->email : ''; $userProfile->emailVerified = $data->get('email_verified') ? $userProfile->email : '';
if ($this->config->get('photo_size')) { if ($this->config->get('photo_size')) {
$userProfile->photoURL .= '?sz=' . $this->config->get('photo_size'); $userProfile->photoURL .= '?sz=' . $this->config->get('photo_size');
@@ -136,6 +136,8 @@ class Google extends OAuth2
if (false !== strpos($this->scope, '/m8/feeds/') || false !== strpos($this->scope, '/auth/contacts.readonly')) { if (false !== strpos($this->scope, '/m8/feeds/') || false !== strpos($this->scope, '/auth/contacts.readonly')) {
return $this->getGmailContacts($parameters); return $this->getGmailContacts($parameters);
} }
return [];
} }
/** /**
@@ -144,6 +146,8 @@ class Google extends OAuth2
* @param array $parameters * @param array $parameters
* *
* @return array * @return array
*
* @throws \Exception
*/ */
protected function getGmailContacts($parameters = []) protected function getGmailContacts($parameters = [])
{ {

View File

@@ -40,7 +40,7 @@ class LinkedIn extends OAuth2
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
protected $apiDocumentation = 'https://docs.microsoft.com/en-us/linkedin/shared/authentication/authorization-code-flow'; protected $apiDocumentation = 'https://docs.microsoft.com/en-us/linkedin/shared/authentication/authentication';
/** /**
* {@inheritdoc} * {@inheritdoc}
@@ -76,12 +76,16 @@ class LinkedIn extends OAuth2
->get($this->getPreferredLocale($data, 'lastName')); ->get($this->getPreferredLocale($data, 'lastName'));
$userProfile->identifier = $data->get('id'); $userProfile->identifier = $data->get('id');
$userProfile->photoURL = $this->getUserPhotoUrl($data->filter('profilePicture')->filter('displayImage~')->get('elements'));
$userProfile->email = $this->getUserEmail(); $userProfile->email = $this->getUserEmail();
$userProfile->emailVerified = $userProfile->email; $userProfile->emailVerified = $userProfile->email;
$userProfile->displayName = trim($userProfile->firstName . ' ' . $userProfile->lastName); $userProfile->displayName = trim($userProfile->firstName . ' ' . $userProfile->lastName);
$photo_elements = $data
->filter('profilePicture')
->filter('displayImage~')
->get('elements');
$userProfile->photoURL = $this->getUserPhotoUrl($photo_elements);
return $userProfile; return $userProfile;
} }
@@ -106,7 +110,7 @@ class LinkedIn extends OAuth2
} }
} }
return NULL; return null;
} }
/** /**
@@ -114,6 +118,8 @@ class LinkedIn extends OAuth2
* *
* @return string * @return string
* The user email address. * The user email address.
*
* @throws \Exception
*/ */
public function getUserEmail() public function getUserEmail()
{ {
@@ -128,7 +134,7 @@ class LinkedIn extends OAuth2
} }
} }
return NULL; return null;
} }
/** /**
@@ -179,7 +185,8 @@ class LinkedIn extends OAuth2
* @return string * @return string
* A field locale. * A field locale.
*/ */
protected function getPreferredLocale($data, $field_name) { protected function getPreferredLocale($data, $field_name)
{
$locale = $data->filter($field_name)->filter('preferredLocale'); $locale = $data->filter($field_name)->filter('preferredLocale');
if ($locale) { if ($locale) {
return $locale->get('language') . '_' . $locale->get('country'); return $locale->get('language') . '_' . $locale->get('country');

View File

@@ -58,7 +58,13 @@ class Mailru extends OAuth2
*/ */
public function getUserProfile() public function getUserProfile()
{ {
$sign = md5('app_id=' . $this->clientId . 'method=users.getInfosecure=1session_key=' . $this->getStoredData('access_token') . $this->clientSecret); $params = [
'app_id' => $this->clientId,
'method' => 'users.getInfo',
'secure' => 1,
'session_key' => $this->getStoredData('access_token'),
];
$sign = md5(http_build_query($params, null, '') . $this->clientSecret);
$param = [ $param = [
'app_id' => $this->clientId, 'app_id' => $this->clientId,
@@ -72,7 +78,7 @@ class Mailru extends OAuth2
$data = new Collection($response[0]); $data = new Collection($response[0]);
if (! $data->exists('uid')) { if (!$data->exists('uid')) {
throw new UnexpectedApiResponseException('Provider API returned an unexpected response.'); throw new UnexpectedApiResponseException('Provider API returned an unexpected response.');
} }

View File

@@ -11,6 +11,7 @@ use Hybridauth\Adapter\OAuth2;
use Hybridauth\Data; use Hybridauth\Data;
use Hybridauth\Exception\UnexpectedApiResponseException; use Hybridauth\Exception\UnexpectedApiResponseException;
use Hybridauth\User; use Hybridauth\User;
/** /**
* Odnoklassniki OAuth2 provider adapter. * Odnoklassniki OAuth2 provider adapter.
* *

View File

@@ -7,7 +7,7 @@
namespace Hybridauth\Provider; namespace Hybridauth\Provider;
use Hybridauth\Adapter\OpenID as OpenIDAdapter; use Hybridauth\Adapter;
/** /**
* Generic OpenID providers adapter. * Generic OpenID providers adapter.
@@ -40,6 +40,6 @@ use Hybridauth\Adapter\OpenID as OpenIDAdapter;
* echo $e->getMessage() ; * echo $e->getMessage() ;
* } * }
*/ */
class OpenID extends OpenIDAdapter class OpenID extends Adapter\OpenID
{ {
} }

View File

@@ -7,13 +7,13 @@
namespace Hybridauth\Provider; namespace Hybridauth\Provider;
use Hybridauth\Adapter\OpenID as OpenIDAdapter; use Hybridauth\Adapter\OpenID;
use Hybridauth\HttpClient; use Hybridauth\HttpClient;
/** /**
* PayPal OpenID provider adapter. * PayPal OpenID provider adapter.
*/ */
class PaypalOpenID extends OpenIDAdapter class PaypalOpenID extends OpenID
{ {
/** /**
* {@inheritdoc} * {@inheritdoc}

View File

@@ -86,5 +86,4 @@ class Spotify extends OAuth2
return $userProfile; return $userProfile;
} }
} }

View File

@@ -7,12 +7,12 @@
namespace Hybridauth\Provider; namespace Hybridauth\Provider;
use Hybridauth\Adapter\OpenID as OpenIDAdapter; use Hybridauth\Adapter\OpenID;
/** /**
* StackExchange OpenID provider adapter. * StackExchange OpenID provider adapter.
*/ */
class StackExchangeOpenID extends OpenIDAdapter class StackExchangeOpenID extends OpenID
{ {
/** /**
* {@inheritdoc} * {@inheritdoc}

View File

@@ -7,7 +7,7 @@
namespace Hybridauth\Provider; namespace Hybridauth\Provider;
use Hybridauth\Adapter\OpenID as OpenIDAdapter; use Hybridauth\Adapter\OpenID;
use Hybridauth\Exception\UnexpectedApiResponseException; use Hybridauth\Exception\UnexpectedApiResponseException;
use Hybridauth\Data; use Hybridauth\Data;
use Hybridauth\User; use Hybridauth\User;
@@ -28,7 +28,7 @@ use Hybridauth\User;
* $userProfile = $adapter->getUserProfile(); * $userProfile = $adapter->getUserProfile();
*/ */
class Steam extends OpenIDAdapter class Steam extends OpenID
{ {
/** /**
* {@inheritdoc} * {@inheritdoc}
@@ -44,9 +44,12 @@ class Steam extends OpenIDAdapter
$userProfile = $this->storage->get($this->providerId . '.user'); $userProfile = $this->storage->get($this->providerId . '.user');
$userProfile->identifier = str_ireplace(array('http://steamcommunity.com/openid/id/', 'https://steamcommunity.com/openid/id/'), '', $userProfile->identifier); $userProfile->identifier = str_ireplace([
'http://steamcommunity.com/openid/id/',
'https://steamcommunity.com/openid/id/',
], '', $userProfile->identifier);
if (! $userProfile->identifier) { if (!$userProfile->identifier) {
throw new UnexpectedApiResponseException('Provider API returned an unexpected response.'); throw new UnexpectedApiResponseException('Provider API returned an unexpected response.');
} }
@@ -56,9 +59,8 @@ class Steam extends OpenIDAdapter
// if api key is provided, we attempt to use steam web api // if api key is provided, we attempt to use steam web api
if ($apiKey) { if ($apiKey) {
$result = $this->getUserProfileWebAPI($apiKey, $userProfile->identifier); $result = $this->getUserProfileWebAPI($apiKey, $userProfile->identifier);
} } else {
// otherwise we fallback to community data // otherwise we fallback to community data
else {
$result = $this->getUserProfileLegacyAPI($userProfile->identifier); $result = $this->getUserProfileLegacyAPI($userProfile->identifier);
} }
@@ -66,9 +68,7 @@ class Steam extends OpenIDAdapter
foreach ($result as $k => $v) { foreach ($result as $k => $v) {
$userProfile->$k = $v ?: $userProfile->$k; $userProfile->$k = $v ?: $userProfile->$k;
} }
} } catch (\Exception $e) {
// these data are not mandatory, so keep it quite
catch (\Exception $e) {
} }
// store user profile // store user profile
@@ -85,7 +85,8 @@ class Steam extends OpenIDAdapter
*/ */
public function getUserProfileWebAPI($apiKey, $steam64) public function getUserProfileWebAPI($apiKey, $steam64)
{ {
$apiUrl = 'http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key=' . $apiKey . '&steamids=' . $steam64; $q = http_build_query(['key' => $apiKey, 'steamid' => $steam64]);
$apiUrl = 'http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?' . $q;
$response = $this->httpClient->request($apiUrl); $response = $this->httpClient->request($apiUrl);

View File

@@ -7,7 +7,6 @@ use Hybridauth\Data\Collection;
use Hybridauth\User\Profile; use Hybridauth\User\Profile;
use Hybridauth\Adapter\AbstractAdapter; use Hybridauth\Adapter\AbstractAdapter;
use Hybridauth\Adapter\AdapterInterface; use Hybridauth\Adapter\AdapterInterface;
use Hybridauth\Exception\InvalidApplicationCredentialsException; use Hybridauth\Exception\InvalidApplicationCredentialsException;
use Hybridauth\Exception\InvalidAuthorizationCodeException; use Hybridauth\Exception\InvalidAuthorizationCodeException;
use Hybridauth\Exception\UnexpectedApiResponseException; use Hybridauth\Exception\UnexpectedApiResponseException;
@@ -30,7 +29,7 @@ use Hybridauth\Exception\UnexpectedApiResponseException;
* $userProfile = $adapter->getUserProfile(); * $userProfile = $adapter->getUserProfile();
* } * }
* catch(\Exception $e) { * catch(\Exception $e) {
* print $e->getMessage() ; * print $e->getMessage();
* } * }
*/ */
class Telegram extends AbstractAdapter implements AdapterInterface class Telegram extends AbstractAdapter implements AdapterInterface
@@ -43,15 +42,15 @@ class Telegram extends AbstractAdapter implements AdapterInterface
protected $callbackUrl = ''; protected $callbackUrl = '';
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
protected function configure() protected function configure()
{ {
$this->botId = $this->config->filter('keys')->get('id'); $this->botId = $this->config->filter('keys')->get('id');
$this->botSecret = $this->config->filter('keys')->get('secret'); $this->botSecret = $this->config->filter('keys')->get('secret');
$this->callbackUrl = $this->config->get('callback'); $this->callbackUrl = $this->config->get('callback');
if (! $this->botId || !$this->botSecret) { if (!$this->botId || !$this->botSecret) {
throw new InvalidApplicationCredentialsException( throw new InvalidApplicationCredentialsException(
'Your application id is required in order to connect to ' . $this->providerId 'Your application id is required in order to connect to ' . $this->providerId
); );
@@ -59,13 +58,15 @@ class Telegram extends AbstractAdapter implements AdapterInterface
} }
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
protected function initialize() {} protected function initialize()
{
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function authenticate() public function authenticate()
{ {
$this->logger->info(sprintf('%s::authenticate()', get_class($this))); $this->logger->info(sprintf('%s::authenticate()', get_class($this)));
@@ -79,8 +80,8 @@ class Telegram extends AbstractAdapter implements AdapterInterface
} }
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function getUserProfile() public function getUserProfile()
{ {
$data = new Collection($this->parseAuthData()); $data = new Collection($this->parseAuthData());
@@ -101,9 +102,9 @@ class Telegram extends AbstractAdapter implements AdapterInterface
} }
/** /**
* See: https://telegram.im/widget-login.php * See: https://telegram.im/widget-login.php
* See: https://gist.github.com/anonymous/6516521b1fb3b464534fbc30ea3573c2 * See: https://gist.github.com/anonymous/6516521b1fb3b464534fbc30ea3573c2
*/ */
protected function authenticateCheckError() protected function authenticateCheckError()
{ {
$auth_data = $this->parseAuthData(); $auth_data = $this->parseAuthData();
@@ -113,7 +114,9 @@ class Telegram extends AbstractAdapter implements AdapterInterface
$data_check_arr = []; $data_check_arr = [];
foreach ($auth_data as $key => $value) { foreach ($auth_data as $key => $value) {
$data_check_arr[] = $key . '=' . $value; if (!empty($value)) {
$data_check_arr[] = $key . '=' . $value;
}
} }
sort($data_check_arr); sort($data_check_arr);
@@ -135,16 +138,14 @@ class Telegram extends AbstractAdapter implements AdapterInterface
} }
/** /**
* See: https://telegram.im/widget-login.php * See: https://telegram.im/widget-login.php
*/ */
protected function authenticateBegin() protected function authenticateBegin()
{ {
$this->logger->debug( $this->logger->debug(sprintf('%s::authenticateBegin(), redirecting user to:', get_class($this)));
sprintf('%s::authenticateBegin(), redirecting user to:', get_class($this))
);
exit( exit(
<<<HTML <<<HTML
<center> <center>
<script async src="https://telegram.org/js/telegram-widget.js?7" <script async src="https://telegram.org/js/telegram-widget.js?7"
data-telegram-login="{$this->botId}" data-telegram-login="{$this->botId}"
@@ -178,5 +179,4 @@ HTML
'hash' => filter_input(INPUT_GET, 'hash'), 'hash' => filter_input(INPUT_GET, 'hash'),
]; ];
} }
} }

View File

@@ -55,7 +55,7 @@ class TwitchTV extends OAuth2
throw new UnexpectedApiResponseException('Provider API returned an unexpected response.'); throw new UnexpectedApiResponseException('Provider API returned an unexpected response.');
} }
$users = $data->filter('data')->properties(); $users = $data->filter('data')->values();
$user = new Data\Collection($users[0]); $user = new Data\Collection($users[0]);
$userProfile = new User\Profile(); $userProfile = new User\Profile();

View File

@@ -11,6 +11,8 @@ use Hybridauth\Adapter\OAuth2;
use Hybridauth\Exception\UnexpectedApiResponseException; use Hybridauth\Exception\UnexpectedApiResponseException;
use Hybridauth\Data\Collection; use Hybridauth\Data\Collection;
use Hybridauth\User\Profile; use Hybridauth\User\Profile;
use Hybridauth\Data;
use Hybridauth\User;
/** /**
* Vkontakte provider adapter. * Vkontakte provider adapter.
@@ -37,6 +39,11 @@ use Hybridauth\User\Profile;
*/ */
class Vkontakte extends OAuth2 class Vkontakte extends OAuth2
{ {
const API_VERSION = '5.95';
const URL = 'https://vk.com/';
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
@@ -57,18 +64,18 @@ class Vkontakte extends OAuth2
*/ */
protected $scope = 'email,offline'; protected $scope = 'email,offline';
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function hasAccessTokenExpired() public function hasAccessTokenExpired()
{ {
// As we using offline scope, $expired will be false. // As we using offline scope, $expired will be false.
$expired = $this->getStoredData('expires_in') $expired = $this->getStoredData('expires_in')
? $this->getStoredData('expires_at') <= time() ? $this->getStoredData('expires_at') <= time()
: false; : false;
return $expired; return $expired;
} }
/** /**
* {@inheritdoc} * {@inheritdoc}
@@ -92,7 +99,7 @@ class Vkontakte extends OAuth2
'user_ids' => $this->getStoredData('user_id'), 'user_ids' => $this->getStoredData('user_id'),
// Required fields: id,first_name,last_name // Required fields: id,first_name,last_name
'fields' => 'screen_name,sex,has_photo,' . $photoField, 'fields' => 'screen_name,sex,has_photo,' . $photoField,
'v' => '5.95', 'v' => static::API_VERSION,
$this->accessTokenName => $this->getStoredData($this->accessTokenName), $this->accessTokenName => $this->getStoredData($this->accessTokenName),
]; ];
@@ -117,7 +124,7 @@ class Vkontakte extends OAuth2
$userProfile->displayName = $data->get('screen_name'); $userProfile->displayName = $data->get('screen_name');
$userProfile->photoURL = $data->get('has_photo') === 1 ? $data->get($photoField) : ''; $userProfile->photoURL = $data->get('has_photo') === 1 ? $data->get($photoField) : '';
$screen_name = 'https://vk.com/' . ($data->get('screen_name') ?: 'id' . $data->get('id')); $screen_name = static::URL . ($data->get('screen_name') ?: 'id' . $data->get('id'));
$userProfile->profileURL = $screen_name; $userProfile->profileURL = $screen_name;
switch ($data->get('sex')) { switch ($data->get('sex')) {
@@ -133,4 +140,52 @@ class Vkontakte extends OAuth2
return $userProfile; return $userProfile;
} }
/**
* {@inheritdoc}
*/
public function getUserContacts()
{
$contacts = [];
$parameters = [
'user_id' => $this->getStoredData('user_id'),
'fields' => 'uid,name,photo_200_orig',
'v' => static::API_VERSION,
$this->accessTokenName => $this->getStoredData($this->accessTokenName),
];
$response = $this->apiRequest('friends.get', 'GET', $parameters);
$data = new Data\Collection($response);
if (!$data->exists('response')) {
throw new UnexpectedApiResponseException('Provider API returned an unexpected response.');
}
if (!$data->filter('response')->filter('items')->isEmpty()) {
foreach ($data->filter('response')->filter('items')->toArray() as $item) {
$contacts[] = $this->fetchUserContact($item);
}
}
return $contacts;
}
/**
* Parse the user contact.
*
* @param array $item
*
* @return \Hybridauth\User\Contact
*/
protected function fetchUserContact($item)
{
$userContact = new User\Contact();
$data = new Data\Collection($item);
$userContact->identifier = $data->get('id');
$userContact->displayName = sprintf('%s %s', $data->get('first_name'), $data->get('last_name'));
$userContact->profileURL = static::URL . ($data->get('screen_name') ?: 'id' . $data->get('id'));
$userContact->photoURL = $data->get('photo_200_orig');
return $userContact;
}
} }

View File

@@ -109,5 +109,4 @@ class WeChat extends OAuth2
return $userProfile; return $userProfile;
} }
} }

View File

@@ -7,12 +7,6 @@
namespace Hybridauth\Provider; namespace Hybridauth\Provider;
use Hybridauth\Adapter\OAuth2;
use Hybridauth\Provider\WeChat;
use Hybridauth\Exception\UnexpectedApiResponseException;
use Hybridauth\Data;
use Hybridauth\User;
/** /**
* WeChat China OAuth2 provider adapter. * WeChat China OAuth2 provider adapter.
*/ */
@@ -38,5 +32,4 @@ class WeChatChina extends WeChat
* {@ịnheritdoc} * {@ịnheritdoc}
*/ */
protected $accessTokenInfoUrl = 'https://api.weixin.qq.com/sns/auth'; protected $accessTokenInfoUrl = 'https://api.weixin.qq.com/sns/auth';
} }

View File

@@ -7,12 +7,12 @@
namespace Hybridauth\Provider; namespace Hybridauth\Provider;
use Hybridauth\Adapter\OpenID as OpenIDAdapter; use Hybridauth\Adapter\OpenID;
/** /**
* Yahoo OpenID provider adapter. * Yahoo OpenID provider adapter.
*/ */
class YahooOpenID extends OpenIDAdapter class YahooOpenID extends OpenID
{ {
/** /**
* {@inheritdoc} * {@inheritdoc}

View File

@@ -13,7 +13,6 @@ use Hybridauth\Exception\UnexpectedApiResponseException;
use Hybridauth\Data\Collection; use Hybridauth\Data\Collection;
use Hybridauth\User\Profile; use Hybridauth\User\Profile;
/** /**
* Yandex provider adapter. * Yandex provider adapter.
* *
@@ -67,7 +66,7 @@ class Yandex extends OAuth2
$response = $this->apiRequest($this->apiBaseUrl . "?format=json"); $response = $this->apiRequest($this->apiBaseUrl . "?format=json");
if (!isset($response->id)) { if (!isset($response->id)) {
throw new UnexpectedApiResponseException("User profile request failed! {$this->providerId} returned an invalid response.", 6); throw new UnexpectedApiResponseException('Provider API returned an unexpected response.');
} }
$data = new Collection($response); $data = new Collection($response);

View File

@@ -40,6 +40,7 @@ class Session implements StorageInterface
} }
if (headers_sent()) { if (headers_sent()) {
// phpcs:ignore
throw new RuntimeException('HTTP headers already sent to browser and Hybridauth won\'t be able to start/resume PHP session. To resolve this, session_start() must be called before outputing any data.'); throw new RuntimeException('HTTP headers already sent to browser and Hybridauth won\'t be able to start/resume PHP session. To resolve this, session_start() must be called before outputing any data.');
} }

View File

@@ -59,7 +59,7 @@ abstract class OAuthSignatureMethod
// Avoid a timing leak with a (hopefully) time insensitive compare // Avoid a timing leak with a (hopefully) time insensitive compare
$result = 0; $result = 0;
for ($i = 0; $i < strlen($signature); $i ++) { for ($i = 0; $i < strlen($signature); $i ++) {
$result |= ord($built {$i}) ^ ord($signature {$i}); $result |= ord($built[$i]) ^ ord($signature[$i]);
} }
return $result == 0; return $result == 0;

View File

@@ -57,15 +57,17 @@ final class Activity
} }
/** /**
* Prevent the providers adapters from adding new fields. * Prevent the providers adapters from adding new fields.
* *
* @var string $name * @var mixed $value
* @var mixed $value *
* * @var string $name
* @throws Exception\UnexpectedValueException *
*/ * @throws UnexpectedValueException
*/
public function __set($name, $value) public function __set($name, $value)
{ {
// phpcs:ignore
throw new UnexpectedValueException(sprintf('Adding new property "%s\' to %s is not allowed.', $name, __CLASS__)); throw new UnexpectedValueException(sprintf('Adding new property "%s\' to %s is not allowed.', $name, __CLASS__));
} }
} }

View File

@@ -1,4 +1,6 @@
<?php namespace HybridauthTest\Hybridauth\Data; <?php
namespace HybridauthTest\Hybridauth\Data;
use Hybridauth\Data\Collection; use Hybridauth\Data\Collection;

View File

@@ -1,4 +1,6 @@
<?php namespace HybridauthTest\Hybridauth\Data; <?php
namespace HybridauthTest\Hybridauth\Data;
use Hybridauth\Data\Parser; use Hybridauth\Data\Parser;

View File

@@ -1,4 +1,6 @@
<?php namespace HybridauthTest\Hybridauth; <?php
namespace HybridauthTest\Hybridauth;
use Hybridauth\Hybridauth; use Hybridauth\Hybridauth;

View File

@@ -1,4 +1,6 @@
<?php namespace HybridauthTest\Hybridauth\Storage; <?php
namespace HybridauthTest\Hybridauth\Storage;
use Hybridauth\Storage\Session; use Hybridauth\Storage\Session;

View File

@@ -1,4 +1,6 @@
<?php namespace HybridauthTest\Hybridauth\User; <?php
namespace HybridauthTest\Hybridauth\User;
use Hybridauth\User\Activity; use Hybridauth\User\Activity;
@@ -30,12 +32,11 @@ class ActivityTest extends \PHPUnit\Framework\TestCase
} }
/** /**
* @expectedException Hybridauth\Exception\UnexpectedValueException * @expectedException \Hybridauth\Exception\UnexpectedValueException
*/ */
public function test_property_overloading() public function test_property_overloading()
{ {
$activity = new Activity; $activity = new Activity;
$activity->slug = true; $activity->slug = true;
} }
} }

View File

@@ -1,4 +1,6 @@
<?php namespace HybridauthTest\Hybridauth\User; <?php
namespace HybridauthTest\Hybridauth\User;
use Hybridauth\User\Contact; use Hybridauth\User\Contact;
@@ -36,12 +38,11 @@ class ContactTest extends \PHPUnit\Framework\TestCase
} }
/** /**
* @expectedException Hybridauth\Exception\UnexpectedValueException * @expectedException \Hybridauth\Exception\UnexpectedValueException
*/ */
public function test_property_overloading() public function test_property_overloading()
{ {
$contact = new Contact; $contact = new Contact;
$contact->slug = true; $contact->slug = true;
} }
} }

View File

@@ -1,4 +1,6 @@
<?php namespace HybridauthTest\Hybridauth\User; <?php
namespace HybridauthTest\Hybridauth\User;
use Hybridauth\User\Profile; use Hybridauth\User\Profile;
@@ -66,12 +68,11 @@ class ProfileTest extends \PHPUnit\Framework\TestCase
} }
/** /**
* @expectedException Hybridauth\Exception\UnexpectedValueException * @expectedException \Hybridauth\Exception\UnexpectedValueException
*/ */
public function test_property_overloading() public function test_property_overloading()
{ {
$profile = new Profile; $profile = new Profile;
$profile->slug = true; $profile->slug = true;
} }
} }