Issues (51)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/Connection/Client.php (17 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
namespace Wonnova\SDK\Connection;
3
4
use Doctrine\Common\Cache\Cache;
5
use Doctrine\Common\Cache\FilesystemCache;
6
use Doctrine\Common\Collections\ArrayCollection;
7
use Doctrine\Common\Collections\Collection;
8
use JMS\Serializer\Serializer;
9
use Wonnova\SDK\Auth\CredentialsInterface;
10
use Wonnova\SDK\Auth\Token;
11
use Wonnova\SDK\Auth\TokenInterface;
12
use Wonnova\SDK\Common\Headers;
13
use Wonnova\SDK\Common\ResponseCodes;
14
use Wonnova\SDK\Exception\InvalidArgumentException;
15
use Wonnova\SDK\Exception\InvalidRequestException;
16
use Wonnova\SDK\Exception\NotFoundException;
17
use Wonnova\SDK\Exception\RuntimeException;
18
use Wonnova\SDK\Exception\UnauthorizedException;
19
use Wonnova\SDK\Exception\ServerException;
20
use Wonnova\SDK\Http\Route;
21
use Wonnova\SDK\Model\Achievement;
22
use Wonnova\SDK\Model\Action;
23
use Wonnova\SDK\Model\Badge;
24
use Wonnova\SDK\Model\Item;
25
use Wonnova\SDK\Model\Level;
26
use Wonnova\SDK\Model\Notification;
27
use Wonnova\SDK\Model\Quest;
28
use Wonnova\SDK\Model\QuestStep;
29
use Wonnova\SDK\Model\Team;
30
use Wonnova\SDK\Model\Update;
31
use Wonnova\SDK\Model\User;
32
use GuzzleHttp\Client as GuzzleClient;
33
use Wonnova\SDK\Serializer\SerializerFactory;
34
35
/**
36
 * Class Client
37
 * @author Wonnova
38
 * @link http://www.wonnova.com
39
 */
40
class Client extends GuzzleClient implements ClientInterface
41
{
42
    const USER_AGENT    = 'wonnova-php-sdk';
43
    const TOKEN_KEY     = 'wonnova_auth_token';
44
45
    /**
46
     * @var CredentialsInterface
47
     */
48
    protected $credentials;
49
    /**
50
     * @var string
51
     */
52
    protected $language;
53
    /**
54
     * @var Cache
55
     */
56
    protected $cache;
57
    /**
58
     * @var Serializer
59
     */
60
    protected $serializer;
61
    /**
62
     * @var TokenInterface
63
     */
64
    protected $token;
65
    /**
66
     * @var string
67
     */
68
    protected $tokenCacheKey;
69
70
    /**
71
     * @param CredentialsInterface $credentials
72
     * @param string $language
73
     * @param Cache $cache
74
     * @param null $baseUrl
75
     */
76 43
    public function __construct(
77
        CredentialsInterface $credentials,
78 1
        $language = 'es',
79
        Cache $cache = null,
80
        $baseUrl = null
81
    ) {
82 43
        parent::__construct([
83 43
            'base_url' => $baseUrl ?: self::HOST,
84
            'defaults' => [
85
                'headers' => [
86
                    'User-Agent' => self::USER_AGENT
87 43
                ],
88 1
                'exceptions' => false
89 43
            ]
90 43
        ]);
91
92 43
        $this->serializer = SerializerFactory::create();
93 43
        $this->credentials = $credentials;
94 43
        $this->language = $language;
95 43
        $this->cache = $cache ?: new FilesystemCache(sys_get_temp_dir());
96
97
        // Initialize the token if it exists in cache
98 43
        $this->tokenCacheKey = sprintf('%s_%s', self::TOKEN_KEY, $credentials->getKey());
99 43
        if ($this->cache->contains($this->tokenCacheKey)) {
100 1
            $this->token = new Token();
101 1
            $this->token->setAccessToken($this->cache->fetch($this->tokenCacheKey));
102 1
        }
103
104 43
    }
105
106
    /**
107
     * Performs a connection to defined endpoint with defined options
108
     *
109
     * @param string $method
110
     * @param Route|string $route
111
     * @param array $options
112
     * @return \GuzzleHttp\Message\ResponseInterface
113
     * @throws \Wonnova\SDK\Exception\ServerException
114
     * @throws \Wonnova\SDK\Exception\InvalidRequestException
115
     * @throws \Wonnova\SDK\Exception\NotFoundException
116
     * @throws \Wonnova\SDK\Exception\RuntimeException
117
     */
118 42
    public function connect($method, $route, array $options = [])
119
    {
120
        // Perform authentication if token has not been set yet
121 42
        if (! isset($this->token)) {
122 41
            $this->authenticate();
123 41
        }
124
125
        // Add the language and token headers
126 42
        $options = $this->processOptionsWithDefaults($options);
127 42
        $response = $this->send($this->createRequest($method, $route, $options));
0 ignored issues
show
It seems like $route defined by parameter $route on line 118 can also be of type object<Wonnova\SDK\Http\Route>; however, GuzzleHttp\Client::createRequest() does only seem to accept string|array|object<GuzzleHttp\Url>|null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
128 42
        $code = $response->getStatusCode();
129 42
        if (intval($code) === 200) {
130 37
            return $response;
131
        }
132
133
        // In case of error throw proper exception
134
        switch ($code) {
135 6
            case 401: // Token not valid. Reconect
136 2
                $message = json_decode($response->getBody()->getContents(), true);
137
138
                // If the server returned an INVALID_TOKEN response, reconnect
139 2
                if (isset($message['error']) && $message['error'] === ResponseCodes::INVALID_TOKEN) {
140 1
                    $this->resetToken();
141 1
                    return $this->connect($method, $route, $options);
142
                    break;
0 ignored issues
show
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
143
                }
144
145 1
                throw new UnauthorizedException(
146 1
                    sprintf(
147 1
                        'Unauthorized request to "%s" with method "%s" and response message "%s"',
148 1
                        $route,
149 1
                        $method,
150 1
                        isset($message['message']) ? $message['message'] : ''
151 1
                    ),
152
                    $code
153 1
                );
154 4
            case 400:
155 1
                $message = json_decode($response->getBody()->getContents(), true);
156 1
                throw new InvalidRequestException(
157 1
                    sprintf(
158 1
                        'Invalid request to "%s" with method "%s" and response message "%s"',
159 1
                        $route,
160 1
                        $method,
161 2
                        isset($message['message']) ? $message['message'] : ''
162 1
                    ),
163
                    $code
164 1
                );
165 3
            case 404:
166 1
                throw new NotFoundException(
167 1
                    sprintf('Route "%s" with method "%s" was not found', $route, $method),
168
                    $code
169 1
                );
170 2
            case 500:
171 1
                throw new ServerException(
172 1
                    sprintf('There was a server error processing a request to "%s" with method "%s"', $route, $method),
173
                    $code
174 1
                );
175 1
            default:
176 1
                throw new RuntimeException(
177 1
                    sprintf(
178 1
                        'Unexpected error occurred whith request to route "%s" with method "%s"',
179 1
                        $route,
180
                        $method
181 1
                    ),
182
                    $code
183 1
                );
184 1
        }
185
    }
186
187
    /**
188
     * Resets the token so that it can be reinitialized.
189
     * This has to be used when current token has expired or is invalid
190
     */
191 1
    private function resetToken()
192
    {
193 1
        $this->token = null;
194 1
        $this->cache->delete($this->tokenCacheKey);
195 1
    }
196
197
    /**
198
     * Performs authentication caching the auth token
199
     */
200 41
    private function authenticate()
201
    {
202 41
        $response = $this->send($this->createRequest('POST', self::AUTH_ROUTE, [
203 41
            'json' => $this->credentials->toArray()
204 41
        ]));
205 41
        $this->token = $this->serializer->deserialize(
206 41
            $response->getBody()->getContents(),
207 41
            'Wonnova\SDK\Auth\Token',
208
            'json'
209 41
        );
210 41
        $this->cache->save($this->tokenCacheKey, $this->token->getAccessToken());
211 41
    }
212
213
    /**
214
     * @param array $options
215
     * @return array
216
     */
217 42
    protected function processOptionsWithDefaults(array $options)
218
    {
219 42
        $options['headers'] = isset($options['headers']) ? $options['headers'] : [];
220
221 42
        $options['headers'][Headers::LANGUAGE_HEADER] = $this->language;
222 42
        $options['headers'][Headers::TOKEN_HEADER] = $this->token->getAccessToken();
223
224 42
        return $options;
225
    }
226
227
    /**
228
     * Fetches a route and maps a resource list of models under provided key
229
     *
230
     * @param Route|string $route
231
     * @param $resourceKey
232
     * @param $resourceClass
233
     * @return ArrayCollection
234
     */
235 11
    protected function getResourceCollection($route, $resourceKey, $resourceClass)
236
    {
237 11
        $response = $this->connect('GET', $route);
238 11
        $contents = $this->serializer->deserialize($response->getBody()->getContents(), 'array', 'json');
239 11
        return new ArrayCollection($this->serializer->deserialize(
240 11
            $contents[$resourceKey],
241 11
            sprintf('array<%s>', $resourceClass),
242
            'array'
243 11
        ));
244
    }
245
246
    /**
247
     * Returns users list
248
     *
249
     * @return Collection|User[]
250
     */
251 7 View Code Duplication
    public function getUsers()
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
252
    {
253 7
        $response = $this->connect('GET', self::USERS_ROUTE);
254 2
        $contents = $response->getBody()->getContents();
255 2
        return new ArrayCollection($this->serializer->deserialize(
256 2
            $contents,
257 2
            'array<Wonnova\SDK\Model\User>',
258
            'json'
259 2
        ));
260
    }
261
262
    /**
263
     * Returns information about certain user
264
     *
265
     * @param $userId
266
     * @return User
267
     */
268 3 View Code Duplication
    public function getUser($userId)
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
269
    {
270 3
        $response = $this->connect('GET', new Route(self::USERS_ROUTE, [], [
271
            'userId' => $userId
272 3
        ]));
273 3
        $contents = $response->getBody()->getContents();
274 3
        return $this->serializer->deserialize($contents, 'Wonnova\SDK\Model\User', 'json');
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->serializer...\Model\\User', 'json'); (object|array|integer|double|string|boolean) is incompatible with the return type declared by the interface Wonnova\SDK\Connection\A...rsAPIInterface::getUser of type Wonnova\SDK\Model\User.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
275
    }
276
277
    /**
278
     * Creates provided user
279
     *
280
     * @param User $user
281
     */
282 1 View Code Duplication
    public function createUser(User $user)
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
283
    {
284 1
        $response = $this->connect('POST', self::USERS_ROUTE, [
285
            'json' => $user
286 1
        ]);
287 1
        $contents = $response->getBody()->getContents();
288 1
        $userData = $this->serializer->deserialize($contents, 'array', 'json');
289
        // The server will return the user ID. Set it to the model
290 1
        $user->setUserId($userData['userId']);
291 1
    }
292
293
    /**
294
     * Updates provided user
295
     *
296
     * @param User $user
297
     */
298 2
    public function updateUser(User $user)
299
    {
300 2
        $userId = $user->getUserId();
301 2
        if (empty($userId)) {
302 1
            throw new InvalidArgumentException('Provided user has an empty userId.');
303
        }
304
305 1
        $response = $this->connect('PUT', new Route(self::UPDATE_USER_ROUTE, [
306
            'userId' => $userId
307 1
        ]), [
308
            'json' => $user
309 1
        ]);
310 1
        $contents = $response->getBody()->getContents();
311
        // The server will return the user data. Refresh the model
312 1
        $user->fromArray($this->serializer->deserialize($contents, 'array', 'json'));
313 1
    }
314
315
    /**
316
     *
317
     *
318
     * @param User|string $user A User model or userId
319
     * @return Collection|Notification[]
320
     */
321 1 View Code Duplication
    public function getUserNotifications($user)
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
322
    {
323 1
        $userId = $user instanceof User ? $user->getUserId() : $user;
324 1
        return $this->getResourceCollection(new Route(self::USER_NOTIFICATIONS_ROUTE, [
325
            'userId' => $userId
326 1
        ]), 'notifications', 'Wonnova\SDK\Model\Notification');
327
    }
328
329
    /**
330
     * Returns the list of badges that certain user has won
331
     *
332
     * @param User|string $user A User model or userId
333
     * @return Collection|Badge[]
334
     */
335 1 View Code Duplication
    public function getUserBadges($user)
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
336
    {
337 1
        $userId = $user instanceof User ? $user->getUserId() : $user;
338 1
        return $this->getResourceCollection(new Route(self::USER_BADGES_ROUTE, [
339
            'userId' => $userId
340 1
        ]), 'badges', 'Wonnova\SDK\Model\Badge');
341
    }
342
343
    /**
344
     * Returns the number of achievements of each type for certain user
345
     *
346
     * @param User|string $user A User model or userId
347
     * @param array|string $types List of types in a comma-separated string or array.
348
     *          All the types will be returned by default
349
     * @return Collection|Achievement[]
350
     */
351 1
    public function getUserAchievements($user, $types = [])
352
    {
353 1
        $userId = $user instanceof User ? $user->getUserId() : $user;
354 1
        $types = empty($types) ? Achievement::getAllTypesList() : $types;
355 1
        $types = is_array($types) ? implode(',', $types) : $types;
356
357 1
        return $this->getResourceCollection(new Route(
358 1
            self::USER_ACHIEVEMENTS_ROUTE,
359 1
            ['userId' => $userId],
360 1
            ['types' => $types]
361 1
        ), 'achievements', 'Wonnova\SDK\Model\Achievement');
362
    }
363
364
    /**
365
     * Returns the list of steps in a quest telling if certain user has already completed them
366
     *
367
     * @param User|string $user A User model or userId
368
     * @param string $questCode
369
     * @return Collection|QuestStep[]
370
     */
371 1 View Code Duplication
    public function getUserProgressInQuest($user, $questCode)
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
372
    {
373 1
        $userId = $user instanceof User ? $user->getUserId() : $user;
374 1
        return $this->getResourceCollection(new Route(self::USER_QUEST_PROGRESS_ROUTE, [
375 1
            'userId' => $userId,
376
            'questCode' => $questCode
377 1
        ]), 'questSteps', 'Wonnova\SDK\Model\QuestStep');
378
    }
379
380
    /**
381
     * Returns the list of created quests
382
     *
383
     * @return Collection|Quest[]
384
     */
385 1
    public function getQuests()
386
    {
387 1
        return $this->getResourceCollection(self::QUESTS_ROUTE, 'quests', 'Wonnova\SDK\Model\Quest');
388
    }
389
390
    /**
391
     * Returns all the quests with which a user is involved with, including the list of their steps
392
     *
393
     * @param User|string $user A User model or userId
394
     * @return Collection|Quest[]
395
     */
396 1 View Code Duplication
    public function getUserStatusInQuests($user)
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
397
    {
398 1
        $userId = $user instanceof User ? $user->getUserId() : $user;
399 1
        return $this->getResourceCollection(new Route(self::USER_QUESTS_STATUS_ROUTE, [
400
            'userId' => $userId
401 1
        ]), 'quests', 'Wonnova\SDK\Model\Quest');
402
    }
403
404
    /**
405
     * @return Collection|Level[]
406
     */
407 1
    public function getLevels()
408
    {
409 1
        return $this->getResourceCollection(self::LEVELS_ROUTE, 'levels', 'Wonnova\SDK\Model\Level');
410
    }
411
412
    /**
413
     * Returns the level of a user in certain scenario
414
     *
415
     * @param User|string $user A User model or userId
416
     * @param string|null $scenarioCode
417
     * @return Level
418
     */
419 2
    public function getUserLevelInScenario($user, $scenarioCode = null)
420
    {
421 2
        $userId = $user instanceof User ? $user->getUserId() : $user;
422 2
        $route = self::USER_LEVEL_ROUTE;
423
        $options = [
424 2
            'userId' => $userId,
425 2
        ];
426
427 2
        if (! empty($scenarioCode)) {
428 1
            $route = self::USER_LEVEL_WITH_SCENARIO_ROUTE;
429 1
            $options['scenarioCode'] = $scenarioCode;
430 1
        }
431
432 2
        $response = $this->connect('GET', new Route($route, $options));
433
434 2
        $contents = $response->getBody()->getContents();
435 2
        $contents = $this->serializer->deserialize($contents, 'array', 'json');
436 2
        return $this->serializer->deserialize($contents['level'], 'Wonnova\SDK\Model\Level', 'array');
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->serializer...odel\\Level', 'array'); (object|array|integer|double|string|boolean) is incompatible with the return type declared by the interface Wonnova\SDK\Connection\A...:getUserLevelInScenario of type Wonnova\SDK\Model\Level.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
437
    }
438
439
    /**
440
     * Returns the list of top teams ordered by total score of their members
441
     * If a user is provided, the list will include the team of that user, even if it is not in the top list
442
     *
443
     * @param int|null $maxCount Maximum number of results
444
     * @param User|string|null $user A User model or userId
445
     * @return Collection|Team[]
446
     */
447 2
    public function getTeamsLeaderboard($maxCount = null, $user = null)
448
    {
449
        $queryParams = [
450 2
            'userId' => null,
451
            'maxCount' => null
452 2
        ];
453 2
        if (is_int($maxCount)) {
454 1
            $queryParams['maxCount'] = $maxCount;
455 1
        }
456 2
        if (isset($user)) {
457 1
            $queryParams['userId'] = $user instanceof User ? $user->getUserId() : $user;
458 1
        }
459
460 2
        return $this->getResourceCollection(
461 2
            new Route(self::TEAMS_LEADERBOARD_ROUTE, [], $queryParams),
462 2
            'scores',
463
            'Wonnova\SDK\Model\Team'
464 2
        );
465
    }
466
467
    /**
468
     * Returns the top rated items
469
     *
470
     * @param int $maxCount
471
     * @return Collection|Item[]
472
     */
473 1
    public function getItemsLeaderboard($maxCount = 6)
474
    {
475 1
        return $this->getResourceCollection(new Route(self::ITEMS_LEADERBOARD_ROUTE, [], [
476
            'minCount' => $maxCount
477 1
        ]), 'leaderboard', 'Wonnova\SDK\Model\Item');
478
    }
479
480
    /**
481
     * Rates an item increasing its score and setting the rate from certain user.
482
     *
483
     * @param User|string $user a User model or userId
484
     * @param Item|string $item an Item model or itemId
485
     * @param int $score
486
     * @return Item
487
     */
488 1
    public function rateItem($user, $item, $score = 0)
489
    {
490
        $data = [
491 1
            'userId' => $user instanceof User ? $user->getUserId() : $user,
492 1
            'points' => $score,
493 1
            'item' => $item instanceof Item ? $item->toArray() : [
494
                'id' => $item
495 1
            ]
496 1
        ];
497 1
        $response = $this->connect('POST', self::ITEM_RATE_ROUTE, [
498
            'json' => $data
499 1
        ]);
500 1
        $contents = $response->getBody()->getContents();
501 1
        $itemData = $this->serializer->deserialize($contents, 'array', 'json');
502
503
        // If an Item wasn't provided, create a new Item instance
504 1
        if (! $item instanceof Item) {
505 1
            $item = new Item();
506 1
        }
507
508
        // Refresh the Item's data and return it
509 1
        $item->fromArray($itemData['item']);
510 1
        return $item;
511
    }
512
513
    /**
514
     * Rates an item increasing its score and setting the rate from certain user.
515
     *
516
     * @param User|string $user a User model or userId
517
     * @param array $items an array of Item models
518
     * @return ArrayCollection
519
     */
520 1
    public function rateSeveralItems($user, array $items)
521
    {
522
        $data = [
523 1
            'userId' => $user instanceof User ? $user->getUserId() : $user,
524 1
            'itemsList' => [],
525 1
        ];
526
527 1
        foreach ($items as $item) {
528 1
            if ($item instanceof Item) {
529 1
                $data['itemsList'][] = $item->toArray();
530 1
            }
531 1
        }
532
533 1
        $response = $this->connect('POST', self::ITEM_RATE_LIST_ROUTE, [
534
            'json' => $data
535 1
        ]);
536 1
        $contents = $this->serializer->deserialize($response->getBody()->getContents(), 'array', 'json');
537
538 1
        return new ArrayCollection($this->serializer->deserialize(
539 1
            $contents['items'],
540 1
            'array<Wonnova\SDK\Model\Item>',
541
            'array'
542 1
        ));
543
    }
544
545
    /**
546
     * Deletes certain item
547
     *
548
     * @param Item|string $item an Item model or itemId
549
     * @return void
550
     */
551 1 View Code Duplication
    public function deleteItem($item)
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
552
    {
553 1
        $itemId = $item instanceof Item ? $item->getItemId() : $item;
554 1
        $this->connect('DELETE', new Route(self::ITEM_ROUTE, [
555
            'itemId' => $itemId
556 1
        ]));
557 1
    }
558
559
    /**
560
     * Resets an item's score to zero
561
     *
562
     * @param Item|string $item an Item model or itemId
563
     * @return void
564
     */
565 1 View Code Duplication
    public function resetItemScore($item)
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
566
    {
567 1
        $itemId = $item instanceof Item ? $item->getItemId() : $item;
568 1
        $this->connect('PUT', new Route(self::ITEM_RESET_SCORE_ROUTE, [
569
            'itemId' => $itemId
570 1
        ]));
571 1
    }
572
573
    /**
574
     * Returns a partial model of certain user defined by its userId
575
     * By calling this method, only the properties username, fullName, avatar and score of the User will be popullated,
576
     * as well as the provided userId
577
     *
578
     * @param $userId
579
     * @return User
580
     */
581 1
    public function getUserData($userId)
582
    {
583 1
        $response = $this->connect('GET', new Route(self::USER_ABOUT_ROUTE, [
584
            'userId' => $userId
585 1
        ]));
586 1
        $contents = $response->getBody()->getContents();
587 1
        $responseData = $this->serializer->deserialize($contents, 'array', 'json');
588
589
        // Create the User model to be returned and populate it
590 1
        $user = new User();
591 1
        $user->fromArray($responseData['userData']);
592 1
        $user->setUserId($userId);
593 1
        return $user;
594
    }
595
596
    /**
597
     * Performs an action notification from certain user
598
     *
599
     * @param User|string $user A User model or userId
600
     * @param string $actionCode
601
     * @param Item|string $item An Item model or itemId
602
     * @param array $categories
603
     * @return void
604
     */
605 3
    public function notifyAction($user, $actionCode, $item = null, array $categories = [])
606
    {
607
        // Prepare request body
608
        $requestData = [
609 3
            'userId' => $user instanceof User ? $user->getUserId() : $user,
610
            'actionCode' => $actionCode
611 3
        ];
612 3 View Code Duplication
        if (isset($item)) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
613 1
            $requestData['item'] = $item instanceof Item ? $item->toArray() : [
614
                'id' => $item
615 1
            ];
616 1
        }
617 3
        if (! empty($categories)) {
618 1
            $requestData['categories'] = $categories;
619 1
        }
620
621
        // Perform request
622 3
        $this->connect('POST', self::ACTION_NOTIFICATION_ROUTE, [
623
            'json' => $requestData
624 3
        ]);
625 3
    }
626
627
    /**
628
     * Performs an action notification from certain user
629
     *
630
     * @param User|string $user A User model or userId
631
     * @param array $actions array of Action objects or strings
632
     * @return void
633
     */
634 4
    public function notifySeveralActions($user, array $actions)
635
    {
636
        // Prepare request body
637
        $requestData = [
638 4
            'userId' => $user instanceof User ? $user->getUserId() : $user,
639 4
            'actions' => [],
640 4
        ];
641
642 4
        foreach ($actions as $action) {
643
644 4
            if (is_string($action)) {
645 1
                $actionCode = $action;
646 1
                $action = new Action();
647 1
                $action->setActionCode($actionCode);
648 1
            }
649
650 4
            if ($action instanceof Action) {
651 4
                $requestData['actions'][] = $action->toArray();
652 4
            }
653 4
        }
654
655
        // Perform request
656 4
        $this->connect('POST', self::ACTION_NOTIFY_SEVERAL_ROUTE, [
657
            'json' => $requestData
658 4
        ]);
659 4
    }
660
661
    /**
662
     * Performs an interaction notification between two users
663
     *
664
     * @param User|string $user A User model or userId
665
     * @param User|string $targetUser A User model or userId
666
     * @param string $interactionCode
667
     * @param Item|null $item An Item model or itemId
668
     * @param array $categories
669
     * @return void
670
     */
671 3
    public function notifyInteraction($user, $targetUser, $interactionCode, $item = null, array $categories = [])
672
    {
673
        // Prepare request body
674
        $requestData = [
675 3
            'userId' => $user instanceof User ? $user->getUserId() : $user,
676 3
            'targetUserId' => $targetUser instanceof User ? $targetUser->getUserId() : $targetUser,
677
            'interactionCode' => $interactionCode
678 3
        ];
679 3 View Code Duplication
        if (isset($item)) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
680 1
            $requestData['item'] = $item instanceof Item ? $item->toArray() : [
681
                'id' => $item
682 1
            ];
683 1
        }
684 3
        if (! empty($categories)) {
685 1
            $requestData['categories'] = $categories;
686 1
        }
687
688
        // Perform request
689 3
        $this->connect('POST', self::INTERACTION_NOTIFICATION_ROUTE, [
690
            'json' => $requestData
691 3
        ]);
692 3
    }
693
694
    /**
695
     * Returns the number of times a user has performed certain action
696
     *
697
     * @param User|string $user A User model or userId
698
     * @param string $actionCode
699
     * @return int
700
     */
701 1 View Code Duplication
    public function getUserActionOccurrences($user, $actionCode)
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
702
    {
703 1
        $response = $this->connect('GET', new Route(self::USER_ACTION_OCCURRENCES_ROUTE, [
704 1
            'userId' => $user instanceof User ? $user->getUserId() : $user,
705
            'actionCode' => $actionCode
706 1
        ]));
707 1
        $contents = $response->getBody()->getContents();
708 1
        return $this->serializer->deserialize($contents, 'array', 'json')['occurrences'];
709
    }
710
711
    /**
712
     * Returns information about the status of an interactionaccording to its limits
713
     *
714
     * @param User|string $user A User model or userId
715
     * @param User|string $targetUser A User model or userId
716
     * @param string $interactionCode
717
     * @param Item|string $item An Item model or itemId
718
     * @return array
719
     */
720 1
    public function getInteractionStatus($user, $targetUser, $interactionCode, $item)
721
    {
722
        // Prepare request body
723
        $requestData = [
724 1
            'user' => $user instanceof User ? $user->getUserId() : $user,
725
            'targetUser' => [
726 1
                'id' => $targetUser instanceof User ? $targetUser->getUserId() : $targetUser
727 1
            ],
728 1
            'interactionCode' => $interactionCode,
729 1
            'item' => $item instanceof Item ? $item->toArray() : [
730
                'id' => $item
731 1
            ]
732 1
        ];
733
734 1
        $response = $this->connect('POST', self::INTERACTION_STATUS_ROUTE, [
735
            'json' => $requestData
736 1
        ]);
737 1
        $contents = $response->getBody()->getContents();
738 1
        $responseData = $this->serializer->deserialize($contents, 'array', 'json');
739
740
        // Process response properties
741 1
        unset($responseData['status']);
742 1
        if (! isset($responseData['score'])) {
743 1
            $responseData['score'] = 0;
744 1
        }
745
746 1
        return $responseData;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $responseData; (object|array|integer|double|string|boolean) is incompatible with the return type declared by the interface Wonnova\SDK\Connection\A...e::getInteractionStatus of type array.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
747
    }
748
749
    /**
750
     * Returns the list of most recent updates for certain user
751
     *
752
     * @param User|string $user A User model or userId
753
     * @param int $maxCount
754
     * @return Collection|Update[]
755
     */
756 1
    public function getUserLastUpdates($user, $maxCount = null)
757
    {
758 1
        $queryParams = [];
759 1
        if (is_int($maxCount)) {
760 1
            $queryParams['minCount'] = $maxCount;
761 1
        }
762 1
        $route = new Route(self::USER_LAST_UPDATES, [
763 1
            'userId' => $user instanceof User ? $user->getUserId() : $user
764 1
        ], $queryParams);
765 1
        return $this->getResourceCollection($route, 'lastUpdates', 'Wonnova\SDK\Model\Update');
766
    }
767
}
768