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