BounceMailHandler   F
last analyzed

Complexity

Total Complexity 81

Size/Duplication

Total Lines 677
Duplicated Lines 3.55 %

Coupling/Cohesion

Components 1
Dependencies 0

Importance

Changes 0
Metric Value
dl 24
loc 677
rs 1.923
c 0
b 0
f 0
wmc 81
lcom 1
cbo 0

8 Methods

Rating   Name   Duplication   Size   Complexity  
A output() 0 9 3
A openMailbox() 0 31 4
A openLocal() 0 18 3
F processMailbox() 14 142 33
A isParameter() 0 12 4
F processBounce() 10 113 20
B mailbox_exist() 0 36 8
B globalDelete() 0 34 6

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like BounceMailHandler 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 BounceMailHandler, and based on these observations, apply Extract Interface, too.

1
<?php
2
/* class.phpmailer-bmh.php
3
.---------------------------------------------------------------------------.
4
|  Software: PHPMailer-BMH (Bounce Mail Handler)                            |
5
|   Version: 5.0.0rc1                                                       |
6
|   Contact: [email protected]                             |
7
|      Info: http://phpmailer.codeworxtech.com                              |
8
| ------------------------------------------------------------------------- |
9
|    Author: Andy Prevost [email protected] (admin)                 |
10
| Copyright (c) 2002-2009, Andy Prevost. All Rights Reserved.               |
11
| ------------------------------------------------------------------------- |
12
|   License: Distributed under the General Public License (GPL)             |
13
|            (http://www.gnu.org/licenses/gpl.html)                         |
14
| This program is distributed in the hope that it will be useful - WITHOUT  |
15
| ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or     |
16
| FITNESS FOR A PARTICULAR PURPOSE.                                         |
17
| ------------------------------------------------------------------------- |
18
| This is a update of the original Bounce Mail Handler script               |
19
| http://sourceforge.net/projects/bmh/                                      |
20
| The script has been renamed from Bounce Mail Handler to PHPMailer-BMH     |
21
| ------------------------------------------------------------------------- |
22
| We offer a number of paid services:                                       |
23
| - Web Hosting on highly optimized fast and secure servers                 |
24
| - Technology Consulting                                                   |
25
| - Oursourcing (highly qualified programmers and graphic designers)        |
26
'---------------------------------------------------------------------------'
27
Last updated: January 21 2009 13:49 EST */
28
29
/**
30
 * PHPMailer-BMH (Bounce Mail Handler)
31
 *
32
 * PHPMailer-BMH is a PHP program to check your IMAP/POP3 inbox and
33
 * delete all 'hard' bounced emails. It features a callback function where
34
 * you can create a custom action. This provides you the ability to write
35
 * a script to match your database records and either set inactive or
36
 * delete records with email addresses that match the 'hard' bounce results.
37
 *
38
 * @package   PHPMailer-BMH
39
 * @author    Andy Prevost
40
 * @copyright 2008-2009, Andy Prevost
41
 * @license   GPL licensed
42
 * @link      http://sourceforge.net/projects/bmh
43
 */
44
require_once dirname(dirname(dirname(dirname(__DIR__)))) . '/mainfile.php';
45
require_once XOOPS_ROOT_PATH . '/modules/xnewsletter/include/phpmailer_bmh/phpmailer-bmh_rules.php';
46
47
define('VERBOSE_QUIET', 0); // means no output at all
48
define('VERBOSE_SIMPLE', 1); // means only output simple report
49
define('VERBOSE_REPORT', 2); // means output a detail report
50
define('VERBOSE_DEBUG', 3); // means output detail report as well as debug info.
51
52
/**
53
 * Class BounceMailHandler
54
 */
55
class BounceMailHandler
56
{
57
    /////////////////////////////////////////////////
58
    // PROPERTIES, PUBLIC
59
    /////////////////////////////////////////////////
60
61
    /**
62
     * Holds Bounce Mail Handler version.
63
     *
64
     * @var string
65
     */
66
    public $Version = '5.0.0rc1 goffy';
67
68
    /**
69
     * Holds result of last processing.
70
     *
71
     * @var array
72
     */
73
    public $result_total       = 0;
74
    public $result_processed   = 0;
75
    public $result_unprocessed = 0;
76
    public $result_deleted     = 0;
77
    public $result_moved       = 0;
78
79
    /**
80
     * Mail server
81
     *
82
     * @var string
83
     */
84
    public $mailhost = 'localhost';
85
86
    /**
87
     * The username of mailbox
88
     *
89
     * @var string
90
     */
91
92
    public $mailbox_username;
93
    /**
94
     * The password needed to access mailbox
95
     *
96
     * @var string
97
     */
98
    public $mailbox_password;
99
100
    /**
101
     * The last error msg
102
     *
103
     * @var string
104
     */
105
    public $error_msg;
106
107
    /**
108
     * Maximum limit messages processed in one batch
109
     *
110
     * @var int
111
     */
112
    public $max_messages = 3000;
113
114
    /**
115
     * Callback Action function name
116
     * the function that handles the bounce mail. Parameters:
117
     *   int     $msgnum        the message number returned by Bounce Mail Handler
118
     *   string  $bounce_type   the bounce type: 'antispam','autoreply','concurrent','content_reject','command_reject','internal_error','defer','delayed'        => array('remove'=>0,'bounce_type'=>'temporary'),'dns_loop','dns_unknown','full','inactive','latin_only','other','oversize','outofoffice','unknown','unrecognized','user_reject','warning'
119
     *   string  $email         the target email address
120
     *   string  $subject       the subject, ignore now
121
     *   string  $xheader       the XBounceHeader from the mail
122
     *   1 or 0  $remove        delete status, 0 is not deleted, 1 is deleted
123
     *   string  $rule_no       bounce mail detect rule no.
124
     *   string  $rule_cat      bounce mail detect rule category
125
     *   int     $totalFetched  total number of messages in the mailbox
126
     *
127
     * @var string
128
     */
129
    public $action_function = 'callbackAction';
130
131
    /**
132
     * Internal variable
133
     * The resource handler for the opened mailbox (POP3/IMAP/NNTP/etc.)
134
     *
135
     * @var object
136
     */
137
    public $_mailbox_link = false;
138
139
    /**
140
     * Test mode, if true will not delete messages
141
     *
142
     * @var bool
143
     */
144
    public $testmode = false;
145
146
    /**
147
     * Purge the unknown messages (or not)
148
     *
149
     * @var bool
150
     */
151
    public $purge_unprocessed = false;
152
153
    /**
154
     * Control the debug output, default is VERBOSE_SIMPLE
155
     *
156
     * @var int
157
     */
158
    public $verbose = VERBOSE_SIMPLE;
159
160
    /**
161
     * control the failed DSN rules output
162
     *
163
     * @var bool
164
     */
165
    public $debug_dsn_rule = false;
166
167
    /**
168
     * control the failed BODY rules output
169
     *
170
     * @var bool
171
     */
172
    public $debug_body_rule = false;
173
174
    /**
175
     * Control the method to process the mail header
176
     * if set true, uses the imap_fetchstructure function
177
     * otherwise, detect message type directly from headers,
178
     * a bit faster than imap_fetchstructure function and take less resources.
179
     * however - the difference is negligible
180
     *
181
     * @var bool
182
     */
183
    public $use_fetchstructure = true;
184
185
    /**
186
     * If disable_delete is equal to true, it will disable the delete function
187
     *
188
     * @var bool
189
     */
190
    public $disable_delete = false;
191
192
    /*
193
     * Defines new line ending
194
     */
195
    public $bmh_newline = "<br>\n";
196
197
    /*
198
     * Defines port number, default is '143', other common choices are '110' (pop3), '993' (gmail)
199
     * @var integer
200
     */
201
    public $port = 143;
202
203
    /*
204
     * Defines service, default is 'imap', choice includes 'pop3'
205
     * @var string
206
     */
207
    public $service = 'imap';
208
209
    /*
210
     * Defines service option, default is 'notls', other choices are 'tls', 'ssl'
211
     * @var string
212
     */
213
    public $service_option = 'notls';
214
215
    /*
216
     * Mailbox type, default is 'INBOX', other choices are (Tasks, Spam, Replies, etc.)
217
     * @var string
218
     */
219
    public $boxname = 'INBOX';
220
221
    /*
222
     * Determines if soft bounces will be moved to another mailbox folder
223
     * @var boolean
224
     */
225
    public $moveSoft = false;
226
227
    /*
228
     * Mailbox folder to move soft bounces to, default is 'soft'
229
     * @var string
230
     */
231
    public $softMailbox = 'INBOX.soft';
232
233
    /*
234
     * Determines if this mail should be moved to softMailbox
235
     * @var boolean
236
     */
237
    public $moveSoftFlag = false;
238
239
    /*
240
     * Determines if hard bounces will be moved to another mailbox folder
241
     * NOTE: If true, this will disable delete and perform a move operation instead
242
     * @var boolean
243
     */
244
    public $moveHard = false;
245
246
    /*
247
     * Mailbox folder to move hard bounces to, default is 'hard'
248
     * @var string
249
     */
250
    public $hardMailbox = 'INBOX.hard';
251
252
    /*
253
     * Determines if this mail should be moved to hardMailbox
254
     * @var boolean
255
     */
256
    public $moveHardFlag = false;
257
258
    /*
259
     * Deletes messages globally prior to date in variable
260
     * NOTE: excludes any message folder that includes 'sent' in mailbox name
261
     * format is same as MySQL: 'yyyy-mm-dd'
262
     * if variable is blank, will not process global delete
263
     * @var string
264
     */
265
    public $deleteMsgDate = '';
266
267
    /////////////////////////////////////////////////
268
    // METHODS
269
    /////////////////////////////////////////////////
270
271
    /**
272
     * Output additional msg for debug
273
     *
274
     * @param bool|string $msg           ,  if not given, output the last error msg
275
     * @param int         $verbose_level ,  the output level of this message
276
     */
277
    public function output($msg = false, $verbose_level = VERBOSE_SIMPLE)
278
    {
279
        if ($this->verbose >= $verbose_level) {
280
            if (empty($msg)) {
281
                echo $this->error_msg . $this->bmh_newline;
282
            }
283
            //echo $msg . $this->bmh_newline;
284
        }
285
    }
286
287
    /**
288
     * Open a mail box
289
     *
290
     * @return bool
291
     */
292
    public function openMailbox()
293
    {
294
        // before starting the processing, let's check the delete flag and do global deletes if true
295
        if ('' != trim($this->deleteMsgDate)) {
296
            //echo "processing global delete based on date of " . $this->deleteMsgDate . "<br>";
297
            //$this->globalDelete($nameRaw);
298
            $this->globalDelete();
299
        }
300
        // disable move operations if server is Gmail ... Gmail does not support mailbox creation
301
        // if ( stristr($this->mailhost,'gmail') ) {
302
        // $this->moveSoft = false;
303
        // $this->moveHard = false;
304
        // }
305
        $port = $this->port . '/' . $this->service . '/' . $this->service_option;
306
        set_time_limit(6000);
307
        //echo "{".$this->mailhost.":".$port."}" . $this->boxname."<br>";
308
        if (!$this->testmode) {
309
            $this->_mailbox_link = imap_open('{' . $this->mailhost . ':' . $port . '}' . $this->boxname, $this->mailbox_username, $this->mailbox_password, CL_EXPUNGE);
0 ignored issues
show
Documentation Bug introduced by
It seems like imap_open('{' . $this->m...x_password, CL_EXPUNGE) of type resource is incompatible with the declared type object of property $_mailbox_link.

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...
310
        } else {
311
            $this->_mailbox_link = imap_open('{' . $this->mailhost . ':' . $port . '}' . $this->boxname, $this->mailbox_username, $this->mailbox_password);
0 ignored issues
show
Documentation Bug introduced by
It seems like imap_open('{' . $this->m...this->mailbox_password) of type resource is incompatible with the declared type object of property $_mailbox_link.

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...
312
        }
313
        if (!$this->_mailbox_link) {
314
            $this->error_msg = 'Cannot create ' . $this->service . ' connection to ' . $this->mailhost . $this->bmh_newline . 'Error MSG: ' . imap_last_error();
315
            $this->output();
316
317
            return false;
318
        }
319
        $this->output('Connected to: ' . $this->mailhost . ' (' . $this->mailbox_username . ')');
320
321
        return true;
322
    }
323
324
    /**
325
     * Open a mail box in local file system
326
     *
327
     * @param string $file_path (The local mailbox file path)
328
     *
329
     * @return bool
330
     */
331
    public function openLocal($file_path)
332
    {
333
        set_time_limit(6000);
334
        if (!$this->testmode) {
335
            $this->_mailbox_link = imap_open((string)$file_path, '', '', CL_EXPUNGE);
0 ignored issues
show
Documentation Bug introduced by
It seems like imap_open((string) $file...th, '', '', CL_EXPUNGE) of type resource is incompatible with the declared type object of property $_mailbox_link.

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...
336
        } else {
337
            $this->_mailbox_link = imap_open((string)$file_path, '', '');
0 ignored issues
show
Documentation Bug introduced by
It seems like imap_open((string) $file_path, '', '') of type resource is incompatible with the declared type object of property $_mailbox_link.

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...
338
        }
339
        if (!$this->_mailbox_link) {
340
            $this->error_msg = 'Cannot open the mailbox file to ' . $file_path . $this->bmh_newline . 'Error MSG: ' . imap_last_error();
341
            $this->output();
342
343
            return false;
344
        }
345
        $this->output('Opened ' . $file_path);
346
347
        return true;
348
    }
349
350
    /**
351
     * Process the messages in a mailbox
352
     *
353
     * @param bool|string $max (maximum limit messages processed in one batch, if not given uses the property $max_messages
354
     *
355
     * @return bool
356
     */
357
    public function processMailbox($max = false)
358
    {
359
        if (empty($this->action_function) || !function_exists($this->action_function)) {
360
            $this->error_msg = 'Action function not found!';
361
            $this->output();
362
363
            return false;
364
        }
365
366
        if ($this->moveHard && (false === $this->disable_delete)) {
367
            $this->disable_delete = true;
368
        }
369
370
        if (!empty($max)) {
371
            $this->max_messages = $max;
0 ignored issues
show
Documentation Bug introduced by
It seems like $max of type boolean or string is incompatible with the declared type integer of property $max_messages.

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...
372
        }
373
374
        // initialize counters
375
        $c_total       = imap_num_msg($this->_mailbox_link);
376
        $c_fetched     = $c_total;
377
        $c_processed   = 0;
378
        $c_unprocessed = 0;
379
        $c_deleted     = 0;
380
        $c_moved       = 0;
381
        $this->output('Total: ' . $c_total . ' messages ');
382
        // proccess maximum number of messages
383
        if ($c_fetched > $this->max_messages) {
384
            $c_fetched = $this->max_messages;
385
            $this->output('Processing first ' . $c_fetched . ' messages ');
386
        }
387
388
        if ($this->testmode) {
389
            $this->output('Running in test mode, not deleting messages from mailbox<br>');
390
        } else {
391
            if ($this->disable_delete) {
392
                if ($this->moveHard) {
393
                    $this->output('Running in move mode<br>');
394
                } else {
395
                    $this->output('Running in disable_delete mode, not deleting messages from mailbox<br>');
396
                }
397
            } else {
398
                $this->output('Processed messages will be deleted from mailbox<br>');
399
            }
400
        }
401
        for ($x = 1; $x <= $c_fetched; ++$x) {
402
            /*
403
            $this->output( $x . ":",VERBOSE_REPORT);
404
            if ($x % 10 == 0) {
405
              $this->output( '.',VERBOSE_SIMPLE);
406
            }
407
            */
408
            // fetch the messages one at a time
409
            if ($this->use_fetchstructure) {
410
                $structure = imap_fetchstructure($this->_mailbox_link, $x);
411
                if (1 == $structure->type
412
                    && $structure->ifsubtype
413
                    && 'REPORT' === $structure->subtype
414
                    && $structure->ifparameters
415
                    && $this->isParameter($structure->parameters, 'REPORT-TYPE', 'delivery-status')) {
416
                    $processed = $this->processBounce($x, 'DSN', $c_total);
417 View Code Duplication
                } else { // not standard DSN msg
0 ignored issues
show
Duplication introduced by
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...
418
                    $this->output('Msg #' . $x . ' is not a standard DSN message', VERBOSE_REPORT);
419
                    if ($this->debug_body_rule) {
420
                        $this->output("  Content-Type : {$match[1]}", VERBOSE_DEBUG);
0 ignored issues
show
Bug introduced by
The variable $match does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
421
                    }
422
                    $processed = $this->processBounce($x, 'BODY', $c_total);
423
                }
424
            } else {
425
                $header = imap_fetchheader($this->_mailbox_link, $x);
426
                // Could be multi-line, if the new line begins with SPACE or HTAB
427
                if (preg_match("/Content-Type:((?:[^\n]|\n[\t ])+)(?:\n[^\t ]|$)/is", $header, $match)) {
428
                    if (preg_match("/multipart\/report/is", $match[1])
429
                        && preg_match("/report-type=[\"']?delivery-status[\"']?/is", $match[1])) {
430
                        // standard DSN msg
431
                        $processed = $this->processBounce($x, 'DSN', $c_total);
432 View Code Duplication
                    } else { // not standard DSN msg
0 ignored issues
show
Duplication introduced by
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...
433
                        $this->output('Msg #' . $x . ' is not a standard DSN message', VERBOSE_REPORT);
434
                        if ($this->debug_body_rule) {
435
                            $this->output("  Content-Type : {$match[1]}", VERBOSE_DEBUG);
436
                        }
437
                        $processed = $this->processBounce($x, 'BODY', $c_total);
438
                    }
439
                } else { // didn't get content-type header
440
                    $this->output('Msg #' . $x . ' is not a well-formatted MIME mail, missing Content-Type', VERBOSE_REPORT);
441
                    if ($this->debug_body_rule) {
442
                        $this->output('  Headers: ' . $this->bmh_newline . $header . $this->bmh_newline, VERBOSE_DEBUG);
443
                    }
444
                    $processed = $this->processBounce($x, 'BODY', $c_total);
445
                }
446
            }
447
448
            $deleteFlag[$x] = false;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$deleteFlag was never initialized. Although not strictly required by PHP, it is generally a good practice to add $deleteFlag = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
449
            $moveFlag[$x]   = false;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$moveFlag was never initialized. Although not strictly required by PHP, it is generally a good practice to add $moveFlag = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
450
            if ($processed) {
451
                ++$c_processed;
452
                if ((false === $this->testmode) && (false === $this->disable_delete)) {
453
                    // delete the bounce if not in test mode and not in disable_delete mode
454
                    @imap_delete($this->_mailbox_link, $x);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
455
                    $deleteFlag[$x] = true;
0 ignored issues
show
Bug introduced by
The variable $deleteFlag does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
456
                    ++$c_deleted;
457
                } elseif ($this->moveHard && $this->moveHardFlag) {
458
                    // check if the move directory exists, if not create it
459
                    $this->mailbox_exist($this->hardMailbox);
460
                    // move the message
461
                    @imap_mail_move($this->_mailbox_link, $x, $this->hardMailbox);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
462
                    $moveFlag[$x] = true;
0 ignored issues
show
Bug introduced by
The variable $moveFlag does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
463
                    ++$c_moved;
464
                } elseif ($this->moveSoft && $this->moveSoftFlag) {
465
                    // check if the move directory exists, if not create it
466
                    $this->mailbox_exist($this->softMailbox);
467
                    // move the message
468
                    @imap_mail_move($this->_mailbox_link, $x, $this->softMailbox);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
469
                    $moveFlag[$x] = true;
470
                    ++$c_moved;
471
                }
472
            } else { // not processed
473
                ++$c_unprocessed;
474
                if (!$this->testmode && !$this->disable_delete && $this->purge_unprocessed) {
475
                    // delete this bounce if not in test mode, not in disable_delete mode, and the flag BOUNCE_PURGE_UNPROCESSED is set
476
                    @imap_delete($this->_mailbox_link, $x);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
477
                    $deleteFlag[$x] = true;
478
                    ++$c_deleted;
479
                }
480
            }
481
            flush();
482
        }
483
        $this->output($this->bmh_newline . 'Closing mailbox, and purging messages');
484
        imap_close($this->_mailbox_link);
485
        $this->output('Read: ' . $c_fetched . ' messages');
486
        $this->output($c_processed . ' action taken');
487
        $this->output($c_unprocessed . ' no action taken');
488
        $this->output($c_deleted . ' messages deleted');
489
        $this->output($c_moved . ' messages moved');
490
491
        $this->result_total       = $c_fetched;
0 ignored issues
show
Documentation Bug introduced by
It seems like $c_fetched of type boolean or string or integer is incompatible with the declared type array of property $result_total.

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...
492
        $this->result_processed   = $c_processed;
493
        $this->result_unprocessed = $c_unprocessed;
494
        $this->result_deleted     = $c_deleted;
495
        $this->result_moved       = $c_moved;
496
497
        return true;
498
    }
499
500
    /**
501
     * Function to determine if a particular value is found in a imap_fetchstructure key
502
     *
503
     * @param array  $currParameters (imap_fetstructure parameters)
504
     * @param string $varKey         (imap_fetstructure key)
505
     * @param string $varValue       (value to check for)
506
     *
507
     * @return bool
508
     */
509
    public function isParameter($currParameters, $varKey, $varValue)
510
    {
511
        foreach ($currParameters as $key => $value) {
512
            if ($key == $varKey) {
513
                if ($value == $varValue) {
514
                    return true;
515
                }
516
            }
517
        }
518
519
        return false;
520
    }
521
522
    /**
523
     * Function to process each individual message
524
     *
525
     * @param int    $pos          (message number)
526
     * @param string $type         (DNS or BODY type)
527
     * @param string $totalFetched (total number of messages in mailbox)
528
     *
529
     * @return bool
530
     */
531
    public function processBounce($pos, $type, $totalFetched)
532
    {
533
        $header  = imap_headerinfo($this->_mailbox_link, $pos);
534
        $subject = strip_tags($header->subject);
535
        if ('DSN' === $type) {
536
            // first part of DSN (Delivery Status Notification), human-readable explanation
537
            $dsn_msg           = imap_fetchbody($this->_mailbox_link, $pos, '1');
538
            $dsn_msg_structure = imap_bodystruct($this->_mailbox_link, $pos, '1');
539
540 View Code Duplication
            if (4 == $dsn_msg_structure->encoding) {
0 ignored issues
show
Duplication introduced by
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...
541
                $dsn_msg = quoted_printable_decode($dsn_msg);
542
            } elseif (3 == $dsn_msg_structure->encoding) {
543
                $dsn_msg = base64_decode($dsn_msg, true);
544
            }
545
546
            // second part of DSN (Delivery Status Notification), delivery-status
547
            $dsn_report = imap_fetchbody($this->_mailbox_link, $pos, '2');
548
549
            // process bounces by rules
550
            $result = bmhDSNRules($dsn_msg, $dsn_report, $this->debug_dsn_rule);
551
        } elseif ('BODY' === $type) {
552
            $structure = imap_fetchstructure($this->_mailbox_link, $pos);
553
            switch ($structure->type) {
554
                case 0: // Content-type = text
555
                case 1: // Content-type = multipart
556
                    $body = imap_fetchbody($this->_mailbox_link, $pos, '1');
557
                    // Detect encoding and decode - only base64
558
                    if (4 == $structure->parts[0]->encoding) {
559
                        $body = quoted_printable_decode($body);
560
                    } elseif (3 == $structure->parts[0]->encoding) {
561
                        $body = base64_decode($body, true);
562
                    }
563
                    $result = bmhBodyRules($body, $structure, $this->debug_body_rule);
0 ignored issues
show
Documentation introduced by
$structure is of type object, but the function expects a string.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
564
                    break;
565
                case 2: // Content-type = message
566
                    $body = imap_body($this->_mailbox_link, $pos);
567 View Code Duplication
                    if (4 == $structure->encoding) {
0 ignored issues
show
Duplication introduced by
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...
568
                        $body = quoted_printable_decode($body);
569
                    } elseif (3 == $structure->encoding) {
570
                        $body = base64_decode($body, true);
571
                    }
572
                    $body   = mb_substr($body, 0, 1000);
573
                    $result = bmhBodyRules($body, $structure, $this->debug_body_rule);
0 ignored issues
show
Documentation introduced by
$structure is of type object, but the function expects a string.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
574
                    break;
575
                default: // unsupport Content-type
576
                    $this->output('Msg #' . $pos . ' is unsupported Content-Type:' . $structure->type, VERBOSE_REPORT);
577
578
                    return false;
579
            }
580
        } else { // internal error
581
            $this->error_msg = 'Internal Error: unknown type';
582
583
            return false;
584
        }
585
        $email              = $result['email'];
586
        $bounce_type        = $result['bounce_type'];
587
        $this->moveHardFlag = false;
588
        $this->moveSoftFlag = false;
589
        if ($this->moveHard && 1 == $result['remove']) {
590
            $remove             = 'moved (hard)';
591
            $this->moveHardFlag = true;
592
        } elseif ($this->moveSoft && 2 == $result['remove']) {
593
            $remove             = 'moved (soft)';
594
            $this->moveSoftFlag = true;
595
        } elseif ($this->disable_delete) {
596
            $remove = 0;
597
        } else {
598
            $remove = $result['remove'];
599
        }
600
        $rule_no  = $result['rule_no'];
601
        $rule_cat = $result['rule_cat'];
602
        $xheader  = false;
603
604
        if ('0000' == $rule_no) { // internal error      return false;
605
            // code below will use the Callback function, but return no value
606
            if ('' == trim($email)) {
607
                $email = $header->fromaddress;
608
            }
609
            $params = [
610
                $pos,
611
                $bounce_type,
612
                $email,
613
                $subject,
614
                $xheader,
615
                $remove,
616
                $rule_no,
617
                $rule_cat,
618
                $totalFetched,
619
            ];
620
            call_user_func_array($this->action_function, $params);
621
        } else { // match rule, do bounce action
622
            if ($this->testmode) {
623
                $this->output('Match: ' . $rule_no . ':' . $rule_cat . '; ' . $bounce_type . '; ' . $email);
624
625
                return true;
626
            }
627
            $params = [
628
                $pos,
629
                $bounce_type,
630
                $email,
631
                $subject,
632
                $xheader,
633
                $remove,
634
                $rule_no,
635
                $rule_cat,
636
                $totalFetched,
637
            ];
638
639
            return call_user_func_array($this->action_function, $params);
640
        }
641
642
        return null;
643
    }
644
645
    /**
646
     * Function to check if a mailbox exists
647
     * - if not found, it will create it
648
     *
649
     * @param string $mailbox (the mailbox name, must be in 'INBOX.checkmailbox' format)
650
     * @param bool   $create  (whether or not to create the checkmailbox if not found, defaults to true)
651
     *
652
     * @return null|bool
653
     */
654
    public function mailbox_exist($mailbox, $create = true)
655
    {
656
        if ('' == trim($mailbox) || false === mb_strpos($mailbox, 'INBOX.')) {
657
            // this is a critical error with either the mailbox name blank or an invalid mailbox name
658
            // need to stop processing and exit at this point
659
            echo "Invalid mailbox name for move operation. Cannot continue.<br>\n";
660
            echo "TIP: the mailbox you want to move the message to must include 'INBOX.' at the start.<br>\n";
661
            exit();
662
        }
663
        $port         = $this->port . '/' . $this->service . '/' . $this->service_option;
664
        $mbox         = imap_open('{' . $this->mailhost . ':' . $port . '}', $this->mailbox_username, $this->mailbox_password, OP_HALFOPEN);
665
        $list         = imap_getmailboxes($mbox, '{' . $this->mailhost . ':' . $port . '}', '*');
666
        $mailboxFound = false;
667
        if (is_array($list)) {
668
            foreach ($list as $key => $val) {
669
                // get the mailbox name only
670
                $nameArr = explode('}', imap_utf7_decode($val->name));
671
                $nameRaw = $nameArr[count($nameArr) - 1];
672
                if ($mailbox == $nameRaw) {
673
                    $mailboxFound = true;
674
                }
675
            }
676
            if ((false === $mailboxFound) && $create) {
677
                @imap_createmailbox($mbox, imap_utf7_encode('{' . $this->mailhost . ':' . $port . '}' . $mailbox));
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
678
                imap_close($mbox);
679
680
                return true;
681
            }
682
            imap_close($mbox);
683
684
            return false;
685
        }
686
        imap_close($mbox);
687
688
        return false;
689
    }
690
691
    /**
692
     * Function to delete messages in a mailbox, based on date
693
     * NOTE: this is global ... will affect all mailboxes except any that have 'sent' in the mailbox name
694
     *
695
     * @internal param string $mailbox (the mailbox name)
696
     */
697
    public function globalDelete()
698
    {
699
        $dateArr = explode('-', $this->deleteMsgDate); // date format is yyyy-mm-dd
700
        $delDate = mktime(0, 0, 0, $dateArr[1], $dateArr[2], $dateArr[0]);
701
702
        $port         = $this->port . '/' . $this->service . '/' . $this->service_option;
703
        $mboxt        = imap_open('{' . $this->mailhost . ':' . $port . '}', $this->mailbox_username, $this->mailbox_password, OP_HALFOPEN);
704
        $list         = imap_getmailboxes($mboxt, '{' . $this->mailhost . ':' . $port . '}', '*');
705
        $mailboxFound = false;
0 ignored issues
show
Unused Code introduced by
$mailboxFound 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...
706
        if (is_array($list)) {
707
            foreach ($list as $key => $val) {
708
                // get the mailbox name only
709
                $nameArr = explode('}', imap_utf7_decode($val->name));
710
                $nameRaw = $nameArr[count($nameArr) - 1];
711
                if (false === mb_stripos($nameRaw, 'sent')) {
712
                    $mboxd    = imap_open('{' . $this->mailhost . ':' . $port . '}' . $nameRaw, $this->mailbox_username, $this->mailbox_password, CL_EXPUNGE);
713
                    $messages = imap_sort($mboxd, SORTDATE, 0);
714
                    $i        = 0;
715
                    $check    = imap_mailboxmsginfo($mboxd);
0 ignored issues
show
Unused Code introduced by
$check 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...
716
                    foreach ($messages as $message) {
717
                        $header = imap_headerinfo($mboxd, $message);
718
                        $fdate  = date('F j, Y', $header->udate);
0 ignored issues
show
Unused Code introduced by
$fdate 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...
719
                        // purge if prior to global delete date
720
                        if ($header->udate < $delDate) {
721
                            imap_delete($mboxd, $message);
722
                        }
723
                        ++$i;
724
                    }
725
                    imap_expunge($mboxd);
726
                    imap_close($mboxd);
727
                }
728
            }
729
        }
730
    }
731
}
732