InstagramAccount::isValidMediaID()   A
last analyzed

Complexity

Conditions 3
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 10
rs 9.4285
c 0
b 0
f 0
ccs 5
cts 5
cp 1
cc 3
eloc 5
nc 2
nop 1
crap 3
1
<?php
2
3
use Larabros\Elogram\Client;
4
5
class InstagramAccount extends DataObject
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
6
{
7
    /**
8
     * @config
9
     * @var string
10
     */
11
    private static $client_id;
0 ignored issues
show
Unused Code introduced by
The property $client_id is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
12
13
    /**
14
     * @config
15
     * @var string
16
     */
17
    private static $client_secret;
0 ignored issues
show
Unused Code introduced by
The property $client_secret is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
18
19
    /**
20
     * @config
21
     * @var string
22
     */
23
    private static $redirect_path;
0 ignored issues
show
Unused Code introduced by
The property $redirect_path is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
24
25
    /**
26
     * @config
27
     */
28
    private static $items_per_page = 9;
0 ignored issues
show
Unused Code introduced by
The property $items_per_page is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
29
30
    /**
31
     * @var array
32
     */
33
    private static $db = [
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $db is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
34
        'Title' => 'Varchar',
35
        'AccessToken' => 'Text',
36
    ];
37
38
    /**
39
     * @return FieldList
40
     */
41
    public function getCMSFields()
42
    {
43
        $fields = parent::getCMSFields();
44
45
        $fields->removeByName('AccessToken');
46
47
        if ($this->AccessToken) {
0 ignored issues
show
Documentation introduced by
The property AccessToken does not exist on object<InstagramAccount>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
48
            $token = json_decode($this->AccessToken);
0 ignored issues
show
Documentation introduced by
The property AccessToken does not exist on object<InstagramAccount>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
49
50
            $usernameField = LiteralField::create(
51
                'Title',
52
                '<div class="field">' .
53
                    '<label class="left">' .
54
                        _t('Instagram.FieldLabelTitle', 'Username') .
55
                    '</label>' .
56
                    '<div class="middleColumn" style="padding-top:8px;">' .
57
                        '<a ' .
58
                            "href='https://www.instagram.com/{$token->user->username}' " .
59
                            'title="View on Instagram" ' .
60
                            'target="_blank">' .
61
                            $token->user->username .
62
                        '</a>' .
63
                    '</div>' .
64
                '</div>'
65
            );
66
        } else {
67
            $usernameField = Textfield::create('Title', _t('Instagram.FieldLabelTitle', 'Username'));
68
            $usernameField->setDescription(
69
                _t(
70
                    'Instagram.FieldDescriptionTitle',
71
                    'The Instagram account you want to pull media from.'
72
                )
73
            );
74
        }
75
76
        $fields->addFieldToTab('Root.Main', $usernameField);
77
78
        $this->extend('updateCMSFields', $fields);
79
80
        return $fields;
81
    }
82
83
    /**
84
     * @return FieldList
85
     */
86
    public function getCMSActions()
87
    {
88
        $actions = parent::getCMSActions();
89
90
        if (!$this->ID || $this->AccessToken) {
0 ignored issues
show
Documentation introduced by
The property AccessToken does not exist on object<InstagramAccount>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
91
            $this->extend('updateCMSActions', $actions);
92
            return $actions;
93
        }
94
95
        $client = self::getNewInstagramClient();
96
        $loginURL = $client->getLoginUrl();
97
98
        $this->setSessionOAuthState($this->getOAuthStateValueFromLoginURL($loginURL));
99
100
        $actions->push(
101
            LiteralField::create(
102
              'OAuthLink',
103
              '<a class="ss-ui-button" href="' . $loginURL . '">' .
104
                _t('Instagram.ButtonLabelAuthoriseAccount', 'Authorise account') .
105
              '</a>'
106
            )
107
        );
108
109
        $this->extend('updateCMSActions', $actions);
110
111
        return $actions;
112
    }
113
114
    /**
115
     * @return RequiredFields
116
     */
117
    public function getCMSValidator()
118
    {
119
        return new RequiredFields('Title');
120
    }
121
122
    /**
123
     * Ensures the person to authorising the account is logged into Instagram as the correct user
124
     * before setting the token.
125
     *
126
     * For example if the user is logged into Instagram as 'FooUser' and they attempt to set a token
127
     * for the InstagramAccount record 'InstagramAccount', the token will contain data relating to
128
     * the 'FooUser' account. So if this happen we display a message telling the user they need to
129
     * log out of Instagram before they can authorise another account.
130
     *
131
     * @param string $token
132
     */
133 1
    public function updateAccessToken($token, $state)
134
    {
135 1
        $newToken = json_decode($token);
136
137 1
        if ($state !== $this->getSessionOAuthState() ||
138 1
            $newToken->user->username !== $this->getField('Title')) {
139 1
            throw new Exception('Trying to set token on wrong InstagramAccount');
140
        }
141
142 1
        if (!$currentToken = $this->getField('AccessToken')) {
143 1
            $this->setField('AccessToken', $token);
144 1
            return;
145
        }
146
147 1
        $currentToken = json_decode($currentToken);
148
149 1
        if ($newToken->user->id !== $currentToken->user->id) {
150 1
            throw new Exception('Trying to set token on wrong InstagramAccount');
151
        }
152
153 1
        $this->setField('AccessToken', $token);
154 1
    }
155
156
    /**
157
     * Create a configured Instagram API interface.
158
     *
159
     * @param string $token
160
     * @return MetzWeb\Instagram\Instagram
161
     */
162
    public static function getNewInstagramClient($token = null)
163
    {
164
        $client_id = Config::inst()->get('InstagramAccount', 'client_id');
165
        $client_secret = Config::inst()->get('InstagramAccount', 'client_secret');
166
        $redirect_path = Config::inst()->get('InstagramAccount', 'redirect_path');
167
168
        if (!$client_id) {
169
            user_error(
170
                'Add a client_id to config (InstagramAdmin::client_id)',
171
                E_USER_ERROR
172
            );
173
        }
174
175
        if (!$client_secret) {
176
            user_error(
177
                'Add a client_secret to config (InstagramAdmin::client_secret)',
178
                E_USER_ERROR
179
            );
180
        }
181
182
        return new Client(
183
            $client_id,
184
            $client_secret,
185
            $token,
186
            Director::absoluteBaseURL() . $redirect_path
187
        );
188
    }
189
190
    /**
191
     * Gets the 'state' value from an OAuth login URL.
192
     *
193
     * @param string $loginURL
194
     * @return string|null
195
     */
196 1
    public function getOAuthStateValueFromLoginURL($loginURL = null)
197
    {
198 1
        if (!$loginURL) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $loginURL of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
199 1
            return null;
200
        }
201
202 1
        $parts = parse_url($loginURL);
203 1
        parse_str($parts['query'], $query);
204
205 1
        return array_key_exists('state', $query)
206 1
            ? $query['state']
207 1
            : null;
208
    }
209
210
    /**
211
     * Gets the InstsgramAccount's OAuth state from Session.
212
     *
213
     * @return string|null
214
     */
215
    public function getSessionOAuthState()
216
    {
217
        $instagramAccounts = Session::get('InstagramAccounts');
218
219
        if (!$this->ID || !$instagramAccounts || !array_key_exists($this->ID, $instagramAccounts)) {
220
            return null;
221
        }
222
223
        return $instagramAccounts[$this->ID];
224
    }
225
226
    /**
227
     * Gets the authorised user's Instagram ID from the AccessToken.
228
     *
229
     * @return string|null
230
     */
231
    public function getInstagramID()
232
    {
233
        if (!$token = $this->getField('AccessToken')) {
234
            return null;
235
        }
236
237
        return json_decode($token)->user->id;
238
    }
239
240
    /**
241
     * Checks if the passed ID is a valid pattern.
242
     *
243
     * @param string $mediaID
244
     * @return boolean
245
     */
246 1
    public function isValidMediaID($mediaID = null)
247
    {
248 1
        if (!$mediaID || !$instagramID = $this->getInstagramID()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $mediaID of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
249 1
            return false;
250
        }
251
252 1
        $pattern = '/^(\d{18}|\d{19})_' . $instagramID . '$/';
253
254 1
        return preg_match($pattern, $mediaID) === 1;
255
    }
256
257
    /**
258
     * Sets a Session variable which is used to keep track of
259
     * the InstagramAccount through the OAuth flow.
260
     *
261
     * @param string $state
262
     */
263
    private function setSessionOAuthState($state = null)
264
    {
265
        if (!$this->ID || !$state) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $state of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
266
            return null;
267
        }
268
269
        $instagramAccounts = Session::get('InstagramAccounts');
270
        $instagramAccounts = $instagramAccounts ? $instagramAccounts : [];
271
272
        $instagramAccounts[$this->ID] = $state;
273
274
        Session::set('InstagramAccounts', $instagramAccounts);
0 ignored issues
show
Documentation introduced by
$instagramAccounts is of type array<integer,string>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
275
    }
276
277
    /**
278
     * Gets a list of media from the user's Instagram.
279
     *
280
     * @param string $maxID Return media earlier than this ID
281
     * @return Larabros\Elogram\Http\Response|null
282
     */
283
    public function getMedia($maxID = null)
284
    {
285
        if (
286
            !is_string($this->getField('AccessToken')) ||
287
            ($maxID && !$this->isValidMediaID($maxID))
0 ignored issues
show
Bug Best Practice introduced by
The expression $maxID of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
288
        ) {
289
            return null;
290
        }
291
292
        $client = $this->getNewInstagramClient($this->getField('AccessToken'));
293
        $client->secureRequests();
294
295
        return $client->users()->getMedia('self', $this->config()->items_per_page, null, $maxID);
296
    }
297
}
298