Mail_mimePart   A
last analyzed

Complexity

Total Complexity 37

Size/Duplication

Total Lines 232
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 97
c 2
b 0
f 0
dl 0
loc 232
rs 9.44
wmc 37

5 Methods

Rating   Name   Duplication   Size   Complexity  
A addSubPart() 0 5 1
C __construct() 0 55 17
A encode() 0 27 4
A _getEncodedData() 0 12 5
B _quotedPrintableEncode() 0 36 10
1
<?php
2
// +-----------------------------------------------------------------------+
3
// | Copyright (c) 2002  Richard Heyes                                     |
4
// | All rights reserved.                                                  |
5
// |                                                                       |
6
// | Redistribution and use in source and binary forms, with or without    |
7
// | modification, are permitted provided that the following conditions    |
8
// | are met:                                                              |
9
// |                                                                       |
10
// | o Redistributions of source code must retain the above copyright      |
11
// |   notice, this list of conditions and the following disclaimer.       |
12
// | o Redistributions in binary form must reproduce the above copyright   |
13
// |   notice, this list of conditions and the following disclaimer in the |
14
// |   documentation and/or other materials provided with the distribution.|
15
// | o The names of the authors may not be used to endorse or promote      |
16
// |   products derived from this software without specific prior written  |
17
// |   permission.                                                         |
18
// |                                                                       |
19
// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS   |
20
// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT     |
21
// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
22
// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT  |
23
// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
24
// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT      |
25
// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
26
// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
27
// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT   |
28
// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
29
// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  |
30
// |                                                                       |
31
// +-----------------------------------------------------------------------+
32
// | Author: Richard Heyes <[email protected]>                           |
33
// +-----------------------------------------------------------------------+
34
35
/**
36
 *  Raw mime encoding class
37
 *
38
 * What is it?
39
 *   This class enables you to manipulate and build
40
 *   a mime email from the ground up.
41
 *
42
 * Why use this instead of mime.php?
43
 *   mime.php is a userfriendly api to this class for
44
 *   people who aren't interested in the internals of
45
 *   mime mail. This class however allows full control
46
 *   over the email.
47
 *
48
 * Eg.
49
 *
50
 * // Since multipart/mixed has no real body, (the body is
51
 * // the subpart), we set the body argument to blank.
52
 *
53
 * $params['content_type'] = 'multipart/mixed';
54
 * $email = new Mail_mimePart('', $params);
55
 *
56
 * // Here we add a text part to the multipart we have
57
 * // already. Assume $body contains plain text.
58
 *
59
 * $params['content_type'] = 'text/plain';
60
 * $params['encoding']     = '7bit';
61
 * $text = $email->addSubPart($body, $params);
62
 *
63
 * // Now add an attachment. Assume $attach is
64
 * the contents of the attachment
65
 *
66
 * $params['content_type'] = 'application/zip';
67
 * $params['encoding']     = 'base64';
68
 * $params['disposition']  = 'attachment';
69
 * $params['dfilename']    = 'example.zip';
70
 * $attach =& $email->addSubPart($body, $params);
71
 *
72
 * // Now build the email. Note that the encode
73
 * // function returns an associative array containing two
74
 * // elements, body and headers. You will need to add extra
75
 * // headers, (eg. Mime-Version) before sending.
76
 *
77
 * $email = $message->encode();
78
 * $email['headers'][] = 'Mime-Version: 1.0';
79
 *
80
 *
81
 * Further examples are available at https://www.phpguru.org
82
 *
83
 * TODO:
84
 *  - Set encode() to return the $obj->encoded if encode()
85
 *    has already been run. Unless a flag is passed to specifically
86
 *    re-build the message.
87
 *
88
 * @author  Richard Heyes <[email protected]>
89
 * @version $Revision: 1.1 $
90
 */
91
class Mail_mimePart
92
{
93
    /**
94
     * The encoding type of this part
95
     * @var string
96
     */
97
    public $_encoding;
98
    /**
99
     * An array of subparts
100
     * @var array
101
     */
102
    public $_subparts;
103
    /**
104
     * The output of this part after being built
105
     * @var string
106
     */
107
    public $_encoded;
108
    /**
109
     * Headers for this part
110
     * @var array
111
     */
112
    public $_headers;
113
    /**
114
     * The body of this part (not encoded)
115
     * @var string
116
     */
117
    public $_body;
118
119
    /**
120
     * Constructor.
121
     *
122
     * Sets up the object.
123
     *
124
     * @param string $body   - The body of the mime part if any.
125
     * @param array  $params - An associative array of parameters:
126
     *                       content_type - The content type for this part eg multipart/mixed
127
     *                       encoding     - The encoding to use, 7bit, 8bit, base64, or quoted-printable
128
     *                       cid          - Content ID to apply
129
     *                       disposition  - Content disposition, inline or attachment
130
     *                       dfilename    - Optional filename parameter for content disposition
131
     *                       description  - Content description
132
     *                       charset      - Character set to use
133
     */
134
    public function __construct(string $body = '', array $params = [])
135
    {
136
        if (!defined('MAIL_MIMEPART_CRLF')) {
137
            define('MAIL_MIMEPART_CRLF', defined('MAIL_MIME_CRLF') ? MAIL_MIME_CRLF : "\r\n", true);
138
        }
139
140
        foreach ($params as $key => $value) {
141
            switch ($key) {
142
                case 'content_type':
143
                    $headers['Content-Type'] = $value . (isset($charset) ? '; charset="' . $charset . '"' : '');
144
                    break;
145
                case 'encoding':
146
                    $this->_encoding                      = $value;
147
                    $headers['Content-Transfer-Encoding'] = $value;
148
                    break;
149
                case 'cid':
150
                    $headers['Content-ID'] = '<' . $value . '>';
151
                    break;
152
                case 'disposition':
153
                    $headers['Content-Disposition'] = $value . (isset($dfilename) ? '; filename="' . $dfilename . '"' : '');
154
                    break;
155
                case 'dfilename':
156
                    if (isset($headers['Content-Disposition'])) {
157
                        $headers['Content-Disposition'] .= '; filename="' . $value . '"';
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $headers seems to be defined later in this foreach loop on line 143. Are you sure it is defined here?
Loading history...
158
                    } else {
159
                        $dfilename = $value;
160
                    }
161
                    break;
162
                case 'description':
163
                    $headers['Content-Description'] = $value;
164
                    break;
165
                case 'charset':
166
                    if (isset($headers['Content-Type'])) {
167
                        $headers['Content-Type'] .= '; charset="' . $value . '"';
168
                    } else {
169
                        $charset = $value;
170
                    }
171
                    break;
172
            }
173
        }
174
175
        // Default content-type
176
        if (!isset($headers['Content-Type'])) {
177
            $headers['Content-Type'] = 'text/plain';
178
        }
179
180
        //Default encoding
181
        if (!isset($this->_encoding)) {
182
            $this->_encoding = '7bit';
183
        }
184
185
        // Assign stuff to member variables
186
        $this->_encoded = [];
0 ignored issues
show
Documentation Bug introduced by
It seems like array() of type array is incompatible with the declared type string of property $_encoded.

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...
187
        $this->_headers = $headers;
188
        $this->_body    = $body;
189
    }
190
191
    /**
192
     * encode()
193
     *
194
     * Encodes and returns the email. Also stores
195
     * it in the encoded member variable
196
     *
197
     * @return array|string An associative array containing two elements,
198
     *            body and headers. The headers element is itself
199
     *            an indexed array.
200
     */
201
    public function encode()
202
    {
203
        $encoded = &$this->_encoded;
204
205
        if (!empty($this->_subparts)) {
206
            $boundary                       = '=_' . md5(uniqid((string)mt_rand(), true) . microtime());
207
            $this->_headers['Content-Type'] .= ';' . MAIL_MIMEPART_CRLF . "\t" . 'boundary="' . $boundary . '"';
208
209
            // Add body parts to $subparts
210
            foreach ($this->_subparts as $iValue) {
211
                $headers = [];
212
                $tmp     = $iValue->encode();
213
                foreach ($tmp['headers'] as $key => $value) {
214
                    $headers[] = $key . ': ' . $value;
215
                }
216
                $subparts[] = implode(MAIL_MIMEPART_CRLF, $headers) . MAIL_MIMEPART_CRLF . MAIL_MIMEPART_CRLF . $tmp['body'];
217
            }
218
219
            $encoded['body'] = '--' . $boundary . MAIL_MIMEPART_CRLF . implode('--' . $boundary . MAIL_MIMEPART_CRLF, $subparts) . '--' . $boundary . '--' . MAIL_MIMEPART_CRLF;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $subparts seems to be defined by a foreach iteration on line 210. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
220
        } else {
221
            $encoded['body'] = $this->_getEncodedData($this->_body, $this->_encoding) . MAIL_MIMEPART_CRLF;
222
        }
223
224
        // Add headers to $encoded
225
        $encoded['headers'] = &$this->_headers;
226
227
        return $encoded;
228
    }
229
230
    /**
231
     * &addSubPart()
232
     *
233
     * Adds a subpart to current mime part and returns
234
     * a reference to it
235
     *
236
     * @param string $body   body of the subpart, if any.
237
     * @param array  $params parameters for the subpart, same
238
     *                       as the $params argument for constructor.
239
     * @return object A reference to the part you just added. It is
240
     *                       crucial if using multipart/* in your subparts that
241
     *                       you use =& in your script when calling this function,
242
     *                       otherwise you will not be able to add further subparts.
243
     */
244
    public function &addSubPart(string $body, array $params)
245
    {
246
        $this->_subparts[] = new self($body, $params);
247
248
        return $this->_subparts[count($this->_subparts) - 1];
249
    }
250
251
    /**
252
     * _getEncodedData()
253
     *
254
     * Returns encoded data based upon encoding passed to it
255
     *
256
     * @param string $data     data to encode.
257
     * @param string $encoding encoding type to use, 7bit, base64,
258
     *                         or quoted-printable.
259
     * @return bool|string|
260
     */
261
    public function _getEncodedData(string $data, string $encoding)
262
    {
263
        switch ($encoding) {
264
            case '8bit':
265
            case '7bit':
266
                return $data;
267
            case 'quoted-printable':
268
                return $this->_quotedPrintableEncode($data);
269
            case 'base64':
270
                return rtrim(chunk_split(base64_encode($data), 76, MAIL_MIMEPART_CRLF));
271
            default:
272
                return $data;
273
        }
274
    }
275
276
    /**
277
     * quoteadPrintableEncode()
278
     *
279
     * Encodes data to quoted-printable standard.
280
     *
281
     * @param string $input    data to encode
282
     * @param int    $line_max Optional max line length. Should
283
     *                         not be more than 76 chars
284
     *
285
     * @return bool|string
286
     */
287
    public function _quotedPrintableEncode(string $input, int $line_max = 76)
288
    {
289
        $lines  = preg_split("/\r?\n/", $input);
290
        $eol    = MAIL_MIMEPART_CRLF;
291
        $escape = '=';
292
        $output = '';
293
294
        while ([, $line] = each($lines)) {
295
            $linlen  = mb_strlen($line);
296
            $newline = '';
297
298
            for ($i = 0; $i < $linlen; ++$i) {
299
                $char = mb_substr($line, $i, 1);
300
                $dec  = ord($char);
301
302
                if ((32 == $dec) && ($i == ($linlen - 1))) {    // convert space at eol only
303
                    $char = '=20';
304
                } elseif (9 == $dec) {
305
                    // Do nothing if a tab.
306
                } elseif ((61 == $dec) || ($dec < 32) || ($dec > 126)) {
307
                    $char = $escape . \mb_strtoupper(sprintf('%02s', dechex($dec)));
308
                }
309
310
                if ((mb_strlen($newline) + mb_strlen($char)) >= $line_max) {                                                                                                                                                                                            // MAIL_MIMEPART_CRLF is not counted
311
                    $output  .= $newline
312
                                . $escape
313
                                . $eol;                                                                                                                                                                                                                                 // soft line break; " =\r\n" is okay
314
                    $newline = '';
315
                }
316
                $newline .= $char;
317
            } // end of for
318
            $output .= $newline . $eol;
319
        }
320
        $output = mb_substr($output, 0, -1 * mb_strlen($eol)); // Don't want last crlf
321
322
        return $output;
323
    }
324
} // End of class
325