Passed
Push — master ( b99c9f...d56d2c )
by Malte
04:37
created

Query   C

Complexity

Total Complexity 55

Size/Duplication

Total Lines 470
Duplicated Lines 0 %

Importance

Changes 9
Bugs 2 Features 3
Metric Value
eloc 134
dl 0
loc 470
rs 6
c 9
b 2
f 3
wmc 55

36 Methods

Rating   Name   Duplication   Size   Complexity  
A setLimit() 0 3 2
A limit() 0 5 2
A idle() 0 21 4
A boot() 0 1 1
A getFetchFlags() 0 2 1
A setQuery() 0 3 1
A getCharset() 0 2 1
A leaveUnread() 0 4 1
A search() 0 18 3
A setPage() 0 3 1
A getQuery() 0 2 1
A markAsRead() 0 4 1
A setCharset() 0 3 1
A getRawQuery() 0 2 1
A getClient() 0 3 1
A parse_date() 0 10 3
B get() 0 40 6
A setClient() 0 3 1
A __construct() 0 10 2
A setFetchAttachment() 0 3 1
A getFetchBody() 0 2 1
A fetchOptions() 0 2 1
A setFetchOptions() 0 3 1
A setFetchFlags() 0 3 1
A setRawQuery() 0 3 1
A generate_query() 0 19 3
A setFetchBody() 0 3 1
A getLimit() 0 2 1
A parse_value() 0 8 2
A getFetchOptions() 0 2 1
A fetchBody() 0 2 1
A fetchAttachment() 0 2 1
A paginate() 0 5 2
A getFetchAttachment() 0 2 1
A getPage() 0 2 1
A count() 0 2 1

How to fix   Complexity   

Complex Class

Complex classes like Query often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Query, and based on these observations, apply Extract Interface, too.

1
<?php
2
/*
3
* File:     Query.php
4
* Category: -
5
* Author:   M. Goldenbaum
6
* Created:  21.07.18 18:54
7
* Updated:  -
8
*
9
* Description:
10
*  -
11
*/
12
13
namespace Webklex\IMAP\Query;
14
15
use Carbon\Carbon;
16
use Webklex\IMAP\Client;
17
use Webklex\IMAP\Events\MessageNewEvent;
18
use Webklex\IMAP\Exceptions\GetMessagesFailedException;
19
use Webklex\IMAP\Exceptions\MessageSearchValidationException;
20
use Webklex\IMAP\IMAP;
21
use Webklex\IMAP\Message;
22
use Webklex\IMAP\Support\MessageCollection;
23
24
/**
25
 * Class Query
26
 *
27
 * @package Webklex\IMAP\Query
28
 */
29
class Query {
30
31
    /** @var array $query */
32
    protected $query;
33
34
    /** @var string $raw_query  */
35
    protected $raw_query;
36
37
    /** @var string $charset */
38
    protected $charset;
39
40
    /** @var Client $client */
41
    protected $client;
42
43
    /** @var int $limit */
44
    protected $limit = null;
45
46
    /** @var int $page */
47
    protected $page = 1;
48
49
    /** @var int $fetch_options */
50
    protected $fetch_options = null;
51
52
    /** @var int $fetch_body */
53
    protected $fetch_body = true;
54
55
    /** @var int $fetch_attachment */
56
    protected $fetch_attachment = true;
57
58
    /** @var int $fetch_flags */
59
    protected $fetch_flags = true;
60
61
    /** @var string $date_format */
62
    protected $date_format;
63
64
    /**
65
     * Query constructor.
66
     * @param Client $client
67
     * @param string $charset
68
     */
69
    public function __construct(Client $client, $charset = 'UTF-8') {
70
        $this->setClient($client);
71
72
        if(config('imap.options.fetch') === IMAP::FT_PEEK) $this->leaveUnread();
73
74
        $this->date_format = config('imap.date_format', 'd M y');
75
76
        $this->charset = $charset;
77
        $this->query = collect();
0 ignored issues
show
Documentation Bug introduced by
It seems like collect() of type Illuminate\Support\Collection is incompatible with the declared type array of property $query.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
78
        $this->boot();
79
    }
80
81
    /**
82
     * Instance boot method for additional functionality
83
     */
84
    protected function boot(){}
85
86
    /**
87
     * Parse a given value
88
     * @param mixed $value
89
     *
90
     * @return string
91
     */
92
    protected function parse_value($value){
93
        switch(true){
94
            case $value instanceof \Carbon\Carbon:
95
                $value = $value->format($this->date_format);
96
                break;
97
        }
98
99
        return (string) $value;
100
    }
101
102
    /**
103
     * Check if a given date is a valid carbon object and if not try to convert it
104
     * @param $date
105
     *
106
     * @return Carbon
107
     * @throws MessageSearchValidationException
108
     */
109
    protected function parse_date($date) {
110
        if($date instanceof \Carbon\Carbon) return $date;
111
112
        try {
113
            $date = Carbon::parse($date);
114
        } catch (\Exception $e) {
115
            throw new MessageSearchValidationException();
116
        }
117
118
        return $date;
119
    }
120
121
    /**
122
     * Don't mark messages as read when fetching
123
     *
124
     * @return $this
125
     */
126
    public function leaveUnread() {
127
        $this->setFetchOptions(IMAP::FT_PEEK);
0 ignored issues
show
Bug introduced by
Webklex\IMAP\IMAP::FT_PEEK of type integer is incompatible with the type boolean expected by parameter $fetch_options of Webklex\IMAP\Query\Query::setFetchOptions(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

127
        $this->setFetchOptions(/** @scrutinizer ignore-type */ IMAP::FT_PEEK);
Loading history...
128
129
        return $this;
130
    }
131
132
    /**
133
     * Mark all messages as read when fetching
134
     *
135
     * @return $this
136
     */
137
    public function markAsRead() {
138
        $this->setFetchOptions(IMAP::FT_UID);
0 ignored issues
show
Bug introduced by
Webklex\IMAP\IMAP::FT_UID of type integer is incompatible with the type boolean expected by parameter $fetch_options of Webklex\IMAP\Query\Query::setFetchOptions(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

138
        $this->setFetchOptions(/** @scrutinizer ignore-type */ IMAP::FT_UID);
Loading history...
139
140
        return $this;
141
    }
142
143
    /**
144
     * Perform an imap search request
145
     *
146
     * @return \Illuminate\Support\Collection
147
     * @throws \Webklex\IMAP\Exceptions\ConnectionFailedException
148
     */
149
    protected function search(){
150
        $this->generate_query();
151
152
        /**
153
         * Don't set the charset if it isn't used - prevent strange outlook mail server errors
154
         * @see https://github.com/Webklex/laravel-imap/issues/100
155
         */
156
        if($this->getCharset() === null){
0 ignored issues
show
introduced by
The condition $this->getCharset() === null is always false.
Loading history...
157
            $available_messages = \imap_search($this->getClient()->getConnection(), $this->getRawQuery(), IMAP::SE_UID);
158
        }else{
159
            $available_messages = \imap_search($this->getClient()->getConnection(), $this->getRawQuery(), IMAP::SE_UID, $this->getCharset());
0 ignored issues
show
Bug introduced by
It seems like $this->getClient()->getConnection() can also be of type true; however, parameter $imap_stream of imap_search() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

159
            $available_messages = \imap_search(/** @scrutinizer ignore-type */ $this->getClient()->getConnection(), $this->getRawQuery(), IMAP::SE_UID, $this->getCharset());
Loading history...
160
        }
161
162
        if ($available_messages !== false) {
163
            return collect($available_messages);
164
        }
165
166
        return collect();
167
    }
168
169
    /**
170
     * Count all available messages matching the current search criteria
171
     *
172
     * @return int
173
     * @throws \Webklex\IMAP\Exceptions\ConnectionFailedException
174
     */
175
    public function count() {
176
        return $this->search()->count();
177
    }
178
179
    /**
180
     * Fetch the current query and return all found messages
181
     *
182
     * @return MessageCollection
183
     * @throws GetMessagesFailedException
184
     */
185
    public function get() {
186
        $messages = MessageCollection::make([]);
187
188
        try {
189
            $available_messages = $this->search();
190
            $available_messages_count = $available_messages->count();
191
192
            if ($available_messages_count > 0) {
193
194
                $messages->total($available_messages_count);
195
196
                $options = config('imap.options');
197
198
                if(strtolower($options['fetch_order']) === 'desc'){
199
                    $available_messages = $available_messages->reverse();
200
                }
201
202
                $query =& $this;
203
204
                $available_messages->forPage($this->page, $this->limit)->each(function($msgno, $msglist) use(&$messages, $options, $query) {
205
                    $oMessage = new Message($msgno, $msglist, $query->getClient(), $query->getFetchOptions(), $query->getFetchBody(), $query->getFetchAttachment(), $query->getFetchFlags());
0 ignored issues
show
Bug introduced by
$query->getFetchFlags() of type integer is incompatible with the type boolean expected by parameter $fetch_flags of Webklex\IMAP\Message::__construct(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

205
                    $oMessage = new Message($msgno, $msglist, $query->getClient(), $query->getFetchOptions(), $query->getFetchBody(), $query->getFetchAttachment(), /** @scrutinizer ignore-type */ $query->getFetchFlags());
Loading history...
206
                    switch ($options['message_key']){
207
                        case 'number':
208
                            $message_key = $oMessage->getMessageNo();
209
                            break;
210
                        case 'list':
211
                            $message_key = $msglist;
212
                            break;
213
                        default:
214
                            $message_key = $oMessage->getMessageId();
215
                            break;
216
217
                    }
218
                    $messages->put($message_key, $oMessage);
219
                });
220
            }
221
222
            return $messages;
223
        } catch (\Exception $e) {
224
            throw new GetMessagesFailedException($e->getMessage());
225
        }
226
    }
227
228
    /**
229
     * Paginate the current query
230
     * @param int $per_page
231
     * @param null $page
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $page is correct as it would always require null to be passed?
Loading history...
232
     * @param string $page_name
233
     *
234
     * @return \Illuminate\Pagination\LengthAwarePaginator
235
     * @throws GetMessagesFailedException
236
     */
237
    public function paginate($per_page = 5, $page = null, $page_name = 'imap_page'){
238
        $this->page = $page > $this->page ? $page : $this->page;
239
        $this->limit = $per_page;
240
241
        return $this->get()->paginate($per_page, $this->page, $page_name);
242
    }
243
244
    /**
245
     * Create an idle like instance to catch incoming messages
246
     * @param callable|null $callback
247
     * @param int $timeout
248
     * @throws GetMessagesFailedException
249
     * @throws \Webklex\IMAP\Exceptions\ConnectionFailedException
250
     */
251
    public function idle(callable $callback = null, $timeout = 10){
252
        $known_messages = [];
253
        $this->get()->each(function($message) use(&$known_messages){
254
            /** @var Message $message */
255
            $known_messages[] = $message->getToken();
256
        });
257
        while ($this->getClient()->isConnected()){
258
            $this->getClient()->expunge();
259
            $this->get()->each(function($message) use(&$known_messages, $callback){
260
                /** @var \Webklex\IMAP\Message $message */
261
                $token = $message->getToken();
262
                if(in_array($token, $known_messages)){
263
                    return;
264
                }
265
                $known_messages[] = $token;
266
                MessageNewEvent::dispatch($message);
267
                if ($callback){
268
                    $callback($message);
269
                }
270
            });
271
            sleep($timeout);
272
        }
273
    }
274
275
    /**
276
     * Get the raw IMAP search query
277
     *
278
     * @return string
279
     */
280
    public function generate_query() {
281
        $query = '';
282
        $this->query->each(function($statement) use(&$query) {
283
            if (count($statement) == 1) {
284
                $query .= $statement[0];
285
            } else {
286
                if($statement[1] === null){
287
                    $query .= $statement[0];
288
                }else{
289
                    $query .= $statement[0].' "'.$statement[1].'"';
290
                }
291
            }
292
            $query .= ' ';
293
294
        });
295
296
        $this->raw_query = trim($query);
297
298
        return $this->raw_query;
299
    }
300
301
    /**
302
     * @return Client
303
     * @throws \Webklex\IMAP\Exceptions\ConnectionFailedException
304
     */
305
    public function getClient() {
306
        $this->client->checkConnection();
307
        return $this->client;
308
    }
309
310
    /**
311
     * Set the limit and page for the current query
312
     * @param int $limit
313
     * @param int $page
314
     *
315
     * @return $this
316
     */
317
    public function limit($limit, $page = 1) {
318
        if($page >= 1) $this->page = $page;
319
        $this->limit = $limit;
320
321
        return $this;
322
    }
323
324
    /**
325
     * @return array
326
     */
327
    public function getQuery() {
328
        return $this->query;
329
    }
330
331
    /**
332
     * @param array $query
333
     * @return Query
334
     */
335
    public function setQuery($query) {
336
        $this->query = $query;
337
        return $this;
338
    }
339
340
    /**
341
     * @return string
342
     */
343
    public function getRawQuery() {
344
        return $this->raw_query;
345
    }
346
347
    /**
348
     * @param string $raw_query
349
     * @return Query
350
     */
351
    public function setRawQuery($raw_query) {
352
        $this->raw_query = $raw_query;
353
        return $this;
354
    }
355
356
    /**
357
     * @return string
358
     */
359
    public function getCharset() {
360
        return $this->charset;
361
    }
362
363
    /**
364
     * @param string $charset
365
     * @return Query
366
     */
367
    public function setCharset($charset) {
368
        $this->charset = $charset;
369
        return $this;
370
    }
371
372
    /**
373
     * @param Client $client
374
     * @return Query
375
     */
376
    public function setClient(Client $client) {
377
        $this->client = $client;
378
        return $this;
379
    }
380
381
    /**
382
     * @return int
383
     */
384
    public function getLimit() {
385
        return $this->limit;
386
    }
387
388
    /**
389
     * @param int $limit
390
     * @return Query
391
     */
392
    public function setLimit($limit) {
393
        $this->limit = $limit <= 0 ? null : $limit;
394
        return $this;
395
    }
396
397
    /**
398
     * @return int
399
     */
400
    public function getPage() {
401
        return $this->page;
402
    }
403
404
    /**
405
     * @param int $page
406
     * @return Query
407
     */
408
    public function setPage($page) {
409
        $this->page = $page;
410
        return $this;
411
    }
412
413
    /**
414
     * @param boolean $fetch_options
415
     * @return Query
416
     */
417
    public function setFetchOptions($fetch_options) {
418
        $this->fetch_options = $fetch_options;
0 ignored issues
show
Documentation Bug introduced by
The property $fetch_options was declared of type integer, but $fetch_options is of type boolean. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
419
        return $this;
420
    }
421
422
    /**
423
     * @param boolean $fetch_options
424
     * @return Query
425
     */
426
    public function fetchOptions($fetch_options) {
427
        return $this->setFetchOptions($fetch_options);
428
    }
429
430
    /**
431
     * @return int
432
     */
433
    public function getFetchOptions() {
434
        return $this->fetch_options;
435
    }
436
437
    /**
438
     * @return boolean
439
     */
440
    public function getFetchBody() {
441
        return $this->fetch_body;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->fetch_body returns the type integer which is incompatible with the documented return type boolean.
Loading history...
442
    }
443
444
    /**
445
     * @param boolean $fetch_body
446
     * @return Query
447
     */
448
    public function setFetchBody($fetch_body) {
449
        $this->fetch_body = $fetch_body;
0 ignored issues
show
Documentation Bug introduced by
The property $fetch_body was declared of type integer, but $fetch_body is of type boolean. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
450
        return $this;
451
    }
452
453
    /**
454
     * @param boolean $fetch_body
455
     * @return Query
456
     */
457
    public function fetchBody($fetch_body) {
458
        return $this->setFetchBody($fetch_body);
459
    }
460
461
    /**
462
     * @return boolean
463
     */
464
    public function getFetchAttachment() {
465
        return $this->fetch_attachment;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->fetch_attachment returns the type integer which is incompatible with the documented return type boolean.
Loading history...
466
    }
467
468
    /**
469
     * @param boolean $fetch_attachment
470
     * @return Query
471
     */
472
    public function setFetchAttachment($fetch_attachment) {
473
        $this->fetch_attachment = $fetch_attachment;
0 ignored issues
show
Documentation Bug introduced by
The property $fetch_attachment was declared of type integer, but $fetch_attachment is of type boolean. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
474
        return $this;
475
    }
476
477
    /**
478
     * @param boolean $fetch_attachment
479
     * @return Query
480
     */
481
    public function fetchAttachment($fetch_attachment) {
482
        return $this->setFetchAttachment($fetch_attachment);
483
    }
484
485
    /**
486
     * @return int
487
     */
488
    public function getFetchFlags() {
489
        return $this->fetch_flags;
490
    }
491
492
    /**
493
     * @param int $fetch_flags
494
     * @return Query
495
     */
496
    public function setFetchFlags($fetch_flags) {
497
        $this->fetch_flags = $fetch_flags;
498
        return $this;
499
    }
500
}
501