Wallhaven::getToken()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 15
rs 9.4285
cc 2
eloc 8
nc 2
nop 0
1
<?php
2
3
namespace Wallhaven;
4
5
use GuzzleHttp\Client;
6
use GuzzleHttp\Cookie\CookieJar;
7
use GuzzleHttp\TransferStats;
8
use PHPHtmlParser\Dom;
9
use Wallhaven\Exceptions\LoginException;
10
use Wallhaven\Exceptions\WallhavenException;
11
12
/**
13
 * Wallhaven
14
 *
15
 * @package Wallhaven
16
 */
17
class Wallhaven
18
{
19
    const URL_HOME         = "https://alpha.wallhaven.cc";
20
    const URL_WALLPAPER    = "/wallpaper";
21
    const URL_LOGIN        = "/auth/login";
22
    const URL_SEARCH       = "/search";
23
    const URL_THUMB_PREFIX = "/wallpapers/thumb/small/th-";
24
    const URL_IMG_PREFIX   = "https://wallpapers.wallhaven.cc/wallpapers/full/wallhaven-";
25
26
    /**
27
     * @var Client HTTP Client.
28
     */
29
    private $client;
30
31
    /**
32
     * @var string Username.
33
     */
34
    private $username;
35
36
    /**
37
     * Create an instance of Wallhaven. Login credentials are optional.
38
     *
39
     * @param string $username
40
     * @param string $password
41
     *
42
     * @throws LoginException
43
     */
44
    public function __construct($username = null, $password = null)
45
    {
46
        if (!empty($username)) {
47
            $this->login($username, $password);
48
        } else {
49
            $this->initClient();
50
        }
51
    }
52
53
    /**
54
     * Login to Wallhaven.
55
     *
56
     * @param string $username Username.
57
     * @param string $password Password.
58
     *
59
     * @throws LoginException
60
     */
61
    public function login($username, $password)
62
    {
63
        if (empty($username) || empty($password)) {
64
            throw new LoginException("Incorrect username or password.");
65
        }
66
67
        $this->initClient(true);
68
69
        $login = $this->client->post(self::URL_LOGIN, [
0 ignored issues
show
Unused Code introduced by
$login is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
70
            'form_params' => [
71
                '_token'   => $this->getToken(),
72
                'username' => $username,
73
                'password' => $password
74
            ],
75
            'on_stats' => function (TransferStats $stats) use (&$url) {
76
                $url = $stats->getEffectiveUri();
77
            }
78
        ]);
79
80
        if ($url == self::URL_HOME . self::URL_LOGIN) {
81
            throw new LoginException("Incorrect username or password.");
82
        }
83
84
        $this->username = $username;
85
    }
86
87
    /**
88
     * Initialize HTTP client.
89
     *
90
     * @param bool $withCookies Whether cookies should be enabled.
91
     */
92
    private function initClient($withCookies = false)
93
    {
94
95
        if ($withCookies) {
96
            $jar = new CookieJar();
97
            $this->client = new Client(
98
                [
99
                    'base_uri' => self::URL_HOME,
100
                    'cookies'  => $jar
101
                ]);
102
        } else {
103
            $this->client = new Client(['base_uri' => self::URL_HOME]);
104
        }
105
    }
106
107
    /**
108
     * Get token for login.
109
     *
110
     * @return string Token.
111
     * @throws WallhavenException Thrown if no token is found.
112
     */
113
    private function getToken()
114
    {
115
        $body = $this->client->get('/')->getBody()->getContents();
116
117
        $dom = new Dom();
118
        $dom->load($body);
119
120
        $token = $dom->find('input[name="_token"]')[0]->value;
121
122
        if (empty($token)) {
123
            throw new LoginException("Cannot find login token on Wallhaven's homepage.");
124
        }
125
126
        return $token;
127
    }
128
129
    /**
130
     * User.
131
     *
132
     * @param string $username Username. If empty, returns the current user.
133
     *
134
     * @return User User.
135
     */
136
    public function user($username = null)
137
    {
138
        return new User($username ?: $this->username);
139
    }
140
141
    /**
142
     * Search for wallpapers.
143
     *
144
     * @param string   $query       What to search for. Searching for specific tags can be done with #tagname, e.g.
145
     *                              <samp>#cars</samp>
146
     * @param int      $categories  Categories to include. This is a bit field, e.g.: <samp>Category::GENERAL |
147
     *                              Category::PEOPLE</samp>
148
     * @param int      $purity      Purity of wallpapers. This is a bit field, e.g.: <samp>Purity::SFW |
149
     *                              Purity::NSFW</samp>
150
     * @param string   $sorting     Sorting, e.g. <samp>Sorting::RELEVANCE</samp>
151
     * @param string   $order       Order of results. Can be <samp>Order::ASC</samp> or <samp>Order::DESC</samp>
152
     * @param string[] $resolutions Array of resolutions in the format of WxH, e.g.: <samp>['1920x1080',
153
     *                              '1280x720']</samp>
154
     * @param string[] $ratios      Array of ratios in the format of WxH, e.g.: <samp>['16x9', '4x3']</samp>
155
     * @param int      $page        The id of the page to fetch. This is <em>not</em> a total number of pages to
156
     *                              fetch.
157
     *
158
     * @return WallpaperList Wallpapers.
159
     */
160
    public function search(
161
        $query,
162
        $categories = Category::ALL,
163
        $purity = Purity::SFW,
164
        $sorting = Sorting::RELEVANCE,
165
        $order = Order::DESC,
166
        $resolutions = [],
167
        $ratios = [],
168
        $page = 1
169
    ) {
170
        $result = $this->client->get(self::URL_SEARCH, [
171
            'query'   => [
172
                'q'           => $query,
173
                'categories'  => self::getBinary($categories),
174
                'purity'      => self::getBinary($purity),
175
                'sorting'     => $sorting,
176
                'order'       => $order,
177
                'resolutions' => implode(',', $resolutions),
178
                'ratios'      => implode(',', $ratios),
179
                'page'        => $page
180
            ],
181
            'headers' => [
182
                'X-Requested-With' => 'XMLHttpRequest'
183
            ]
184
        ]);
185
186
        $body = $result->getBody()->getContents();
187
        $dom = new Dom();
188
        $dom->load($body);
189
190
        $figures = $dom->find('figure.thumb');
191
192
        $wallpapers = new WallpaperList();
193
194
        foreach ($figures as $figure) {
0 ignored issues
show
Bug introduced by
The expression $figures of type array|object<PHPHtmlParser\Dom\AbstractNode> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
195
            $id = preg_split('#' . self::URL_HOME . self::URL_WALLPAPER . '/#',
196
                $figure->find('a.preview')->getAttribute('href'))[1];
197
198
            $classText = $figure->getAttribute('class');
199
            preg_match("/thumb thumb-(sfw|sketchy|nsfw) thumb-(general|anime|people)/", $classText, $classMatches);
200
201
            $purity = constant('Wallhaven\Purity::' . strtoupper($classMatches[1]));
202
            $category = constant('Wallhaven\Category::' . strtoupper($classMatches[2]));
203
            $resolution = str_replace(' ', '', trim($figure->find('span.wall-res')->text));
204
            $favorites = (int)$figure->find('.wall-favs')->text;
205
206
            $w = new Wallpaper($id, $this->client);
207
208
            $w->setProperties([
209
                'purity'     => $purity,
210
                'category'   => $category,
211
                'resolution' => $resolution,
212
                'favorites'  => $favorites
213
            ]);
214
215
            $wallpapers[] = $w;
216
        }
217
218
        return $wallpapers;
219
    }
220
221
    /**
222
     * Convert a bit field into Wallhaven's format.
223
     *
224
     * @param int $bitField Bit field.
225
     *
226
     * @return string Converted to binary.
227
     */
228
    private static function getBinary($bitField)
229
    {
230
        return str_pad(decbin($bitField), 3, '0', STR_PAD_LEFT);
231
    }
232
233
    /**
234
     * Wallpaper.
235
     *
236
     * @param int $id Wallpaper's ID.
237
     *
238
     * @return Wallpaper Wallpaper.
239
     */
240
    public function wallpaper($id)
241
    {
242
        return new Wallpaper($id, $this->client);
243
    }
244
245
    /**
246
     * Returns a new Filter object to use as a fluent interface.
247
     *
248
     * @return Filter
249
     */
250
    public function filter()
251
    {
252
        return new Filter($this);
253
    }
254
}
255