EmailParser   A
last analyzed

Complexity

Total Complexity 20

Size/Duplication

Total Lines 188
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 73
c 0
b 0
f 0
dl 0
loc 188
rs 10
wmc 20

9 Methods

Rating   Name   Duplication   Size   Complexity  
A parseMessage() 0 6 1
A _quoteBody() 0 37 5
A _parseMsg() 0 14 1
A _parseFrom() 0 16 1
A parseTicketID() 0 13 2
B _getBody() 0 27 7
A _parseBody() 0 6 1
A __construct() 0 6 1
A _stripMd5Key() 0 5 1
1
<?php declare(strict_types=1);
2
3
namespace XoopsModules\Xhelp;
4
5
/*
6
 * You may not change or alter any portion of this comment or credits
7
 * of supporting developers from this source code or any supporting source code
8
 * which is considered copyrighted (c) material of the original comment or credit authors.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13
 */
14
15
/**
16
 * @copyright    {@link https://xoops.org/ XOOPS Project}
17
 * @license      {@link https://www.gnu.org/licenses/gpl-2.0.html GNU GPL 2 or later}
18
 * @author       Brian Wahoff <[email protected]>
19
 * @author       XOOPS Development Team
20
 */
21
22
if (!\defined('XHELP_CONSTANTS_INCLUDED')) {
23
    exit();
24
}
25
26
\define('MD5SIGNATUREPATTERN', '/{([^ ]*)}/i');
27
\define('HEADER_PRIORITY', 'Importance');
28
\define('_XHELP_MSGTYPE_TICKET', 1);
29
\define('_XHELP_MSGTYPE_RESPONSE', 2);
30
31
require_once \XHELP_PEAR_PATH . '/Mail/mimeDecode.php';
32
33
/**
34
 * EmailParser class
35
 *
36
 * Part of the email submission subsystem
37
 *
38
 * @author     Brian Wahoff <[email protected]>
39
 */
40
class EmailParser
41
{
42
    public $_params = [];
43
44
    /**
45
     * Class Constructor
46
     */
47
    public function __construct()
48
    {
49
        $this->_params['include_bodies'] = true;
50
        $this->_params['decode_bodies']  = true;
51
        $this->_params['decode_headers'] = true;
52
        $this->_params['input']          = '';
53
    }
54
55
    /**
56
     * Parses Message
57
     * @param array $msg
58
     * @return ParsedMessage
59
     */
60
    public function &parseMessage(array $msg): ParsedMessage
61
    {
62
        $struct = $this->_parseMsg($msg);
63
        $newMsg = new ParsedMessage($struct);
64
65
        return $newMsg;
66
    }
67
68
    /**
69
     * @param array $msg
70
     * @return array
71
     */
72
    public function &_parseMsg(array $msg): array
73
    {
74
        $arr = [];
75
        //Parse out attachments/HTML
76
        $this->_params['input'] = $msg['msg'];
77
78
        $structure          = Mail_mimeDecode::decode($this->_params);
0 ignored issues
show
Bug introduced by
The type XoopsModules\Xhelp\Mail_mimeDecode was not found. Did you mean Mail_mimeDecode? If so, make sure to prefix the type with \.
Loading history...
79
        $body               = $this->_getBody($structure);
80
        $arr['hash']        = $this->parseTicketID($structure->headers['subject']);
81
        $arr['msg']         = $this->_parseBody($body);
0 ignored issues
show
Bug introduced by
It seems like $body can also be of type boolean; however, parameter $msg of XoopsModules\Xhelp\EmailParser::_parseBody() does only seem to accept array, 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

81
        $arr['msg']         = $this->_parseBody(/** @scrutinizer ignore-type */ $body);
Loading history...
82
        $arr['mime_struct'] = $structure;
83
        $arr                = \array_merge($arr, $this->_parseFrom($structure->headers['from']));
84
85
        return $arr;
86
    }
87
88
    /**
89
     * @param string $from
90
     * @return array
91
     */
92
    public function &_parseFrom(string $from): array
93
    {
94
        //Extract Name & Email from supplied message
95
        //eregi("From: (.*)\nTo:",$headers, $addr);
96
        \preg_match('/"?([^"<@]*)"?/i', $from, $name);
97
        //eregi("<(.*)>",$from, $email);
98
99
        $arr['name'] = $name[1];
0 ignored issues
show
Comprehensibility Best Practice introduced by
$arr was never initialized. Although not strictly required by PHP, it is generally a good practice to add $arr = array(); before regardless.
Loading history...
100
101
        $pattern['email'] = "/([a-z0-9\-_\.]+?)@([^, \r\n\"\(\)'<>\[\]]+)/i";
0 ignored issues
show
Comprehensibility Best Practice introduced by
$pattern was never initialized. Although not strictly required by PHP, it is generally a good practice to add $pattern = array(); before regardless.
Loading history...
102
103
        \preg_match($pattern['email'], $from, $matches);
104
        $arr['email'] = $matches[0];
105
106
        //$arr['email'] = $email[1];
107
        return $arr;
108
    }
109
110
    /**
111
     * @param string $msg
112
     * @return mixed|string
113
     */
114
    public function parseTicketID(string $msg)
115
    {
116
        $matches = [];
117
        $ret     = \preg_match(MD5SIGNATUREPATTERN, $msg, $matches);
118
119
        if ($ret) {
120
            // This function assumes that the ticket id is stored in the first
121
            // regex subquery. If the regex needs to be changed, this logic
122
            // may need to be changed as well.
123
            return $matches[1];
124
        }
125
126
        return '';
127
    }
128
129
    /**
130
     * @param array $msg
131
     * @return array|string|string[]|null
132
     */
133
    public function _parseBody(array $msg)
134
    {
135
        $msg = $this->_quoteBody((string)$msg);
136
        $msg = $this->_stripMd5Key($msg);
137
138
        return $msg;
139
    }
140
141
    /**
142
     * @param string $msg
143
     * @return string
144
     */
145
    public function _quoteBody(string $msg): string
146
    {
147
        $current = 0;
148
149
        $msg = \explode("\r\n", $msg);
150
151
        foreach ($msg as $i => $iValue) {
152
            $pattern    = [];
153
            $replace    = [];
154
            $next       = $current + 1;
155
            $prev       = $current - 1;
156
            $pattern[0] = '/^(>[\s]?){' . $next . '}(.*)/i';
157
            $replace[0] = '[quote]\\2';
158
            $pattern[1] = '/^(>[\s]?){' . $current . '}(.*)/i';
159
            $replace[1] = '\\2';
160
161
            $pattern[2] = '/^(>[\s]?){' . $prev . '}(.*)/i';
162
            $replace[2] = '[/quote]\\2';
163
164
            //Check if current line indicates a quote
165
            if (\preg_match($pattern[0], $iValue)) {
166
                $msg[$i] = \preg_replace($pattern[0], $replace[0], $iValue);
167
                ++$current;
168
            } else {
169
                if ($current) {
170
                    //Check if line indicates a closed quote
171
                    if (\preg_match($pattern[1], $iValue)) {
172
                        $msg[$i] = \preg_replace($pattern[1], $replace[1], $iValue);
173
                    } else {
174
                        $msg[$i] = \preg_replace($pattern[2], $replace[2], $iValue);
175
                        $current--;
176
                    }
177
                }
178
            }
179
        }
180
181
        return \implode("\r\n", $msg);
182
    }
183
184
    /**
185
     * @param string $msg
186
     * @return array|string|string[]|null
187
     */
188
    public function _stripMd5Key(string $msg)
189
    {
190
        $pattern = '/^\*{4}\s' . \_XHELP_TICKET_MD5SIGNATURE . '\s(.)*\*{4}/im';
191
192
        return \preg_replace($pattern, '', $msg);
193
    }
194
195
    /**
196
     * @param object|array $part
197
     * @param string       $primary
198
     * @param string       $secondary
199
     * @return bool|array
200
     */
201
    public function _getBody($part, string $primary = 'text', string $secondary = 'plain')
202
    {
203
        $body = false;
204
205
        // 1. No subparts:
206
207
        // 2. array of subparts
208
        // 2a. subarray of subparts (recursion)?
209
210
        if (\is_array($part)) {
211
            foreach ($part as $subpart) {
212
                if (!$body = $this->_getBody($subpart, $primary, $secondary)) {
213
                    continue;
214
                }
215
216
                return $body;
217
            }
218
        } else {
219
            if (isset($part->parts)) {
220
                return $this->_getBody($part->parts, $primary, $secondary);
221
            }
222
            if ($part->ctype_primary == $primary && $part->ctype_secondary == $secondary) {
223
                return $part->body;
224
            }
225
        }
226
227
        return $body;
228
    }
229
}
230