Passed
Push — master ( 81c85c...b3bf46 )
by Malte
02:06
created

Query::markAsRead()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 2
c 1
b 0
f 0
dl 0
loc 4
rs 10
cc 1
nc 1
nop 0
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\PHPIMAP\Query;
14
15
use Carbon\Carbon;
16
use Illuminate\Pagination\LengthAwarePaginator;
17
use Illuminate\Support\Collection;
18
use Webklex\PHPIMAP\Client;
19
use Webklex\PHPIMAP\ClientManager;
20
use Webklex\PHPIMAP\Exceptions\ConnectionFailedException;
21
use Webklex\PHPIMAP\Exceptions\GetMessagesFailedException;
22
use Webklex\PHPIMAP\Exceptions\InvalidMessageDateException;
23
use Webklex\PHPIMAP\Exceptions\MessageContentFetchingException;
24
use Webklex\PHPIMAP\Exceptions\MessageHeaderFetchingException;
25
use Webklex\PHPIMAP\Exceptions\MessageSearchValidationException;
26
use Webklex\PHPIMAP\Exceptions\RuntimeException;
27
use Webklex\PHPIMAP\IMAP;
28
use Webklex\PHPIMAP\Message;
29
use Webklex\PHPIMAP\Support\MessageCollection;
30
31
/**
32
 * Class Query
33
 *
34
 * @package Webklex\PHPIMAP\Query
35
 */
36
class Query {
37
38
    /** @var array $query */
39
    protected $query;
40
41
    /** @var string $raw_query  */
42
    protected $raw_query;
43
44
    /** @var string $charset */
45
    protected $charset;
46
47
    /** @var Client $client */
48
    protected $client;
49
50
    /** @var int $limit */
51
    protected $limit = null;
52
53
    /** @var int $page */
54
    protected $page = 1;
55
56
    /** @var int $fetch_options */
57
    protected $fetch_options = null;
58
59
    /** @var int $fetch_body */
60
    protected $fetch_body = true;
61
62
    /** @var int $fetch_flags */
63
    protected $fetch_flags = true;
64
65
    /** @var int $sequence */
66
    protected $sequence = IMAP::NIL;
67
68
    /** @var string $fetch_order */
69
    protected $fetch_order;
70
71
    /** @var string $date_format */
72
    protected $date_format;
73
74
    /**
75
     * Query constructor.
76
     * @param Client $client
77
     * @param string $charset
78
     */
79
    public function __construct(Client $client, $charset = 'UTF-8') {
80
        $this->setClient($client);
81
82
        $this->sequence = ClientManager::get('options.sequence', IMAP::ST_MSGN);
83
        if(ClientManager::get('options.fetch') === IMAP::FT_PEEK) $this->leaveUnread();
84
85
        if (ClientManager::get('options.fetch_order') === 'desc') {
86
            $this->fetch_order = 'desc';
87
        } else {
88
            $this->fetch_order = 'asc';
89
        }
90
91
        $this->date_format = ClientManager::get('date_format', 'd M y');
92
93
        $this->charset = $charset;
94
        $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...
95
        $this->boot();
96
    }
97
98
    /**
99
     * Instance boot method for additional functionality
100
     */
101
    protected function boot(){}
102
103
    /**
104
     * Parse a given value
105
     * @param mixed $value
106
     *
107
     * @return string
108
     */
109
    protected function parse_value($value){
110
        switch(true){
111
            case $value instanceof \Carbon\Carbon:
112
                $value = $value->format($this->date_format);
113
                break;
114
        }
115
116
        return (string) $value;
117
    }
118
119
    /**
120
     * Check if a given date is a valid carbon object and if not try to convert it
121
     * @param $date
122
     *
123
     * @return Carbon
124
     * @throws MessageSearchValidationException
125
     */
126
    protected function parse_date($date) {
127
        if($date instanceof \Carbon\Carbon) return $date;
128
129
        try {
130
            $date = Carbon::parse($date);
131
        } catch (\Exception $e) {
132
            throw new MessageSearchValidationException();
133
        }
134
135
        return $date;
136
    }
137
138
    /**
139
     * Don't mark messages as read when fetching
140
     *
141
     * @return $this
142
     */
143
    public function leaveUnread() {
144
        $this->setFetchOptions(IMAP::FT_PEEK);
0 ignored issues
show
Bug introduced by
Webklex\PHPIMAP\IMAP::FT_PEEK of type integer is incompatible with the type boolean expected by parameter $fetch_options of Webklex\PHPIMAP\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

144
        $this->setFetchOptions(/** @scrutinizer ignore-type */ IMAP::FT_PEEK);
Loading history...
145
146
        return $this;
147
    }
148
149
    /**
150
     * Mark all messages as read when fetching
151
     *
152
     * @return $this
153
     */
154
    public function markAsRead() {
155
        $this->setFetchOptions(IMAP::FT_UID);
0 ignored issues
show
Bug introduced by
Webklex\PHPIMAP\IMAP::FT_UID of type integer is incompatible with the type boolean expected by parameter $fetch_options of Webklex\PHPIMAP\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

155
        $this->setFetchOptions(/** @scrutinizer ignore-type */ IMAP::FT_UID);
Loading history...
156
157
        return $this;
158
    }
159
160
    /**
161
     * Set the sequence type
162
     * @param int $sequence
163
     *
164
     * @return $this
165
     */
166
    public function setSequence($sequence) {
167
        $this->sequence = $sequence != IMAP::ST_MSGN ? IMAP::ST_UID : $sequence;
168
169
        return $this;
170
    }
171
172
    /**
173
     * Perform an imap search request
174
     *
175
     * @return Collection
176
     * @throws GetMessagesFailedException
177
     */
178
    protected function search(){
179
        $this->generate_query();
180
181
        try {
182
            $available_messages = $this->client->getConnection()->search([$this->getRawQuery()], $this->sequence == IMAP::ST_UID);
0 ignored issues
show
Bug introduced by
The method search() does not exist on Webklex\PHPIMAP\Connection\Protocols\Protocol. Since it exists in all sub-types, consider adding an abstract or default implementation to Webklex\PHPIMAP\Connection\Protocols\Protocol. ( Ignorable by Annotation )

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

182
            $available_messages = $this->client->getConnection()->/** @scrutinizer ignore-call */ search([$this->getRawQuery()], $this->sequence == IMAP::ST_UID);
Loading history...
183
        } catch (RuntimeException $e) {
184
            $available_messages = false;
185
        } catch (ConnectionFailedException $e) {
186
            throw new GetMessagesFailedException("failed to fetch messages", 0, $e);
187
        }
188
189
        if ($available_messages !== false) {
190
            return collect($available_messages);
191
        }
192
193
        return collect();
194
    }
195
196
    /**
197
     * Count all available messages matching the current search criteria
198
     *
199
     * @return int
200
     * @throws GetMessagesFailedException
201
     */
202
    public function count() {
203
        return $this->search()->count();
204
    }
205
206
    /**
207
     * Fetch the current query and return all found messages
208
     *
209
     * @return MessageCollection
210
     * @throws GetMessagesFailedException
211
     */
212
    public function get() {
213
        $messages = MessageCollection::make([]);
214
215
        $available_messages = $this->search();
216
        try {
217
            if (($available_messages_count = $available_messages->count()) > 0) {
218
219
                $messages->total($available_messages_count);
220
221
                if ($this->fetch_order === 'desc') {
222
                    $available_messages = $available_messages->reverse();
223
                }
224
225
                $message_key = ClientManager::get('options.message_key');
226
227
                $uids = $available_messages->forPage($this->page, $this->limit)->toArray();
228
229
                $raw_flags = $this->client->getConnection()->flags($uids, $this->sequence == IMAP::ST_UID);
0 ignored issues
show
Bug introduced by
The method flags() does not exist on Webklex\PHPIMAP\Connection\Protocols\Protocol. Since it exists in all sub-types, consider adding an abstract or default implementation to Webklex\PHPIMAP\Connection\Protocols\Protocol. ( Ignorable by Annotation )

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

229
                $raw_flags = $this->client->getConnection()->/** @scrutinizer ignore-call */ flags($uids, $this->sequence == IMAP::ST_UID);
Loading history...
230
                $raw_headers = $this->client->getConnection()->headers($uids, "RFC822", $this->sequence == IMAP::ST_UID);
0 ignored issues
show
Bug introduced by
The method headers() does not exist on Webklex\PHPIMAP\Connection\Protocols\Protocol. Since it exists in all sub-types, consider adding an abstract or default implementation to Webklex\PHPIMAP\Connection\Protocols\Protocol. ( Ignorable by Annotation )

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

230
                $raw_headers = $this->client->getConnection()->/** @scrutinizer ignore-call */ headers($uids, "RFC822", $this->sequence == IMAP::ST_UID);
Loading history...
231
232
                $raw_contents = [];
233
                if ($this->getFetchBody()) {
234
                    $raw_contents = $this->client->getConnection()->content($uids, "RFC822", $this->sequence == IMAP::ST_UID);
0 ignored issues
show
Bug introduced by
The method content() does not exist on Webklex\PHPIMAP\Connection\Protocols\Protocol. Since it exists in all sub-types, consider adding an abstract or default implementation to Webklex\PHPIMAP\Connection\Protocols\Protocol. ( Ignorable by Annotation )

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

234
                    $raw_contents = $this->client->getConnection()->/** @scrutinizer ignore-call */ content($uids, "RFC822", $this->sequence == IMAP::ST_UID);
Loading history...
235
                }
236
237
                $msglist = 0;
238
                foreach ($raw_headers as $uid => $raw_header) {
239
                    $raw_content = isset($raw_contents[$uid]) ? $raw_contents[$uid] : "";
240
                    $raw_flag = isset($raw_flags[$uid]) ? $raw_flags[$uid] : [];
241
242
                    $message = Message::make($uid, $msglist, $this->getClient(), $raw_header, $raw_content, $raw_flag, $this->getFetchOptions(), $this->sequence);
0 ignored issues
show
Bug introduced by
It seems like $raw_flag can also be of type array; however, parameter $raw_flags of Webklex\PHPIMAP\Message::make() does only seem to accept string, 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

242
                    $message = Message::make($uid, $msglist, $this->getClient(), $raw_header, $raw_content, /** @scrutinizer ignore-type */ $raw_flag, $this->getFetchOptions(), $this->sequence);
Loading history...
243
                    switch ($message_key){
244
                        case 'number':
245
                            $message_key = $message->getMessageNo();
246
                            break;
247
                        case 'list':
248
                            $message_key = $msglist;
249
                            break;
250
                        case 'uid':
251
                            $message_key = $message->getUid();
252
                            break;
253
                        default:
254
                            $message_key = $message->getMessageId();
255
                            break;
256
257
                    }
258
                    $messages->put($message_key, $message);
259
                    $msglist++;
260
                }
261
            }
262
263
            return $messages;
264
        } catch (\Exception $e) {
265
            throw new GetMessagesFailedException($e->getMessage(),0, $e);
266
        }
267
    }
268
269
    /**
270
     * Get a new Message instance
271
     * @param int $uid
272
     * @param null $msglist
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $msglist is correct as it would always require null to be passed?
Loading history...
273
     * @param null $sequence
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $sequence is correct as it would always require null to be passed?
Loading history...
274
     *
275
     * @return Message
276
     * @throws ConnectionFailedException
277
     * @throws RuntimeException
278
     * @throws InvalidMessageDateException
279
     * @throws MessageContentFetchingException
280
     * @throws MessageHeaderFetchingException
281
     * @throws \Webklex\PHPIMAP\Exceptions\EventNotFoundException
282
     */
283
    public function getMessage($uid, $msglist = null, $sequence = null){
284
        return new Message($uid, $msglist, $this->getClient(), $this->getFetchOptions(), $this->getFetchBody(), $this->getFetchFlags(), $sequence ? $sequence : $this->sequence);
0 ignored issues
show
introduced by
$sequence is of type null, thus it always evaluated to false.
Loading history...
Bug introduced by
$this->getFetchFlags() of type integer is incompatible with the type boolean expected by parameter $fetch_flags of Webklex\PHPIMAP\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

284
        return new Message($uid, $msglist, $this->getClient(), $this->getFetchOptions(), $this->getFetchBody(), /** @scrutinizer ignore-type */ $this->getFetchFlags(), $sequence ? $sequence : $this->sequence);
Loading history...
285
    }
286
287
    /**
288
     * Paginate the current query
289
     * @param int $per_page
290
     * @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...
291
     * @param string $page_name
292
     *
293
     * @return LengthAwarePaginator
294
     * @throws GetMessagesFailedException
295
     */
296
    public function paginate($per_page = 5, $page = null, $page_name = 'imap_page'){
297
        if (
298
               $page === null
299
            && isset($_GET[$page_name])
300
            && $_GET[$page_name] > 0
301
        ) {
302
            $this->page = intval($_GET[$page_name]);
303
        } elseif ($page > 0) {
304
            $this->page = $page;
305
        }
306
307
        $this->limit = $per_page;
308
309
        return $this->get()->paginate($per_page, $this->page, $page_name, true);
310
    }
311
312
    /**
313
     * Get the raw IMAP search query
314
     *
315
     * @return string
316
     */
317
    public function generate_query() {
318
        $query = '';
319
        $this->query->each(function($statement) use(&$query) {
320
            if (count($statement) == 1) {
321
                $query .= $statement[0];
322
            } else {
323
                if($statement[1] === null){
324
                    $query .= $statement[0];
325
                }else{
326
                    $query .= $statement[0].' "'.$statement[1].'"';
327
                }
328
            }
329
            $query .= ' ';
330
331
        });
332
333
        $this->raw_query = trim($query);
334
335
        return $this->raw_query;
336
    }
337
338
    /**
339
     * @return Client
340
     * @throws ConnectionFailedException
341
     */
342
    public function getClient() {
343
        $this->client->checkConnection();
344
        return $this->client;
345
    }
346
347
    /**
348
     * Set the limit and page for the current query
349
     * @param int $limit
350
     * @param int $page
351
     *
352
     * @return $this
353
     */
354
    public function limit($limit, $page = 1) {
355
        if($page >= 1) $this->page = $page;
356
        $this->limit = $limit;
357
358
        return $this;
359
    }
360
361
    /**
362
     * @return array
363
     */
364
    public function getQuery() {
365
        return $this->query;
366
    }
367
368
    /**
369
     * @param array $query
370
     * @return Query
371
     */
372
    public function setQuery($query) {
373
        $this->query = $query;
374
        return $this;
375
    }
376
377
    /**
378
     * @return string
379
     */
380
    public function getRawQuery() {
381
        return $this->raw_query;
382
    }
383
384
    /**
385
     * @param string $raw_query
386
     * @return Query
387
     */
388
    public function setRawQuery($raw_query) {
389
        $this->raw_query = $raw_query;
390
        return $this;
391
    }
392
393
    /**
394
     * @return string
395
     */
396
    public function getCharset() {
397
        return $this->charset;
398
    }
399
400
    /**
401
     * @param string $charset
402
     * @return Query
403
     */
404
    public function setCharset($charset) {
405
        $this->charset = $charset;
406
        return $this;
407
    }
408
409
    /**
410
     * @param Client $client
411
     * @return Query
412
     */
413
    public function setClient(Client $client) {
414
        $this->client = $client;
415
        return $this;
416
    }
417
418
    /**
419
     * @return int
420
     */
421
    public function getLimit() {
422
        return $this->limit;
423
    }
424
425
    /**
426
     * @param int $limit
427
     * @return Query
428
     */
429
    public function setLimit($limit) {
430
        $this->limit = $limit <= 0 ? null : $limit;
431
        return $this;
432
    }
433
434
    /**
435
     * @return int
436
     */
437
    public function getPage() {
438
        return $this->page;
439
    }
440
441
    /**
442
     * @param int $page
443
     * @return Query
444
     */
445
    public function setPage($page) {
446
        $this->page = $page;
447
        return $this;
448
    }
449
450
    /**
451
     * @param boolean $fetch_options
452
     * @return Query
453
     */
454
    public function setFetchOptions($fetch_options) {
455
        $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...
456
        return $this;
457
    }
458
459
    /**
460
     * @param boolean $fetch_options
461
     * @return Query
462
     */
463
    public function fetchOptions($fetch_options) {
464
        return $this->setFetchOptions($fetch_options);
465
    }
466
467
    /**
468
     * @return int
469
     */
470
    public function getFetchOptions() {
471
        return $this->fetch_options;
472
    }
473
474
    /**
475
     * @return boolean
476
     */
477
    public function getFetchBody() {
478
        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...
479
    }
480
481
    /**
482
     * @param boolean $fetch_body
483
     * @return Query
484
     */
485
    public function setFetchBody($fetch_body) {
486
        $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...
487
        return $this;
488
    }
489
490
    /**
491
     * @param boolean $fetch_body
492
     * @return Query
493
     */
494
    public function fetchBody($fetch_body) {
495
        return $this->setFetchBody($fetch_body);
496
    }
497
498
    /**
499
     * @return int
500
     */
501
    public function getFetchFlags() {
502
        return $this->fetch_flags;
503
    }
504
505
    /**
506
     * @param int $fetch_flags
507
     * @return Query
508
     */
509
    public function setFetchFlags($fetch_flags) {
510
        $this->fetch_flags = $fetch_flags;
511
        return $this;
512
    }
513
514
    /**
515
     * @param string $fetch_order
516
     * @return Query
517
     */
518
    public function setFetchOrder($fetch_order) {
519
        $fetch_order = strtolower($fetch_order);
520
521
        if (in_array($fetch_order, ['asc', 'desc'])) {
522
            $this->fetch_order = $fetch_order;
523
        }
524
525
        return $this;
526
    }
527
528
    /**
529
     * @param string $fetch_order
530
     * @return Query
531
     */
532
    public function fetchOrder($fetch_order) {
533
        return $this->setFetchOrder($fetch_order);
534
    }
535
536
    /**
537
     * @return string
538
     */
539
    public function getFetchOrder() {
540
        return $this->fetch_order;
541
    }
542
543
    /**
544
     * @return Query
545
     */
546
    public function setFetchOrderAsc() {
547
        return $this->setFetchOrder('asc');
548
    }
549
550
    /**
551
     * @return Query
552
     */
553
    public function fetchOrderAsc() {
554
        return $this->setFetchOrderAsc();
555
    }
556
557
    /**
558
     * @return Query
559
     */
560
    public function setFetchOrderDesc() {
561
        return $this->setFetchOrder('desc');
562
    }
563
564
    /**
565
     * @return Query
566
     */
567
    public function fetchOrderDesc() {
568
        return $this->setFetchOrderDesc();
569
    }
570
}
571