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