*
* PHP version 5
*
- * Copyright © 2009-2012 The Galette Team
+ * Copyright © 2009-2023 The Galette Team
*
* This file is part of Galette (http://galette.tuxfamily.org).
*
* @package Galette
*
* @author Johan Cwiklinski <johan@x-tnd.be>
- * @copyright 2009-2012 The Galette Team
+ * @copyright 2009-2023 The Galette Team
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
- * @version SVN: $Id$
* @link http://galette.tuxfamily.org
* @since Available since 0.7dev - 2009-03-07
*/
namespace Galette\Core;
-use Galette\Common\KLogger as KLogger;
+use Analog\Analog;
+use ArrayObject;
+use Galette\Entity\Adherent;
+use Galette\IO\File;
+use Laminas\Db\ResultSet\ResultSet;
/**
* Mailing features
* @name Mailing
* @package Galette
* @author Johan Cwiklinski <johan@x-tnd.be>
- * @copyright 2009-2012 The Galette Team
+ * @copyright 2009-2023 The Galette Team
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
* @link http://galette.tuxfamily.org
* @since Available since 0.7dev - 2009-03-07
+ *
+ * @property string $subject
+ * @property string $message
+ * @property boolean $html
+ * @property integer $current_step
+ * @property-read integer $step
+ * @property integer|string $id
+ * @property-read string $alt_message
+ * @property-read string $wrapped_message
+ * @property-read PHPMailer\PHPMailer\PHPMailer $mail
+ * @property-read PHPMailer\PHPMailer\PHPMailer $_mail
+ * @property-read array $errors
+ * @property-read array $recipients
+ * @property-read string|false $tmp_path
+ * @property array $attachments
+ * @property-read string $sender_name
+ * @property-read string $sender_address
+ * @property integer $history_id
*/
class Mailing extends GaletteMail
{
- const STEP_START = 0;
- const STEP_PREVIEW = 1;
- const STEP_SEND = 2;
- const STEP_SENT = 3;
+ public const STEP_START = 0;
+ public const STEP_PREVIEW = 1;
+ public const STEP_SEND = 2;
+ public const STEP_SENT = 3;
+
+ public const MIME_HTML = 'text/html';
+ public const MIME_TEXT = 'text/plain';
+ public const MIME_DEFAULT = self::MIME_TEXT;
- const MIME_HTML = 'text/html';
- const MIME_TEXT = 'text/plain';
- const MIME_DEFAULT = self::MIME_TEXT;
+ private $id;
- private $_unreachables;
- private $_mrecipients;
- private $_current_step;
+ private $unreachables = array();
+ private $mrecipients = array();
+ private $current_step;
- private $_mime_type;
+ private $mime_type;
+
+ private $tmp_path;
+ private $history_id;
/**
- * Default constructor
- *
- * @param array $members An array of members
- */
- public function __construct($members)
+ * Default constructor
+ *
+ * @param Preferences $preferences Preferences instance
+ * @param array $members An array of members
+ * @param int $id Identifier, defaults to null
+ */
+ public function __construct(Preferences $preferences, array $members = [], int $id = null)
{
- $this->_current_step = self::STEP_START;
- $this->_mime_type = self::MIME_DEFAULT;
+ parent::__construct($preferences);
+ $this->id = $id ?? $this->generateNewId();
+
+ $this->current_step = self::STEP_START;
+ $this->mime_type = self::MIME_DEFAULT;
/** TODO: add a preference that propose default mime-type to use,
then init it here */
- if ( $members !== null) {
- //Check which members have a valid email adress and which have not
+ if (count($members)) {
+ //Check which members have a valid email address and which have not
$this->setRecipients($members);
}
+ $this->loadAttachments();
+ }
+
+ /**
+ * Generate new mailing id and temporary path
+ *
+ * @return string
+ */
+ private function generateNewId(): string
+ {
+ $id = '';
+ $chars = 'abcdefghjkmnpqrstuvwxyz0123456789';
+ $i = 0;
+ $size = 30;
+ while ($i <= $size - 1) {
+ $num = mt_rand(0, strlen($chars) - 1) % strlen($chars);
+ $id .= substr($chars, $num, 1);
+ $i++;
+ }
+
+ $this->id = $id;
+ $this->generateTmpPath($this->id);
+ return $this->id;
+ }
+
+ /**
+ * Generate temporary path
+ *
+ * @param string $id Random id, defautls to null
+ *
+ * @return void
+ */
+ private function generateTmpPath($id = null)
+ {
+ if ($id === null) {
+ $id = $this->generateNewId();
+ }
+ $this->tmp_path = GALETTE_ATTACHMENTS_PATH . '/' . $id;
+ }
+
+ /**
+ * Load mailing attachments
+ *
+ * @return void
+ */
+ private function loadAttachments()
+ {
+ $dir = '';
+ if (
+ isset($this->tmp_path)
+ && trim($this->tmp_path) !== ''
+ ) {
+ $dir = $this->tmp_path;
+ } else {
+ $dir = GALETTE_ATTACHMENTS_PATH . $this->id . '/';
+ }
+
+ $files = glob($dir . '*.*');
+ foreach ($files as $file) {
+ $f = new File($dir);
+ $f->setFileName(str_replace($dir, '', $file));
+ $this->attachments[] = $f;
+ }
}
/**
- * Apply final header to mail and send it :-)
- *
- * @return GaletteMail::MAIL_ERROR|GaletteMail::MAIL_SENT
- */
+ * Loads a mailing from history
+ *
+ * @param ArrayObject $rs Mailing entry
+ * @param boolean $new True if we create a 'new' mailing,
+ * false otherwise (from preview for example)
+ *
+ * @return boolean
+ */
+ public function loadFromHistory(ArrayObject $rs, $new = true)
+ {
+ global $zdb;
+
+ try {
+ $orig_recipients = unserialize($rs->mailing_recipients);
+ } catch (\Throwable $e) {
+ Analog::log(
+ 'Unable to unserialize recipients for mailing ' . $rs->mailing_id,
+ Analog::ERROR
+ );
+ $orig_recipients = [];
+ }
+
+ $_recipients = array();
+ $mdeps = ['parent' => true];
+ foreach ($orig_recipients as $k => $v) {
+ $m = new Adherent($zdb, $k, $mdeps);
+ $_recipients[] = $m;
+ }
+ $this->setRecipients($_recipients);
+ $this->subject = $rs->mailing_subject;
+ $this->message = $rs->mailing_body;
+ $this->html = $this->message != strip_tags($this->message) ? true : false;
+ if ($rs->mailing_sender_name !== null || $rs->mailing_sender_address !== null) {
+ $this->setSender(
+ $rs->mailing_sender_name,
+ $rs->mailing_sender_address
+ );
+ }
+ //if mailing has already been sent, generate a new id and copy attachments
+ if ($rs->mailing_sent && $new) {
+ $this->generateNewId();
+ $this->copyAttachments($rs->mailing_id);
+ } else {
+ $this->tmp_path = null;
+ $this->id = $rs->mailing_id;
+ if (!$this->attachments) {
+ $this->loadAttachments();
+ }
+ $this->history_id = $rs->mailing_id;
+ }
+ return true;
+ }
+
+ /**
+ * Copy attachments from another mailing
+ *
+ * @param int $id Original mailing id
+ *
+ * @return void
+ */
+ private function copyAttachments($id)
+ {
+ $source_dir = GALETTE_ATTACHMENTS_PATH . $id . '/';
+ $dest_dir = GALETTE_ATTACHMENTS_PATH . $this->id . '/';
+
+ if (file_exists($source_dir)) {
+ if (file_exists($dest_dir)) {
+ throw new \RuntimeException(
+ str_replace(
+ '%s',
+ $this->id,
+ 'Attachments directory already exists for mailing %s!'
+ )
+ );
+ } else {
+ //create directory
+ mkdir($dest_dir);
+ //copy attachments from source mailing and populate attachments
+ $this->attachments = array();
+ $files = glob($source_dir . '*.*');
+ foreach ($files as $file) {
+ $f = new File($source_dir);
+ $f->setFileName(str_replace($source_dir, '', $file));
+ $f->copyTo($dest_dir);
+ $this->attachments[] = $f;
+ }
+ }
+ } else {
+ Analog::log(
+ 'No attachments in source directory',
+ Analog::DEBUG
+ );
+ }
+ }
+
+ /**
+ * Apply final header to email and send it :-)
+ *
+ * @return int
+ */
public function send()
{
$m = array();
- foreach ( $this->_mrecipients as $member ) {
- $m[$member->email] = $member->sname;
+ foreach ($this->mrecipients as $member) {
+ $email = $member->getEmail();
+ $m[$email] = $member->sname;
}
parent::setRecipients($m);
return parent::send();
/**
* Set mailing recipients
*
- * @param <type> $members Array of Adherent objects
+ * @param array $members Array of Adherent objects
*
- * @return void
+ * @return bool
*/
public function setRecipients($members)
{
$m = array();
- $this->_mrecipients = array();
- $this->_unreachables = array();
+ $this->mrecipients = array();
+ $this->unreachables = array();
foreach ($members as $member) {
- if ( trim($member->email) != '' && self::isValidEmail($member->email) ) {
- if ( !in_array($member, $this->_mrecipients) ) {
- $this->_mrecipients[] = $member;
+ $email = $member->getEmail();
+
+ if (trim($email) != '' && self::isValidEmail($email)) {
+ if (!in_array($member, $this->mrecipients)) {
+ $this->mrecipients[] = $member;
}
- $m[$member->email] = $member->sname;
+ $m[$email] = $member->sname;
} else {
- if ( !in_array($member, $this->_unreachables) ) {
- $this->_unreachables[] = $member;
+ if (!in_array($member, $this->unreachables)) {
+ $this->unreachables[] = $member;
}
}
}
- parent::setRecipients($m);
+ return parent::setRecipients($m);
+ }
+
+ /**
+ * Store maling attachments
+ *
+ * @param array $files Array of uploaded files to store
+ *
+ * @return true|int error code
+ */
+ public function store($files)
+ {
+ if ($this->tmp_path === null) {
+ $this->generateTmpPath();
+ }
+
+ if (!file_exists($this->tmp_path)) {
+ //directory does not exist, create it
+ mkdir($this->tmp_path);
+ }
+
+ if (!is_dir($this->tmp_path)) {
+ throw new \RuntimeException(
+ $this->tmp_path . ' should be a directory!'
+ );
+ }
+
+ //store files
+ $attachment = new File($this->tmp_path);
+ $res = $attachment->store($files);
+ if ($res < 0) {
+ return $res;
+ } else {
+ $this->attachments[] = $attachment;
+ }
+
+ return true;
+ }
+
+ /**
+ * Move attachments with final id once mailing has been stored
+ *
+ * @param int $id Mailing history id
+ *
+ * @return void
+ */
+ public function moveAttachments($id)
+ {
+ if (
+ isset($this->tmp_path)
+ && trim($this->tmp_path) !== ''
+ && count($this->attachments) > 0
+ ) {
+ foreach ($this->attachments as &$attachment) {
+ $old_path = $attachment->getDestDir() . $attachment->getFileName();
+ $new_path = GALETTE_ATTACHMENTS_PATH . $id . '/' .
+ $attachment->getFileName();
+ if (!file_exists(GALETTE_ATTACHMENTS_PATH . $id)) {
+ mkdir(GALETTE_ATTACHMENTS_PATH . $id);
+ }
+ $moved = rename($old_path, $new_path);
+ if ($moved) {
+ $attachment->setDestDir(GALETTE_ATTACHMENTS_PATH);
+ }
+ }
+ rmdir($this->tmp_path);
+ $this->tmp_path = null;
+ }
+ }
+
+ /**
+ * Remove specified attachment
+ *
+ * @param string $name Filename
+ *
+ * @return void
+ */
+ public function removeAttachment($name)
+ {
+ $to_remove = null;
+ if (
+ isset($this->tmp_path)
+ && trim($this->tmp_path) !== ''
+ && file_exists($this->tmp_path)
+ ) {
+ $to_remove = $this->tmp_path;
+ } elseif (file_exists(GALETTE_ATTACHMENTS_PATH . $this->id)) {
+ $to_remove = GALETTE_ATTACHMENTS_PATH . $this->id;
+ }
+
+ if ($to_remove !== null) {
+ $to_remove .= '/' . $name;
+
+ if (!$this->attachments) {
+ $this->loadAttachments();
+ }
+
+ if (file_exists($to_remove)) {
+ $i = 0;
+ foreach ($this->attachments as $att) {
+ if ($att->getFileName() == $name) {
+ unset($this->attachments[$i]);
+ unlink($to_remove);
+ break;
+ }
+ $i++;
+ }
+ } else {
+ Analog::log(
+ str_replace(
+ '%file',
+ $name,
+ 'File %file does not exists and cannot be removed!'
+ ),
+ Analog::WARNING
+ );
+ }
+ } else {
+ throw new \RuntimeException(
+ 'Unable to get attachments path!'
+ );
+ }
+ }
+
+ /**
+ * Remove mailing attachments
+ *
+ * @param boolean $temp Remove only tmporary attachments,
+ * to avoid history breaking
+ *
+ * @return void|false
+ */
+ public function removeAttachments($temp = false)
+ {
+ $to_remove = null;
+ if (
+ isset($this->tmp_path)
+ && trim($this->tmp_path) !== ''
+ && file_exists($this->tmp_path)
+ ) {
+ $to_remove = $this->tmp_path;
+ } elseif (file_exists(GALETTE_ATTACHMENTS_PATH . $this->id)) {
+ if ($temp === true) {
+ return false;
+ }
+ $to_remove = GALETTE_ATTACHMENTS_PATH . $this->id;
+ }
+
+ if ($to_remove !== null) {
+ $rdi = new \RecursiveDirectoryIterator(
+ $to_remove,
+ \FilesystemIterator::SKIP_DOTS
+ );
+ $contents = new \RecursiveIteratorIterator(
+ $rdi,
+ \RecursiveIteratorIterator::CHILD_FIRST
+ );
+ foreach ($contents as $path) {
+ if ($path->isFile()) {
+ unlink($path->getPathname());
+ } else {
+ rmdir($path->getPathname());
+ }
+ }
+ rmdir($to_remove);
+ }
+ }
+
+ /**
+ * Return textual error message
+ *
+ * @param int $code The error code
+ *
+ * @return string Localized message
+ */
+ public function getAttachmentErrorMessage($code)
+ {
+ $f = new File($this->tmp_path);
+ return $f->getErrorMessage($code);
+ }
+
+ /**
+ * Does mailing already exists in history?
+ *
+ * @return boolean
+ */
+ public function existsInHistory()
+ {
+ return isset($this->history_id);
}
/**
- * Global getter method
- *
- * @param string $name name of the property we want to retrive
- *
- * @return false|object the called property
- */
+ * Global getter method
+ *
+ * @param string $name name of the property we want to retrieve
+ *
+ * @return mixed the called property
+ */
public function __get($name)
{
- global $log;
$forbidden = array('ordered');
- if ( !in_array($name, $forbidden) ) {
- switch($name) {
- case 'alt_message':
- return $this->cleanedHtml();
- break;
- case 'step':
- return $this->current_step;
- break;
- case 'subject':
- return $this->getSubject();
- break;
- case 'message':
- return $this->getMessage();
- break;
- case 'html':
- return $this->isHTML();
- break;
- case 'mail':
- case '_mail':
- return $this->getPhpMailer();
- break;
- case 'errors':
- return $this->getErrors();
- break;
- case 'recipients':
- return $this->_mrecipients;
- break;
- default:
- $rname = '_' . $name;
- $log->log(
- '[' . get_class($this) . 'Trying to get ' . $name .
- ' renamed: ' . $rname,
- KLogger::DEBUG
- );
- return $this->$rname;
- break;
+ if (!in_array($name, $forbidden)) {
+ switch ($name) {
+ case 'alt_message':
+ return $this->cleanedHtml();
+ case 'step':
+ return $this->current_step;
+ case 'subject':
+ return $this->getSubject();
+ case 'message':
+ return $this->getMessage();
+ case 'wrapped_message':
+ return $this->getWrappedMessage();
+ case 'html':
+ return $this->isHTML();
+ case 'mail':
+ case '_mail':
+ return $this->getPhpMailer();
+ case 'errors':
+ return $this->getErrors();
+ case 'recipients':
+ return $this->mrecipients;
+ case 'tmp_path':
+ if (isset($this->tmp_path) && trim($this->tmp_path) !== '') {
+ return $this->tmp_path;
+ } else {
+ //no attachments
+ return false;
+ }
+ case 'attachments':
+ return $this->attachments;
+ case 'sender_name':
+ return $this->getSenderName();
+ case 'sender_address':
+ return $this->getSenderAddress();
+ case 'history_id':
+ return $this->$name;
+ default:
+ Analog::log(
+ '[' . get_class($this) . 'Trying to get ' . $name,
+ Analog::DEBUG
+ );
+ return $this->$name;
}
} else {
- $log->log(
- '[' . get_class($this) . 'Unable to get ' . $name .
- ' renamed: ' . $rname,
- KLogger::ERR
+ Analog::log(
+ '[' . get_class($this) . 'Unable to get ' . $name,
+ Analog::ERROR
);
return false;
}
}
/**
- * Global setter method
- *
- * @param string $name name of the property we want to assign a value to
- * @param object $value a relevant value for the property
- *
- * @return void
- */
- public function __set($name, $value)
+ * Global isset method
+ * Required for twig to access properties via __get
+ *
+ * @param string $name name of the property we want to retrieve
+ *
+ * @return bool
+ */
+ public function __isset($name)
{
- global $log;
- $rname = '_' . $name;
-
- switch( $name ) {
- case 'subject':
- $this->setSubject($value);
- break;
- case 'message':
- $this->setMessage($value);
- break;
- case 'html':
- if ( is_bool($value) ) {
- $this->isHTML($value);
- } else {
- $log->log(
- '[' . get_class($this) . '] Value for field `' . $name .
- '` should be boolean - (' . gettype($value) . ')' .
- $value . ' given',
- KLogger::WARN
- );
+ $forbidden = array('ordered');
+ if (!in_array($name, $forbidden)) {
+ switch ($name) {
+ case 'alt_message':
+ case 'step':
+ case 'subject':
+ case 'message':
+ case 'wrapped_message':
+ case 'html':
+ case 'mail':
+ case '_mail':
+ case 'errors':
+ case 'recipients':
+ case 'tmp_path':
+ case 'attachments':
+ case 'sender_name':
+ case 'sender_address':
+ return true;
}
- break;
- /** FIXME: remove... should no longer exists with phpMailer */
- /*case 'message':
- $this->$rname = (get_magic_quotes_gpc())
- ? stripslashes($value)
- : $value;
- break;*/
- case 'current_step':
- if ( is_int($value)
- && ( $value == self::STEP_START
- || $value == self::STEP_PREVIEW
- || $value == self::STEP_SEND
- || $value == self::STEP_SENT )
- ) {
- $this->_current_step = (int)$value;
- } else {
- $log->log(
- '[' . get_class($this) . '] Value for field `' . $name .
- '` should be integer and know - (' . gettype($value) . ')' .
- $value . ' given',
- KLogger::WARN
+ return isset($this->$name);
+ }
+
+ return false;
+ }
+
+ /**
+ * Global setter method
+ *
+ * @param string $name name of the property we want to assign a value to
+ * @param mixed $value a relevant value for the property
+ *
+ * @return void
+ */
+ public function __set($name, $value)
+ {
+ switch ($name) {
+ case 'subject':
+ $this->setSubject($value);
+ break;
+ case 'message':
+ $this->setMessage($value);
+ break;
+ case 'html':
+ if (is_bool($value)) {
+ $this->isHTML($value);
+ } else {
+ Analog::log(
+ '[' . get_class($this) . '] Value for field `' . $name .
+ '` should be boolean - (' . gettype($value) . ')' .
+ $value . ' given',
+ Analog::WARNING
+ );
+ }
+ break;
+ case 'current_step':
+ if (
+ is_int($value)
+ && ($value == self::STEP_START
+ || $value == self::STEP_PREVIEW
+ || $value == self::STEP_SEND
+ || $value == self::STEP_SENT)
+ ) {
+ $this->current_step = (int)$value;
+ } else {
+ Analog::log(
+ '[' . get_class($this) . '] Value for field `' . $name .
+ '` should be integer and know - (' . gettype($value) . ')' .
+ $value . ' given',
+ Analog::WARNING
+ );
+ }
+ break;
+ case 'id':
+ $this->id = $value;
+ break;
+ default:
+ Analog::log(
+ '[' . get_class($this) . '] Unable to set property `' . $name . '`',
+ Analog::WARNING
);
- }
- break;
- default:
- $log->log(
- '[' . get_class($this) . '] Unable to set proprety `' . $name . '`',
- KLogger::WARN
- );
- break;
}
}
}
-?>