3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
10 * Copyright © 2009-2023 The Galette Team
12 * This file is part of Galette (http://galette.tuxfamily.org).
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.
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.
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/>.
30 * @author Johan Cwiklinski <johan@x-tnd.be>
31 * @copyright 2009-2023 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
37 namespace Galette\Core
;
41 use Galette\Entity\Adherent
;
43 use Laminas\Db\ResultSet\ResultSet
;
51 * @author Johan Cwiklinski <johan@x-tnd.be>
52 * @copyright 2009-2023 The Galette Team
53 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
54 * @link http://galette.tuxfamily.org
55 * @since Available since 0.7dev - 2009-03-07
57 * @property string $subject
58 * @property string $message
59 * @property boolean $html
60 * @property integer $current_step
61 * @property-read integer $step
62 * @property integer|string $id
63 * @property-read string $alt_message
64 * @property-read string $wrapped_message
65 * @property-read PHPMailer\PHPMailer\PHPMailer $mail
66 * @property-read PHPMailer\PHPMailer\PHPMailer $_mail
67 * @property-read array $errors
68 * @property-read array $recipients
69 * @property-read string|false $tmp_path
70 * @property array $attachments
71 * @property-read string $sender_name
72 * @property-read string $sender_address
73 * @property integer $history_id
75 class Mailing
extends GaletteMail
77 public const STEP_START
= 0;
78 public const STEP_PREVIEW
= 1;
79 public const STEP_SEND
= 2;
80 public const STEP_SENT
= 3;
82 public const MIME_HTML
= 'text/html';
83 public const MIME_TEXT
= 'text/plain';
84 public const MIME_DEFAULT
= self
::MIME_TEXT
;
88 private $unreachables = array();
89 private $mrecipients = array();
90 private $current_step;
100 * @param Preferences $preferences Preferences instance
101 * @param array $members An array of members
102 * @param int $id Identifier, defaults to null
104 public function __construct(Preferences
$preferences, array $members = [], int $id = null)
106 parent
::__construct($preferences);
107 $this->id
= $id ??
$this->generateNewId();
109 $this->current_step
= self
::STEP_START
;
110 $this->mime_type
= self
::MIME_DEFAULT
;
111 /** TODO: add a preference that propose default mime-type to use,
113 if (count($members)) {
114 //Check which members have a valid email address and which have not
115 $this->setRecipients($members);
117 $this->loadAttachments();
121 * Generate new mailing id and temporary path
125 private function generateNewId(): string
128 $chars = 'abcdefghjkmnpqrstuvwxyz0123456789';
131 while ($i <= $size - 1) {
132 $num = mt_rand(0, strlen($chars) - 1) %
strlen($chars);
133 $id .= substr($chars, $num, 1);
138 $this->generateTmpPath($this->id
);
143 * Generate temporary path
145 * @param string $id Random id, defautls to null
149 private function generateTmpPath($id = null)
152 $id = $this->generateNewId();
154 $this->tmp_path
= GALETTE_ATTACHMENTS_PATH
. '/' . $id;
158 * Load mailing attachments
162 private function loadAttachments()
166 isset($this->tmp_path
)
167 && trim($this->tmp_path
) !== ''
169 $dir = $this->tmp_path
;
171 $dir = GALETTE_ATTACHMENTS_PATH
. $this->id
. '/';
174 $files = glob($dir . '*.*');
175 foreach ($files as $file) {
177 $f->setFileName(str_replace($dir, '', $file));
178 $this->attachments
[] = $f;
183 * Loads a mailing from history
185 * @param ArrayObject $rs Mailing entry
186 * @param boolean $new True if we create a 'new' mailing,
187 * false otherwise (from preview for example)
191 public function loadFromHistory(ArrayObject
$rs, $new = true)
196 $orig_recipients = unserialize($rs->mailing_recipients
);
197 } catch (\Throwable
$e) {
199 'Unable to unserialize recipients for mailing ' . $rs->mailing_id
,
202 $orig_recipients = [];
205 $_recipients = array();
206 $mdeps = ['parent' => true];
207 foreach ($orig_recipients as $k => $v) {
208 $m = new Adherent($zdb, $k, $mdeps);
211 $this->setRecipients($_recipients);
212 $this->subject
= $rs->mailing_subject
;
213 $this->message
= $rs->mailing_body
;
214 $this->html
= $this->message
!= strip_tags($this->message
) ?
true : false;
215 if ($rs->mailing_sender_name
!== null ||
$rs->mailing_sender_address
!== null) {
217 $rs->mailing_sender_name
,
218 $rs->mailing_sender_address
221 //if mailing has already been sent, generate a new id and copy attachments
222 if ($rs->mailing_sent
&& $new) {
223 $this->generateNewId();
224 $this->copyAttachments($rs->mailing_id
);
226 $this->tmp_path
= null;
227 $this->id
= $rs->mailing_id
;
228 if (!$this->attachments
) {
229 $this->loadAttachments();
231 $this->history_id
= $rs->mailing_id
;
237 * Copy attachments from another mailing
239 * @param int $id Original mailing id
243 private function copyAttachments($id)
245 $source_dir = GALETTE_ATTACHMENTS_PATH
. $id . '/';
246 $dest_dir = GALETTE_ATTACHMENTS_PATH
. $this->id
. '/';
248 if (file_exists($source_dir)) {
249 if (file_exists($dest_dir)) {
250 throw new \
RuntimeException(
254 'Attachments directory already exists for mailing %s!'
260 //copy attachments from source mailing and populate attachments
261 $this->attachments
= array();
262 $files = glob($source_dir . '*.*');
263 foreach ($files as $file) {
264 $f = new File($source_dir);
265 $f->setFileName(str_replace($source_dir, '', $file));
266 $f->copyTo($dest_dir);
267 $this->attachments
[] = $f;
272 'No attachments in source directory',
279 * Apply final header to email and send it :-)
283 public function send()
286 foreach ($this->mrecipients
as $member) {
287 $email = $member->getEmail();
288 $m[$email] = $member->sname
;
290 parent
::setRecipients($m);
291 return parent
::send();
295 * Set mailing recipients
297 * @param array $members Array of Adherent objects
301 public function setRecipients($members)
304 $this->mrecipients
= array();
305 $this->unreachables
= array();
307 foreach ($members as $member) {
308 $email = $member->getEmail();
310 if (trim($email) != '' && self
::isValidEmail($email)) {
311 if (!in_array($member, $this->mrecipients
)) {
312 $this->mrecipients
[] = $member;
314 $m[$email] = $member->sname
;
316 if (!in_array($member, $this->unreachables
)) {
317 $this->unreachables
[] = $member;
321 return parent
::setRecipients($m);
325 * Store maling attachments
327 * @param array $files Array of uploaded files to store
329 * @return true|int error code
331 public function store($files)
333 if ($this->tmp_path
=== null) {
334 $this->generateTmpPath();
337 if (!file_exists($this->tmp_path
)) {
338 //directory does not exist, create it
339 mkdir($this->tmp_path
);
342 if (!is_dir($this->tmp_path
)) {
343 throw new \
RuntimeException(
344 $this->tmp_path
. ' should be a directory!'
349 $attachment = new File($this->tmp_path
);
350 $res = $attachment->store($files);
354 $this->attachments
[] = $attachment;
361 * Move attachments with final id once mailing has been stored
363 * @param int $id Mailing history id
367 public function moveAttachments($id)
370 isset($this->tmp_path
)
371 && trim($this->tmp_path
) !== ''
372 && count($this->attachments
) > 0
374 foreach ($this->attachments
as &$attachment) {
375 $old_path = $attachment->getDestDir() . $attachment->getFileName();
376 $new_path = GALETTE_ATTACHMENTS_PATH
. $id . '/' .
377 $attachment->getFileName();
378 if (!file_exists(GALETTE_ATTACHMENTS_PATH
. $id)) {
379 mkdir(GALETTE_ATTACHMENTS_PATH
. $id);
381 $moved = rename($old_path, $new_path);
383 $attachment->setDestDir(GALETTE_ATTACHMENTS_PATH
);
386 rmdir($this->tmp_path
);
387 $this->tmp_path
= null;
392 * Remove specified attachment
394 * @param string $name Filename
398 public function removeAttachment($name)
402 isset($this->tmp_path
)
403 && trim($this->tmp_path
) !== ''
404 && file_exists($this->tmp_path
)
406 $to_remove = $this->tmp_path
;
407 } elseif (file_exists(GALETTE_ATTACHMENTS_PATH
. $this->id
)) {
408 $to_remove = GALETTE_ATTACHMENTS_PATH
. $this->id
;
411 if ($to_remove !== null) {
412 $to_remove .= '/' . $name;
414 if (!$this->attachments
) {
415 $this->loadAttachments();
418 if (file_exists($to_remove)) {
420 foreach ($this->attachments
as $att) {
421 if ($att->getFileName() == $name) {
422 unset($this->attachments
[$i]);
433 'File %file does not exists and cannot be removed!'
439 throw new \
RuntimeException(
440 'Unable to get attachments path!'
446 * Remove mailing attachments
448 * @param boolean $temp Remove only tmporary attachments,
449 * to avoid history breaking
453 public function removeAttachments($temp = false)
457 isset($this->tmp_path
)
458 && trim($this->tmp_path
) !== ''
459 && file_exists($this->tmp_path
)
461 $to_remove = $this->tmp_path
;
462 } elseif (file_exists(GALETTE_ATTACHMENTS_PATH
. $this->id
)) {
463 if ($temp === true) {
466 $to_remove = GALETTE_ATTACHMENTS_PATH
. $this->id
;
469 if ($to_remove !== null) {
470 $rdi = new \
RecursiveDirectoryIterator(
472 \FilesystemIterator
::SKIP_DOTS
474 $contents = new \
RecursiveIteratorIterator(
476 \RecursiveIteratorIterator
::CHILD_FIRST
478 foreach ($contents as $path) {
479 if ($path->isFile()) {
480 unlink($path->getPathname());
482 rmdir($path->getPathname());
490 * Return textual error message
492 * @param int $code The error code
494 * @return string Localized message
496 public function getAttachmentErrorMessage($code)
498 $f = new File($this->tmp_path
);
499 return $f->getErrorMessage($code);
503 * Does mailing already exists in history?
507 public function existsInHistory()
509 return isset($this->history_id
);
513 * Global getter method
515 * @param string $name name of the property we want to retrieve
517 * @return mixed the called property
519 public function __get($name)
521 $forbidden = array('ordered');
522 if (!in_array($name, $forbidden)) {
525 return $this->cleanedHtml();
527 return $this->current_step
;
529 return $this->getSubject();
531 return $this->getMessage();
532 case 'wrapped_message':
533 return $this->getWrappedMessage();
535 return $this->isHTML();
538 return $this->getPhpMailer();
540 return $this->getErrors();
542 return $this->mrecipients
;
544 if (isset($this->tmp_path
) && trim($this->tmp_path
) !== '') {
545 return $this->tmp_path
;
551 return $this->attachments
;
553 return $this->getSenderName();
554 case 'sender_address':
555 return $this->getSenderAddress();
560 '[' . get_class($this) . 'Trying to get ' . $name,
567 '[' . get_class($this) . 'Unable to get ' . $name,
575 * Global isset method
576 * Required for twig to access properties via __get
578 * @param string $name name of the property we want to retrieve
582 public function __isset($name)
584 $forbidden = array('ordered');
585 if (!in_array($name, $forbidden)) {
591 case 'wrapped_message':
600 case 'sender_address':
603 return isset($this->$name);
610 * Global setter method
612 * @param string $name name of the property we want to assign a value to
613 * @param mixed $value a relevant value for the property
617 public function __set($name, $value)
621 $this->setSubject($value);
624 $this->setMessage($value);
627 if (is_bool($value)) {
628 $this->isHTML($value);
631 '[' . get_class($this) . '] Value for field `' . $name .
632 '` should be boolean - (' . gettype($value) . ')' .
641 && ($value == self
::STEP_START
642 ||
$value == self
::STEP_PREVIEW
643 ||
$value == self
::STEP_SEND
644 ||
$value == self
::STEP_SENT
)
646 $this->current_step
= (int)$value;
649 '[' . get_class($this) . '] Value for field `' . $name .
650 '` should be integer and know - (' . gettype($value) . ')' .
661 '[' . get_class($this) . '] Unable to set property `' . $name . '`',