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