3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
10 * Copyright © 2009-2020 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-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
37 namespace Galette\Core
;
40 use Galette\Entity\Adherent
;
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
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 $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
72 class Mailing
extends GaletteMail
74 public const STEP_START
= 0;
75 public const STEP_PREVIEW
= 1;
76 public const STEP_SEND
= 2;
77 public const STEP_SENT
= 3;
79 public const MIME_HTML
= 'text/html';
80 public const MIME_TEXT
= 'text/plain';
81 public const MIME_DEFAULT
= self
::MIME_TEXT
;
85 private $unreachables = array();
86 private $mrecipients = array();
87 private $current_step;
97 * @param Preferences $preferences Preferences instance
98 * @param array $members An array of members
99 * @param int $id Identifier, defaults to null
101 public function __construct(Preferences
$preferences, array $members = [], int $id = null)
103 parent
::__construct($preferences);
104 $this->id
= $id ??
$this->generateNewId();
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,
110 if (count($members)) {
111 //Check which members have a valid email address and which have not
112 $this->setRecipients($members);
114 $this->loadAttachments();
118 * Generate new mailing id and temporary path
122 private function generateNewId(): string
125 $chars = 'abcdefghjkmnpqrstuvwxyz0123456789';
128 while ($i <= $size - 1) {
129 $num = mt_rand(0, strlen($chars) - 1) %
strlen($chars);
130 $id .= substr($chars, $num, 1);
135 $this->generateTmpPath($this->id
);
140 * Generate temporary path
142 * @param string $id Random id, defautls to null
146 private function generateTmpPath($id = null)
149 $id = $this->generateNewId();
151 $this->tmp_path
= GALETTE_ATTACHMENTS_PATH
. '/' . $id;
155 * Load mailing attachments
159 private function loadAttachments()
163 isset($this->tmp_path
)
164 && trim($this->tmp_path
) !== ''
166 $dir = $this->tmp_path
;
168 $dir = GALETTE_ATTACHMENTS_PATH
. $this->id
. '/';
171 $files = glob($dir . '*.*');
172 foreach ($files as $file) {
174 $f->setFileName(str_replace($dir, '', $file));
175 $this->attachments
[] = $f;
180 * Loads a mailing from history
182 * @param ResultSet $rs Mailing entry
183 * @param boolean $new True if we create a 'new' mailing,
184 * false otherwise (from preview for example)
188 public function loadFromHistory($rs, $new = true)
192 $orig_recipients = unserialize($rs->mailing_recipients
);
194 $_recipients = array();
195 $mdeps = ['parent' => true];
196 foreach ($orig_recipients as $k => $v) {
197 $m = new Adherent($zdb, $k, $mdeps);
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) {
205 $rs->mailing_sender_name
,
206 $rs->mailing_sender_address
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
);
214 $this->tmp_path
= null;
215 $this->id
= $rs->mailing_id
;
216 if (!$this->attachments
) {
217 $this->loadAttachments();
219 $this->history_id
= $rs->mailing_id
;
224 * Copy attachments from another mailing
226 * @param int $id Original mailing id
230 private function copyAttachments($id)
232 $source_dir = GALETTE_ATTACHMENTS_PATH
. $id . '/';
233 $dest_dir = GALETTE_ATTACHMENTS_PATH
. $this->id
. '/';
235 if (file_exists($source_dir)) {
236 if (file_exists($dest_dir)) {
237 throw new \
RuntimeException(
241 'Attachments directory already exists for mailing %s!'
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;
259 'No attachments in source directory',
266 * Apply final header to email and send it :-)
268 * @return GaletteMail::MAIL_ERROR|GaletteMail::MAIL_SENT
270 public function send()
273 foreach ($this->mrecipients
as $member) {
274 $email = $member->getEmail();
275 $m[$email] = $member->sname
;
277 parent
::setRecipients($m);
278 return parent
::send();
282 * Set mailing recipients
284 * @param array $members Array of Adherent objects
288 public function setRecipients($members)
291 $this->mrecipients
= array();
292 $this->unreachables
= array();
294 foreach ($members as $member) {
295 $email = $member->getEmail();
297 if (trim($email) != '' && self
::isValidEmail($email)) {
298 if (!in_array($member, $this->mrecipients
)) {
299 $this->mrecipients
[] = $member;
301 $m[$email] = $member->sname
;
303 if (!in_array($member, $this->unreachables
)) {
304 $this->unreachables
[] = $member;
308 parent
::setRecipients($m);
312 * Store maling attachments
314 * @param array $files Array of uploaded files to store
316 * @return true|int error code
318 public function store($files)
320 if ($this->tmp_path
=== null) {
321 $this->generateTmpPath();
324 if (!file_exists($this->tmp_path
)) {
325 //directory does not exists, create it
326 mkdir($this->tmp_path
);
329 if (!is_dir($this->tmp_path
)) {
330 throw new \
RuntimeException(
331 $this->tmp_path
. ' should be a directory!'
336 $attachment = new File($this->tmp_path
);
337 $res = $attachment->store($files);
341 $this->attachments
[] = $attachment;
348 * Move attachments with final id once mailing has been stored
350 * @param int $id Mailing history id
354 public function moveAttachments($id)
357 isset($this->tmp_path
)
358 && trim($this->tmp_path
) !== ''
359 && count($this->attachments
) > 0
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);
368 $moved = rename($old_path, $new_path);
370 $attachment->setDestDir(GALETTE_ATTACHMENTS_PATH
);
373 rmdir($this->tmp_path
);
374 $this->tmp_path
= null;
379 * Remove specified attachment
381 * @param string $name Filename
385 public function removeAttachment($name)
389 isset($this->tmp_path
)
390 && trim($this->tmp_path
) !== ''
391 && file_exists($this->tmp_path
)
393 $to_remove = $this->tmp_path
;
394 } elseif (file_exists(GALETTE_ATTACHMENTS_PATH
. $this->id
)) {
395 $to_remove = GALETTE_ATTACHMENTS_PATH
. $this->id
;
398 if ($to_remove !== null) {
399 $to_remove .= '/' . $name;
401 if (!$this->attachments
) {
402 $this->loadAttachments();
405 if (file_exists($to_remove)) {
407 foreach ($this->attachments
as $att) {
408 if ($att->getFileName() == $name) {
409 unset($this->attachments
[$i]);
420 'File %file does not exists and cannot be removed!'
426 throw new \
RuntimeException(
427 'Unable to get attachments path!'
433 * Remove mailing attachments
435 * @param boolean $temp Remove only tmporary attachments,
436 * to avoid history breaking
440 public function removeAttachments($temp = false)
444 isset($this->tmp_path
)
445 && trim($this->tmp_path
) !== ''
446 && file_exists($this->tmp_path
)
448 $to_remove = $this->tmp_path
;
449 } elseif (file_exists(GALETTE_ATTACHMENTS_PATH
. $this->id
)) {
450 if ($temp === true) {
453 $to_remove = GALETTE_ATTACHMENTS_PATH
. $this->id
;
456 if ($to_remove !== null) {
457 $rdi = new \
RecursiveDirectoryIterator(
459 \FilesystemIterator
::SKIP_DOTS
461 $contents = new \
RecursiveIteratorIterator(
463 \RecursiveIteratorIterator
::CHILD_FIRST
465 foreach ($contents as $path) {
466 if ($path->isFile()) {
467 unlink($path->getPathname());
469 rmdir($path->getPathname());
477 * Return textual error message
479 * @param int $code The error code
481 * @return string Localized message
483 public function getAttachmentErrorMessage($code)
485 $f = new File($this->tmp_path
);
486 return $f->getErrorMessage($code);
490 * Does mailing already exists in history?
494 public function existsInHistory()
496 return isset($this->history_id
);
500 * Global getter method
502 * @param string $name name of the property we want to retrive
504 * @return false|object the called property
506 public function __get($name)
508 $forbidden = array('ordered');
509 if (!in_array($name, $forbidden)) {
512 return $this->cleanedHtml();
515 return $this->current_step
;
518 return $this->getSubject();
521 return $this->getMessage();
523 case 'wrapped_message':
524 return $this->getWrappedMessage();
527 return $this->isHTML();
531 return $this->getPhpMailer();
534 return $this->getErrors();
537 return $this->mrecipients
;
540 if (isset($this->tmp_path
) && trim($this->tmp_path
) !== '') {
541 return $this->tmp_path
;
548 return $this->attachments
;
551 return $this->getSenderName();
553 case 'sender_address':
554 return $this->getSenderAddress();
558 '[' . get_class($this) . 'Trying to get ' . $name,
566 '[' . get_class($this) . 'Unable to get ' . $name,
574 * Global setter method
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
581 public function __set($name, $value)
585 $this->setSubject($value);
588 $this->setMessage($value);
591 if (is_bool($value)) {
592 $this->isHTML($value);
595 '[' . get_class($this) . '] Value for field `' . $name .
596 '` should be boolean - (' . gettype($value) . ')' .
605 && ($value == self
::STEP_START
606 ||
$value == self
::STEP_PREVIEW
607 ||
$value == self
::STEP_SEND
608 ||
$value == self
::STEP_SENT
)
610 $this->current_step
= (int)$value;
613 '[' . get_class($this) . '] Value for field `' . $name .
614 '` should be integer and know - (' . gettype($value) . ')' .
625 '[' . get_class($this) . '] Unable to set proprety `' . $name . '`',