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