SendMail.php 17.7 KB
Newer Older
1
2
3
4
5
6
<?php

namespace qfq;

//use qfq;

7
require_once(__DIR__ . '/../Constants.php');
8
require_once(__DIR__ . '/../database/Database.php');
9
require_once(__DIR__ . '/../store/Store.php');
10
require_once(__DIR__ . '/../report/Download.php');
11
require_once(__DIR__ . '/../helper/Sanitize.php');
12
require_once(__DIR__ . '/../helper/HelperFile.php');
13
require_once(__DIR__ . '/../helper/Support.php');
14

15

16
class SendMail {
17

18
19
20
21
22
    /**
     * @var Store
     */
    private $store = null;

23
    /**
24
25
     * Sends a mail as specified in $mailarr.
     * If there is no receiver specified as 'TO': no mail is sent. This is ok and no error.
26
     * Logs every send mail as a record in table `MailLog`. Additionally a `grId`, `xId` , `xId2` and `xId3`  can be specified
27
     * to assign the log entry to a specific action.
28
     * The log record also contains some information who/where generates the mail (form/formelement or QFQ query).
Carsten  Rose's avatar
Carsten Rose committed
29
     *
30
     * @throws UserFormException
31
     */
32
33
34
35
36
37
38
    public function __construct() {

        $this->store = Store::getInstance('');
    }


    /**
39
40
     * @param array $mailConfig
     * @throws UserFormException
41
42
     */
    public function process(array $mailConfig) {
43

44
        // If there is no 'Receiver': do not send a mail.
45
        if (!isset($mailConfig[SENDMAIL_TOKEN_RECEIVER]) || $mailConfig[SENDMAIL_TOKEN_RECEIVER] === '') {
46
47
48
            return;
        }

49
        if (count($mailConfig) < 4 || $mailConfig[SENDMAIL_TOKEN_SENDER] === '' || $mailConfig[SENDMAIL_TOKEN_SUBJECT] === '' || $mailConfig[SENDMAIL_TOKEN_BODY] === '') {
50
51
52
            throw new UserFormException("Error sendmail missing one of: receiver, sender, subject or body", ERROR_SENDMAIL_MISSING_VALUE);
        }

53
54
        $redirectAllMail = $this->store->getVar(SYSTEM_REDIRECT_ALL_MAIL_TO, STORE_SYSTEM);

55
        if ($redirectAllMail !== false) {
56
            $addBody = "All QFQ outgoing mails are catched and redirected to you." . PHP_EOL . "Original receiver:" . PHP_EOL;
57
58
59
            $addBody .= 'TO: ' . $mailConfig[SENDMAIL_TOKEN_RECEIVER] . PHP_EOL;
            $addBody .= 'CC: ' . $mailConfig[SENDMAIL_TOKEN_RECEIVER_CC] . PHP_EOL;
            $addBody .= 'BCC: ' . $mailConfig[SENDMAIL_TOKEN_RECEIVER_BCC] . PHP_EOL;
60
61
            $addBody .= PHP_EOL . "==========================================" . PHP_EOL . PHP_EOL;

62
            $mailConfig[SENDMAIL_TOKEN_BODY] = $addBody . $mailConfig[SENDMAIL_TOKEN_BODY];
63

64
65
66
            $mailConfig[SENDMAIL_TOKEN_RECEIVER] = $redirectAllMail;
            $mailConfig[SENDMAIL_TOKEN_RECEIVER_CC] = '';
            $mailConfig[SENDMAIL_TOKEN_RECEIVER_BCC] = '';
67
68
        }

69
70
71
72
73
74
75
76
77
78
79
80
        $mailConfig = $this->setDefault($mailConfig);

        $logAttachments = $this->sendEmail($mailConfig);
        $this->mailLog($mailConfig, $logAttachments);
    }

    /**
     * @param array $mailConfig
     * @return array
     */
    private function setDefault(array $mailConfig) {

81
82
        if (empty($mailConfig[SENDMAIL_TOKEN_FLAG_AUTO_SUBMIT]) || $mailConfig[SENDMAIL_TOKEN_FLAG_AUTO_SUBMIT] === '') {
            $mailConfig[SENDMAIL_TOKEN_FLAG_AUTO_SUBMIT] = 'on';
83
84
        }

85
86
87
88
89
90
91
92
93
        if (empty($mailConfig[SENDMAIL_TOKEN_SUBJECT_HTML_ENTITY]) || $mailConfig[SENDMAIL_TOKEN_SUBJECT_HTML_ENTITY] !== MODE_ENCODE) {
            $mailConfig[SENDMAIL_TOKEN_SUBJECT_HTML_ENTITY] = MODE_DECODE;
        }

        if (empty($mailConfig[SENDMAIL_TOKEN_BODY_HTML_ENTITY]) || $mailConfig[SENDMAIL_TOKEN_BODY_HTML_ENTITY] !== MODE_ENCODE) {
            $mailConfig[SENDMAIL_TOKEN_BODY_HTML_ENTITY] = MODE_DECODE;
        }

        return $mailConfig;
94
95
96
97
    }

    /**
     * Use the programm 'sendEmail' - http://caspian.dotconf.net/menu/Software/SendEmail
98
     * Body and Subject is UTF8 encoded. Append attachments with '-a <file>'.
99
100
     *
     * @param array $mailConfig
101
     * @return string
102
103
     * @throws CodeException
     * @throws UserFormException
104
     * @throws UserReportException
105
106
107
     */
    private function sendEmail(array $mailConfig) {
        $args = array();
108
        $attachments = array();
109
        $cmdAttachments = '';
110

111
112
113
        $mailConfig[SENDMAIL_TOKEN_SUBJECT] = Support::htmlEntityEncodeDecode($mailConfig[SENDMAIL_TOKEN_SUBJECT_HTML_ENTITY], $mailConfig[SENDMAIL_TOKEN_SUBJECT]);
        $mailConfig[SENDMAIL_TOKEN_BODY] = Support::htmlEntityEncodeDecode($mailConfig[SENDMAIL_TOKEN_BODY_HTML_ENTITY], $mailConfig[SENDMAIL_TOKEN_BODY]);

114
        foreach ($mailConfig as $key => $value) {
115
116
117
            if (is_array($value)) {
                continue;
            }
118
            $mailConfig[$key] = Support::escapeDoubleTick($value);
119
//            $mailConfig[$key] = addslashes($value);
120
121
        }

122
123
        $args[] = '-f "' . $mailConfig[SENDMAIL_TOKEN_SENDER] . '"';
        $args[] = '-t "' . $mailConfig[SENDMAIL_TOKEN_RECEIVER] . '"';
124
125
126
127
128
129
130
        $args[] = '-o message-charset="utf-8"';

        $logFile = $this->store->getVar(SYSTEM_MAIL_LOG, STORE_SYSTEM);
        if ($logFile != '' && $logFile !== false) {
            $args[] = '-l "' . $logFile . '"';;
        }

131
        if (!empty($mailConfig[SENDMAIL_TOKEN_RECEIVER_CC])) {
132
            $args[] = '-cc "' . $mailConfig[SENDMAIL_TOKEN_RECEIVER_CC] . '"';
133
134
        }

135
        if (!empty($mailConfig[SENDMAIL_TOKEN_RECEIVER_BCC])) {
Carsten  Rose's avatar
Carsten Rose committed
136
            $args[] = '-bcc "' . $mailConfig[SENDMAIL_TOKEN_RECEIVER_BCC] . '"';
137
138
        }

139
        if (!empty($mailConfig[SENDMAIL_TOKEN_SUBJECT])) {
140

141
142
            // The subject needs to be encoded to UTF-8 separately - https://stackoverflow.com/questions/4389676/email-from-php-has-broken-subject-header-encoding/27648245#27648245
            $preferences = ["scheme" => "Q", "input-charset" => "UTF-8", "output-charset" => "UTF-8"];
143
            $encodedSubject = iconv_mime_encode("Subject", $mailConfig[SENDMAIL_TOKEN_SUBJECT], $preferences);
144
145
146
147
148
            $encodedSubject = substr($encodedSubject, 9); // remove 'Subject: '

            $args[] = '-u "' . $encodedSubject . '"';;
        }

149
        if (!empty($mailConfig[SENDMAIL_TOKEN_BODY])) {
150

151
            $args[] = '-m "' . $mailConfig[SENDMAIL_TOKEN_BODY] . '"';;
152
153
        }

154
155
        if (!empty($mailConfig[SENDMAIL_TOKEN_REPLY_TO])) {
            $args[] = '-o reply-to="' . $mailConfig[SENDMAIL_TOKEN_REPLY_TO] . '"';;
156
157
        }

158
        if ($mailConfig[SENDMAIL_TOKEN_FLAG_AUTO_SUBMIT] === 'on') {
159
160
161
            $args[] = '-o message-header="Auto-Submitted: auto-send"';
        }

162
163
        if (!empty($mailConfig[SENDMAIL_TOKEN_ATTACHMENT])) {
            $attachments = $this->attachmentsBuild($mailConfig[SENDMAIL_TOKEN_ATTACHMENT]);
164
            if (!empty($attachments)) {
165
166
                $cmdAttachments = '-a ' . implode(' -a ', $attachments);
                $args[] = $cmdAttachments;
167
            }
168
169
        }

170
171
        if (!empty($mailConfig[SENDMAIL_TOKEN_HEADER])) {
            $args[] = '-o message-header="' . $mailConfig[SENDMAIL_TOKEN_HEADER] . '"';
172
173
174
175
176
177
178
179
180
181
182
183
        }

        $sendEmail = $this->store->getVar(SYSTEM_SEND_E_MAIL, STORE_SYSTEM);
        if (empty($sendEmail)) {
            throw new UserFormException("Missing 'sendEmail'", ERROR_SENDMAIL);
        }

        $sendEmailOptions = $this->store->getVar(SYSTEM_SEND_E_MAIL_OPTIONS, STORE_SYSTEM);
        if (!empty($sendEmailOptions)) {
            $args[] = $sendEmailOptions;
        }

184
        OnArray::arrayEscapeshellarg($args);
185
186
187
        $cmd = $sendEmail . ' ' . implode(' ', $args);

        exec($cmd, $arr, $rc);
188

189
        if ($rc != 0) {
190
            // After first installation of QFQ extension, the PERL script is not executable: is this the problem here?
191
192
193
194
195
196
197
            $perms = fileperms($sendEmail);
            if (!($perms & 0x0040)) {
                chmod($sendEmail, 0755);
                exec($cmd, $arr, $rc); // Give it a second try.
            }

            if ($rc != 0) {
198
                $output = $rc . " - " . implode('<br>', $arr) . " - " . $cmd;
199
200
201
                throw new UserFormException("Error sendmail failed: " . $output, ERROR_SENDMAIL);
            }
        }
202

203
        HelperFile::cleanTempFiles($attachments);
204

205
        return $cmdAttachments;
206
    }
207

208

209
    /**
210
     * Creates a new MailLog Record based on $mailArr / $header.
211
     *
212
     * @param array $mailConfig
Carsten  Rose's avatar
Carsten Rose committed
213
     *
214
215
     * @throws CodeException
     * @throws DbException
216
     */
217
    private function mailLog(array $mailConfig, $attachmentsLine = '') {
218

219
        $log = array();
220

221
222
223
        $header = 'OoO:' . $mailConfig[SENDMAIL_TOKEN_FLAG_AUTO_SUBMIT];
        if (!empty($mailConfig[SENDMAIL_TOKEN_HEADER])) {
            $header .= PHP_EOL . 'Custom: ' . $mailConfig[SENDMAIL_TOKEN_HEADER];
224
        }
225
226
        if (!empty($mailConfig[SENDMAIL_TOKEN_ATTACHMENT])) {
            $header .= PHP_EOL . 'Attachment: ' . $mailConfig[SENDMAIL_TOKEN_ATTACHMENT];
227
228
        }

229
        // Log
230
231
232
233
        $log[] = $mailConfig[SENDMAIL_TOKEN_RECEIVER];
        $log[] = $mailConfig[SENDMAIL_TOKEN_SENDER];
        $log[] = $mailConfig[SENDMAIL_TOKEN_SUBJECT];
        $log[] = $mailConfig[SENDMAIL_TOKEN_BODY];
234
        $log[] = $header;
235
        $log[] = $attachmentsLine;
236
237
238
239
240
        $log[] = empty($mailConfig[SENDMAIL_TOKEN_GR_ID]) ? 0 : $mailConfig[SENDMAIL_TOKEN_GR_ID];
        $log[] = empty($mailConfig[SENDMAIL_TOKEN_X_ID]) ? 0 : $mailConfig[SENDMAIL_TOKEN_X_ID];
        $log[] = empty($mailConfig[SENDMAIL_TOKEN_X_ID2]) ? 0 : $mailConfig[SENDMAIL_TOKEN_X_ID2];
        $log[] = empty($mailConfig[SENDMAIL_TOKEN_X_ID3]) ? 0 : $mailConfig[SENDMAIL_TOKEN_X_ID3];
        $log[] = empty($mailConfig[SENDMAIL_TOKEN_SRC]) ? 0 : $mailConfig[SENDMAIL_TOKEN_SRC];
241

242
        $db = new Database();
243
        $db->sql('INSERT INTO MailLog (`receiver`, `sender`, `subject`, `body`, `header`, `attach`, `grId`, `xId`, `xId2`, `xId3`, `src`, `modified`, `created`) VALUES ( ?, ? , ? , ? ,?, ?, ? ,?, ?, ?, ?, NOW(), NOW() )', ROW_REGULAR, $log);
244

245
    }
246
247

    /**
248
249
250
251
     *
     * @param array $attachments Array of attachments. Per attachment, different & multiple sources are possible.
     *              [ [ 0 -> 'F:/etc/hostname' ],
     *                [ 0 -> 'u:http://nzz.ch', 1 -> 'd:nzz.pdf' ],
252
     *                [ 0 -> 'p:id=detailPerson&form=Person&r=1&_sip=1', 1 -> 'F:/etc/hostname'  ]
253
254
255
     *                [ 0 -> 'd:all.pdf', 1 -> 'U:?id=detailPerson&form=Person&r=1&_sip=1', 2 -> 'F:/etc/hostname'  ] ]
     * @return array Array of filenames. Those files has to be deleted later, if they are temporary files.
     * @throws UserReportException
256
257
258
     */
    private function attachmentsBuild(array $attachments) {
        $files = array();
259
260
        $download = new Download();
        $tmpDir = sys_get_temp_dir();
261

262
        // Several attachments are possible. Process one by one.
263
        foreach ($attachments as $attach) {
264
265
            $vars = array();

266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
            // Extract Filename
            $exportFilename = '';
            $downloadMode = '';

            // Per attachment: extract 'mode','filename'. Leave the 'sources' untouched.
            foreach ($attach as $key => $element) {
                $token = $element[0];
                switch ($token) {

                    case SENDMAIL_TOKEN_DOWNLOAD_FILENAME:
                        $exportFilename = substr($element, 2);
                        unset($attach[$key]);
                        break;

                    case SENDMAIL_TOKEN_DOWNLOAD_MODE:
                        $downloadMode = substr($element, 2);
                        unset($attach[$key]);
                        break;

                    case SENDMAIL_TOKEN_ATTACHMENT_FILE:
                        if ($downloadMode == '') { // Set only if not explicit given.
                            $downloadMode = DOWNLOAD_MODE_FILE;
                        }
                        break;

                    case SENDMAIL_TOKEN_ATTACHMENT_URL:
292
293
                    case SENDMAIL_TOKEN_ATTACHMENT_URL_PARAM:
                    case SENDMAIL_TOKEN_ATTACHMENT_PAGE:
294
295
296
297
298
299
300
301
302
303
304
305
306
                        if ($downloadMode == '') { // Set only if not explicit given.
                            $downloadMode = DOWNLOAD_MODE_PDF;
                        }
                        break;

                    default:
                        throw new UserReportException('Unknown token in _sendmail: ' . $token, ERROR_UNKNOWN_TOKEN);
                        break;
                }
            }

            $vars[SIP_DOWNLOAD_PARAMETER] = implode(PARAM_DELIMITER, $attach);
            $vars[DOWNLOAD_MODE] = (count($attach) > 1) ? DOWNLOAD_MODE_PDF : $downloadMode;
307

308
            $file = $download->process($vars, OUTPUT_MODE_FILE);
309

310
            if (empty($exportFilename) && HelperFile::isQfqTemp($file)) {
311

312
313
314
                $exportFilename = DOWNLOAD_OUTPUT_FILENAME;
                if ($downloadMode == DOWNLOAD_MODE_PDF) {
                    $exportFilename .= '.pdf';
315
                }
316
317
318
319
320
321
322
323
324
325
326
327
328
329
            }

            // In case an exportFilename is given: move/rename it, if it is necessary.
            if (!empty($exportFilename)) {

                $exportFilename = Sanitize::safeFilename($exportFilename, true);

                $dir = HelperFile::mktempdir(); // Attachments might have the same filename - create one directory per attachment, to ensure same filenames do not conflict.
                $exportFilename = $dir . '/' . $exportFilename;

                if (HelperFile::isQfqTemp($file)) {
                    rename($file, $exportFilename);
                } else {
                    copy($file, $exportFilename);
330
                }
331
                $file = $exportFilename;
332
            }
333
            $files[] = $file;
334
        }
335
336

        return $files;
337
    }
338
339
340
341


    /**
     * Convert a token based sendMail string into an array.
342
     * - Each attachment (single file or multiple concatenated files) is an array in the array.
343
344
345
346
347
348
349
350
     *
     * @param string $data E.g.: 't:john@doe.com|f:jane@miller.com|s:Latest|b:Dear John ...'
     * @return array
     * @throws UserFormException
     * @throws UserReportException
     */
    public function parseStringToArray($data) {

351
        $args = array();
352
353
354
355
356
357
        $attachment = array();

        $flagConcat = false;
        $flagSource = false;
        $flagFilename = false;

358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
        $convertToShorthandToken = [
            SENDMAIL_TOKEN_RECEIVER_LONG => SENDMAIL_TOKEN_RECEIVER,
            SENDMAIL_TOKEN_SENDER_LONG => SENDMAIL_TOKEN_SENDER,
            SENDMAIL_TOKEN_SUBJECT_LONG => SENDMAIL_TOKEN_SUBJECT,
            SENDMAIL_TOKEN_BODY_LONG => SENDMAIL_TOKEN_BODY,
            SENDMAIL_TOKEN_RECEIVER_CC_LONG => SENDMAIL_TOKEN_RECEIVER_CC,
            SENDMAIL_TOKEN_RECEIVER_BCC_LONG => SENDMAIL_TOKEN_RECEIVER_BCC,
            SENDMAIL_TOKEN_REPLY_TO_LONG => SENDMAIL_TOKEN_REPLY_TO,
            SENDMAIL_TOKEN_FLAG_AUTO_SUBMIT_LONG => SENDMAIL_TOKEN_FLAG_AUTO_SUBMIT,
            SENDMAIL_TOKEN_GR_ID_LONG => SENDMAIL_TOKEN_GR_ID,
            SENDMAIL_TOKEN_X_ID_LONG => SENDMAIL_TOKEN_X_ID,
            SENDMAIL_TOKEN_X_ID2_LONG => SENDMAIL_TOKEN_X_ID2,
            SENDMAIL_TOKEN_X_ID3_LONG => SENDMAIL_TOKEN_X_ID3,
            SENDMAIL_TOKEN_HEADER_LONG => SENDMAIL_TOKEN_HEADER,
            SENDMAIL_TOKEN_SRC_LONG => SENDMAIL_TOKEN_SRC,
        ];

375
        $param = explode(PARAM_DELIMITER, $data);
376

377
378
379
380
381
382
383
        // Iterate over all parameter: use token as key. Collect corresponding attachments arguments in separate array elements
        foreach ($param AS $line) {

            if (empty($line)) {
                continue;
            }

384
            $tokenAndValue = explode(PARAM_TOKEN_DELIMITER, $line, 2);
385
            if (count($tokenAndValue) < 2 && $tokenAndValue[0] !== SENDMAIL_TOKEN_CONCAT) {
386
                throw new UserFormException('Missing token delimiter "' . PARAM_TOKEN_DELIMITER . '" in: ' . $line, ERROR_UNKNOWN_TOKEN);
387
388
            }

389
390
391
392
393
394
395
396
397
398
            $token = $tokenAndValue[0];
            // convert speaking word tokens to shorthand
            if (strlen($token) > 1) {
                $token = strtolower($token); // speaking word tokens are all lowercase
                if (isset($convertToShorthandToken[$token])) {
                    $token = $convertToShorthandToken[$token];
                } else {
                    throw new UserFormException('Unknown token "' . $token . '" in: ' . $line, ERROR_UNKNOWN_TOKEN);
                }
            }
399

400
401
402
            // Check for deprecated token.
            if ($token == SENDMAIL_TOKEN_ATTACHMENT_FILE_DEPRECATED) {
                throw new UserFormException('Sendmail: Option "a:" is deprecated, please use "' . SENDMAIL_TOKEN_ATTACHMENT_FILE . '" instead', ERROR_UNKNOWN_TOKEN);
403
404
            }

405
            switch ($token) {
406
407
408
                case SENDMAIL_TOKEN_CONCAT:
                    $flagConcat = true;
                    if (!empty($attachment)) {
409
                        $args[SENDMAIL_TOKEN_ATTACHMENT][] = $attachment;
410
411
412
413
414
415
                        $attachment = array();
                    }
                    break;

                case SENDMAIL_TOKEN_ATTACHMENT_FILE:
                case SENDMAIL_TOKEN_ATTACHMENT_URL:
416
417
                case SENDMAIL_TOKEN_ATTACHMENT_URL_PARAM:
                case SENDMAIL_TOKEN_ATTACHMENT_PAGE:
418
                    if ($flagSource && !$flagConcat) {
419
                        $args[SENDMAIL_TOKEN_ATTACHMENT][] = $attachment;
420
421
422
423
424
425
426
427
428
                        $attachment = array();
                        $flagFilename = false;
                    }
                    $flagSource = true;
                    $attachment[] = $line;
                    break;

                case SENDMAIL_TOKEN_DOWNLOAD_FILENAME:
                    if ($flagFilename && !$flagConcat) {
429
                        $args[SENDMAIL_TOKEN_ATTACHMENT][] = $attachment;
430
431
432
433
434
435
436
437
                        $attachment = array();
                        $flagSource = false;
                    }
                    $flagFilename = true;
                    $attachment[] = $line;
                    break;

                default:
438
                    $args[$token] = $tokenAndValue[1];
439
440
441
442
443
                    break;
            }
        }

        if (!empty($attachment)) {
444
            $args[SENDMAIL_TOKEN_ATTACHMENT][] = $attachment;
445
446
        }

447
        return ($args);
448
449
    }

450
}