]> git.agnieray.net Git - galette.git/blob - galette/lib/Galette/Core/Mailing.php
phpdoc, non used code
[galette.git] / galette / lib / Galette / Core / Mailing.php
1 <?php
2
3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4
5 /**
6 * Mailing features
7 *
8 * PHP version 5
9 *
10 * Copyright © 2009-2020 The Galette Team
11 *
12 * This file is part of Galette (http://galette.tuxfamily.org).
13 *
14 * Galette is free software: you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation, either version 3 of the License, or
17 * (at your option) any later version.
18 *
19 * Galette is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * You should have received a copy of the GNU General Public License
25 * along with Galette. If not, see <http://www.gnu.org/licenses/>.
26 *
27 * @category Core
28 * @package Galette
29 *
30 * @author Johan Cwiklinski <johan@x-tnd.be>
31 * @copyright 2009-2020 The Galette Team
32 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
33 * @link http://galette.tuxfamily.org
34 * @since Available since 0.7dev - 2009-03-07
35 */
36
37 namespace Galette\Core;
38
39 use Analog\Analog;
40 use Galette\Entity\Adherent;
41 use Galette\IO\File;
42
43 /**
44 * Mailing features
45 *
46 * @category Core
47 * @name Mailing
48 * @package Galette
49 * @author Johan Cwiklinski <johan@x-tnd.be>
50 * @copyright 2009-2014 The Galette Team
51 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
52 * @link http://galette.tuxfamily.org
53 * @since Available since 0.7dev - 2009-03-07
54 *
55 * @property string $subject
56 * @property string $message
57 * @property boolean $html
58 * @property integer $current_step
59 * @property-read integer $step
60 * @property integer|string $id
61 * @property-read string $alt_message
62 * @property-read string $wrapped_message
63 * @property-read PHPMailer\PHPMailer\PHPMailer $mail
64 * @property-read PHPMailer\PHPMailer\PHPMailer $_mail
65 * @property-read array $errors
66 * @property-read array $recipients
67 * @property-read string|false $tmp_path
68 * @property-read array $attachments
69 * @property-read string $sender_name
70 * @property-read string $sender_address
71 */
72 class Mailing extends GaletteMail
73 {
74 public const STEP_START = 0;
75 public const STEP_PREVIEW = 1;
76 public const STEP_SEND = 2;
77 public const STEP_SENT = 3;
78
79 public const MIME_HTML = 'text/html';
80 public const MIME_TEXT = 'text/plain';
81 public const MIME_DEFAULT = self::MIME_TEXT;
82
83 private $id;
84
85 private $unreachables = array();
86 private $mrecipients = array();
87 private $current_step;
88
89 private $mime_type;
90
91 private $tmp_path;
92 private $history_id;
93
94 /**
95 * Default constructor
96 *
97 * @param Preferences $preferences Preferences instance
98 * @param array $members An array of members
99 * @param int $id Identifier, defaults to null
100 */
101 public function __construct(Preferences $preferences, array $members = [], int $id = null)
102 {
103 parent::__construct($preferences);
104 $this->id = $id ?? $this->generateNewId();
105
106 $this->current_step = self::STEP_START;
107 $this->mime_type = self::MIME_DEFAULT;
108 /** TODO: add a preference that propose default mime-type to use,
109 then init it here */
110 if (count($members)) {
111 //Check which members have a valid email address and which have not
112 $this->setRecipients($members);
113 }
114 $this->loadAttachments();
115 }
116
117 /**
118 * Generate new mailing id and temporary path
119 *
120 * @return string
121 */
122 private function generateNewId(): string
123 {
124 $id = '';
125 $chars = 'abcdefghjkmnpqrstuvwxyz0123456789';
126 $i = 0;
127 $size = 30;
128 while ($i <= $size - 1) {
129 $num = mt_rand(0, strlen($chars) - 1) % strlen($chars);
130 $id .= substr($chars, $num, 1);
131 $i++;
132 }
133
134 $this->id = $id;
135 $this->generateTmpPath($this->id);
136 return $this->id;
137 }
138
139 /**
140 * Generate temporary path
141 *
142 * @param string $id Random id, defautls to null
143 *
144 * @return void
145 */
146 private function generateTmpPath($id = null)
147 {
148 if ($id === null) {
149 $id = $this->generateNewId();
150 }
151 $this->tmp_path = GALETTE_ATTACHMENTS_PATH . '/' . $id;
152 }
153
154 /**
155 * Load mailing attachments
156 *
157 * @return void
158 */
159 private function loadAttachments()
160 {
161 $dir = '';
162 if (
163 isset($this->tmp_path)
164 && trim($this->tmp_path) !== ''
165 ) {
166 $dir = $this->tmp_path;
167 } else {
168 $dir = GALETTE_ATTACHMENTS_PATH . $this->id . '/';
169 }
170
171 $files = glob($dir . '*.*');
172 foreach ($files as $file) {
173 $f = new File($dir);
174 $f->setFileName(str_replace($dir, '', $file));
175 $this->attachments[] = $f;
176 }
177 }
178
179 /**
180 * Loads a mailing from history
181 *
182 * @param ResultSet $rs Mailing entry
183 * @param boolean $new True if we create a 'new' mailing,
184 * false otherwise (from preview for example)
185 *
186 * @return boolean
187 */
188 public function loadFromHistory($rs, $new = true)
189 {
190 global $zdb;
191
192 $orig_recipients = unserialize($rs->mailing_recipients);
193
194 $_recipients = array();
195 $mdeps = ['parent' => true];
196 foreach ($orig_recipients as $k => $v) {
197 $m = new Adherent($zdb, $k, $mdeps);
198 $_recipients[] = $m;
199 }
200 $this->setRecipients($_recipients);
201 $this->subject = $rs->mailing_subject;
202 $this->message = $rs->mailing_body;
203 if ($rs->mailing_sender_name !== null || $rs->mailing_sender_address !== null) {
204 $this->setSender(
205 $rs->mailing_sender_name,
206 $rs->mailing_sender_address
207 );
208 }
209 //if mailing has already been sent, generate a new id and copy attachments
210 if ($rs->mailing_sent && $new) {
211 $this->generateNewId();
212 $this->copyAttachments($rs->mailing_id);
213 } else {
214 $this->tmp_path = null;
215 $this->id = $rs->mailing_id;
216 if (!$this->attachments) {
217 $this->loadAttachments();
218 }
219 $this->history_id = $rs->mailing_id;
220 }
221 }
222
223 /**
224 * Copy attachments from another mailing
225 *
226 * @param int $id Original mailing id
227 *
228 * @return void
229 */
230 private function copyAttachments($id)
231 {
232 $source_dir = GALETTE_ATTACHMENTS_PATH . $id . '/';
233 $dest_dir = GALETTE_ATTACHMENTS_PATH . $this->id . '/';
234
235 if (file_exists($source_dir)) {
236 if (file_exists($dest_dir)) {
237 throw new \RuntimeException(
238 str_replace(
239 '%s',
240 $this->id,
241 'Attachments directory already exists for mailing %s!'
242 )
243 );
244 } else {
245 //create directory
246 mkdir($dest_dir);
247 //copy attachments from source mailing and populate attachments
248 $this->attachments = array();
249 $files = glob($source_dir . '*.*');
250 foreach ($files as $file) {
251 $f = new File($source_dir);
252 $f->setFileName(str_replace($source_dir, '', $file));
253 $f->copyTo($dest_dir);
254 $this->attachments[] = $f;
255 }
256 }
257 } else {
258 Analog::log(
259 'No attachments in source directory',
260 Analog::DEBUG
261 );
262 }
263 }
264
265 /**
266 * Apply final header to email and send it :-)
267 *
268 * @return GaletteMail::MAIL_ERROR|GaletteMail::MAIL_SENT
269 */
270 public function send()
271 {
272 $m = array();
273 foreach ($this->mrecipients as $member) {
274 $email = $member->getEmail();
275 $m[$email] = $member->sname;
276 }
277 parent::setRecipients($m);
278 return parent::send();
279 }
280
281 /**
282 * Set mailing recipients
283 *
284 * @param array $members Array of Adherent objects
285 *
286 * @return void
287 */
288 public function setRecipients($members)
289 {
290 $m = array();
291 $this->mrecipients = array();
292 $this->unreachables = array();
293
294 foreach ($members as $member) {
295 $email = $member->getEmail();
296
297 if (trim($email) != '' && self::isValidEmail($email)) {
298 if (!in_array($member, $this->mrecipients)) {
299 $this->mrecipients[] = $member;
300 }
301 $m[$email] = $member->sname;
302 } else {
303 if (!in_array($member, $this->unreachables)) {
304 $this->unreachables[] = $member;
305 }
306 }
307 }
308 parent::setRecipients($m);
309 }
310
311 /**
312 * Store maling attachments
313 *
314 * @param array $files Array of uploaded files to store
315 *
316 * @return true|int error code
317 */
318 public function store($files)
319 {
320 if ($this->tmp_path === null) {
321 $this->generateTmpPath();
322 }
323
324 if (!file_exists($this->tmp_path)) {
325 //directory does not exists, create it
326 mkdir($this->tmp_path);
327 }
328
329 if (!is_dir($this->tmp_path)) {
330 throw new \RuntimeException(
331 $this->tmp_path . ' should be a directory!'
332 );
333 }
334
335 //store files
336 $attachment = new File($this->tmp_path);
337 $res = $attachment->store($files);
338 if ($res < 0) {
339 return $res;
340 } else {
341 $this->attachments[] = $attachment;
342 }
343
344 return true;
345 }
346
347 /**
348 * Move attachments with final id once mailing has been stored
349 *
350 * @param int $id Mailing history id
351 *
352 * @return boolean
353 */
354 public function moveAttachments($id)
355 {
356 if (
357 isset($this->tmp_path)
358 && trim($this->tmp_path) !== ''
359 && count($this->attachments) > 0
360 ) {
361 foreach ($this->attachments as &$attachment) {
362 $old_path = $attachment->getDestDir() . $attachment->getFileName();
363 $new_path = GALETTE_ATTACHMENTS_PATH . $id . '/' .
364 $attachment->getFileName();
365 if (!file_exists(GALETTE_ATTACHMENTS_PATH . $id)) {
366 mkdir(GALETTE_ATTACHMENTS_PATH . $id);
367 }
368 $moved = rename($old_path, $new_path);
369 if ($moved) {
370 $attachment->setDestDir(GALETTE_ATTACHMENTS_PATH);
371 }
372 }
373 rmdir($this->tmp_path);
374 $this->tmp_path = null;
375 }
376 }
377
378 /**
379 * Remove specified attachment
380 *
381 * @param string $name Filename
382 *
383 * @return void
384 */
385 public function removeAttachment($name)
386 {
387 $to_remove = null;
388 if (
389 isset($this->tmp_path)
390 && trim($this->tmp_path) !== ''
391 && file_exists($this->tmp_path)
392 ) {
393 $to_remove = $this->tmp_path;
394 } elseif (file_exists(GALETTE_ATTACHMENTS_PATH . $this->id)) {
395 $to_remove = GALETTE_ATTACHMENTS_PATH . $this->id;
396 }
397
398 if ($to_remove !== null) {
399 $to_remove .= '/' . $name;
400
401 if (!$this->attachments) {
402 $this->loadAttachments();
403 }
404
405 if (file_exists($to_remove)) {
406 $i = 0;
407 foreach ($this->attachments as $att) {
408 if ($att->getFileName() == $name) {
409 unset($this->attachments[$i]);
410 unlink($to_remove);
411 break;
412 }
413 $i++;
414 }
415 } else {
416 Analog::log(
417 str_replace(
418 '%file',
419 $name,
420 'File %file does not exists and cannot be removed!'
421 ),
422 Analog::WARNING
423 );
424 }
425 } else {
426 throw new \RuntimeException(
427 'Unable to get attachments path!'
428 );
429 }
430 }
431
432 /**
433 * Remove mailing attachments
434 *
435 * @param boolean $temp Remove only tmporary attachments,
436 * to avoid history breaking
437 *
438 * @return void
439 */
440 public function removeAttachments($temp = false)
441 {
442 $to_remove = null;
443 if (
444 isset($this->tmp_path)
445 && trim($this->tmp_path) !== ''
446 && file_exists($this->tmp_path)
447 ) {
448 $to_remove = $this->tmp_path;
449 } elseif (file_exists(GALETTE_ATTACHMENTS_PATH . $this->id)) {
450 if ($temp === true) {
451 return false;
452 }
453 $to_remove = GALETTE_ATTACHMENTS_PATH . $this->id;
454 }
455
456 if ($to_remove !== null) {
457 $rdi = new \RecursiveDirectoryIterator(
458 $to_remove,
459 \FilesystemIterator::SKIP_DOTS
460 );
461 $contents = new \RecursiveIteratorIterator(
462 $rdi,
463 \RecursiveIteratorIterator::CHILD_FIRST
464 );
465 foreach ($contents as $path) {
466 if ($path->isFile()) {
467 unlink($path->getPathname());
468 } else {
469 rmdir($path->getPathname());
470 }
471 }
472 rmdir($to_remove);
473 }
474 }
475
476 /**
477 * Return textual error message
478 *
479 * @param int $code The error code
480 *
481 * @return string Localized message
482 */
483 public function getAttachmentErrorMessage($code)
484 {
485 $f = new File($this->tmp_path);
486 return $f->getErrorMessage($code);
487 }
488
489 /**
490 * Does mailing already exists in history?
491 *
492 * @return boolean
493 */
494 public function existsInHistory()
495 {
496 return isset($this->history_id);
497 }
498
499 /**
500 * Global getter method
501 *
502 * @param string $name name of the property we want to retrive
503 *
504 * @return false|object the called property
505 */
506 public function __get($name)
507 {
508 $forbidden = array('ordered');
509 if (!in_array($name, $forbidden)) {
510 switch ($name) {
511 case 'alt_message':
512 return $this->cleanedHtml();
513 break;
514 case 'step':
515 return $this->current_step;
516 break;
517 case 'subject':
518 return $this->getSubject();
519 break;
520 case 'message':
521 return $this->getMessage();
522 break;
523 case 'wrapped_message':
524 return $this->getWrappedMessage();
525 break;
526 case 'html':
527 return $this->isHTML();
528 break;
529 case 'mail':
530 case '_mail':
531 return $this->getPhpMailer();
532 break;
533 case 'errors':
534 return $this->getErrors();
535 break;
536 case 'recipients':
537 return $this->mrecipients;
538 break;
539 case 'tmp_path':
540 if (isset($this->tmp_path) && trim($this->tmp_path) !== '') {
541 return $this->tmp_path;
542 } else {
543 //no attachments
544 return false;
545 }
546 break;
547 case 'attachments':
548 return $this->attachments;
549 break;
550 case 'sender_name':
551 return $this->getSenderName();
552 break;
553 case 'sender_address':
554 return $this->getSenderAddress();
555 break;
556 default:
557 Analog::log(
558 '[' . get_class($this) . 'Trying to get ' . $name,
559 Analog::DEBUG
560 );
561 return $this->$name;
562 break;
563 }
564 } else {
565 Analog::log(
566 '[' . get_class($this) . 'Unable to get ' . $name,
567 Analog::ERROR
568 );
569 return false;
570 }
571 }
572
573 /**
574 * Global setter method
575 *
576 * @param string $name name of the property we want to assign a value to
577 * @param object $value a relevant value for the property
578 *
579 * @return void
580 */
581 public function __set($name, $value)
582 {
583 switch ($name) {
584 case 'subject':
585 $this->setSubject($value);
586 break;
587 case 'message':
588 $this->setMessage($value);
589 break;
590 case 'html':
591 if (is_bool($value)) {
592 $this->isHTML($value);
593 } else {
594 Analog::log(
595 '[' . get_class($this) . '] Value for field `' . $name .
596 '` should be boolean - (' . gettype($value) . ')' .
597 $value . ' given',
598 Analog::WARNING
599 );
600 }
601 break;
602 case 'current_step':
603 if (
604 is_int($value)
605 && ($value == self::STEP_START
606 || $value == self::STEP_PREVIEW
607 || $value == self::STEP_SEND
608 || $value == self::STEP_SENT)
609 ) {
610 $this->current_step = (int)$value;
611 } else {
612 Analog::log(
613 '[' . get_class($this) . '] Value for field `' . $name .
614 '` should be integer and know - (' . gettype($value) . ')' .
615 $value . ' given',
616 Analog::WARNING
617 );
618 }
619 break;
620 case 'id':
621 $this->id = $value;
622 break;
623 default:
624 Analog::log(
625 '[' . get_class($this) . '] Unable to set proprety `' . $name . '`',
626 Analog::WARNING
627 );
628 return false;
629 break;
630 }
631 }
632 }