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