Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
35 / 35
100.00% covered (success)
100.00%
7 / 7
CRAP
100.00% covered (success)
100.00%
1 / 1
Instagram
100.00% covered (success)
100.00%
35 / 35
100.00% covered (success)
100.00%
7 / 7
11
100.00% covered (success)
100.00%
1 / 1
 getAuthorizeUrl
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getAccessTokenUrl
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getScope
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getUserProfile
100.00% covered (success)
100.00%
26 / 26
100.00% covered (success)
100.00%
1 / 1
4
 getLogoSource
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getThemeColor
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getCurrentUri
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3namespace Miniframe\SocialLogin\Provider;
4
5use Miniframe\SocialLogin\Model\User;
6
7class Instagram extends AbstractOAuth2Provider
8{
9    /**
10     * Returns the Authorize URL
11     *
12     * @return string
13     */
14    protected function getAuthorizeUrl(): string
15    {
16        return 'https://api.instagram.com/oauth/authorize';
17    }
18
19    /**
20     * Returns the Access Token URL
21     *
22     * @return string
23     */
24    protected function getAccessTokenUrl(): string
25    {
26        return 'https://api.instagram.com/oauth/access_token';
27    }
28
29    /**
30     * Returns the requested scope
31     *
32     * @return string|null
33     */
34    protected function getScope(): ?string
35    {
36        return 'user_profile user_media';
37    }
38
39    /**
40     * Returns the user profile
41     *
42     * @param array $accessToken The access token.
43     *
44     * @return User
45     */
46    protected function getUserProfile(array $accessToken): User
47    {
48        // Fetch username and ID
49        $userData = $this->curlRequest('https://graph.instagram.com/v11.0/me?fields=id,username', 'GET', null, [
50            'Authorization: Bearer ' . $accessToken['access_token'],
51        ]);
52
53        try {
54            $this->userAgent = $this->request->getServer('HTTP_USER_AGENT');
55            $extendedData = $this->curlRequest(
56                'https://www.instagram.com/' . $userData['username'] . '/?__a=1',
57                'GET',
58                [],
59                [
60                    'X-Forwarded-For: ' . $this->request->getServer('REMOTE_ADDR'),
61                    'Accept: ' . $this->request->getServer('HTTP_ACCEPT'),
62                    'Accept-Language: ' . $this->request->getServer('HTTP_ACCEPT_LANGUAGE'),
63                    'Connection: Close',
64                    'Cache-Control: max-age=0',
65                ]
66            );
67
68            $profilePicture = $extendedData['graphql']['user']['profile_pic_url'];
69            $fullName = $extendedData['graphql']['user']['full_name'];
70        } catch (\RuntimeException $exception) {
71            // Redirected to the login page; rate limit for anonymous users exceeded
72            if ($exception->getMessage() != 'HTTP #302 error') {
73                throw $exception;
74            }
75            $extendedData = null;
76        }
77
78        if (isset($profilePicture)) {
79            // Fetch profile picture, because we can't embed it on the site because of the Facebook Container in Firefox
80            $profileBlob = $this->curlRequest($profilePicture, 'GET', [], [
81                'X-Forwarded-For: ' . $this->request->getServer('REMOTE_ADDR'),
82                'Connection: Close',
83                'Cache-Control: max-age=0',
84            ], true);
85            $profilePicture = 'data:' . $profileBlob['contentType']
86                . ';base64,' . base64_encode($profileBlob['responseBody']);
87        } else {
88            // When no profile picture can be found, use a default one
89            $profilePicture = 'data:image/svg+xml;base64,'
90                . base64_encode(file_get_contents(__DIR__ . '/../../templates/unknown-user.svg'));
91        }
92
93        return new User(
94            $userData['id'],
95            $userData['username'],
96            $fullName ?? $userData['username'],
97            $profilePicture,
98            static::class,
99            ['accessToken' => $accessToken, 'userData' => $userData, 'extendedData' => $extendedData]
100        );
101    }
102
103    /**
104     * Returns the image source for the logo of this provider.
105     *
106     * @return string
107     */
108    public static function getLogoSource(): string
109    {
110        return 'data:image/svg+xml;base64,'
111            . base64_encode(file_get_contents(__DIR__ . '/../../templates/logos/Instagram.svg'));
112    }
113
114    /**
115     * Returns the theme color for this provider.
116     *
117     * @return string
118     */
119    public static function getThemeColor(): string
120    {
121        return 'rgb(193,53,132)';
122    }
123
124    /**
125     * Instagram really wants HTTPS, even when developing.
126     *
127     * @return string
128     */
129    protected function getCurrentUri(): string
130    {
131        $url = parent::getCurrentUri();
132        if (substr($url, 0, 5) == 'http:') {
133            $url = 'https:' . substr($url, 5);
134        }
135        return $url;
136    }
137}