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