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