diff --git a/app/Controller/Base.php b/app/Controller/Base.php
index 7b1cfd852..11841e09b 100644
--- a/app/Controller/Base.php
+++ b/app/Controller/Base.php
@@ -2,6 +2,7 @@
namespace Controller;
+use Core\Tool;
use Core\Registry;
use Core\Security;
use Core\Translator;
@@ -24,6 +25,7 @@ use Model\LastLogin;
* @property \Model\GitHub $gitHub
* @property \Model\LastLogin $lastLogin
* @property \Model\Ldap $ldap
+ * @property \Model\Notification $notification
* @property \Model\Project $project
* @property \Model\RememberMe $rememberMe
* @property \Model\ReverseProxyAuth $reverseProxyAuth
@@ -93,9 +95,7 @@ abstract class Base
*/
public function __get($name)
{
- $class = '\Model\\'.ucfirst($name);
- $this->registry->$name = new $class($this->registry->shared('db'), $this->registry->shared('event'));
- return $this->registry->shared($name);
+ return Tool::loadModel($this->registry, $name);
}
/**
@@ -157,6 +157,7 @@ abstract class Base
$this->action->attachEvents();
$this->project->attachEvents();
$this->webhook->attachEvents();
+ $this->notification->attachEvents();
}
/**
diff --git a/app/Controller/Board.php b/app/Controller/Board.php
index 14b1c02cb..7fe9c4ae3 100644
--- a/app/Controller/Board.php
+++ b/app/Controller/Board.php
@@ -373,7 +373,7 @@ class Board extends Base
}
if (isset($values['positions'])) {
- $this->board->saveTasksPosition($values['positions']);
+ $this->board->saveTasksPosition($values['positions'], $values['selected_task_id']);
}
$this->response->html(
diff --git a/app/Controller/Config.php b/app/Controller/Config.php
index daa577900..498f32149 100644
--- a/app/Controller/Config.php
+++ b/app/Controller/Config.php
@@ -20,7 +20,8 @@ class Config extends Base
$this->response->html($this->template->layout('config_index', array(
'db_size' => $this->config->getDatabaseSize(),
'user' => $_SESSION['user'],
- 'projects' => $this->project->getList(),
+ 'user_projects' => $this->project->getAvailableList($this->acl->getUserId()),
+ 'notifications' => $this->notification->readSettings($this->acl->getUserId()),
'languages' => $this->config->getLanguages(),
'values' => $this->config->getAll(),
'errors' => array(),
@@ -32,6 +33,13 @@ class Config extends Base
)));
}
+ public function notifications()
+ {
+ $values = $this->request->getValues();
+ $this->notification->saveSettings($this->acl->getUserId(), $values);
+ $this->response->redirect('?controller=config#notifications');
+ }
+
/**
* Validate and save settings
*
@@ -57,7 +65,8 @@ class Config extends Base
$this->response->html($this->template->layout('config_index', array(
'db_size' => $this->config->getDatabaseSize(),
'user' => $_SESSION['user'],
- 'projects' => $this->project->getList(),
+ 'user_projects' => $this->project->getAvailableList($this->acl->getUserId()),
+ 'notifications' => $this->notification->readSettings($this->acl->getUserId()),
'languages' => $this->config->getLanguages(),
'values' => $values,
'errors' => $errors,
diff --git a/app/Core/Tool.php b/app/Core/Tool.php
index ade99cad1..1a2e99044 100644
--- a/app/Core/Tool.php
+++ b/app/Core/Tool.php
@@ -31,4 +31,11 @@ class Tool
fclose($fp);
}
}
+
+ public static function loadModel(Registry $registry, $name)
+ {
+ $class = '\Model\\'.ucfirst($name);
+ $registry->$name = new $class($registry);
+ return $registry->shared($name);
+ }
}
diff --git a/app/Event/BaseNotificationListener.php b/app/Event/BaseNotificationListener.php
new file mode 100644
index 000000000..6c1728cb7
--- /dev/null
+++ b/app/Event/BaseNotificationListener.php
@@ -0,0 +1,76 @@
+template = $template;
+ $this->notification = $notification;
+ }
+
+ /**
+ * Execute the action
+ *
+ * @access public
+ * @param array $data Event data dictionary
+ * @return bool True if the action was executed or false when not executed
+ */
+ public function execute(array $data)
+ {
+ $values = $this->getTemplateData($data);
+
+ // Get the list of users to be notified
+ $users = $this->notification->getUsersList($values['task']['project_id']);
+
+ // Send notifications
+ if ($users) {
+ $this->notification->sendEmails($this->template, $users, $values);
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/app/Event/CommentNotificationListener.php b/app/Event/CommentNotificationListener.php
new file mode 100644
index 000000000..3771ea7e8
--- /dev/null
+++ b/app/Event/CommentNotificationListener.php
@@ -0,0 +1,30 @@
+notification->comment->getById($data['id']);
+ $values['task'] = $this->notification->task->getById($data['task_id'], true);
+
+ return $values;
+ }
+}
diff --git a/app/Event/FileNotificationListener.php b/app/Event/FileNotificationListener.php
new file mode 100644
index 000000000..98fc42601
--- /dev/null
+++ b/app/Event/FileNotificationListener.php
@@ -0,0 +1,30 @@
+notification->task->getById($data['task_id'], true);
+
+ return $values;
+ }
+}
diff --git a/app/Event/SubTaskNotificationListener.php b/app/Event/SubTaskNotificationListener.php
new file mode 100644
index 000000000..0a2394213
--- /dev/null
+++ b/app/Event/SubTaskNotificationListener.php
@@ -0,0 +1,30 @@
+notification->subtask->getById($data['id'], true);
+ $values['task'] = $this->notification->task->getById($data['task_id'], true);
+
+ return $values;
+ }
+}
diff --git a/app/Event/TaskNotificationListener.php b/app/Event/TaskNotificationListener.php
new file mode 100644
index 000000000..ffbe7a8cb
--- /dev/null
+++ b/app/Event/TaskNotificationListener.php
@@ -0,0 +1,29 @@
+notification->task->getById($data['task_id'], true);
+
+ return $values;
+ }
+}
diff --git a/app/Locales/de_DE/translations.php b/app/Locales/de_DE/translations.php
index 73aed1605..df5a359f2 100644
--- a/app/Locales/de_DE/translations.php
+++ b/app/Locales/de_DE/translations.php
@@ -402,4 +402,31 @@ return array(
// 'Clone Project' => '',
// 'Project cloned successfully.' => '',
// 'Unable to clone this project.' => '',
+ // 'Email notifications' => '',
+ // 'Enable email notifications' => '',
+ // 'Task position:' => '',
+ // 'The task #%d have been opened.' => '',
+ // 'The task #%d have been closed.' => '',
+ // 'Sub-task updated' => '',
+ // 'Title:' => '',
+ // 'Status:' => '',
+ // 'Assignee:' => '',
+ // 'Time tracking:' => '',
+ // 'New sub-task' => '',
+ // 'New attachment added "%s"' => '',
+ // 'Comment updated' => '',
+ // 'New comment posted by %s' => '',
+ // 'List of due tasks for the project "%s"' => '',
+ // '[%s][New attachment] %s (#%d)' => '',
+ // '[%s][New comment] %s (#%d)' => '',
+ // '[%s][Comment updated] %s (#%d)' => '',
+ // '[%s][New subtask] %s (#%d)' => '',
+ // '[%s][Subtask updated] %s (#%d)' => '',
+ // '[%s][New task] %s (#%d)' => '',
+ // '[%s][Task updated] %s (#%d)' => '',
+ // '[%s][Task closed] %s (#%d)' => '',
+ // '[%s][Task opened] %s (#%d)' => '',
+ // '[%s][Due tasks]' => '',
+ // '[Kanboard] Notification' => '',
+ // 'I want to receive notifications only for those projects:' => '',
);
diff --git a/app/Locales/es_ES/translations.php b/app/Locales/es_ES/translations.php
index a8a8024cb..b91e95c3d 100644
--- a/app/Locales/es_ES/translations.php
+++ b/app/Locales/es_ES/translations.php
@@ -401,4 +401,31 @@ return array(
// 'Clone Project' => '',
// 'Project cloned successfully.' => '',
// 'Unable to clone this project.' => '',
+ // 'Email notifications' => '',
+ // 'Enable email notifications' => '',
+ // 'Task position:' => '',
+ // 'The task #%d have been opened.' => '',
+ // 'The task #%d have been closed.' => '',
+ // 'Sub-task updated' => '',
+ // 'Title:' => '',
+ // 'Status:' => '',
+ // 'Assignee:' => '',
+ // 'Time tracking:' => '',
+ // 'New sub-task' => '',
+ // 'New attachment added "%s"' => '',
+ // 'Comment updated' => '',
+ // 'New comment posted by %s' => '',
+ // 'List of due tasks for the project "%s"' => '',
+ // '[%s][New attachment] %s (#%d)' => '',
+ // '[%s][New comment] %s (#%d)' => '',
+ // '[%s][Comment updated] %s (#%d)' => '',
+ // '[%s][New subtask] %s (#%d)' => '',
+ // '[%s][Subtask updated] %s (#%d)' => '',
+ // '[%s][New task] %s (#%d)' => '',
+ // '[%s][Task updated] %s (#%d)' => '',
+ // '[%s][Task closed] %s (#%d)' => '',
+ // '[%s][Task opened] %s (#%d)' => '',
+ // '[%s][Due tasks]' => '',
+ // '[Kanboard] Notification' => '',
+ // 'I want to receive notifications only for those projects:' => '',
);
diff --git a/app/Locales/fi_FI/translations.php b/app/Locales/fi_FI/translations.php
index 801ec5c15..2b2289604 100644
--- a/app/Locales/fi_FI/translations.php
+++ b/app/Locales/fi_FI/translations.php
@@ -401,4 +401,31 @@ return array(
// 'Clone Project' => '',
// 'Project cloned successfully.' => '',
// 'Unable to clone this project.' => '',
+ // 'Email notifications' => '',
+ // 'Enable email notifications' => '',
+ // 'Task position:' => '',
+ // 'The task #%d have been opened.' => '',
+ // 'The task #%d have been closed.' => '',
+ // 'Sub-task updated' => '',
+ // 'Title:' => '',
+ // 'Status:' => '',
+ // 'Assignee:' => '',
+ // 'Time tracking:' => '',
+ // 'New sub-task' => '',
+ // 'New attachment added "%s"' => '',
+ // 'Comment updated' => '',
+ // 'New comment posted by %s' => '',
+ // 'List of due tasks for the project "%s"' => '',
+ // '[%s][New attachment] %s (#%d)' => '',
+ // '[%s][New comment] %s (#%d)' => '',
+ // '[%s][Comment updated] %s (#%d)' => '',
+ // '[%s][New subtask] %s (#%d)' => '',
+ // '[%s][Subtask updated] %s (#%d)' => '',
+ // '[%s][New task] %s (#%d)' => '',
+ // '[%s][Task updated] %s (#%d)' => '',
+ // '[%s][Task closed] %s (#%d)' => '',
+ // '[%s][Task opened] %s (#%d)' => '',
+ // '[%s][Due tasks]' => '',
+ // '[Kanboard] Notification' => '',
+ // 'I want to receive notifications only for those projects:' => '',
);
diff --git a/app/Locales/fr_FR/translations.php b/app/Locales/fr_FR/translations.php
index 552060c56..4bb4359eb 100644
--- a/app/Locales/fr_FR/translations.php
+++ b/app/Locales/fr_FR/translations.php
@@ -399,4 +399,31 @@ return array(
'Clone Project' => 'Cloner le projet',
'Project cloned successfully.' => 'Projet cloné avec succès.',
'Unable to clone this project.' => 'Impossible de cloner ce projet.',
+ 'Email notifications' => 'Notifications par email',
+ 'Enable email notifications' => 'Activer les notifications par emails',
+ 'Task position:' => 'Position de la tâche :',
+ 'The task #%d have been opened.' => 'La tâche #%d a été ouverte.',
+ 'The task #%d have been closed.' => 'La tâche #%d a été fermée.',
+ 'Sub-task updated' => 'Sous-tâche mise à jour',
+ 'Title:' => 'Titre :',
+ 'Status:' => 'État :',
+ 'Assignee:' => 'Assigné :',
+ 'Time tracking:' => 'Gestion du temps :',
+ 'New sub-task' => 'Nouvelle sous-tâche',
+ 'New attachment added "%s"' => 'Nouvelle pièce-jointe ajoutée « %s »',
+ 'Comment updated' => 'Commentaire ajouté',
+ 'New comment posted by %s' => 'Nouveau commentaire ajouté par « %s »',
+ 'List of due tasks for the project "%s"' => 'Liste des tâches expirées pour le projet « %s »',
+ '[%s][New attachment] %s (#%d)' => '[%s][Pièce-jointe] %s (#%d)',
+ '[%s][New comment] %s (#%d)' => '[%s][Nouveau commentaire] %s (#%d)',
+ '[%s][Comment updated] %s (#%d)' => '[%s][Commentaire mis à jour] %s (#%d)',
+ '[%s][New subtask] %s (#%d)' => '[%s][Nouvelle sous-tâche] %s (#%d)',
+ '[%s][Subtask updated] %s (#%d)' => '[%s][Sous-tâche mise à jour] %s (#%d)',
+ '[%s][New task] %s (#%d)' => '[%s][Nouvelle tâche] %s (#%d)',
+ '[%s][Task updated] %s (#%d)' => '[%s][Tâche mise à jour] %s (#%d)',
+ '[%s][Task closed] %s (#%d)' => '[%s][Tâche fermée] %s (#%d)',
+ '[%s][Task opened] %s (#%d)' => '[%s][Tâche ouverte] %s (#%d)',
+ '[%s][Due tasks]' => '[%s][Tâches expirées]',
+ '[Kanboard] Notification' => '[Kanboard] Notification',
+ 'I want to receive notifications only for those projects:' => 'Je souhaite reçevoir les notifications uniquement pour les projets sélectionnés :',
);
diff --git a/app/Locales/pl_PL/translations.php b/app/Locales/pl_PL/translations.php
index 41e7f80a3..53d57974a 100644
--- a/app/Locales/pl_PL/translations.php
+++ b/app/Locales/pl_PL/translations.php
@@ -402,4 +402,31 @@ return array(
// 'Clone Project' => '',
// 'Project cloned successfully.' => '',
// 'Unable to clone this project.' => '',
+ // 'Email notifications' => '',
+ // 'Enable email notifications' => '',
+ // 'Task position:' => '',
+ // 'The task #%d have been opened.' => '',
+ // 'The task #%d have been closed.' => '',
+ // 'Sub-task updated' => '',
+ // 'Title:' => '',
+ // 'Status:' => '',
+ // 'Assignee:' => '',
+ // 'Time tracking:' => '',
+ // 'New sub-task' => '',
+ // 'New attachment added "%s"' => '',
+ // 'Comment updated' => '',
+ // 'New comment posted by %s' => '',
+ // 'List of due tasks for the project "%s"' => '',
+ // '[%s][New attachment] %s (#%d)' => '',
+ // '[%s][New comment] %s (#%d)' => '',
+ // '[%s][Comment updated] %s (#%d)' => '',
+ // '[%s][New subtask] %s (#%d)' => '',
+ // '[%s][Subtask updated] %s (#%d)' => '',
+ // '[%s][New task] %s (#%d)' => '',
+ // '[%s][Task updated] %s (#%d)' => '',
+ // '[%s][Task closed] %s (#%d)' => '',
+ // '[%s][Task opened] %s (#%d)' => '',
+ // '[%s][Due tasks]' => '',
+ // '[Kanboard] Notification' => '',
+ // 'I want to receive notifications only for those projects:' => '',
);
diff --git a/app/Locales/pt_BR/translations.php b/app/Locales/pt_BR/translations.php
index 6ebb78c99..04117b504 100644
--- a/app/Locales/pt_BR/translations.php
+++ b/app/Locales/pt_BR/translations.php
@@ -404,4 +404,31 @@ return array(
'Clone Project' => 'Clonar Projeto',
'Project cloned successfully.' => 'Projeto clonado com sucesso.',
'Unable to clone this project.' => 'Impossível clonar este projeto.',
+ // 'Email notifications' => '',
+ // 'Enable email notifications' => '',
+ // 'Task position:' => '',
+ // 'The task #%d have been opened.' => '',
+ // 'The task #%d have been closed.' => '',
+ // 'Sub-task updated' => '',
+ // 'Title:' => '',
+ // 'Status:' => '',
+ // 'Assignee:' => '',
+ // 'Time tracking:' => '',
+ // 'New sub-task' => '',
+ // 'New attachment added "%s"' => '',
+ // 'Comment updated' => '',
+ // 'New comment posted by %s' => '',
+ // 'List of due tasks for the project "%s"' => '',
+ // '[%s][New attachment] %s (#%d)' => '',
+ // '[%s][New comment] %s (#%d)' => '',
+ // '[%s][Comment updated] %s (#%d)' => '',
+ // '[%s][New subtask] %s (#%d)' => '',
+ // '[%s][Subtask updated] %s (#%d)' => '',
+ // '[%s][New task] %s (#%d)' => '',
+ // '[%s][Task updated] %s (#%d)' => '',
+ // '[%s][Task closed] %s (#%d)' => '',
+ // '[%s][Task opened] %s (#%d)' => '',
+ // '[%s][Due tasks]' => '',
+ // '[Kanboard] Notification' => '',
+ // 'I want to receive notifications only for those projects:' => '',
);
diff --git a/app/Locales/sv_SE/translations.php b/app/Locales/sv_SE/translations.php
index 68248b72e..dddccc2fd 100644
--- a/app/Locales/sv_SE/translations.php
+++ b/app/Locales/sv_SE/translations.php
@@ -401,4 +401,31 @@ return array(
// 'Clone Project' => '',
// 'Project cloned successfully.' => '',
// 'Unable to clone this project.' => '',
+ // 'Email notifications' => '',
+ // 'Enable email notifications' => '',
+ // 'Task position:' => '',
+ // 'The task #%d have been opened.' => '',
+ // 'The task #%d have been closed.' => '',
+ // 'Sub-task updated' => '',
+ // 'Title:' => '',
+ // 'Status:' => '',
+ // 'Assignee:' => '',
+ // 'Time tracking:' => '',
+ // 'New sub-task' => '',
+ // 'New attachment added "%s"' => '',
+ // 'Comment updated' => '',
+ // 'New comment posted by %s' => '',
+ // 'List of due tasks for the project "%s"' => '',
+ // '[%s][New attachment] %s (#%d)' => '',
+ // '[%s][New comment] %s (#%d)' => '',
+ // '[%s][Comment updated] %s (#%d)' => '',
+ // '[%s][New subtask] %s (#%d)' => '',
+ // '[%s][Subtask updated] %s (#%d)' => '',
+ // '[%s][New task] %s (#%d)' => '',
+ // '[%s][Task updated] %s (#%d)' => '',
+ // '[%s][Task closed] %s (#%d)' => '',
+ // '[%s][Task opened] %s (#%d)' => '',
+ // '[%s][Due tasks]' => '',
+ // '[Kanboard] Notification' => '',
+ // 'I want to receive notifications only for those projects:' => '',
);
diff --git a/app/Locales/zh_CN/translations.php b/app/Locales/zh_CN/translations.php
index 1d7593366..823075fef 100644
--- a/app/Locales/zh_CN/translations.php
+++ b/app/Locales/zh_CN/translations.php
@@ -407,4 +407,30 @@ return array(
// 'Clone Project' => '',
// 'Project cloned successfully.' => '',
// 'Unable to clone this project.' => '',
+ // 'Email notifications' => '',
+ // 'Enable email notifications' => '',
+ // 'Task position:' => '',
+ // 'The task #%d have been opened.' => '',
+ // 'The task #%d have been closed.' => '',
+ // 'Sub-task updated' => '',
+ // 'Title:' => '',
+ // 'Status:' => '',
+ // 'Assignee:' => '',
+ // 'Time tracking:' => '',
+ // 'New sub-task' => '',
+ // 'New attachment added "%s"' => '',
+ // 'Comment updated' => '',
+ // 'New comment posted by %s' => '',
+ // 'List of due tasks for the project "%s"' => '',
+ // '[%s][New attachment] %s (#%d)' => '',
+ // '[%s][New comment] %s (#%d)' => '',
+ // '[%s][Comment updated] %s (#%d)' => '',
+ // '[%s][New subtask] %s (#%d)' => '',
+ // '[%s][Subtask updated] %s (#%d)' => '',
+ // '[%s][New task] %s (#%d)' => '',
+ // '[%s][Task updated] %s (#%d)' => '',
+ // '[%s][Task closed] %s (#%d)' => '',
+ // '[%s][Task opened] %s (#%d)' => '',
+ // '[%s][Due tasks]' => '',
+ // '[Kanboard] Notification' => '',
);
diff --git a/app/Model/Acl.php b/app/Model/Acl.php
index 8a87a6b29..c4b310798 100644
--- a/app/Model/Acl.php
+++ b/app/Model/Acl.php
@@ -33,7 +33,7 @@ class Acl extends Base
'board' => array('index', 'show', 'assign', 'assigntask', 'save', 'check'),
'project' => array('tasks', 'index', 'forbidden', 'search'),
'user' => array('index', 'edit', 'update', 'forbidden', 'logout', 'index', 'unlinkgoogle', 'unlinkgithub'),
- 'config' => array('index', 'removeremembermetoken'),
+ 'config' => array('index', 'removeremembermetoken', 'notifications'),
'comment' => array('create', 'save', 'confirm', 'remove', 'update', 'edit', 'forbidden'),
'file' => array('create', 'save', 'download', 'confirm', 'remove', 'open', 'image'),
'subtask' => array('create', 'save', 'edit', 'update', 'confirm', 'remove'),
diff --git a/app/Model/Action.php b/app/Model/Action.php
index 25e72f585..effe87071 100644
--- a/app/Model/Action.php
+++ b/app/Model/Action.php
@@ -224,25 +224,25 @@ class Action extends Base
switch ($name) {
case 'TaskClose':
$className = '\Action\TaskClose';
- return new $className($project_id, new Task($this->db, $this->event));
+ return new $className($project_id, new Task($this->registry));
case 'TaskAssignCurrentUser':
$className = '\Action\TaskAssignCurrentUser';
- return new $className($project_id, new Task($this->db, $this->event), new Acl($this->db, $this->event));
+ return new $className($project_id, new Task($this->registry), new Acl($this->registry));
case 'TaskAssignSpecificUser':
$className = '\Action\TaskAssignSpecificUser';
- return new $className($project_id, new Task($this->db, $this->event));
+ return new $className($project_id, new Task($this->registry));
case 'TaskDuplicateAnotherProject':
$className = '\Action\TaskDuplicateAnotherProject';
- return new $className($project_id, new Task($this->db, $this->event));
+ return new $className($project_id, new Task($this->registry));
case 'TaskAssignColorUser':
$className = '\Action\TaskAssignColorUser';
- return new $className($project_id, new Task($this->db, $this->event));
+ return new $className($project_id, new Task($this->registry));
case 'TaskAssignColorCategory':
$className = '\Action\TaskAssignColorCategory';
- return new $className($project_id, new Task($this->db, $this->event));
+ return new $className($project_id, new Task($this->registry));
case 'TaskAssignCategoryColor':
$className = '\Action\TaskAssignCategoryColor';
- return new $className($project_id, new Task($this->db, $this->event));
+ return new $className($project_id, new Task($this->registry));
default:
throw new LogicException('Action not found: '.$name);
}
diff --git a/app/Model/Base.php b/app/Model/Base.php
index 66185aeb1..92578ffc4 100644
--- a/app/Model/Base.php
+++ b/app/Model/Base.php
@@ -17,6 +17,8 @@ require __DIR__.'/../../vendor/SimpleValidator/Validators/Email.php';
require __DIR__.'/../../vendor/SimpleValidator/Validators/Numeric.php';
use Core\Event;
+use Core\Tool;
+use Core\Registry;
use PicoDb\Database;
/**
@@ -24,6 +26,21 @@ use PicoDb\Database;
*
* @package model
* @author Frederic Guillot
+ *
+ * @property \Model\Acl $acl
+ * @property \Model\Action $action
+ * @property \Model\Board $board
+ * @property \Model\Category $category
+ * @property \Model\Comment $comment
+ * @property \Model\Config $config
+ * @property \Model\File $file
+ * @property \Model\LastLogin $lastLogin
+ * @property \Model\Ldap $ldap
+ * @property \Model\Notification $notification
+ * @property \Model\Project $project
+ * @property \Model\SubTask $subTask
+ * @property \Model\Task $task
+ * @property \Model\User $user
*/
abstract class Base
{
@@ -43,16 +60,36 @@ abstract class Base
*/
protected $event;
+ /**
+ * Registry instance
+ *
+ * @access protected
+ * @var \Core\Registry
+ */
+ protected $registry;
+
/**
* Constructor
*
* @access public
- * @param \PicoDb\Database $db Database instance
- * @param \Core\Event $event Event dispatcher instance
+ * @param \Core\Registry $registry Registry instance
*/
- public function __construct(Database $db, Event $event)
+ public function __construct(Registry $registry)
{
- $this->db = $db;
- $this->event = $event;
+ $this->registry = $registry;
+ $this->db = $this->registry->shared('db');
+ $this->event = $this->registry->shared('event');
+ }
+
+ /**
+ * Load automatically models
+ *
+ * @access public
+ * @param string $name Model name
+ * @return mixed
+ */
+ public function __get($name)
+ {
+ return Tool::loadModel($this->registry, $name);
}
}
diff --git a/app/Model/Board.php b/app/Model/Board.php
index a4e0a345b..168b185f0 100644
--- a/app/Model/Board.php
+++ b/app/Model/Board.php
@@ -24,17 +24,18 @@ class Board extends Base
* Save task positions for each column
*
* @access public
- * @param array $values [['task_id' => X, 'column_id' => X, 'position' => X], ...]
+ * @param array $positions [['task_id' => X, 'column_id' => X, 'position' => X], ...]
+ * @param integer $selected_task_id The selected task id
* @return boolean
*/
- public function saveTasksPosition(array $values)
+ public function saveTasksPosition(array $positions, $selected_task_id)
{
- $taskModel = new Task($this->db, $this->event);
-
$this->db->startTransaction();
- foreach ($values as $value) {
- if (! $taskModel->move($value['task_id'], $value['column_id'], $value['position'])) {
+ foreach ($positions as $value) {
+
+ // We trigger events only for the selected task
+ if (! $this->task->move($value['task_id'], $value['column_id'], $value['position'], $value['task_id'] == $selected_task_id)) {
$this->db->cancelTransaction();
return false;
}
@@ -201,8 +202,7 @@ class Board extends Base
$filters[] = array('column' => 'project_id', 'operator' => 'eq', 'value' => $project_id);
$filters[] = array('column' => 'is_active', 'operator' => 'eq', 'value' => Task::STATUS_OPEN);
- $taskModel = new Task($this->db, $this->event);
- $tasks = $taskModel->find($filters);
+ $tasks = $this->task->find($filters);
foreach ($columns as &$column) {
diff --git a/app/Model/Comment.php b/app/Model/Comment.php
index b51020700..b849fc23f 100644
--- a/app/Model/Comment.php
+++ b/app/Model/Comment.php
@@ -20,6 +20,14 @@ class Comment extends Base
*/
const TABLE = 'comments';
+ /**
+ * Events
+ *
+ * @var string
+ */
+ const EVENT_UPDATE = 'comment.update';
+ const EVENT_CREATE = 'comment.create';
+
/**
* Get all comments for a given task
*
@@ -95,7 +103,14 @@ class Comment extends Base
{
$values['date'] = time();
- return $this->db->table(self::TABLE)->save($values);
+ if ($this->db->table(self::TABLE)->save($values)) {
+
+ $values['id'] = $this->db->getConnection()->getLastId();
+ $this->event->trigger(self::EVENT_CREATE, $values);
+ return true;
+ }
+
+ return false;
}
/**
@@ -107,10 +122,14 @@ class Comment extends Base
*/
public function update(array $values)
{
- return $this->db
+ $result = $this->db
->table(self::TABLE)
->eq('id', $values['id'])
->update(array('comment' => $values['comment']));
+
+ $this->event->trigger(self::EVENT_UPDATE, $values);
+
+ return $result;
}
/**
diff --git a/app/Model/File.php b/app/Model/File.php
index 2a793217e..d5a0c7cdc 100644
--- a/app/Model/File.php
+++ b/app/Model/File.php
@@ -24,6 +24,13 @@ class File extends Base
*/
const BASE_PATH = 'data/files/';
+ /**
+ * Events
+ *
+ * @var string
+ */
+ const EVENT_CREATE = 'file.create';
+
/**
* Get a file by the id
*
@@ -82,6 +89,8 @@ class File extends Base
*/
public function create($task_id, $name, $path, $is_image)
{
+ $this->event->trigger(self::EVENT_CREATE, array('task_id' => $task_id, 'name' => $name));
+
return $this->db->table(self::TABLE)->save(array(
'task_id' => $task_id,
'name' => $name,
diff --git a/app/Model/GitHub.php b/app/Model/GitHub.php
index 3380218df..bf4f4c51c 100644
--- a/app/Model/GitHub.php
+++ b/app/Model/GitHub.php
@@ -26,22 +26,19 @@ class GitHub extends Base
*/
public function authenticate($github_id)
{
- $userModel = new User($this->db, $this->event);
-
- $user = $userModel->getByGitHubId($github_id);
+ $user = $this->user->getByGitHubId($github_id);
if ($user) {
// Create the user session
- $userModel->updateSession($user);
+ $this->user->updateSession($user);
// Update login history
- $lastLogin = new LastLogin($this->db, $this->event);
- $lastLogin->create(
+ $this->lastLogin->create(
LastLogin::AUTH_GITHUB,
$user['id'],
- $userModel->getIpAddress(),
- $userModel->getUserAgent()
+ $this->user->getIpAddress(),
+ $this->user->getUserAgent()
);
return true;
@@ -59,9 +56,7 @@ class GitHub extends Base
*/
public function unlink($user_id)
{
- $userModel = new User($this->db, $this->event);
-
- return $userModel->update(array(
+ return $this->user->update(array(
'id' => $user_id,
'github_id' => '',
));
@@ -78,9 +73,7 @@ class GitHub extends Base
*/
public function updateUser($user_id, array $profile)
{
- $userModel = new User($this->db, $this->event);
-
- return $userModel->update(array(
+ return $this->user->update(array(
'id' => $user_id,
'github_id' => $profile['id'],
'email' => $profile['email'],
@@ -141,7 +134,7 @@ class GitHub extends Base
try {
$gitHubService = $this->getService();
$gitHubService->requestAccessToken($code);
-
+
return json_decode($gitHubService->request('user'), true);
}
catch (TokenResponseException $e) {
@@ -150,7 +143,7 @@ class GitHub extends Base
return false;
}
-
+
/**
* Revokes this user's GitHub tokens for Kanboard
*
diff --git a/app/Model/Google.php b/app/Model/Google.php
index f5beb8f85..cca4f6686 100644
--- a/app/Model/Google.php
+++ b/app/Model/Google.php
@@ -27,21 +27,19 @@ class Google extends Base
*/
public function authenticate($google_id)
{
- $userModel = new User($this->db, $this->event);
- $user = $userModel->getByGoogleId($google_id);
+ $user = $this->user->getByGoogleId($google_id);
if ($user) {
// Create the user session
- $userModel->updateSession($user);
+ $this->user->updateSession($user);
// Update login history
- $lastLogin = new LastLogin($this->db, $this->event);
- $lastLogin->create(
+ $this->lastLogin->create(
LastLogin::AUTH_GOOGLE,
$user['id'],
- $userModel->getIpAddress(),
- $userModel->getUserAgent()
+ $this->user->getIpAddress(),
+ $this->user->getUserAgent()
);
return true;
@@ -59,9 +57,7 @@ class Google extends Base
*/
public function unlink($user_id)
{
- $userModel = new User($this->db, $this->event);
-
- return $userModel->update(array(
+ return $this->user->update(array(
'id' => $user_id,
'google_id' => '',
));
@@ -77,9 +73,7 @@ class Google extends Base
*/
public function updateUser($user_id, array $profile)
{
- $userModel = new User($this->db, $this->event);
-
- return $userModel->update(array(
+ return $this->user->update(array(
'id' => $user_id,
'google_id' => $profile['id'],
'email' => $profile['email'],
diff --git a/app/Model/Ldap.php b/app/Model/Ldap.php
index dabcd5ff7..007f71710 100644
--- a/app/Model/Ldap.php
+++ b/app/Model/Ldap.php
@@ -73,8 +73,7 @@ class Ldap extends Base
*/
public function create($username, $name, $email)
{
- $userModel = new User($this->db, $this->event);
- $user = $userModel->getByUsername($username);
+ $user = $this->user->getByUsername($username);
// There is an existing user account
if ($user) {
diff --git a/app/Model/Notification.php b/app/Model/Notification.php
new file mode 100644
index 000000000..e0f932a4a
--- /dev/null
+++ b/app/Model/Notification.php
@@ -0,0 +1,215 @@
+db->table(User::TABLE)
+ ->columns('id', 'username', 'name', 'email')
+ ->eq('notifications_enabled', '1')
+ ->neq('email', '')
+ ->findAll();
+
+ foreach ($users as $index => $user) {
+
+ $projects = $this->db->table(self::TABLE)
+ ->eq('user_id', $user['id'])
+ ->findAllByColumn('project_id');
+
+ // The user have selected only some projects
+ if (! empty($projects)) {
+
+ // If the user didn't select this project we remove that guy from the list
+ if (! in_array($project_id, $projects)) {
+ unset($users[$index]);
+ }
+ }
+ }
+
+ return $users;
+ }
+
+ /**
+ * Attach events
+ *
+ * @access public
+ */
+ public function attachEvents()
+ {
+ $this->event->attach(File::EVENT_CREATE, new FileNotificationListener($this, 'notification_file_creation'));
+
+ $this->event->attach(Comment::EVENT_CREATE, new CommentNotificationListener($this, 'notification_comment_creation'));
+ $this->event->attach(Comment::EVENT_UPDATE, new CommentNotificationListener($this, 'notification_comment_update'));
+
+ $this->event->attach(SubTask::EVENT_CREATE, new SubTaskNotificationListener($this, 'notification_subtask_creation'));
+ $this->event->attach(SubTask::EVENT_UPDATE, new SubTaskNotificationListener($this, 'notification_subtask_update'));
+
+ $this->event->attach(Task::EVENT_CREATE, new TaskNotificationListener($this, 'notification_task_creation'));
+ $this->event->attach(Task::EVENT_UPDATE, new TaskNotificationListener($this, 'notification_task_update'));
+ $this->event->attach(Task::EVENT_CLOSE, new TaskNotificationListener($this, 'notification_task_close'));
+ $this->event->attach(Task::EVENT_OPEN, new TaskNotificationListener($this, 'notification_task_open'));
+ }
+
+ /**
+ * Send the email notifications
+ *
+ * @access public
+ * @param string $template Template name
+ * @param array $users List of users
+ * @param array $data Template data
+ */
+ public function sendEmails($template, array $users, array $data)
+ {
+ $transport = $this->registry->shared('mailer');
+ $mailer = Swift_Mailer::newInstance($transport);
+
+ $message = Swift_Message::newInstance()
+ ->setSubject($this->getMailSubject($template, $data))
+ ->setFrom(array(MAIL_FROM => 'Kanboard'))
+ //->setTo(array($user['email'] => $user['name']))
+ ->setBody($this->getMailContent($template, $data), 'text/html');
+
+ foreach ($users as $user) {
+ $message->setTo(array($user['email'] => $user['name'] ?: $user['username']));
+ $mailer->send($message);
+ }
+ }
+
+ /**
+ * Get the mail subject for a given template name
+ *
+ * @access public
+ * @param string $template Template name
+ * @param array $data Template data
+ */
+ public function getMailSubject($template, array $data)
+ {
+ switch ($template) {
+ case 'notification_file_creation':
+ return t('[%s][New attachment] %s (#%d)', $data['task']['project_name'], $data['task']['title'], $data['task']['id']);
+ case 'notification_comment_creation':
+ return t('[%s][New comment] %s (#%d)', $data['task']['project_name'], $data['task']['title'], $data['task']['id']);
+ case 'notification_comment_update':
+ return t('[%s][Comment updated] %s (#%d)', $data['task']['project_name'], $data['task']['title'], $data['task']['id']);
+ case 'notification_subtask_creation':
+ return t('[%s][New subtask] %s (#%d)', $data['task']['project_name'], $data['task']['title'], $data['task']['id']);
+ case 'notification_subtask_update':
+ return t('[%s][Subtask updated] %s (#%d)', $data['task']['project_name'], $data['task']['title'], $data['task']['id']);
+ case 'notification_task_creation':
+ return t('[%s][New task] %s (#%d)', $data['task']['project_name'], $data['task']['title'], $data['task']['id']);
+ case 'notification_task_update':
+ return t('[%s][Task updated] %s (#%d)', $data['task']['project_name'], $data['task']['title'], $data['task']['id']);
+ case 'notification_task_close':
+ return t('[%s][Task closed] %s (#%d)', $data['task']['project_name'], $data['task']['title'], $data['task']['id']);
+ case 'notification_task_open':
+ return t('[%s][Task opened] %s (#%d)', $data['task']['project_name'], $data['task']['title'], $data['task']['id']);
+ case 'notification_task_due':
+ return t('[%s][Due tasks]', $data['project']);
+ }
+
+ return t('[Kanboard] Notification');
+ }
+
+ /**
+ * Get the mail content for a given template name
+ *
+ * @access public
+ * @param string $template Template name
+ * @param array $data Template data
+ */
+ public function getMailContent($template, array $data)
+ {
+ $tpl = new Template;
+ return $tpl->load($template, $data);
+ }
+
+ /**
+ * Save settings for the given user
+ *
+ * @access public
+ * @param integer $user_id User id
+ * @param array $values Form values
+ */
+ public function saveSettings($user_id, array $values)
+ {
+ // Delete all selected projects
+ $this->db->table(self::TABLE)->eq('user_id', $user_id)->remove();
+
+ if (isset($values['notifications_enabled']) && $values['notifications_enabled'] == 1) {
+
+ // Activate notifications
+ $this->db->table(User::TABLE)->eq('id', $user_id)->update(array(
+ 'notifications_enabled' => '1'
+ ));
+
+ // Save selected projects
+ if (! empty($values['projects'])) {
+
+ foreach ($values['projects'] as $project_id => $checkbox_value) {
+ $this->db->table(self::TABLE)->insert(array(
+ 'user_id' => $user_id,
+ 'project_id' => $project_id,
+ ));
+ }
+ }
+ }
+ else {
+
+ // Disable notifications
+ $this->db->table(User::TABLE)->eq('id', $user_id)->update(array(
+ 'notifications_enabled' => '0'
+ ));
+ }
+ }
+
+ /**
+ * Read user settings to display the form
+ *
+ * @access public
+ * @param integer $user_id User id
+ * @return array
+ */
+ public function readSettings($user_id)
+ {
+ $values = array();
+ $values['notifications_enabled'] = $this->db->table(User::TABLE)->eq('id', $user_id)->findOneColumn('notifications_enabled');
+
+ $projects = $this->db->table(self::TABLE)->eq('user_id', $user_id)->findAllByColumn('project_id');
+
+ foreach ($projects as $project_id) {
+ $values['project_'.$project_id] = true;
+ }
+
+ return $values;
+ }
+}
diff --git a/app/Model/Project.php b/app/Model/Project.php
index f598c96f9..63458fa3c 100644
--- a/app/Model/Project.php
+++ b/app/Model/Project.php
@@ -55,10 +55,9 @@ class Project extends Base
public function getUsersList($project_id, $prepend_unassigned = true, $prepend_everybody = false)
{
$allowed_users = $this->getAllowedUsers($project_id);
- $userModel = new User($this->db, $this->event);
if (empty($allowed_users)) {
- $allowed_users = $userModel->getList();
+ $allowed_users = $this->user->getList();
}
if ($prepend_unassigned) {
@@ -103,8 +102,7 @@ class Project extends Base
'not_allowed' => array(),
);
- $userModel = new User($this->db, $this->event);
- $all_users = $userModel->getList();
+ $all_users = $this->user->getList();
$users['allowed'] = $this->getAllowedUsers($project_id);
@@ -253,27 +251,23 @@ class Project extends Base
->asc('name')
->findAll();
- $boardModel = new Board($this->db, $this->event);
- $taskModel = new Task($this->db, $this->event);
- $aclModel = new Acl($this->db, $this->event);
-
foreach ($projects as $pkey => &$project) {
- if ($check_permissions && ! $this->isUserAllowed($project['id'], $aclModel->getUserId())) {
+ if ($check_permissions && ! $this->isUserAllowed($project['id'], $this->acl->getUserId())) {
unset($projects[$pkey]);
}
else {
- $columns = $boardModel->getcolumns($project['id']);
+ $columns = $this->board->getcolumns($project['id']);
$project['nb_active_tasks'] = 0;
foreach ($columns as &$column) {
- $column['nb_active_tasks'] = $taskModel->countByColumnId($project['id'], $column['id']);
+ $column['nb_active_tasks'] = $this->task->countByColumnId($project['id'], $column['id']);
$project['nb_active_tasks'] += $column['nb_active_tasks'];
}
$project['columns'] = $columns;
- $project['nb_tasks'] = $taskModel->countByProjectId($project['id']);
+ $project['nb_tasks'] = $this->task->countByProjectId($project['id']);
$project['nb_inactive_tasks'] = $project['nb_tasks'] - $project['nb_active_tasks'];
}
}
@@ -416,9 +410,8 @@ class Project extends Base
*/
public function copyBoardFromAnotherProject($project_from, $project_to)
{
- $boardModel = new Board($this->db, $this->event);
$columns = $this->db->table(Board::TABLE)->eq('project_id', $project_from)->asc('position')->findAllByColumn('title');
- return $boardModel->create($project_to, $columns);
+ return $this->board->create($project_to, $columns);
}
/**
@@ -431,8 +424,7 @@ class Project extends Base
*/
public function copyCategoriesFromAnotherProject($project_from, $project_to)
{
- $categoryModel = new Category($this->db, $this->event);
- $categoriesTemplate = $categoryModel->getAll($project_from);
+ $categoriesTemplate = $this->category->getAll($project_from);
foreach ($categoriesTemplate as $category) {
@@ -478,8 +470,7 @@ class Project extends Base
*/
public function copyActionsFromAnotherProject($project_from, $project_to)
{
- $actionModel = new Action($this->db, $this->event);
- $actionTemplate = $actionModel->getAllByProject($project_from);
+ $actionTemplate = $this->action->getAllByProject($project_from);
foreach ($actionTemplate as $action) {
@@ -522,13 +513,11 @@ class Project extends Base
case 'project_id':
return $project_to;
case 'category_id':
- $categoryModel = new Category($this->db, $this->event);
- $categoryTemplate = $categoryModel->getById($param['value']);
+ $categoryTemplate = $this->category->getById($param['value']);
$categoryFromNewProject = $this->db->table(Category::TABLE)->eq('project_id', $project_to)->eq('name', $categoryTemplate['name'])->findOne();
return $categoryFromNewProject['id'];
case 'column_id':
- $boardModel = new Board($this->db, $this->event);
- $boardTemplate = $boardModel->getColumn($param['value']);
+ $boardTemplate = $this->board->getColumn($param['value']);
$boardFromNewProject = $this->db->table(Board::TABLE)->eq('project_id', $project_to)->eq('title', $boardTemplate['title'])->findOne();
return $boardFromNewProject['id'];
default:
@@ -603,8 +592,7 @@ class Project extends Base
$project_id = $this->db->getConnection()->getLastId();
- $boardModel = new Board($this->db, $this->event);
- $boardModel->create($project_id, array(
+ $this->board->create($project_id, array(
t('Backlog'),
t('Ready'),
t('Work in progress'),
diff --git a/app/Model/RememberMe.php b/app/Model/RememberMe.php
index 272b49161..e23ed8870 100644
--- a/app/Model/RememberMe.php
+++ b/app/Model/RememberMe.php
@@ -92,11 +92,8 @@ class RememberMe extends Base
);
// Create the session
- $user = new User($this->db, $this->event);
- $acl = new Acl($this->db, $this->event);
-
- $user->updateSession($user->getById($record['user_id']));
- $acl->isRememberMe(true);
+ $this->user->updateSession($this->user->getById($record['user_id']));
+ $this->acl->isRememberMe(true);
return true;
}
diff --git a/app/Model/ReverseProxyAuth.php b/app/Model/ReverseProxyAuth.php
index 1b9ed06ce..14d18ba34 100644
--- a/app/Model/ReverseProxyAuth.php
+++ b/app/Model/ReverseProxyAuth.php
@@ -23,24 +23,22 @@ class ReverseProxyAuth extends Base
if (isset($_SERVER[REVERSE_PROXY_USER_HEADER])) {
$login = $_SERVER[REVERSE_PROXY_USER_HEADER];
- $userModel = new User($this->db, $this->event);
- $user = $userModel->getByUsername($login);
+ $user = $this->user->getByUsername($login);
if (! $user) {
$this->createUser($login);
- $user = $userModel->getByUsername($login);
+ $user = $this->user->getByUsername($login);
}
// Create the user session
- $userModel->updateSession($user);
+ $this->user->updateSession($user);
// Update login history
- $lastLogin = new LastLogin($this->db, $this->event);
- $lastLogin->create(
+ $this->lastLogin->create(
LastLogin::AUTH_REVERSE_PROXY,
$user['id'],
- $userModel->getIpAddress(),
- $userModel->getUserAgent()
+ $this->user->getIpAddress(),
+ $this->user->getUserAgent()
);
return true;
@@ -58,9 +56,7 @@ class ReverseProxyAuth extends Base
*/
private function createUser($login)
{
- $userModel = new User($this->db, $this->event);
-
- return $userModel->create(array(
+ return $this->user->create(array(
'email' => strpos($login, '@') !== false ? $login : '',
'username' => $login,
'is_admin' => REVERSE_PROXY_DEFAULT_ADMIN === $login,
diff --git a/app/Model/SubTask.php b/app/Model/SubTask.php
index 21ccdaac5..c7bab44b4 100644
--- a/app/Model/SubTask.php
+++ b/app/Model/SubTask.php
@@ -41,6 +41,14 @@ class SubTask extends Base
*/
const STATUS_TODO = 0;
+ /**
+ * Events
+ *
+ * @var string
+ */
+ const EVENT_UPDATE = 'subtask.update';
+ const EVENT_CREATE = 'subtask.create';
+
/**
* Get available status
*
@@ -88,10 +96,27 @@ class SubTask extends Base
*
* @access public
* @param integer $subtask_id Subtask id
+ * @param bool $more Fetch more data
* @return array
*/
- public function getById($subtask_id)
+ public function getById($subtask_id, $more = false)
{
+ if ($more) {
+
+ $subtask = $this->db->table(self::TABLE)
+ ->eq(self::TABLE.'.id', $subtask_id)
+ ->columns(self::TABLE.'.*', User::TABLE.'.username', User::TABLE.'.name')
+ ->join(User::TABLE, 'id', 'user_id')
+ ->findOne();
+
+ if ($subtask) {
+ $status = $this->getStatusList();
+ $subtask['status_name'] = $status[$subtask['status']];
+ }
+
+ return $subtask;
+ }
+
return $this->db->table(self::TABLE)->eq('id', $subtask_id)->findOne();
}
@@ -116,7 +141,14 @@ class SubTask extends Base
$values['time_spent'] = 0;
}
- return $this->db->table(self::TABLE)->save($values);
+ $result = $this->db->table(self::TABLE)->save($values);
+
+ if ($result) {
+ $values['id'] = $this->db->getConnection()->getLastId();
+ $this->event->trigger(self::EVENT_CREATE, $values);
+ }
+
+ return $result;
}
/**
@@ -136,7 +168,13 @@ class SubTask extends Base
$values['time_spent'] = 0;
}
- return $this->db->table(self::TABLE)->eq('id', $values['id'])->save($values);
+ $result = $this->db->table(self::TABLE)->eq('id', $values['id'])->save($values);
+
+ if ($result) {
+ $this->event->trigger(self::EVENT_UPDATE, $values);
+ }
+
+ return $result;
}
/**
diff --git a/app/Model/Task.php b/app/Model/Task.php
index 0b2b9cf9b..6647f0418 100644
--- a/app/Model/Task.php
+++ b/app/Model/Task.php
@@ -62,6 +62,35 @@ class Task extends Base
);
}
+ /**
+ * Get a list of due tasks for all projects
+ *
+ * @access public
+ * @return array
+ */
+ public function getTasksDue()
+ {
+ $tasks = $this->db->table(self::TABLE)
+ ->columns(
+ self::TABLE.'.id',
+ self::TABLE.'.title',
+ self::TABLE.'.date_due',
+ self::TABLE.'.project_id',
+ Project::TABLE.'.name AS project_name',
+ User::TABLE.'.username AS assignee_username',
+ User::TABLE.'.name AS assignee_name'
+ )
+ ->join(Project::TABLE, 'id', 'project_id')
+ ->join(User::TABLE, 'id', 'owner_id')
+ ->eq(Project::TABLE.'.is_active', 1)
+ ->eq(self::TABLE.'.is_active', 1)
+ ->neq(self::TABLE.'.date_due', '')
+ ->lte(self::TABLE.'.date_due', mktime(23, 59, 59))
+ ->findAll();
+
+ return $tasks;
+ }
+
/**
* Fetch one task
*
@@ -182,7 +211,7 @@ class Task extends Base
'tasks.category_id',
'users.username'
)
- ->join('users', 'id', 'owner_id');
+ ->join(User::TABLE, 'id', 'owner_id');
foreach ($filters as $key => $filter) {
@@ -282,8 +311,6 @@ class Task extends Base
{
$this->db->startTransaction();
- $boardModel = new Board($this->db, $this->event);
-
// Get the original task
$task = $this->getById($task_id);
@@ -296,7 +323,7 @@ class Task extends Base
$task['owner_id'] = 0;
$task['category_id'] = 0;
$task['is_active'] = 1;
- $task['column_id'] = $boardModel->getFirstColumn($project_id);
+ $task['column_id'] = $this->board->getFirstColumn($project_id);
$task['project_id'] = $project_id;
$task['position'] = $this->countByColumnId($task['project_id'], $task['column_id']);
@@ -317,6 +344,32 @@ class Task extends Base
return $task_id;
}
+ /**
+ * Prepare data before task creation or modification
+ *
+ * @access public
+ * @param array $values Form values
+ */
+ public function prepare(array &$values)
+ {
+ if (isset($values['another_task'])) {
+ unset($values['another_task']);
+ }
+
+ if (! empty($values['date_due']) && ! is_numeric($values['date_due'])) {
+ $values['date_due'] = $this->parseDate($values['date_due']);
+ }
+
+ // Force integer fields at 0 (for Postgresql)
+ if (isset($values['date_due']) && empty($values['date_due'])) {
+ $values['date_due'] = 0;
+ }
+
+ if (isset($values['score']) && empty($values['score'])) {
+ $values['score'] = 0;
+ }
+ }
+
/**
* Create a task
*
@@ -329,21 +382,7 @@ class Task extends Base
$this->db->startTransaction();
// Prepare data
- if (isset($values['another_task'])) {
- unset($values['another_task']);
- }
-
- if (! empty($values['date_due']) && ! is_numeric($values['date_due'])) {
- $values['date_due'] = $this->parseDate($values['date_due']);
- }
- else {
- $values['date_due'] = 0;
- }
-
- if (empty($values['score'])) {
- $values['score'] = 0;
- }
-
+ $this->prepare($values);
$values['date_creation'] = time();
$values['position'] = $this->countByColumnId($values['project_id'], $values['column_id']);
@@ -368,31 +407,21 @@ class Task extends Base
* Update a task
*
* @access public
- * @param array $values Form values
+ * @param array $values Form values
+ * @param boolean $trigger_events Flag to trigger events
* @return boolean
*/
- public function update(array $values)
+ public function update(array $values, $trigger_events = true)
{
- // Prepare data
- if (! empty($values['date_due']) && ! is_numeric($values['date_due'])) {
- $values['date_due'] = $this->parseDate($values['date_due']);
- }
-
- // Force integer fields at 0 (for Postgresql)
- if (isset($values['date_due']) && empty($values['date_due'])) {
- $values['date_due'] = 0;
- }
-
- if (isset($values['score']) && empty($values['score'])) {
- $values['score'] = 0;
- }
-
+ // Fetch original task
$original_task = $this->getById($values['id']);
if ($original_task === false) {
return false;
}
+ // Prepare data
+ $this->prepare($values);
$updated_task = $values;
$updated_task['date_modification'] = time();
unset($updated_task['id']);
@@ -400,31 +429,42 @@ class Task extends Base
$result = $this->db->table(self::TABLE)->eq('id', $values['id'])->update($updated_task);
// Trigger events
- if ($result) {
-
- $events = array(
- self::EVENT_CREATE_UPDATE,
- self::EVENT_UPDATE,
- );
-
- if (isset($values['column_id']) && $original_task['column_id'] != $values['column_id']) {
- $events[] = self::EVENT_MOVE_COLUMN;
- }
- else if (isset($values['position']) && $original_task['position'] != $values['position']) {
- $events[] = self::EVENT_MOVE_POSITION;
- }
-
- $event_data = array_merge($original_task, $values);
- $event_data['task_id'] = $original_task['id'];
-
- foreach ($events as $event) {
- $this->event->trigger($event, $event_data);
- }
+ if ($result && $trigger_events) {
+ $this->triggerUpdateEvents($original_task, $updated_task);
}
return $result;
}
+ /**
+ * Trigger events for task modification
+ *
+ * @access public
+ * @param array $original_task Original task data
+ * @param array $updated_task Updated task data
+ */
+ public function triggerUpdateEvents(array $original_task, array $updated_task)
+ {
+ $events = array(
+ self::EVENT_CREATE_UPDATE,
+ self::EVENT_UPDATE,
+ );
+
+ if (isset($updated_task['column_id']) && $original_task['column_id'] != $updated_task['column_id']) {
+ $events[] = self::EVENT_MOVE_COLUMN;
+ }
+ else if (isset($updated_task['position']) && $original_task['position'] != $updated_task['position']) {
+ $events[] = self::EVENT_MOVE_POSITION;
+ }
+
+ $event_data = array_merge($original_task, $updated_task);
+ $event_data['task_id'] = $original_task['id'];
+
+ foreach ($events as $event) {
+ $this->event->trigger($event, $event_data);
+ }
+ }
+
/**
* Mark a task closed
*
@@ -482,8 +522,7 @@ class Task extends Base
*/
public function remove($task_id)
{
- $file = new File($this->db, $this->event);
- $file->removeAll($task_id);
+ $this->file->removeAll($task_id);
return $this->db->table(self::TABLE)->eq('id', $task_id)->remove();
}
@@ -492,20 +531,23 @@ class Task extends Base
* Move a task to another column or to another position
*
* @access public
- * @param integer $task_id Task id
- * @param integer $column_id Column id
- * @param integer $position Position (must be greater than 1)
+ * @param integer $task_id Task id
+ * @param integer $column_id Column id
+ * @param integer $position Position (must be greater than 1)
+ * @param boolean $trigger_events Flag to trigger events
* @return boolean
*/
- public function move($task_id, $column_id, $position)
+ public function move($task_id, $column_id, $position, $trigger_events = true)
{
$this->event->clearTriggeredEvents();
- return $this->update(array(
+ $values = array(
'id' => $task_id,
'column_id' => $column_id,
'position' => $position,
- ));
+ );
+
+ return $this->update($values, $trigger_events);
}
/**
diff --git a/app/Model/User.php b/app/Model/User.php
index b5744c442..d0e33fd0a 100644
--- a/app/Model/User.php
+++ b/app/Model/User.php
@@ -340,8 +340,7 @@ class User extends Base
$this->updateSession($user);
// Update login history
- $lastLogin = new LastLogin($this->db, $this->event);
- $lastLogin->create(
+ $this->lastLogin->create(
$method,
$user['id'],
$this->getIpAddress(),
@@ -350,9 +349,8 @@ class User extends Base
// Setup the remember me feature
if (! empty($values['remember_me'])) {
- $rememberMe = new RememberMe($this->db, $this->event);
- $credentials = $rememberMe->create($user['id'], $this->getIpAddress(), $this->getUserAgent());
- $rememberMe->writeCookie($credentials['token'], $credentials['sequence'], $credentials['expiration']);
+ $credentials = $this->rememberMe->create($user['id'], $this->getIpAddress(), $this->getUserAgent());
+ $this->rememberMe->writeCookie($credentials['token'], $credentials['sequence'], $credentials['expiration']);
}
}
else {
@@ -384,8 +382,7 @@ class User extends Base
// LDAP authentication
if (! $authenticated && LDAP_AUTH) {
- $ldap = new Ldap($this->db, $this->event);
- $authenticated = $ldap->authenticate($username, $password);
+ $authenticated = $this->ldap->authenticate($username, $password);
$method = LastLogin::AUTH_LDAP;
}
diff --git a/app/Model/Webhook.php b/app/Model/Webhook.php
index 679d3edc7..872031cc4 100644
--- a/app/Model/Webhook.php
+++ b/app/Model/Webhook.php
@@ -64,11 +64,9 @@ class Webhook extends Base
*/
public function attachEvents()
{
- $config = new Config($this->db, $this->event);
-
- $this->url_task_creation = $config->get('webhooks_url_task_creation');
- $this->url_task_modification = $config->get('webhooks_url_task_modification');
- $this->token = $config->get('webhooks_token');
+ $this->url_task_creation = $this->config->get('webhooks_url_task_creation');
+ $this->url_task_modification = $this->config->get('webhooks_url_task_modification');
+ $this->token = $this->config->get('webhooks_token');
if ($this->url_task_creation) {
$this->attachCreateEvents();
diff --git a/app/Schema/Mysql.php b/app/Schema/Mysql.php
index 46fc6d430..8f3ae5a13 100644
--- a/app/Schema/Mysql.php
+++ b/app/Schema/Mysql.php
@@ -4,7 +4,22 @@ namespace Schema;
use Core\Security;
-const VERSION = 22;
+const VERSION = 23;
+
+function version_23($pdo)
+{
+ $pdo->exec("ALTER TABLE users ADD COLUMN notifications_enabled TINYINT(1) DEFAULT '0'");
+
+ $pdo->exec("
+ CREATE TABLE user_has_notifications (
+ user_id INT,
+ project_id INT,
+ FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
+ FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE,
+ UNIQUE(project_id, user_id)
+ );
+ ");
+}
function version_22($pdo)
{
diff --git a/app/Schema/Postgres.php b/app/Schema/Postgres.php
index a9eea531c..ce77a4ed2 100644
--- a/app/Schema/Postgres.php
+++ b/app/Schema/Postgres.php
@@ -4,7 +4,22 @@ namespace Schema;
use Core\Security;
-const VERSION = 3;
+const VERSION = 4;
+
+function version_4($pdo)
+{
+ $pdo->exec("ALTER TABLE users ADD COLUMN notifications_enabled BOOLEAN DEFAULT '0'");
+
+ $pdo->exec("
+ CREATE TABLE user_has_notifications (
+ user_id INTEGER,
+ project_id INTEGER,
+ FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
+ FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE,
+ UNIQUE(project_id, user_id)
+ );
+ ");
+}
function version_3($pdo)
{
diff --git a/app/Schema/Sqlite.php b/app/Schema/Sqlite.php
index 4660251f6..c3a3f10e7 100644
--- a/app/Schema/Sqlite.php
+++ b/app/Schema/Sqlite.php
@@ -4,7 +4,22 @@ namespace Schema;
use Core\Security;
-const VERSION = 22;
+const VERSION = 23;
+
+function version_23($pdo)
+{
+ $pdo->exec("ALTER TABLE users ADD COLUMN notifications_enabled INTEGER DEFAULT '0'");
+
+ $pdo->exec("
+ CREATE TABLE user_has_notifications (
+ user_id INTEGER,
+ project_id INTEGER,
+ FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
+ FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE,
+ UNIQUE(project_id, user_id)
+ );
+ ");
+}
function version_22($pdo)
{
diff --git a/app/Templates/comment_edit.php b/app/Templates/comment_edit.php
index 52695f00d..4ce48964c 100644
--- a/app/Templates/comment_edit.php
+++ b/app/Templates/comment_edit.php
@@ -6,6 +6,7 @@
= Helper\form_csrf() ?>
= Helper\form_hidden('id', $values) ?>
+ = Helper\form_hidden('task_id', $values) ?>
= Helper\form_textarea('comment', $values, $errors, array('autofocus', 'required', 'placeholder="'.t('Leave a comment').'"'), 'comment-textarea') ?>
Kanboard
\ No newline at end of file diff --git a/app/Templates/notification_comment_update.php b/app/Templates/notification_comment_update.php new file mode 100644 index 000000000..9dbd23329 --- /dev/null +++ b/app/Templates/notification_comment_update.php @@ -0,0 +1,8 @@ +Kanboard
\ No newline at end of file diff --git a/app/Templates/notification_file_creation.php b/app/Templates/notification_file_creation.php new file mode 100644 index 000000000..57d69f517 --- /dev/null +++ b/app/Templates/notification_file_creation.php @@ -0,0 +1,6 @@ +Kanboard
\ No newline at end of file diff --git a/app/Templates/notification_subtask_creation.php b/app/Templates/notification_subtask_creation.php new file mode 100644 index 000000000..e02ceaf0f --- /dev/null +++ b/app/Templates/notification_subtask_creation.php @@ -0,0 +1,18 @@ +Kanboard
diff --git a/app/Templates/notification_subtask_update.php b/app/Templates/notification_subtask_update.php new file mode 100644 index 000000000..5ec5ed052 --- /dev/null +++ b/app/Templates/notification_subtask_update.php @@ -0,0 +1,22 @@ +Kanboard
\ No newline at end of file diff --git a/app/Templates/notification_task_close.php b/app/Templates/notification_task_close.php new file mode 100644 index 000000000..8b8cedb38 --- /dev/null +++ b/app/Templates/notification_task_close.php @@ -0,0 +1,6 @@ += t('The task #%d have been closed.', $task['id']) ?>
+ +Kanboard
\ No newline at end of file diff --git a/app/Templates/notification_task_creation.php b/app/Templates/notification_task_creation.php new file mode 100644 index 000000000..9515e889e --- /dev/null +++ b/app/Templates/notification_task_creation.php @@ -0,0 +1,44 @@ +Kanboard
\ No newline at end of file diff --git a/app/Templates/notification_task_due.php b/app/Templates/notification_task_due.php new file mode 100644 index 000000000..1686c7def --- /dev/null +++ b/app/Templates/notification_task_due.php @@ -0,0 +1,10 @@ +Kanboard
\ No newline at end of file diff --git a/app/Templates/notification_task_open.php b/app/Templates/notification_task_open.php new file mode 100644 index 000000000..2ef0f04ff --- /dev/null +++ b/app/Templates/notification_task_open.php @@ -0,0 +1,6 @@ += t('The task #%d have been opened.', $task['id']) ?>
+ +Kanboard
\ No newline at end of file diff --git a/app/Templates/notification_task_update.php b/app/Templates/notification_task_update.php new file mode 100644 index 000000000..28be9db2b --- /dev/null +++ b/app/Templates/notification_task_update.php @@ -0,0 +1,44 @@ +Kanboard
\ No newline at end of file diff --git a/app/common.php b/app/common.php index 312b930b8..9ce0016ab 100644 --- a/app/common.php +++ b/app/common.php @@ -4,6 +4,8 @@ require __DIR__.'/Core/Loader.php'; require __DIR__.'/helpers.php'; require __DIR__.'/translator.php'; +require 'vendor/swiftmailer/swift_required.php'; + use Core\Event; use Core\Loader; use Core\Registry; @@ -63,6 +65,15 @@ defined('REVERSE_PROXY_AUTH') or define('REVERSE_PROXY_AUTH', false); defined('REVERSE_PROXY_USER_HEADER') or define('REVERSE_PROXY_USER_HEADER', 'REMOTE_USER'); defined('REVERSE_PROXY_DEFAULT_ADMIN') or define('REVERSE_PROXY_DEFAULT_ADMIN', ''); +// Mail configuration +defined('MAIL_FROM') or define('MAIL_FROM', 'notifications@kanboard.net'); +defined('MAIL_TRANSPORT') or define('MAIL_TRANSPORT', 'mail'); +defined('MAIL_SMTP_HOSTNAME') or define('MAIL_SMTP_HOSTNAME', ''); +defined('MAIL_SMTP_PORT') or define('MAIL_SMTP_PORT', 25); +defined('MAIL_SMTP_USERNAME') or define('MAIL_SMTP_USERNAME', ''); +defined('MAIL_SMTP_PASSWORD') or define('MAIL_SMTP_PASSWORD', ''); +defined('MAIL_SENDMAIL_COMMAND') or define('MAIL_SENDMAIL_COMMAND', '/usr/sbin/sendmail -bs'); + $loader = new Loader; $loader->execute(); @@ -126,3 +137,25 @@ $registry->db = function() use ($registry) { $registry->event = function() use ($registry) { return new Event; }; + +$registry->mailer = function() use ($registry) { + + require_once 'vendor/swiftmailer/swift_required.php'; + + $transport = null; + + switch (MAIL_TRANSPORT) { + case 'smtp': + $transport = Swift_SmtpTransport::newInstance(MAIL_SMTP_HOSTNAME, MAIL_SMTP_PORT); + $transport->setUsername(MAIL_SMTP_USERNAME); + $transport->setPassword(MAIL_SMTP_PASSWORD); + break; + case 'sendmail': + $transport = Swift_SendmailTransport::newInstance(MAIL_SENDMAIL_COMMAND); + break; + default: + $transport = Swift_MailTransport::newInstance(); + } + + return $transport; +}; diff --git a/assets/css/app.css b/assets/css/app.css index 3be402b61..fae922ab3 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -265,6 +265,10 @@ input.form-date { line-height: 25px; } +.form-checkbox-group label { + display: inline; +} + /* alerts */ .alert { padding: 8px 35px 8px 14px; diff --git a/assets/js/app.js b/assets/js/app.js index bf98d689a..78354dde1 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -77,7 +77,8 @@ Kanboard.Board = (function() { connectWith: ".column", placeholder: "draggable-placeholder", stop: function(event, ui) { - board_save(); + var task_id = parseInt(ui.item[0].getAttribute("data-task-id")); + board_save(task_id); } }); @@ -112,7 +113,7 @@ Kanboard.Board = (function() { } // Save and refresh the board - function board_save() + function board_save(selected_task_id) { var data = []; var boardSelector = $("#board"); @@ -135,7 +136,7 @@ Kanboard.Board = (function() { $.ajax({ cache: false, url: "?controller=board&action=save&project_id=" + projectId, - data: {"positions": data, "csrf_token": boardSelector.attr("data-csrf-token")}, + data: {"positions": data, "csrf_token": boardSelector.attr("data-csrf-token"), "selected_task_id": selected_task_id}, type: "POST", success: function(data) { $("#board").remove(); diff --git a/config.default.php b/config.default.php index b10287671..5f8313a90 100644 --- a/config.default.php +++ b/config.default.php @@ -1,5 +1,20 @@ shared('db'), $registry->shared('event')); -$project = new Project($registry->shared('db'), $registry->shared('event')); -$task = new Task($registry->shared('db'), $registry->shared('event')); -$user = new User($registry->shared('db'), $registry->shared('event')); -$category = new Category($registry->shared('db'), $registry->shared('event')); -$comment = new Comment($registry->shared('db'), $registry->shared('event')); -$subtask = new SubTask($registry->shared('db'), $registry->shared('event')); -$board = new Board($registry->shared('db'), $registry->shared('event')); -$action = new Action($registry->shared('db'), $registry->shared('event')); -$webhook = new Webhook($registry->shared('db'), $registry->shared('event')); +$config = new Config($registry); +$project = new Project($registry); +$task = new Task($registry); +$user = new User($registry); +$category = new Category($registry); +$comment = new Comment($registry); +$subtask = new SubTask($registry); +$board = new Board($registry); +$action = new Action($registry); +$webhook = new Webhook($registry); +$notification = new Notification($registry); $action->attachEvents(); $project->attachEvents(); $webhook->attachEvents(); +$notification->attachEvents(); + +// Load translations +$language = $config->get('language', 'en_US'); +if ($language !== 'en_US') Translator::load($language); $server = new Server; $server->authentication(array('jsonrpc' => $config->get('api_token'))); diff --git a/kanboard b/kanboard index 95a977bdb..34e183e49 100755 --- a/kanboard +++ b/kanboard @@ -8,8 +8,9 @@ use Core\Tool; use Core\Translator; use Model\Config; use Model\Task; +use Model\Notification; -$config = new Config($registry->shared('db'), $registry->shared('event')); +$config = new Config($registry); // Load translations $language = $config->get('language', 'en_US'); @@ -25,6 +26,7 @@ $cli = new Cli; $cli->register('help', function() { echo 'Kanboard command line interface'.PHP_EOL.'==============================='.PHP_EOL.PHP_EOL; echo '- Task export to stdout (CSV format): '.$GLOBALS['argv'][0].' export-csv
+ * setNameAddresses(array(
+ * 'chris@swiftmailer.org' => 'Chris Corbyn',
+ * 'mark@swiftmailer.org' //No associated personal name
+ * ));
+ * ?>
+ *
+ *
+ * @see __construct()
+ * @see setAddresses()
+ * @see setValue()
+ *
+ * @param string|string[] $mailboxes
+ *
+ * @throws Swift_RfcComplianceException
+ */
+ public function setNameAddresses($mailboxes)
+ {
+ $this->_mailboxes = $this->normalizeMailboxes((array) $mailboxes);
+ $this->setCachedValue(null); //Clear any cached value
+ }
+
+ /**
+ * Get the full mailbox list of this Header as an array of valid RFC 2822 strings.
+ *
+ * Example:
+ *
+ * 'Chris Corbyn',
+ * 'mark@swiftmailer.org' => 'Mark Corbyn')
+ * );
+ * print_r($header->getNameAddressStrings());
+ * // array (
+ * // 0 => Chris Corbyn ,
+ * // 1 => Mark Corbyn
+ * // )
+ * ?>
+ *
+ *
+ * @see getNameAddresses()
+ * @see toString()
+ *
+ * @return string[]
+ *
+ * @throws Swift_RfcComplianceException
+ */
+ public function getNameAddressStrings()
+ {
+ return $this->_createNameAddressStrings($this->getNameAddresses());
+ }
+
+ /**
+ * Get all mailboxes in this Header as key=>value pairs.
+ *
+ * The key is the address and the value is the name (or null if none set).
+ * Example:
+ *
+ * 'Chris Corbyn',
+ * 'mark@swiftmailer.org' => 'Mark Corbyn')
+ * );
+ * print_r($header->getNameAddresses());
+ * // array (
+ * // chris@swiftmailer.org => Chris Corbyn,
+ * // mark@swiftmailer.org => Mark Corbyn
+ * // )
+ * ?>
+ *
+ *
+ * @see getAddresses()
+ * @see getNameAddressStrings()
+ *
+ * @return string[]
+ */
+ public function getNameAddresses()
+ {
+ return $this->_mailboxes;
+ }
+
+ /**
+ * Makes this Header represent a list of plain email addresses with no names.
+ *
+ * Example:
+ *
+ * setAddresses(
+ * array('one@domain.tld', 'two@domain.tld', 'three@domain.tld')
+ * );
+ * ?>
+ *
+ *
+ * @see setNameAddresses()
+ * @see setValue()
+ *
+ * @param string[] $addresses
+ *
+ * @throws Swift_RfcComplianceException
+ */
+ public function setAddresses($addresses)
+ {
+ $this->setNameAddresses(array_values((array) $addresses));
+ }
+
+ /**
+ * Get all email addresses in this Header.
+ *
+ * @see getNameAddresses()
+ *
+ * @return string[]
+ */
+ public function getAddresses()
+ {
+ return array_keys($this->_mailboxes);
+ }
+
+ /**
+ * Remove one or more addresses from this Header.
+ *
+ * @param string|string[] $addresses
+ */
+ public function removeAddresses($addresses)
+ {
+ $this->setCachedValue(null);
+ foreach ((array) $addresses as $address) {
+ unset($this->_mailboxes[$address]);
+ }
+ }
+
+ /**
+ * Get the string value of the body in this Header.
+ *
+ * This is not necessarily RFC 2822 compliant since folding white space will
+ * not be added at this stage (see {@link toString()} for that).
+ *
+ * @see toString()
+ *
+ * @return string
+ *
+ * @throws Swift_RfcComplianceException
+ */
+ public function getFieldBody()
+ {
+ // Compute the string value of the header only if needed
+ if (is_null($this->getCachedValue())) {
+ $this->setCachedValue($this->createMailboxListString($this->_mailboxes));
+ }
+
+ return $this->getCachedValue();
+ }
+
+ // -- Points of extension
+
+ /**
+ * Normalizes a user-input list of mailboxes into consistent key=>value pairs.
+ *
+ * @param string[] $mailboxes
+ *
+ * @return string[]
+ */
+ protected function normalizeMailboxes(array $mailboxes)
+ {
+ $actualMailboxes = array();
+
+ foreach ($mailboxes as $key => $value) {
+ if (is_string($key)) { //key is email addr
+ $address = $key;
+ $name = $value;
+ } else {
+ $address = $value;
+ $name = null;
+ }
+ $this->_assertValidAddress($address);
+ $actualMailboxes[$address] = $name;
+ }
+
+ return $actualMailboxes;
+ }
+
+ /**
+ * Produces a compliant, formatted display-name based on the string given.
+ *
+ * @param string $displayName as displayed
+ * @param bool $shorten the first line to make remove for header name
+ *
+ * @return string
+ */
+ protected function createDisplayNameString($displayName, $shorten = false)
+ {
+ return $this->createPhrase($this, $displayName,
+ $this->getCharset(), $this->getEncoder(), $shorten
+ );
+ }
+
+ /**
+ * Creates a string form of all the mailboxes in the passed array.
+ *
+ * @param string[] $mailboxes
+ *
+ * @return string
+ *
+ * @throws Swift_RfcComplianceException
+ */
+ protected function createMailboxListString(array $mailboxes)
+ {
+ return implode(', ', $this->_createNameAddressStrings($mailboxes));
+ }
+
+ /**
+ * Redefine the encoding requirements for mailboxes.
+ *
+ * Commas and semicolons are used to separate
+ * multiple addresses, and should therefore be encoded
+ *
+ * @param string $token
+ *
+ * @return bool
+ */
+ protected function tokenNeedsEncoding($token)
+ {
+ return preg_match('/[,;]/', $token) || parent::tokenNeedsEncoding($token);
+ }
+
+ /**
+ * Return an array of strings conforming the the name-addr spec of RFC 2822.
+ *
+ * @param string[] $mailboxes
+ *
+ * @return string[]
+ */
+ private function _createNameAddressStrings(array $mailboxes)
+ {
+ $strings = array();
+
+ foreach ($mailboxes as $email => $name) {
+ $mailboxStr = $email;
+ if (!is_null($name)) {
+ $nameStr = $this->createDisplayNameString($name, empty($strings));
+ $mailboxStr = $nameStr . ' <' . $mailboxStr . '>';
+ }
+ $strings[] = $mailboxStr;
+ }
+
+ return $strings;
+ }
+
+ /**
+ * Throws an Exception if the address passed does not comply with RFC 2822.
+ *
+ * @param string $address
+ *
+ * @throws Swift_RfcComplianceException If invalid.
+ */
+ private function _assertValidAddress($address)
+ {
+ if (!preg_match('/^' . $this->getGrammar()->getDefinition('addr-spec') . '$/D',
+ $address))
+ {
+ throw new Swift_RfcComplianceException(
+ 'Address in mailbox given [' . $address .
+ '] does not comply with RFC 2822, 3.6.2.'
+ );
+ }
+ }
+}
diff --git a/vendor/swiftmailer/classes/Swift/Mime/Headers/OpenDKIMHeader.php b/vendor/swiftmailer/classes/Swift/Mime/Headers/OpenDKIMHeader.php
new file mode 100644
index 000000000..0b72e15db
--- /dev/null
+++ b/vendor/swiftmailer/classes/Swift/Mime/Headers/OpenDKIMHeader.php
@@ -0,0 +1,137 @@
+
+ */
+class Swift_Mime_Headers_OpenDKIMHeader implements Swift_Mime_Header
+{
+ /**
+ * The value of this Header.
+ *
+ * @var string
+ */
+ private $_value;
+
+ /**
+ * The name of this Header
+ * @var string
+ */
+ private $_fieldName;
+
+ /**
+ * Creates a new SimpleHeader with $name.
+ *
+ * @param string $name
+ * @param Swift_Mime_HeaderEncoder $encoder
+ * @param Swift_Mime_Grammar $grammar
+ */
+ public function __construct($name)
+ {
+ $this->_fieldName = $name;
+ }
+
+ /**
+ * Get the type of Header that this instance represents.
+ *
+ * @see TYPE_TEXT, TYPE_PARAMETERIZED, TYPE_MAILBOX
+ * @see TYPE_DATE, TYPE_ID, TYPE_PATH
+ *
+ * @return int
+ */
+ public function getFieldType()
+ {
+ return self::TYPE_TEXT;
+ }
+
+ /**
+ * Set the model for the field body.
+ *
+ * This method takes a string for the field value.
+ *
+ * @param string $model
+ */
+ public function setFieldBodyModel($model)
+ {
+ $this->setValue($model);
+ }
+
+ /**
+ * Get the model for the field body.
+ *
+ * This method returns a string.
+ *
+ * @return string
+ */
+ public function getFieldBodyModel()
+ {
+ return $this->getValue();
+ }
+
+ /**
+ * Get the (unencoded) value of this header.
+ *
+ * @return string
+ */
+ public function getValue()
+ {
+ return $this->_value;
+ }
+
+ /**
+ * Set the (unencoded) value of this header.
+ *
+ * @param string $value
+ */
+ public function setValue($value)
+ {
+ $this->_value = $value;
+ }
+
+ /**
+ * Get the value of this header prepared for rendering.
+ *
+ * @return string
+ */
+ public function getFieldBody()
+ {
+ return $this->_value;
+ }
+
+ /**
+ * Get this Header rendered as a RFC 2822 compliant string.
+ *
+ * @return string
+ */
+ public function toString()
+ {
+ return $this->_fieldName.': '.$this->_value;
+ }
+
+ /**
+ * Set the Header FieldName
+ * @see Swift_Mime_Header::getFieldName()
+ */
+ public function getFieldName()
+ {
+ return $this->_fieldName;
+ }
+
+ /**
+ * Ignored
+ */
+ public function setCharset($charset)
+ {
+
+ }
+
+}
diff --git a/vendor/swiftmailer/classes/Swift/Mime/Headers/ParameterizedHeader.php b/vendor/swiftmailer/classes/Swift/Mime/Headers/ParameterizedHeader.php
new file mode 100644
index 000000000..6bfdc9bc2
--- /dev/null
+++ b/vendor/swiftmailer/classes/Swift/Mime/Headers/ParameterizedHeader.php
@@ -0,0 +1,260 @@
+_paramEncoder = $paramEncoder;
+ }
+
+ /**
+ * Get the type of Header that this instance represents.
+ *
+ * @see TYPE_TEXT, TYPE_PARAMETERIZED, TYPE_MAILBOX
+ * @see TYPE_DATE, TYPE_ID, TYPE_PATH
+ *
+ * @return int
+ */
+ public function getFieldType()
+ {
+ return self::TYPE_PARAMETERIZED;
+ }
+
+ /**
+ * Set the character set used in this Header.
+ *
+ * @param string $charset
+ */
+ public function setCharset($charset)
+ {
+ parent::setCharset($charset);
+ if (isset($this->_paramEncoder)) {
+ $this->_paramEncoder->charsetChanged($charset);
+ }
+ }
+
+ /**
+ * Set the value of $parameter.
+ *
+ * @param string $parameter
+ * @param string $value
+ */
+ public function setParameter($parameter, $value)
+ {
+ $this->setParameters(array_merge($this->getParameters(), array($parameter => $value)));
+ }
+
+ /**
+ * Get the value of $parameter.
+ *
+ * @param string $parameter
+ *
+ * @return string
+ */
+ public function getParameter($parameter)
+ {
+ $params = $this->getParameters();
+
+ return array_key_exists($parameter, $params)
+ ? $params[$parameter]
+ : null;
+ }
+
+ /**
+ * Set an associative array of parameter names mapped to values.
+ *
+ * @param string[] $parameters
+ */
+ public function setParameters(array $parameters)
+ {
+ $this->clearCachedValueIf($this->_params != $parameters);
+ $this->_params = $parameters;
+ }
+
+ /**
+ * Returns an associative array of parameter names mapped to values.
+ *
+ * @return string[]
+ */
+ public function getParameters()
+ {
+ return $this->_params;
+ }
+
+ /**
+ * Get the value of this header prepared for rendering.
+ *
+ * @return string
+ */
+ public function getFieldBody() //TODO: Check caching here
+ {
+ $body = parent::getFieldBody();
+ foreach ($this->_params as $name => $value) {
+ if (!is_null($value)) {
+ // Add the parameter
+ $body .= '; ' . $this->_createParameter($name, $value);
+ }
+ }
+
+ return $body;
+ }
+
+
+ /**
+ * Generate a list of all tokens in the final header.
+ *
+ * This doesn't need to be overridden in theory, but it is for implementation
+ * reasons to prevent potential breakage of attributes.
+ *
+ * @param string $string The string to tokenize
+ *
+ * @return array An array of tokens as strings
+ */
+ protected function toTokens($string = null)
+ {
+ $tokens = parent::toTokens(parent::getFieldBody());
+
+ // Try creating any parameters
+ foreach ($this->_params as $name => $value) {
+ if (!is_null($value)) {
+ // Add the semi-colon separator
+ $tokens[count($tokens)-1] .= ';';
+ $tokens = array_merge($tokens, $this->generateTokenLines(
+ ' ' . $this->_createParameter($name, $value)
+ ));
+ }
+ }
+
+ return $tokens;
+ }
+
+ /**
+ * Render a RFC 2047 compliant header parameter from the $name and $value.
+ *
+ * @param string $name
+ * @param string $value
+ *
+ * @return string
+ */
+ private function _createParameter($name, $value)
+ {
+ $origValue = $value;
+
+ $encoded = false;
+ // Allow room for parameter name, indices, "=" and DQUOTEs
+ $maxValueLength = $this->getMaxLineLength() - strlen($name . '=*N"";') - 1;
+ $firstLineOffset = 0;
+
+ // If it's not already a valid parameter value...
+ if (!preg_match('/^' . self::TOKEN_REGEX . '$/D', $value)) {
+ // TODO: text, or something else??
+ // ... and it's not ascii
+ if (!preg_match('/^' . $this->getGrammar()->getDefinition('text') . '*$/D', $value)) {
+ $encoded = true;
+ // Allow space for the indices, charset and language
+ $maxValueLength = $this->getMaxLineLength() - strlen($name . '*N*="";') - 1;
+ $firstLineOffset = strlen(
+ $this->getCharset() . "'" . $this->getLanguage() . "'"
+ );
+ }
+ }
+
+ // Encode if we need to
+ if ($encoded || strlen($value) > $maxValueLength) {
+ if (isset($this->_paramEncoder)) {
+ $value = $this->_paramEncoder->encodeString(
+ $origValue, $firstLineOffset, $maxValueLength, $this->getCharset()
+ );
+ } else { // We have to go against RFC 2183/2231 in some areas for interoperability
+ $value = $this->getTokenAsEncodedWord($origValue);
+ $encoded = false;
+ }
+ }
+
+ $valueLines = isset($this->_paramEncoder) ? explode("\r\n", $value) : array($value);
+
+ // Need to add indices
+ if (count($valueLines) > 1) {
+ $paramLines = array();
+ foreach ($valueLines as $i => $line) {
+ $paramLines[] = $name . '*' . $i .
+ $this->_getEndOfParameterValue($line, true, $i == 0);
+ }
+
+ return implode(";\r\n ", $paramLines);
+ } else {
+ return $name . $this->_getEndOfParameterValue(
+ $valueLines[0], $encoded, true
+ );
+ }
+ }
+
+ /**
+ * Returns the parameter value from the "=" and beyond.
+ *
+ * @param string $value to append
+ * @param bool $encoded
+ * @param bool $firstLine
+ *
+ * @return string
+ */
+ private function _getEndOfParameterValue($value, $encoded = false, $firstLine = false)
+ {
+ if (!preg_match('/^' . self::TOKEN_REGEX . '$/D', $value)) {
+ $value = '"' . $value . '"';
+ }
+ $prepend = '=';
+ if ($encoded) {
+ $prepend = '*=';
+ if ($firstLine) {
+ $prepend = '*=' . $this->getCharset() . "'" . $this->getLanguage() .
+ "'";
+ }
+ }
+
+ return $prepend . $value;
+ }
+}
diff --git a/vendor/swiftmailer/classes/Swift/Mime/Headers/PathHeader.php b/vendor/swiftmailer/classes/Swift/Mime/Headers/PathHeader.php
new file mode 100644
index 000000000..9db2f9f48
--- /dev/null
+++ b/vendor/swiftmailer/classes/Swift/Mime/Headers/PathHeader.php
@@ -0,0 +1,144 @@
+setFieldName($name);
+ parent::__construct($grammar);
+ }
+
+ /**
+ * Get the type of Header that this instance represents.
+ *
+ * @see TYPE_TEXT, TYPE_PARAMETERIZED, TYPE_MAILBOX
+ * @see TYPE_DATE, TYPE_ID, TYPE_PATH
+ *
+ * @return int
+ */
+ public function getFieldType()
+ {
+ return self::TYPE_PATH;
+ }
+
+ /**
+ * Set the model for the field body.
+ * This method takes a string for an address.
+ *
+ * @param string $model
+ *
+ * @throws Swift_RfcComplianceException
+ */
+ public function setFieldBodyModel($model)
+ {
+ $this->setAddress($model);
+ }
+
+ /**
+ * Get the model for the field body.
+ * This method returns a string email address.
+ *
+ * @return mixed
+ */
+ public function getFieldBodyModel()
+ {
+ return $this->getAddress();
+ }
+
+ /**
+ * Set the Address which should appear in this Header.
+ *
+ * @param string $address
+ *
+ * @throws Swift_RfcComplianceException
+ */
+ public function setAddress($address)
+ {
+ if (is_null($address)) {
+ $this->_address = null;
+ } elseif ('' == $address) {
+ $this->_address = '';
+ } else {
+ $this->_assertValidAddress($address);
+ $this->_address = $address;
+ }
+ $this->setCachedValue(null);
+ }
+
+ /**
+ * Get the address which is used in this Header (if any).
+ *
+ * Null is returned if no address is set.
+ *
+ * @return string
+ */
+ public function getAddress()
+ {
+ return $this->_address;
+ }
+
+ /**
+ * Get the string value of the body in this Header.
+ *
+ * This is not necessarily RFC 2822 compliant since folding white space will
+ * not be added at this stage (see {@link toString()} for that).
+ *
+ * @see toString()
+ *
+ * @return string
+ */
+ public function getFieldBody()
+ {
+ if (!$this->getCachedValue()) {
+ if (isset($this->_address)) {
+ $this->setCachedValue('<' . $this->_address . '>');
+ }
+ }
+
+ return $this->getCachedValue();
+ }
+
+ /**
+ * Throws an Exception if the address passed does not comply with RFC 2822.
+ *
+ * @param string $address
+ *
+ * @throws Swift_RfcComplianceException If address is invalid
+ */
+ private function _assertValidAddress($address)
+ {
+ if (!preg_match('/^' . $this->getGrammar()->getDefinition('addr-spec') . '$/D',
+ $address))
+ {
+ throw new Swift_RfcComplianceException(
+ 'Address set in PathHeader does not comply with addr-spec of RFC 2822.'
+ );
+ }
+ }
+}
diff --git a/vendor/swiftmailer/classes/Swift/Mime/Headers/UnstructuredHeader.php b/vendor/swiftmailer/classes/Swift/Mime/Headers/UnstructuredHeader.php
new file mode 100644
index 000000000..41d4e63d7
--- /dev/null
+++ b/vendor/swiftmailer/classes/Swift/Mime/Headers/UnstructuredHeader.php
@@ -0,0 +1,112 @@
+setFieldName($name);
+ $this->setEncoder($encoder);
+ parent::__construct($grammar);
+ }
+
+ /**
+ * Get the type of Header that this instance represents.
+ *
+ * @see TYPE_TEXT, TYPE_PARAMETERIZED, TYPE_MAILBOX
+ * @see TYPE_DATE, TYPE_ID, TYPE_PATH
+ *
+ * @return int
+ */
+ public function getFieldType()
+ {
+ return self::TYPE_TEXT;
+ }
+
+ /**
+ * Set the model for the field body.
+ *
+ * This method takes a string for the field value.
+ *
+ * @param string $model
+ */
+ public function setFieldBodyModel($model)
+ {
+ $this->setValue($model);
+ }
+
+ /**
+ * Get the model for the field body.
+ *
+ * This method returns a string.
+ *
+ * @return string
+ */
+ public function getFieldBodyModel()
+ {
+ return $this->getValue();
+ }
+
+ /**
+ * Get the (unencoded) value of this header.
+ *
+ * @return string
+ */
+ public function getValue()
+ {
+ return $this->_value;
+ }
+
+ /**
+ * Set the (unencoded) value of this header.
+ *
+ * @param string $value
+ */
+ public function setValue($value)
+ {
+ $this->clearCachedValueIf($this->_value != $value);
+ $this->_value = $value;
+ }
+
+ /**
+ * Get the value of this header prepared for rendering.
+ *
+ * @return string
+ */
+ public function getFieldBody()
+ {
+ if (!$this->getCachedValue()) {
+ $this->setCachedValue(
+ $this->encodeWords($this, $this->_value)
+ );
+ }
+
+ return $this->getCachedValue();
+ }
+}
diff --git a/vendor/swiftmailer/classes/Swift/Mime/Message.php b/vendor/swiftmailer/classes/Swift/Mime/Message.php
new file mode 100644
index 000000000..29bc4b337
--- /dev/null
+++ b/vendor/swiftmailer/classes/Swift/Mime/Message.php
@@ -0,0 +1,223 @@
+ 'Real Name').
+ *
+ * If the second parameter is provided and the first is a string, then $name
+ * is associated with the address.
+ *
+ * @param mixed $address
+ * @param string $name optional
+ */
+ public function setSender($address, $name = null);
+
+ /**
+ * Get the sender address for this message.
+ *
+ * This has a higher significance than the From address.
+ *
+ * @return string
+ */
+ public function getSender();
+
+ /**
+ * Set the From address of this message.
+ *
+ * It is permissible for multiple From addresses to be set using an array.
+ *
+ * If multiple From addresses are used, you SHOULD set the Sender address and
+ * according to RFC 2822, MUST set the sender address.
+ *
+ * An array can be used if display names are to be provided: i.e.
+ * array('email@address.com' => 'Real Name').
+ *
+ * If the second parameter is provided and the first is a string, then $name
+ * is associated with the address.
+ *
+ * @param mixed $addresses
+ * @param string $name optional
+ */
+ public function setFrom($addresses, $name = null);
+
+ /**
+ * Get the From address(es) of this message.
+ *
+ * This method always returns an associative array where the keys are the
+ * addresses.
+ *
+ * @return string[]
+ */
+ public function getFrom();
+
+ /**
+ * Set the Reply-To address(es).
+ *
+ * Any replies from the receiver will be sent to this address.
+ *
+ * It is permissible for multiple reply-to addresses to be set using an array.
+ *
+ * This method has the same synopsis as {@link setFrom()} and {@link setTo()}.
+ *
+ * If the second parameter is provided and the first is a string, then $name
+ * is associated with the address.
+ *
+ * @param mixed $addresses
+ * @param string $name optional
+ */
+ public function setReplyTo($addresses, $name = null);
+
+ /**
+ * Get the Reply-To addresses for this message.
+ *
+ * This method always returns an associative array where the keys provide the
+ * email addresses.
+ *
+ * @return string[]
+ */
+ public function getReplyTo();
+
+ /**
+ * Set the To address(es).
+ *
+ * Recipients set in this field will receive a copy of this message.
+ *
+ * This method has the same synopsis as {@link setFrom()} and {@link setCc()}.
+ *
+ * If the second parameter is provided and the first is a string, then $name
+ * is associated with the address.
+ *
+ * @param mixed $addresses
+ * @param string $name optional
+ */
+ public function setTo($addresses, $name = null);
+
+ /**
+ * Get the To addresses for this message.
+ *
+ * This method always returns an associative array, whereby the keys provide
+ * the actual email addresses.
+ *
+ * @return string[]
+ */
+ public function getTo();
+
+ /**
+ * Set the Cc address(es).
+ *
+ * Recipients set in this field will receive a 'carbon-copy' of this message.
+ *
+ * This method has the same synopsis as {@link setFrom()} and {@link setTo()}.
+ *
+ * @param mixed $addresses
+ * @param string $name optional
+ */
+ public function setCc($addresses, $name = null);
+
+ /**
+ * Get the Cc addresses for this message.
+ *
+ * This method always returns an associative array, whereby the keys provide
+ * the actual email addresses.
+ *
+ * @return string[]
+ */
+ public function getCc();
+
+ /**
+ * Set the Bcc address(es).
+ *
+ * Recipients set in this field will receive a 'blind-carbon-copy' of this
+ * message.
+ *
+ * In other words, they will get the message, but any other recipients of the
+ * message will have no such knowledge of their receipt of it.
+ *
+ * This method has the same synopsis as {@link setFrom()} and {@link setTo()}.
+ *
+ * @param mixed $addresses
+ * @param string $name optional
+ */
+ public function setBcc($addresses, $name = null);
+
+ /**
+ * Get the Bcc addresses for this message.
+ *
+ * This method always returns an associative array, whereby the keys provide
+ * the actual email addresses.
+ *
+ * @return string[]
+ */
+ public function getBcc();
+}
diff --git a/vendor/swiftmailer/classes/Swift/Mime/MimeEntity.php b/vendor/swiftmailer/classes/Swift/Mime/MimeEntity.php
new file mode 100644
index 000000000..cd8b8a2b8
--- /dev/null
+++ b/vendor/swiftmailer/classes/Swift/Mime/MimeEntity.php
@@ -0,0 +1,115 @@
+setContentType('text/plain');
+ if (!is_null($charset)) {
+ $this->setCharset($charset);
+ }
+ }
+
+ /**
+ * Set the body of this entity, either as a string, or as an instance of
+ * {@link Swift_OutputByteStream}.
+ *
+ * @param mixed $body
+ * @param string $contentType optional
+ * @param string $charset optional
+ *
+ * @return Swift_Mime_MimePart
+ */
+ public function setBody($body, $contentType = null, $charset = null)
+ {
+ if (isset($charset)) {
+ $this->setCharset($charset);
+ }
+ $body = $this->_convertString($body);
+
+ parent::setBody($body, $contentType);
+
+ return $this;
+ }
+
+ /**
+ * Get the character set of this entity.
+ *
+ * @return string
+ */
+ public function getCharset()
+ {
+ return $this->_getHeaderParameter('Content-Type', 'charset');
+ }
+
+ /**
+ * Set the character set of this entity.
+ *
+ * @param string $charset
+ *
+ * @return Swift_Mime_MimePart
+ */
+ public function setCharset($charset)
+ {
+ $this->_setHeaderParameter('Content-Type', 'charset', $charset);
+ if ($charset !== $this->_userCharset) {
+ $this->_clearCache();
+ }
+ $this->_userCharset = $charset;
+ parent::charsetChanged($charset);
+
+ return $this;
+ }
+
+ /**
+ * Get the format of this entity (i.e. flowed or fixed).
+ *
+ * @return string
+ */
+ public function getFormat()
+ {
+ return $this->_getHeaderParameter('Content-Type', 'format');
+ }
+
+ /**
+ * Set the format of this entity (flowed or fixed).
+ *
+ * @param string $format
+ *
+ * @return Swift_Mime_MimePart
+ */
+ public function setFormat($format)
+ {
+ $this->_setHeaderParameter('Content-Type', 'format', $format);
+ $this->_userFormat = $format;
+
+ return $this;
+ }
+
+ /**
+ * Test if delsp is being used for this entity.
+ *
+ * @return bool
+ */
+ public function getDelSp()
+ {
+ return ($this->_getHeaderParameter('Content-Type', 'delsp') == 'yes')
+ ? true
+ : false;
+ }
+
+ /**
+ * Turn delsp on or off for this entity.
+ *
+ * @param bool $delsp
+ *
+ * @return Swift_Mime_MimePart
+ */
+ public function setDelSp($delsp = true)
+ {
+ $this->_setHeaderParameter('Content-Type', 'delsp', $delsp ? 'yes' : null);
+ $this->_userDelSp = $delsp;
+
+ return $this;
+ }
+
+ /**
+ * Get the nesting level of this entity.
+ *
+ * @see LEVEL_TOP, LEVEL_ALTERNATIVE, LEVEL_MIXED, LEVEL_RELATED
+ *
+ * @return int
+ */
+ public function getNestingLevel()
+ {
+ return $this->_nestingLevel;
+ }
+
+ /**
+ * Receive notification that the charset has changed on this document, or a
+ * parent document.
+ *
+ * @param string $charset
+ */
+ public function charsetChanged($charset)
+ {
+ $this->setCharset($charset);
+ }
+
+ /** Fix the content-type and encoding of this entity */
+ protected function _fixHeaders()
+ {
+ parent::_fixHeaders();
+ if (count($this->getChildren())) {
+ $this->_setHeaderParameter('Content-Type', 'charset', null);
+ $this->_setHeaderParameter('Content-Type', 'format', null);
+ $this->_setHeaderParameter('Content-Type', 'delsp', null);
+ } else {
+ $this->setCharset($this->_userCharset);
+ $this->setFormat($this->_userFormat);
+ $this->setDelSp($this->_userDelSp);
+ }
+ }
+
+ /** Set the nesting level of this entity */
+ protected function _setNestingLevel($level)
+ {
+ $this->_nestingLevel = $level;
+ }
+
+ /** Encode charset when charset is not utf-8 */
+ protected function _convertString($string)
+ {
+ $charset = strtolower($this->getCharset());
+ if (!in_array($charset, array('utf-8', 'iso-8859-1', ''))) {
+ // mb_convert_encoding must be the first one to check, since iconv cannot convert some words.
+ if (function_exists('mb_convert_encoding')) {
+ $string = mb_convert_encoding($string, 'utf-8', $charset);
+ } elseif (function_exists('iconv')) {
+ $string = iconv($charset, 'utf-8//TRANSLIT//IGNORE', $string);
+ } else {
+ throw new Swift_SwiftException('No suitable convert encoding function (use UTF-8 as your charset or install the mbstring or iconv extension).');
+ }
+
+ return $string;
+ }
+
+ return $string;
+ }
+}
diff --git a/vendor/swiftmailer/classes/Swift/Mime/ParameterizedHeader.php b/vendor/swiftmailer/classes/Swift/Mime/ParameterizedHeader.php
new file mode 100644
index 000000000..ea793201e
--- /dev/null
+++ b/vendor/swiftmailer/classes/Swift/Mime/ParameterizedHeader.php
@@ -0,0 +1,34 @@
+_encoder = $encoder;
+ $this->_paramEncoder = $paramEncoder;
+ $this->_grammar = $grammar;
+ $this->_charset = $charset;
+ }
+
+ /**
+ * Create a new Mailbox Header with a list of $addresses.
+ *
+ * @param string $name
+ * @param array|string|null $addresses
+ *
+ * @return Swift_Mime_Header
+ */
+ public function createMailboxHeader($name, $addresses = null)
+ {
+ $header = new Swift_Mime_Headers_MailboxHeader($name, $this->_encoder, $this->_grammar);
+ if (isset($addresses)) {
+ $header->setFieldBodyModel($addresses);
+ }
+ $this->_setHeaderCharset($header);
+
+ return $header;
+ }
+
+ /**
+ * Create a new Date header using $timestamp (UNIX time).
+ * @param string $name
+ * @param int|null $timestamp
+ *
+ * @return Swift_Mime_Header
+ */
+ public function createDateHeader($name, $timestamp = null)
+ {
+ $header = new Swift_Mime_Headers_DateHeader($name, $this->_grammar);
+ if (isset($timestamp)) {
+ $header->setFieldBodyModel($timestamp);
+ }
+ $this->_setHeaderCharset($header);
+
+ return $header;
+ }
+
+ /**
+ * Create a new basic text header with $name and $value.
+ *
+ * @param string $name
+ * @param string $value
+ *
+ * @return Swift_Mime_Header
+ */
+ public function createTextHeader($name, $value = null)
+ {
+ $header = new Swift_Mime_Headers_UnstructuredHeader($name, $this->_encoder, $this->_grammar);
+ if (isset($value)) {
+ $header->setFieldBodyModel($value);
+ }
+ $this->_setHeaderCharset($header);
+
+ return $header;
+ }
+
+ /**
+ * Create a new ParameterizedHeader with $name, $value and $params.
+ *
+ * @param string $name
+ * @param string $value
+ * @param array $params
+ *
+ * @return Swift_Mime_ParameterizedHeader
+ */
+ public function createParameterizedHeader($name, $value = null,
+ $params = array())
+ {
+ $header = new Swift_Mime_Headers_ParameterizedHeader($name,
+ $this->_encoder, (strtolower($name) == 'content-disposition')
+ ? $this->_paramEncoder
+ : null,
+ $this->_grammar
+ );
+ if (isset($value)) {
+ $header->setFieldBodyModel($value);
+ }
+ foreach ($params as $k => $v) {
+ $header->setParameter($k, $v);
+ }
+ $this->_setHeaderCharset($header);
+
+ return $header;
+ }
+
+ /**
+ * Create a new ID header for Message-ID or Content-ID.
+ *
+ * @param string $name
+ * @param string|array $ids
+ *
+ * @return Swift_Mime_Header
+ */
+ public function createIdHeader($name, $ids = null)
+ {
+ $header = new Swift_Mime_Headers_IdentificationHeader($name, $this->_grammar);
+ if (isset($ids)) {
+ $header->setFieldBodyModel($ids);
+ }
+ $this->_setHeaderCharset($header);
+
+ return $header;
+ }
+
+ /**
+ * Create a new Path header with an address (path) in it.
+ *
+ * @param string $name
+ * @param string $path
+ *
+ * @return Swift_Mime_Header
+ */
+ public function createPathHeader($name, $path = null)
+ {
+ $header = new Swift_Mime_Headers_PathHeader($name, $this->_grammar);
+ if (isset($path)) {
+ $header->setFieldBodyModel($path);
+ }
+ $this->_setHeaderCharset($header);
+
+ return $header;
+ }
+
+ /**
+ * Notify this observer that the entity's charset has changed.
+ *
+ * @param string $charset
+ */
+ public function charsetChanged($charset)
+ {
+ $this->_charset = $charset;
+ $this->_encoder->charsetChanged($charset);
+ $this->_paramEncoder->charsetChanged($charset);
+ }
+
+ /** Apply the charset to the Header */
+ private function _setHeaderCharset(Swift_Mime_Header $header)
+ {
+ if (isset($this->_charset)) {
+ $header->setCharset($this->_charset);
+ }
+ }
+}
diff --git a/vendor/swiftmailer/classes/Swift/Mime/SimpleHeaderSet.php b/vendor/swiftmailer/classes/Swift/Mime/SimpleHeaderSet.php
new file mode 100644
index 000000000..3bc60e149
--- /dev/null
+++ b/vendor/swiftmailer/classes/Swift/Mime/SimpleHeaderSet.php
@@ -0,0 +1,383 @@
+_factory = $factory;
+ if (isset($charset)) {
+ $this->setCharset($charset);
+ }
+ }
+
+ /**
+ * Set the charset used by these headers.
+ *
+ * @param string $charset
+ */
+ public function setCharset($charset)
+ {
+ $this->_charset = $charset;
+ $this->_factory->charsetChanged($charset);
+ $this->_notifyHeadersOfCharset($charset);
+ }
+
+ /**
+ * Add a new Mailbox Header with a list of $addresses.
+ *
+ * @param string $name
+ * @param array|string $addresses
+ */
+ public function addMailboxHeader($name, $addresses = null)
+ {
+ $this->_storeHeader($name,
+ $this->_factory->createMailboxHeader($name, $addresses));
+ }
+
+ /**
+ * Add a new Date header using $timestamp (UNIX time).
+ *
+ * @param string $name
+ * @param int $timestamp
+ */
+ public function addDateHeader($name, $timestamp = null)
+ {
+ $this->_storeHeader($name,
+ $this->_factory->createDateHeader($name, $timestamp));
+ }
+
+ /**
+ * Add a new basic text header with $name and $value.
+ *
+ * @param string $name
+ * @param string $value
+ */
+ public function addTextHeader($name, $value = null)
+ {
+ $this->_storeHeader($name,
+ $this->_factory->createTextHeader($name, $value));
+ }
+
+ /**
+ * Add a new ParameterizedHeader with $name, $value and $params.
+ *
+ * @param string $name
+ * @param string $value
+ * @param array $params
+ */
+ public function addParameterizedHeader($name, $value = null, $params = array())
+ {
+ $this->_storeHeader($name, $this->_factory->createParameterizedHeader($name, $value, $params));
+ }
+
+ /**
+ * Add a new ID header for Message-ID or Content-ID.
+ *
+ * @param string $name
+ * @param string|array $ids
+ */
+ public function addIdHeader($name, $ids = null)
+ {
+ $this->_storeHeader($name, $this->_factory->createIdHeader($name, $ids));
+ }
+
+ /**
+ * Add a new Path header with an address (path) in it.
+ *
+ * @param string $name
+ * @param string $path
+ */
+ public function addPathHeader($name, $path = null)
+ {
+ $this->_storeHeader($name, $this->_factory->createPathHeader($name, $path));
+ }
+
+ /**
+ * Returns true if at least one header with the given $name exists.
+ *
+ * If multiple headers match, the actual one may be specified by $index.
+ *
+ * @param string $name
+ * @param int $index
+ *
+ * @return bool
+ */
+ public function has($name, $index = 0)
+ {
+ $lowerName = strtolower($name);
+
+ return array_key_exists($lowerName, $this->_headers) && array_key_exists($index, $this->_headers[$lowerName]);
+ }
+
+ /**
+ * Set a header in the HeaderSet.
+ *
+ * The header may be a previously fetched header via {@link get()} or it may
+ * be one that has been created separately.
+ *
+ * If $index is specified, the header will be inserted into the set at this
+ * offset.
+ *
+ * @param Swift_Mime_Header $header
+ * @param int $index
+ */
+ public function set(Swift_Mime_Header $header, $index = 0)
+ {
+ $this->_storeHeader($header->getFieldName(), $header, $index);
+ }
+
+ /**
+ * Get the header with the given $name.
+ *
+ * If multiple headers match, the actual one may be specified by $index.
+ * Returns NULL if none present.
+ *
+ * @param string $name
+ * @param int $index
+ *
+ * @return Swift_Mime_Header
+ */
+ public function get($name, $index = 0)
+ {
+ if ($this->has($name, $index)) {
+ $lowerName = strtolower($name);
+
+ return $this->_headers[$lowerName][$index];
+ }
+ }
+
+ /**
+ * Get all headers with the given $name.
+ *
+ * @param string $name
+ *
+ * @return array
+ */
+ public function getAll($name = null)
+ {
+ if (!isset($name)) {
+ $headers = array();
+ foreach ($this->_headers as $collection) {
+ $headers = array_merge($headers, $collection);
+ }
+
+ return $headers;
+ }
+
+ $lowerName = strtolower($name);
+ if (!array_key_exists($lowerName, $this->_headers)) {
+ return array();
+ }
+
+ return $this->_headers[$lowerName];
+ }
+
+ /**
+ * Return the name of all Headers
+ *
+ * @return array
+ */
+ public function listAll()
+ {
+ $headers = $this->_headers;
+ if ($this->_canSort()) {
+ uksort($headers, array($this, '_sortHeaders'));
+ }
+
+ return array_keys($headers);
+ }
+
+ /**
+ * Remove the header with the given $name if it's set.
+ *
+ * If multiple headers match, the actual one may be specified by $index.
+ *
+ * @param string $name
+ * @param int $index
+ */
+ public function remove($name, $index = 0)
+ {
+ $lowerName = strtolower($name);
+ unset($this->_headers[$lowerName][$index]);
+ }
+
+ /**
+ * Remove all headers with the given $name.
+ *
+ * @param string $name
+ */
+ public function removeAll($name)
+ {
+ $lowerName = strtolower($name);
+ unset($this->_headers[$lowerName]);
+ }
+
+ /**
+ * Create a new instance of this HeaderSet.
+ *
+ * @return Swift_Mime_HeaderSet
+ */
+ public function newInstance()
+ {
+ return new self($this->_factory);
+ }
+
+ /**
+ * Define a list of Header names as an array in the correct order.
+ *
+ * These Headers will be output in the given order where present.
+ *
+ * @param array $sequence
+ */
+ public function defineOrdering(array $sequence)
+ {
+ $this->_order = array_flip(array_map('strtolower', $sequence));
+ }
+
+ /**
+ * Set a list of header names which must always be displayed when set.
+ *
+ * Usually headers without a field value won't be output unless set here.
+ *
+ * @param array $names
+ */
+ public function setAlwaysDisplayed(array $names)
+ {
+ $this->_required = array_flip(array_map('strtolower', $names));
+ }
+
+ /**
+ * Notify this observer that the entity's charset has changed.
+ *
+ * @param string $charset
+ */
+ public function charsetChanged($charset)
+ {
+ $this->setCharset($charset);
+ }
+
+ /**
+ * Returns a string with a representation of all headers.
+ *
+ * @return string
+ */
+ public function toString()
+ {
+ $string = '';
+ $headers = $this->_headers;
+ if ($this->_canSort()) {
+ uksort($headers, array($this, '_sortHeaders'));
+ }
+ foreach ($headers as $collection) {
+ foreach ($collection as $header) {
+ if ($this->_isDisplayed($header) || $header->getFieldBody() != '') {
+ $string .= $header->toString();
+ }
+ }
+ }
+
+ return $string;
+ }
+
+ /**
+ * Returns a string representation of this object.
+ *
+ * @return string
+ *
+ * @see toString()
+ */
+ public function __toString()
+ {
+ return $this->toString();
+ }
+
+ /** Save a Header to the internal collection */
+ private function _storeHeader($name, Swift_Mime_Header $header, $offset = null)
+ {
+ if (!isset($this->_headers[strtolower($name)])) {
+ $this->_headers[strtolower($name)] = array();
+ }
+ if (!isset($offset)) {
+ $this->_headers[strtolower($name)][] = $header;
+ } else {
+ $this->_headers[strtolower($name)][$offset] = $header;
+ }
+ }
+
+ /** Test if the headers can be sorted */
+ private function _canSort()
+ {
+ return count($this->_order) > 0;
+ }
+
+ /** uksort() algorithm for Header ordering */
+ private function _sortHeaders($a, $b)
+ {
+ $lowerA = strtolower($a);
+ $lowerB = strtolower($b);
+ $aPos = array_key_exists($lowerA, $this->_order)
+ ? $this->_order[$lowerA]
+ : -1;
+ $bPos = array_key_exists($lowerB, $this->_order)
+ ? $this->_order[$lowerB]
+ : -1;
+
+ if ($aPos == -1) {
+ return 1;
+ } elseif ($bPos == -1) {
+ return -1;
+ }
+
+ return ($aPos < $bPos) ? -1 : 1;
+ }
+
+ /** Test if the given Header is always displayed */
+ private function _isDisplayed(Swift_Mime_Header $header)
+ {
+ return array_key_exists(strtolower($header->getFieldName()), $this->_required);
+ }
+
+ /** Notify all Headers of the new charset */
+ private function _notifyHeadersOfCharset($charset)
+ {
+ foreach ($this->_headers as $headerGroup) {
+ foreach ($headerGroup as $header) {
+ $header->setCharset($charset);
+ }
+ }
+ }
+}
diff --git a/vendor/swiftmailer/classes/Swift/Mime/SimpleMessage.php b/vendor/swiftmailer/classes/Swift/Mime/SimpleMessage.php
new file mode 100644
index 000000000..e0f7e63e1
--- /dev/null
+++ b/vendor/swiftmailer/classes/Swift/Mime/SimpleMessage.php
@@ -0,0 +1,651 @@
+getHeaders()->defineOrdering(array(
+ 'Return-Path',
+ 'Received',
+ 'DKIM-Signature',
+ 'DomainKey-Signature',
+ 'Sender',
+ 'Message-ID',
+ 'Date',
+ 'Subject',
+ 'From',
+ 'Reply-To',
+ 'To',
+ 'Cc',
+ 'Bcc',
+ 'MIME-Version',
+ 'Content-Type',
+ 'Content-Transfer-Encoding'
+ ));
+ $this->getHeaders()->setAlwaysDisplayed(array('Date', 'Message-ID', 'From'));
+ $this->getHeaders()->addTextHeader('MIME-Version', '1.0');
+ $this->setDate(time());
+ $this->setId($this->getId());
+ $this->getHeaders()->addMailboxHeader('From');
+ }
+
+ /**
+ * Always returns {@link LEVEL_TOP} for a message instance.
+ *
+ * @return int
+ */
+ public function getNestingLevel()
+ {
+ return self::LEVEL_TOP;
+ }
+
+ /**
+ * Set the subject of this message.
+ *
+ * @param string $subject
+ *
+ * @return Swift_Mime_SimpleMessage
+ */
+ public function setSubject($subject)
+ {
+ if (!$this->_setHeaderFieldModel('Subject', $subject)) {
+ $this->getHeaders()->addTextHeader('Subject', $subject);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the subject of this message.
+ *
+ * @return string
+ */
+ public function getSubject()
+ {
+ return $this->_getHeaderFieldModel('Subject');
+ }
+
+ /**
+ * Set the date at which this message was created.
+ *
+ * @param int $date
+ *
+ * @return Swift_Mime_SimpleMessage
+ */
+ public function setDate($date)
+ {
+ if (!$this->_setHeaderFieldModel('Date', $date)) {
+ $this->getHeaders()->addDateHeader('Date', $date);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the date at which this message was created.
+ *
+ * @return int
+ */
+ public function getDate()
+ {
+ return $this->_getHeaderFieldModel('Date');
+ }
+
+ /**
+ * Set the return-path (the bounce address) of this message.
+ *
+ * @param string $address
+ *
+ * @return Swift_Mime_SimpleMessage
+ */
+ public function setReturnPath($address)
+ {
+ if (!$this->_setHeaderFieldModel('Return-Path', $address)) {
+ $this->getHeaders()->addPathHeader('Return-Path', $address);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the return-path (bounce address) of this message.
+ *
+ * @return string
+ */
+ public function getReturnPath()
+ {
+ return $this->_getHeaderFieldModel('Return-Path');
+ }
+
+ /**
+ * Set the sender of this message.
+ *
+ * This does not override the From field, but it has a higher significance.
+ *
+ * @param string $address
+ * @param string $name optional
+ *
+ * @return Swift_Mime_SimpleMessage
+ */
+ public function setSender($address, $name = null)
+ {
+ if (!is_array($address) && isset($name)) {
+ $address = array($address => $name);
+ }
+
+ if (!$this->_setHeaderFieldModel('Sender', (array) $address)) {
+ $this->getHeaders()->addMailboxHeader('Sender', (array) $address);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the sender of this message.
+ *
+ * @return string
+ */
+ public function getSender()
+ {
+ return $this->_getHeaderFieldModel('Sender');
+ }
+
+ /**
+ * Add a From: address to this message.
+ *
+ * If $name is passed this name will be associated with the address.
+ *
+ * @param string $address
+ * @param string $name optional
+ *
+ * @return Swift_Mime_SimpleMessage
+ */
+ public function addFrom($address, $name = null)
+ {
+ $current = $this->getFrom();
+ $current[$address] = $name;
+
+ return $this->setFrom($current);
+ }
+
+ /**
+ * Set the from address of this message.
+ *
+ * You may pass an array of addresses if this message is from multiple people.
+ *
+ * If $name is passed and the first parameter is a string, this name will be
+ * associated with the address.
+ *
+ * @param string $addresses
+ * @param string $name optional
+ *
+ * @return Swift_Mime_SimpleMessage
+ */
+ public function setFrom($addresses, $name = null)
+ {
+ if (!is_array($addresses) && isset($name)) {
+ $addresses = array($addresses => $name);
+ }
+
+ if (!$this->_setHeaderFieldModel('From', (array) $addresses)) {
+ $this->getHeaders()->addMailboxHeader('From', (array) $addresses);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the from address of this message.
+ *
+ * @return mixed
+ */
+ public function getFrom()
+ {
+ return $this->_getHeaderFieldModel('From');
+ }
+
+ /**
+ * Add a Reply-To: address to this message.
+ *
+ * If $name is passed this name will be associated with the address.
+ *
+ * @param string $address
+ * @param string $name optional
+ *
+ * @return Swift_Mime_SimpleMessage
+ */
+ public function addReplyTo($address, $name = null)
+ {
+ $current = $this->getReplyTo();
+ $current[$address] = $name;
+
+ return $this->setReplyTo($current);
+ }
+
+ /**
+ * Set the reply-to address of this message.
+ *
+ * You may pass an array of addresses if replies will go to multiple people.
+ *
+ * If $name is passed and the first parameter is a string, this name will be
+ * associated with the address.
+ *
+ * @param string $addresses
+ * @param string $name optional
+ *
+ * @return Swift_Mime_SimpleMessage
+ */
+ public function setReplyTo($addresses, $name = null)
+ {
+ if (!is_array($addresses) && isset($name)) {
+ $addresses = array($addresses => $name);
+ }
+
+ if (!$this->_setHeaderFieldModel('Reply-To', (array) $addresses)) {
+ $this->getHeaders()->addMailboxHeader('Reply-To', (array) $addresses);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the reply-to address of this message.
+ *
+ * @return string
+ */
+ public function getReplyTo()
+ {
+ return $this->_getHeaderFieldModel('Reply-To');
+ }
+
+ /**
+ * Add a To: address to this message.
+ *
+ * If $name is passed this name will be associated with the address.
+ *
+ * @param string $address
+ * @param string $name optional
+ *
+ * @return Swift_Mime_SimpleMessage
+ */
+ public function addTo($address, $name = null)
+ {
+ $current = $this->getTo();
+ $current[$address] = $name;
+
+ return $this->setTo($current);
+ }
+
+ /**
+ * Set the to addresses of this message.
+ *
+ * If multiple recipients will receive the message an array should be used.
+ * Example: array('receiver@domain.org', 'other@domain.org' => 'A name')
+ *
+ * If $name is passed and the first parameter is a string, this name will be
+ * associated with the address.
+ *
+ * @param mixed $addresses
+ * @param string $name optional
+ *
+ * @return Swift_Mime_SimpleMessage
+ */
+ public function setTo($addresses, $name = null)
+ {
+ if (!is_array($addresses) && isset($name)) {
+ $addresses = array($addresses => $name);
+ }
+
+ if (!$this->_setHeaderFieldModel('To', (array) $addresses)) {
+ $this->getHeaders()->addMailboxHeader('To', (array) $addresses);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the To addresses of this message.
+ *
+ * @return array
+ */
+ public function getTo()
+ {
+ return $this->_getHeaderFieldModel('To');
+ }
+
+ /**
+ * Add a Cc: address to this message.
+ *
+ * If $name is passed this name will be associated with the address.
+ *
+ * @param string $address
+ * @param string $name optional
+ *
+ * @return Swift_Mime_SimpleMessage
+ */
+ public function addCc($address, $name = null)
+ {
+ $current = $this->getCc();
+ $current[$address] = $name;
+
+ return $this->setCc($current);
+ }
+
+ /**
+ * Set the Cc addresses of this message.
+ *
+ * If $name is passed and the first parameter is a string, this name will be
+ * associated with the address.
+ *
+ * @param mixed $addresses
+ * @param string $name optional
+ *
+ * @return Swift_Mime_SimpleMessage
+ */
+ public function setCc($addresses, $name = null)
+ {
+ if (!is_array($addresses) && isset($name)) {
+ $addresses = array($addresses => $name);
+ }
+
+ if (!$this->_setHeaderFieldModel('Cc', (array) $addresses)) {
+ $this->getHeaders()->addMailboxHeader('Cc', (array) $addresses);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the Cc address of this message.
+ *
+ * @return array
+ */
+ public function getCc()
+ {
+ return $this->_getHeaderFieldModel('Cc');
+ }
+
+ /**
+ * Add a Bcc: address to this message.
+ *
+ * If $name is passed this name will be associated with the address.
+ *
+ * @param string $address
+ * @param string $name optional
+ *
+ * @return Swift_Mime_SimpleMessage
+ */
+ public function addBcc($address, $name = null)
+ {
+ $current = $this->getBcc();
+ $current[$address] = $name;
+
+ return $this->setBcc($current);
+ }
+
+ /**
+ * Set the Bcc addresses of this message.
+ *
+ * If $name is passed and the first parameter is a string, this name will be
+ * associated with the address.
+ *
+ * @param mixed $addresses
+ * @param string $name optional
+ *
+ * @return Swift_Mime_SimpleMessage
+ */
+ public function setBcc($addresses, $name = null)
+ {
+ if (!is_array($addresses) && isset($name)) {
+ $addresses = array($addresses => $name);
+ }
+
+ if (!$this->_setHeaderFieldModel('Bcc', (array) $addresses)) {
+ $this->getHeaders()->addMailboxHeader('Bcc', (array) $addresses);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the Bcc addresses of this message.
+ *
+ * @return array
+ */
+ public function getBcc()
+ {
+ return $this->_getHeaderFieldModel('Bcc');
+ }
+
+ /**
+ * Set the priority of this message.
+ *
+ * The value is an integer where 1 is the highest priority and 5 is the lowest.
+ *
+ * @param int $priority
+ *
+ * @return Swift_Mime_SimpleMessage
+ */
+ public function setPriority($priority)
+ {
+ $priorityMap = array(
+ 1 => 'Highest',
+ 2 => 'High',
+ 3 => 'Normal',
+ 4 => 'Low',
+ 5 => 'Lowest'
+ );
+ $pMapKeys = array_keys($priorityMap);
+ if ($priority > max($pMapKeys)) {
+ $priority = max($pMapKeys);
+ } elseif ($priority < min($pMapKeys)) {
+ $priority = min($pMapKeys);
+ }
+ if (!$this->_setHeaderFieldModel('X-Priority',
+ sprintf('%d (%s)', $priority, $priorityMap[$priority])))
+ {
+ $this->getHeaders()->addTextHeader('X-Priority',
+ sprintf('%d (%s)', $priority, $priorityMap[$priority]));
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the priority of this message.
+ *
+ * The returned value is an integer where 1 is the highest priority and 5
+ * is the lowest.
+ *
+ * @return int
+ */
+ public function getPriority()
+ {
+ list($priority) = sscanf($this->_getHeaderFieldModel('X-Priority'),
+ '%[1-5]'
+ );
+
+ return isset($priority) ? $priority : 3;
+ }
+
+ /**
+ * Ask for a delivery receipt from the recipient to be sent to $addresses
+ *
+ * @param array $addresses
+ *
+ * @return Swift_Mime_SimpleMessage
+ */
+ public function setReadReceiptTo($addresses)
+ {
+ if (!$this->_setHeaderFieldModel('Disposition-Notification-To', $addresses)) {
+ $this->getHeaders()
+ ->addMailboxHeader('Disposition-Notification-To', $addresses);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the addresses to which a read-receipt will be sent.
+ *
+ * @return string
+ */
+ public function getReadReceiptTo()
+ {
+ return $this->_getHeaderFieldModel('Disposition-Notification-To');
+ }
+
+ /**
+ * Attach a {@link Swift_Mime_MimeEntity} such as an Attachment or MimePart.
+ *
+ * @param Swift_Mime_MimeEntity $entity
+ *
+ * @return Swift_Mime_SimpleMessage
+ */
+ public function attach(Swift_Mime_MimeEntity $entity)
+ {
+ $this->setChildren(array_merge($this->getChildren(), array($entity)));
+
+ return $this;
+ }
+
+ /**
+ * Remove an already attached entity.
+ *
+ * @param Swift_Mime_MimeEntity $entity
+ *
+ * @return Swift_Mime_SimpleMessage
+ */
+ public function detach(Swift_Mime_MimeEntity $entity)
+ {
+ $newChildren = array();
+ foreach ($this->getChildren() as $child) {
+ if ($entity !== $child) {
+ $newChildren[] = $child;
+ }
+ }
+ $this->setChildren($newChildren);
+
+ return $this;
+ }
+
+ /**
+ * Attach a {@link Swift_Mime_MimeEntity} and return it's CID source.
+ * This method should be used when embedding images or other data in a message.
+ *
+ * @param Swift_Mime_MimeEntity $entity
+ *
+ * @return string
+ */
+ public function embed(Swift_Mime_MimeEntity $entity)
+ {
+ $this->attach($entity);
+
+ return 'cid:' . $entity->getId();
+ }
+
+ /**
+ * Get this message as a complete string.
+ *
+ * @return string
+ */
+ public function toString()
+ {
+ if (count($children = $this->getChildren()) > 0 && $this->getBody() != '') {
+ $this->setChildren(array_merge(array($this->_becomeMimePart()), $children));
+ $string = parent::toString();
+ $this->setChildren($children);
+ } else {
+ $string = parent::toString();
+ }
+
+ return $string;
+ }
+
+ /**
+ * Returns a string representation of this object.
+ *
+ * @see toString()
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->toString();
+ }
+
+ /**
+ * Write this message to a {@link Swift_InputByteStream}.
+ *
+ * @param Swift_InputByteStream $is
+ */
+ public function toByteStream(Swift_InputByteStream $is)
+ {
+ if (count($children = $this->getChildren()) > 0 && $this->getBody() != '') {
+ $this->setChildren(array_merge(array($this->_becomeMimePart()), $children));
+ parent::toByteStream($is);
+ $this->setChildren($children);
+ } else {
+ parent::toByteStream($is);
+ }
+ }
+
+
+ /** @see Swift_Mime_SimpleMimeEntity::_getIdField() */
+ protected function _getIdField()
+ {
+ return 'Message-ID';
+ }
+
+ /** Turn the body of this message into a child of itself if needed */
+ protected function _becomeMimePart()
+ {
+ $part = new parent($this->getHeaders()->newInstance(), $this->getEncoder(),
+ $this->_getCache(), $this->_getGrammar(), $this->_userCharset
+ );
+ $part->setContentType($this->_userContentType);
+ $part->setBody($this->getBody());
+ $part->setFormat($this->_userFormat);
+ $part->setDelSp($this->_userDelSp);
+ $part->_setNestingLevel($this->_getTopNestingLevel());
+
+ return $part;
+ }
+
+ /** Get the highest nesting level nested inside this message */
+ private function _getTopNestingLevel()
+ {
+ $highestLevel = $this->getNestingLevel();
+ foreach ($this->getChildren() as $child) {
+ $childLevel = $child->getNestingLevel();
+ if ($highestLevel < $childLevel) {
+ $highestLevel = $childLevel;
+ }
+ }
+
+ return $highestLevel;
+ }
+}
diff --git a/vendor/swiftmailer/classes/Swift/Mime/SimpleMimeEntity.php b/vendor/swiftmailer/classes/Swift/Mime/SimpleMimeEntity.php
new file mode 100644
index 000000000..d69508147
--- /dev/null
+++ b/vendor/swiftmailer/classes/Swift/Mime/SimpleMimeEntity.php
@@ -0,0 +1,853 @@
+ array(self::LEVEL_TOP, self::LEVEL_MIXED),
+ 'multipart/alternative' => array(self::LEVEL_MIXED, self::LEVEL_ALTERNATIVE),
+ 'multipart/related' => array(self::LEVEL_ALTERNATIVE, self::LEVEL_RELATED)
+ );
+
+ /** A set of filter rules to define what level an entity should be nested at */
+ private $_compoundLevelFilters = array();
+
+ /** The nesting level of this entity */
+ private $_nestingLevel = self::LEVEL_ALTERNATIVE;
+
+ /** A KeyCache instance used during encoding and streaming */
+ private $_cache;
+
+ /** Direct descendants of this entity */
+ private $_immediateChildren = array();
+
+ /** All descendants of this entity */
+ private $_children = array();
+
+ /** The maximum line length of the body of this entity */
+ private $_maxLineLength = 78;
+
+ /** The order in which alternative mime types should appear */
+ private $_alternativePartOrder = array(
+ 'text/plain' => 1,
+ 'text/html' => 2,
+ 'multipart/related' => 3
+ );
+
+ /** The CID of this entity */
+ private $_id;
+
+ /** The key used for accessing the cache */
+ private $_cacheKey;
+
+ protected $_userContentType;
+
+ /**
+ * Create a new SimpleMimeEntity with $headers, $encoder and $cache.
+ *
+ * @param Swift_Mime_HeaderSet $headers
+ * @param Swift_Mime_ContentEncoder $encoder
+ * @param Swift_KeyCache $cache
+ * @param Swift_Mime_Grammar $grammar
+ */
+ public function __construct(Swift_Mime_HeaderSet $headers, Swift_Mime_ContentEncoder $encoder, Swift_KeyCache $cache, Swift_Mime_Grammar $grammar)
+ {
+ $this->_cacheKey = md5(uniqid(getmypid().mt_rand(), true));
+ $this->_cache = $cache;
+ $this->_headers = $headers;
+ $this->_grammar = $grammar;
+ $this->setEncoder($encoder);
+ $this->_headers->defineOrdering(array('Content-Type', 'Content-Transfer-Encoding'));
+
+ // This array specifies that, when the entire MIME document contains
+ // $compoundLevel, then for each child within $level, if its Content-Type
+ // is $contentType then it should be treated as if it's level is
+ // $neededLevel instead. I tried to write that unambiguously! :-\
+ // Data Structure:
+ // array (
+ // $compoundLevel => array(
+ // $level => array(
+ // $contentType => $neededLevel
+ // )
+ // )
+ // )
+
+ $this->_compoundLevelFilters = array(
+ (self::LEVEL_ALTERNATIVE + self::LEVEL_RELATED) => array(
+ self::LEVEL_ALTERNATIVE => array(
+ 'text/plain' => self::LEVEL_ALTERNATIVE,
+ 'text/html' => self::LEVEL_RELATED
+ )
+ )
+ );
+
+ $this->_id = $this->getRandomId();
+ }
+
+ /**
+ * Generate a new Content-ID or Message-ID for this MIME entity.
+ *
+ * @return string
+ */
+ public function generateId()
+ {
+ $this->setId($this->getRandomId());
+
+ return $this->_id;
+ }
+
+ /**
+ * Get the {@link Swift_Mime_HeaderSet} for this entity.
+ *
+ * @return Swift_Mime_HeaderSet
+ */
+ public function getHeaders()
+ {
+ return $this->_headers;
+ }
+
+ /**
+ * Get the nesting level of this entity.
+ *
+ * @see LEVEL_TOP, LEVEL_MIXED, LEVEL_RELATED, LEVEL_ALTERNATIVE
+ *
+ * @return int
+ */
+ public function getNestingLevel()
+ {
+ return $this->_nestingLevel;
+ }
+
+ /**
+ * Get the Content-type of this entity.
+ *
+ * @return string
+ */
+ public function getContentType()
+ {
+ return $this->_getHeaderFieldModel('Content-Type');
+ }
+
+ /**
+ * Set the Content-type of this entity.
+ *
+ * @param string $type
+ *
+ * @return Swift_Mime_SimpleMimeEntity
+ */
+ public function setContentType($type)
+ {
+ $this->_setContentTypeInHeaders($type);
+ // Keep track of the value so that if the content-type changes automatically
+ // due to added child entities, it can be restored if they are later removed
+ $this->_userContentType = $type;
+
+ return $this;
+ }
+
+ /**
+ * Get the CID of this entity.
+ *
+ * The CID will only be present in headers if a Content-ID header is present.
+ *
+ * @return string
+ */
+ public function getId()
+ {
+ $tmp = (array) $this->_getHeaderFieldModel($this->_getIdField());
+
+ return $this->_headers->has($this->_getIdField()) ? current($tmp) : $this->_id;
+ }
+
+ /**
+ * Set the CID of this entity.
+ *
+ * @param string $id
+ *
+ * @return Swift_Mime_SimpleMimeEntity
+ */
+ public function setId($id)
+ {
+ if (!$this->_setHeaderFieldModel($this->_getIdField(), $id)) {
+ $this->_headers->addIdHeader($this->_getIdField(), $id);
+ }
+ $this->_id = $id;
+
+ return $this;
+ }
+
+ /**
+ * Get the description of this entity.
+ *
+ * This value comes from the Content-Description header if set.
+ *
+ * @return string
+ */
+ public function getDescription()
+ {
+ return $this->_getHeaderFieldModel('Content-Description');
+ }
+
+ /**
+ * Set the description of this entity.
+ *
+ * This method sets a value in the Content-ID header.
+ *
+ * @param string $description
+ *
+ * @return Swift_Mime_SimpleMimeEntity
+ */
+ public function setDescription($description)
+ {
+ if (!$this->_setHeaderFieldModel('Content-Description', $description)) {
+ $this->_headers->addTextHeader('Content-Description', $description);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the maximum line length of the body of this entity.
+ *
+ * @return int
+ */
+ public function getMaxLineLength()
+ {
+ return $this->_maxLineLength;
+ }
+
+ /**
+ * Set the maximum line length of lines in this body.
+ *
+ * Though not enforced by the library, lines should not exceed 1000 chars.
+ *
+ * @param int $length
+ *
+ * @return Swift_Mime_SimpleMimeEntity
+ */
+ public function setMaxLineLength($length)
+ {
+ $this->_maxLineLength = $length;
+
+ return $this;
+ }
+
+ /**
+ * Get all children added to this entity.
+ *
+ * @return array of Swift_Mime_Entity
+ */
+ public function getChildren()
+ {
+ return $this->_children;
+ }
+
+ /**
+ * Set all children of this entity.
+ *
+ * @param array $children Swift_Mime_Entity instances
+ * @param int $compoundLevel For internal use only
+ *
+ * @return Swift_Mime_SimpleMimeEntity
+ */
+ public function setChildren(array $children, $compoundLevel = null)
+ {
+ // TODO: Try to refactor this logic
+
+ $compoundLevel = isset($compoundLevel)
+ ? $compoundLevel
+ : $this->_getCompoundLevel($children)
+ ;
+
+ $immediateChildren = array();
+ $grandchildren = array();
+ $newContentType = $this->_userContentType;
+
+ foreach ($children as $child) {
+ $level = $this->_getNeededChildLevel($child, $compoundLevel);
+ if (empty($immediateChildren)) { //first iteration
+ $immediateChildren = array($child);
+ } else {
+ $nextLevel = $this->_getNeededChildLevel($immediateChildren[0], $compoundLevel);
+ if ($nextLevel == $level) {
+ $immediateChildren[] = $child;
+ } elseif ($level < $nextLevel) {
+ // Re-assign immediateChildren to grandchildren
+ $grandchildren = array_merge($grandchildren, $immediateChildren);
+ // Set new children
+ $immediateChildren = array($child);
+ } else {
+ $grandchildren[] = $child;
+ }
+ }
+ }
+
+ if (!empty($immediateChildren)) {
+ $lowestLevel = $this->_getNeededChildLevel($immediateChildren[0], $compoundLevel);
+
+ // Determine which composite media type is needed to accommodate the
+ // immediate children
+ foreach ($this->_compositeRanges as $mediaType => $range) {
+ if ($lowestLevel > $range[0]
+ && $lowestLevel <= $range[1])
+ {
+ $newContentType = $mediaType;
+ break;
+ }
+ }
+
+ // Put any grandchildren in a subpart
+ if (!empty($grandchildren)) {
+ $subentity = $this->_createChild();
+ $subentity->_setNestingLevel($lowestLevel);
+ $subentity->setChildren($grandchildren, $compoundLevel);
+ array_unshift($immediateChildren, $subentity);
+ }
+ }
+
+ $this->_immediateChildren = $immediateChildren;
+ $this->_children = $children;
+ $this->_setContentTypeInHeaders($newContentType);
+ $this->_fixHeaders();
+ $this->_sortChildren();
+
+ return $this;
+ }
+
+ /**
+ * Get the body of this entity as a string.
+ *
+ * @return string
+ */
+ public function getBody()
+ {
+ return ($this->_body instanceof Swift_OutputByteStream)
+ ? $this->_readStream($this->_body)
+ : $this->_body;
+ }
+
+ /**
+ * Set the body of this entity, either as a string, or as an instance of
+ * {@link Swift_OutputByteStream}.
+ *
+ * @param mixed $body
+ * @param string $contentType optional
+ *
+ * @return Swift_Mime_SimpleMimeEntity
+ */
+ public function setBody($body, $contentType = null)
+ {
+ if ($body !== $this->_body) {
+ $this->_clearCache();
+ }
+
+ $this->_body = $body;
+ if (isset($contentType)) {
+ $this->setContentType($contentType);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the encoder used for the body of this entity.
+ *
+ * @return Swift_Mime_ContentEncoder
+ */
+ public function getEncoder()
+ {
+ return $this->_encoder;
+ }
+
+ /**
+ * Set the encoder used for the body of this entity.
+ *
+ * @param Swift_Mime_ContentEncoder $encoder
+ *
+ * @return Swift_Mime_SimpleMimeEntity
+ */
+ public function setEncoder(Swift_Mime_ContentEncoder $encoder)
+ {
+ if ($encoder !== $this->_encoder) {
+ $this->_clearCache();
+ }
+
+ $this->_encoder = $encoder;
+ $this->_setEncoding($encoder->getName());
+ $this->_notifyEncoderChanged($encoder);
+
+ return $this;
+ }
+
+ /**
+ * Get the boundary used to separate children in this entity.
+ *
+ * @return string
+ */
+ public function getBoundary()
+ {
+ if (!isset($this->_boundary)) {
+ $this->_boundary = '_=_swift_v4_' . time() . '_' . md5(getmypid().mt_rand().uniqid('', true)) . '_=_';
+ }
+
+ return $this->_boundary;
+ }
+
+ /**
+ * Set the boundary used to separate children in this entity.
+ *
+ * @param string $boundary
+ *
+ * @return Swift_Mime_SimpleMimeEntity
+ *
+ * @throws Swift_RfcComplianceException
+ */
+ public function setBoundary($boundary)
+ {
+ $this->_assertValidBoundary($boundary);
+ $this->_boundary = $boundary;
+
+ return $this;
+ }
+
+ /**
+ * Receive notification that the charset of this entity, or a parent entity
+ * has changed.
+ *
+ * @param string $charset
+ */
+ public function charsetChanged($charset)
+ {
+ $this->_notifyCharsetChanged($charset);
+ }
+
+ /**
+ * Receive notification that the encoder of this entity or a parent entity
+ * has changed.
+ *
+ * @param Swift_Mime_ContentEncoder $encoder
+ */
+ public function encoderChanged(Swift_Mime_ContentEncoder $encoder)
+ {
+ $this->_notifyEncoderChanged($encoder);
+ }
+
+ /**
+ * Get this entire entity as a string.
+ *
+ * @return string
+ */
+ public function toString()
+ {
+ $string = $this->_headers->toString();
+ $string .= $this->_bodyToString();
+
+ return $string;
+ }
+
+ /**
+ * Get this entire entity as a string.
+ *
+ * @return string
+ */
+ protected function _bodyToString()
+ {
+ $string = '';
+
+ if (isset($this->_body) && empty($this->_immediateChildren)) {
+ if ($this->_cache->hasKey($this->_cacheKey, 'body')) {
+ $body = $this->_cache->getString($this->_cacheKey, 'body');
+ } else {
+ $body = "\r\n" . $this->_encoder->encodeString($this->getBody(), 0,
+ $this->getMaxLineLength()
+ );
+ $this->_cache->setString($this->_cacheKey, 'body', $body,
+ Swift_KeyCache::MODE_WRITE
+ );
+ }
+ $string .= $body;
+ }
+
+ if (!empty($this->_immediateChildren)) {
+ foreach ($this->_immediateChildren as $child) {
+ $string .= "\r\n\r\n--" . $this->getBoundary() . "\r\n";
+ $string .= $child->toString();
+ }
+ $string .= "\r\n\r\n--" . $this->getBoundary() . "--\r\n";
+ }
+
+ return $string;
+ }
+
+ /**
+ * Returns a string representation of this object.
+ *
+ * @see toString()
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->toString();
+ }
+
+ /**
+ * Write this entire entity to a {@see Swift_InputByteStream}.
+ *
+ * @param Swift_InputByteStream
+ */
+ public function toByteStream(Swift_InputByteStream $is)
+ {
+ $is->write($this->_headers->toString());
+ $is->commit();
+
+ $this->_bodyToByteStream($is);
+ }
+
+ /**
+ * Write this entire entity to a {@link Swift_InputByteStream}.
+ *
+ * @param Swift_InputByteStream
+ */
+ protected function _bodyToByteStream(Swift_InputByteStream $is)
+ {
+ if (empty($this->_immediateChildren)) {
+ if (isset($this->_body)) {
+ if ($this->_cache->hasKey($this->_cacheKey, 'body')) {
+ $this->_cache->exportToByteStream($this->_cacheKey, 'body', $is);
+ } else {
+ $cacheIs = $this->_cache->getInputByteStream($this->_cacheKey, 'body');
+ if ($cacheIs) {
+ $is->bind($cacheIs);
+ }
+
+ $is->write("\r\n");
+
+ if ($this->_body instanceof Swift_OutputByteStream) {
+ $this->_body->setReadPointer(0);
+
+ $this->_encoder->encodeByteStream($this->_body, $is, 0, $this->getMaxLineLength());
+ } else {
+ $is->write($this->_encoder->encodeString($this->getBody(), 0, $this->getMaxLineLength()));
+ }
+
+ if ($cacheIs) {
+ $is->unbind($cacheIs);
+ }
+ }
+ }
+ }
+
+ if (!empty($this->_immediateChildren)) {
+ foreach ($this->_immediateChildren as $child) {
+ $is->write("\r\n\r\n--" . $this->getBoundary() . "\r\n");
+ $child->toByteStream($is);
+ }
+ $is->write("\r\n\r\n--" . $this->getBoundary() . "--\r\n");
+ }
+ }
+
+ /**
+ * Get the name of the header that provides the ID of this entity
+ */
+ protected function _getIdField()
+ {
+ return 'Content-ID';
+ }
+
+ /**
+ * Get the model data (usually an array or a string) for $field.
+ */
+ protected function _getHeaderFieldModel($field)
+ {
+ if ($this->_headers->has($field)) {
+ return $this->_headers->get($field)->getFieldBodyModel();
+ }
+ }
+
+ /**
+ * Set the model data for $field.
+ */
+ protected function _setHeaderFieldModel($field, $model)
+ {
+ if ($this->_headers->has($field)) {
+ $this->_headers->get($field)->setFieldBodyModel($model);
+
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Get the parameter value of $parameter on $field header.
+ */
+ protected function _getHeaderParameter($field, $parameter)
+ {
+ if ($this->_headers->has($field)) {
+ return $this->_headers->get($field)->getParameter($parameter);
+ }
+ }
+
+ /**
+ * Set the parameter value of $parameter on $field header.
+ */
+ protected function _setHeaderParameter($field, $parameter, $value)
+ {
+ if ($this->_headers->has($field)) {
+ $this->_headers->get($field)->setParameter($parameter, $value);
+
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Re-evaluate what content type and encoding should be used on this entity.
+ */
+ protected function _fixHeaders()
+ {
+ if (count($this->_immediateChildren)) {
+ $this->_setHeaderParameter('Content-Type', 'boundary',
+ $this->getBoundary()
+ );
+ $this->_headers->remove('Content-Transfer-Encoding');
+ } else {
+ $this->_setHeaderParameter('Content-Type', 'boundary', null);
+ $this->_setEncoding($this->_encoder->getName());
+ }
+ }
+
+ /**
+ * Get the KeyCache used in this entity.
+ *
+ * @return Swift_KeyCache
+ */
+ protected function _getCache()
+ {
+ return $this->_cache;
+ }
+
+ /**
+ * Get the grammar used for validation.
+ *
+ * @return Swift_Mime_Grammar
+ */
+ protected function _getGrammar()
+ {
+ return $this->_grammar;
+ }
+
+ /**
+ * Empty the KeyCache for this entity.
+ */
+ protected function _clearCache()
+ {
+ $this->_cache->clearKey($this->_cacheKey, 'body');
+ }
+
+ /**
+ * Returns a random Content-ID or Message-ID.
+ *
+ * @return string
+ */
+ protected function getRandomId()
+ {
+ $idLeft = md5(getmypid() . '.' . time() . '.' . uniqid(mt_rand(), true));
+ $idRight = !empty($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : 'swift.generated';
+ $id = $idLeft . '@' . $idRight;
+
+ try {
+ $this->_assertValidId($id);
+ } catch (Swift_RfcComplianceException $e) {
+ $id = $idLeft . '@swift.generated';
+ }
+
+ return $id;
+ }
+
+ private function _readStream(Swift_OutputByteStream $os)
+ {
+ $string = '';
+ while (false !== $bytes = $os->read(8192)) {
+ $string .= $bytes;
+ }
+
+ return $string;
+ }
+
+ private function _setEncoding($encoding)
+ {
+ if (!$this->_setHeaderFieldModel('Content-Transfer-Encoding', $encoding)) {
+ $this->_headers->addTextHeader('Content-Transfer-Encoding', $encoding);
+ }
+ }
+
+ private function _assertValidBoundary($boundary)
+ {
+ if (!preg_match(
+ '/^[a-z0-9\'\(\)\+_\-,\.\/:=\?\ ]{0,69}[a-z0-9\'\(\)\+_\-,\.\/:=\?]$/Di',
+ $boundary))
+ {
+ throw new Swift_RfcComplianceException('Mime boundary set is not RFC 2046 compliant.');
+ }
+ }
+
+ private function _setContentTypeInHeaders($type)
+ {
+ if (!$this->_setHeaderFieldModel('Content-Type', $type)) {
+ $this->_headers->addParameterizedHeader('Content-Type', $type);
+ }
+ }
+
+ private function _setNestingLevel($level)
+ {
+ $this->_nestingLevel = $level;
+ }
+
+ private function _getCompoundLevel($children)
+ {
+ $level = 0;
+ foreach ($children as $child) {
+ $level |= $child->getNestingLevel();
+ }
+
+ return $level;
+ }
+
+ private function _getNeededChildLevel($child, $compoundLevel)
+ {
+ $filter = array();
+ foreach ($this->_compoundLevelFilters as $bitmask => $rules) {
+ if (($compoundLevel & $bitmask) === $bitmask) {
+ $filter = $rules + $filter;
+ }
+ }
+
+ $realLevel = $child->getNestingLevel();
+ $lowercaseType = strtolower($child->getContentType());
+
+ if (isset($filter[$realLevel])
+ && isset($filter[$realLevel][$lowercaseType]))
+ {
+ return $filter[$realLevel][$lowercaseType];
+ } else {
+ return $realLevel;
+ }
+ }
+
+ private function _createChild()
+ {
+ return new self($this->_headers->newInstance(),
+ $this->_encoder, $this->_cache, $this->_grammar);
+ }
+
+ private function _notifyEncoderChanged(Swift_Mime_ContentEncoder $encoder)
+ {
+ foreach ($this->_immediateChildren as $child) {
+ $child->encoderChanged($encoder);
+ }
+ }
+
+ private function _notifyCharsetChanged($charset)
+ {
+ $this->_encoder->charsetChanged($charset);
+ $this->_headers->charsetChanged($charset);
+ foreach ($this->_immediateChildren as $child) {
+ $child->charsetChanged($charset);
+ }
+ }
+
+ private function _sortChildren()
+ {
+ $shouldSort = false;
+ foreach ($this->_immediateChildren as $child) {
+ // NOTE: This include alternative parts moved into a related part
+ if ($child->getNestingLevel() == self::LEVEL_ALTERNATIVE) {
+ $shouldSort = true;
+ break;
+ }
+ }
+
+ // Sort in order of preference, if there is one
+ if ($shouldSort) {
+ usort($this->_immediateChildren, array($this, '_childSortAlgorithm'));
+ }
+ }
+
+ private function _childSortAlgorithm($a, $b)
+ {
+ $typePrefs = array();
+ $types = array(
+ strtolower($a->getContentType()),
+ strtolower($b->getContentType())
+ );
+ foreach ($types as $type) {
+ $typePrefs[] = (array_key_exists($type, $this->_alternativePartOrder))
+ ? $this->_alternativePartOrder[$type]
+ : (max($this->_alternativePartOrder) + 1);
+ }
+
+ return ($typePrefs[0] >= $typePrefs[1]) ? 1 : -1;
+ }
+
+ // -- Destructor
+
+ /**
+ * Empties it's own contents from the cache.
+ */
+ public function __destruct()
+ {
+ $this->_cache->clearAll($this->_cacheKey);
+ }
+
+ /**
+ * Throws an Exception if the id passed does not comply with RFC 2822.
+ *
+ * @param string $id
+ *
+ * @throws Swift_RfcComplianceException
+ */
+ private function _assertValidId($id)
+ {
+ if (!preg_match(
+ '/^' . $this->_grammar->getDefinition('id-left') . '@' .
+ $this->_grammar->getDefinition('id-right') . '$/D',
+ $id
+ ))
+ {
+ throw new Swift_RfcComplianceException(
+ 'Invalid ID given <' . $id . '>'
+ );
+ }
+ }
+}
diff --git a/vendor/swiftmailer/classes/Swift/MimePart.php b/vendor/swiftmailer/classes/Swift/MimePart.php
new file mode 100644
index 000000000..5702d1c14
--- /dev/null
+++ b/vendor/swiftmailer/classes/Swift/MimePart.php
@@ -0,0 +1,59 @@
+createDependenciesFor('mime.part')
+ );
+
+ if (!isset($charset)) {
+ $charset = Swift_DependencyContainer::getInstance()
+ ->lookup('properties.charset');
+ }
+ $this->setBody($body);
+ $this->setCharset($charset);
+ if ($contentType) {
+ $this->setContentType($contentType);
+ }
+ }
+
+ /**
+ * Create a new MimePart.
+ *
+ * @param string $body
+ * @param string $contentType
+ * @param string $charset
+ *
+ * @return Swift_Mime_MimePart
+ */
+ public static function newInstance($body = null, $contentType = null, $charset = null)
+ {
+ return new self($body, $contentType, $charset);
+ }
+}
diff --git a/vendor/swiftmailer/classes/Swift/NullTransport.php b/vendor/swiftmailer/classes/Swift/NullTransport.php
new file mode 100644
index 000000000..726d83ca1
--- /dev/null
+++ b/vendor/swiftmailer/classes/Swift/NullTransport.php
@@ -0,0 +1,39 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Pretends messages have been sent, but just ignores them.
+ *
+ * @author Fabien Potencier
+ */
+class Swift_NullTransport extends Swift_Transport_NullTransport
+{
+ /**
+ * Create a new NullTransport.
+ */
+ public function __construct()
+ {
+ call_user_func_array(
+ array($this, 'Swift_Transport_NullTransport::__construct'),
+ Swift_DependencyContainer::getInstance()
+ ->createDependenciesFor('transport.null')
+ );
+ }
+
+ /**
+ * Create a new NullTransport instance.
+ *
+ * @return Swift_NullTransport
+ */
+ public static function newInstance()
+ {
+ return new self();
+ }
+}
diff --git a/vendor/swiftmailer/classes/Swift/OutputByteStream.php b/vendor/swiftmailer/classes/Swift/OutputByteStream.php
new file mode 100644
index 000000000..0c2783f0f
--- /dev/null
+++ b/vendor/swiftmailer/classes/Swift/OutputByteStream.php
@@ -0,0 +1,46 @@
+setThreshold($threshold);
+ $this->setSleepTime($sleep);
+ $this->_sleeper = $sleeper;
+ }
+
+ /**
+ * Set the number of emails to send before restarting.
+ *
+ * @param int $threshold
+ */
+ public function setThreshold($threshold)
+ {
+ $this->_threshold = $threshold;
+ }
+
+ /**
+ * Get the number of emails to send before restarting.
+ *
+ * @return int
+ */
+ public function getThreshold()
+ {
+ return $this->_threshold;
+ }
+
+ /**
+ * Set the number of seconds to sleep for during a restart.
+ *
+ * @param int $sleep time
+ */
+ public function setSleepTime($sleep)
+ {
+ $this->_sleep = $sleep;
+ }
+
+ /**
+ * Get the number of seconds to sleep for during a restart.
+ *
+ * @return int
+ */
+ public function getSleepTime()
+ {
+ return $this->_sleep;
+ }
+
+ /**
+ * Invoked immediately before the Message is sent.
+ *
+ * @param Swift_Events_SendEvent $evt
+ */
+ public function beforeSendPerformed(Swift_Events_SendEvent $evt)
+ {
+ }
+
+ /**
+ * Invoked immediately after the Message is sent.
+ *
+ * @param Swift_Events_SendEvent $evt
+ */
+ public function sendPerformed(Swift_Events_SendEvent $evt)
+ {
+ ++$this->_counter;
+ if ($this->_counter >= $this->_threshold) {
+ $transport = $evt->getTransport();
+ $transport->stop();
+ if ($this->_sleep) {
+ $this->sleep($this->_sleep);
+ }
+ $transport->start();
+ $this->_counter = 0;
+ }
+ }
+
+ /**
+ * Sleep for $seconds.
+ *
+ * @param int $seconds
+ */
+ public function sleep($seconds)
+ {
+ if (isset($this->_sleeper)) {
+ $this->_sleeper->sleep($seconds);
+ } else {
+ sleep($seconds);
+ }
+ }
+}
diff --git a/vendor/swiftmailer/classes/Swift/Plugins/BandwidthMonitorPlugin.php b/vendor/swiftmailer/classes/Swift/Plugins/BandwidthMonitorPlugin.php
new file mode 100644
index 000000000..af1701a06
--- /dev/null
+++ b/vendor/swiftmailer/classes/Swift/Plugins/BandwidthMonitorPlugin.php
@@ -0,0 +1,164 @@
+getMessage();
+ $message->toByteStream($this);
+ }
+
+ /**
+ * Invoked immediately following a command being sent.
+ *
+ * @param Swift_Events_CommandEvent $evt
+ */
+ public function commandSent(Swift_Events_CommandEvent $evt)
+ {
+ $command = $evt->getCommand();
+ $this->_out += strlen($command);
+ }
+
+ /**
+ * Invoked immediately following a response coming back.
+ *
+ * @param Swift_Events_ResponseEvent $evt
+ */
+ public function responseReceived(Swift_Events_ResponseEvent $evt)
+ {
+ $response = $evt->getResponse();
+ $this->_in += strlen($response);
+ }
+
+ /**
+ * Called when a message is sent so that the outgoing counter can be increased.
+ *
+ * @param string $bytes
+ */
+ public function write($bytes)
+ {
+ $this->_out += strlen($bytes);
+ foreach ($this->_mirrors as $stream) {
+ $stream->write($bytes);
+ }
+ }
+
+ /**
+ * Not used.
+ */
+ public function commit()
+ {
+ }
+
+ /**
+ * Attach $is to this stream.
+ *
+ * The stream acts as an observer, receiving all data that is written.
+ * All {@link write()} and {@link flushBuffers()} operations will be mirrored.
+ *
+ * @param Swift_InputByteStream $is
+ */
+ public function bind(Swift_InputByteStream $is)
+ {
+ $this->_mirrors[] = $is;
+ }
+
+ /**
+ * Remove an already bound stream.
+ *
+ * If $is is not bound, no errors will be raised.
+ * If the stream currently has any buffered data it will be written to $is
+ * before unbinding occurs.
+ *
+ * @param Swift_InputByteStream $is
+ */
+ public function unbind(Swift_InputByteStream $is)
+ {
+ foreach ($this->_mirrors as $k => $stream) {
+ if ($is === $stream) {
+ unset($this->_mirrors[$k]);
+ }
+ }
+ }
+
+ /**
+ * Not used.
+ */
+ public function flushBuffers()
+ {
+ foreach ($this->_mirrors as $stream) {
+ $stream->flushBuffers();
+ }
+ }
+
+ /**
+ * Get the total number of bytes sent to the server.
+ *
+ * @return int
+ */
+ public function getBytesOut()
+ {
+ return $this->_out;
+ }
+
+ /**
+ * Get the total number of bytes received from the server.
+ *
+ * @return int
+ */
+ public function getBytesIn()
+ {
+ return $this->_in;
+ }
+
+ /**
+ * Reset the internal counters to zero.
+ */
+ public function reset()
+ {
+ $this->_out = 0;
+ $this->_in = 0;
+ }
+}
diff --git a/vendor/swiftmailer/classes/Swift/Plugins/Decorator/Replacements.php b/vendor/swiftmailer/classes/Swift/Plugins/Decorator/Replacements.php
new file mode 100644
index 000000000..861843398
--- /dev/null
+++ b/vendor/swiftmailer/classes/Swift/Plugins/Decorator/Replacements.php
@@ -0,0 +1,31 @@
+
+ * $replacements = array(
+ * "address1@domain.tld" => array("{a}" => "b", "{c}" => "d"),
+ * "address2@domain.tld" => array("{a}" => "x", "{c}" => "y")
+ * )
+ *
+ *
+ * When using an instance of {@link Swift_Plugins_Decorator_Replacements},
+ * the object should return just the array of replacements for the address
+ * given to {@link Swift_Plugins_Decorator_Replacements::getReplacementsFor()}.
+ *
+ * @param mixed $replacements Array or Swift_Plugins_Decorator_Replacements
+ */
+ public function __construct($replacements)
+ {
+ $this->setReplacements($replacements);
+ }
+
+ /**
+ * Sets replacements.
+ *
+ * @param mixed $replacements Array or Swift_Plugins_Decorator_Replacements
+ *
+ * @see __construct()
+ */
+ public function setReplacements($replacements)
+ {
+ if (!($replacements instanceof Swift_Plugins_Decorator_Replacements)) {
+ $this->_replacements = (array) $replacements;
+ } else {
+ $this->_replacements = $replacements;
+ }
+ }
+
+ /**
+ * Invoked immediately before the Message is sent.
+ *
+ * @param Swift_Events_SendEvent $evt
+ */
+ public function beforeSendPerformed(Swift_Events_SendEvent $evt)
+ {
+ $message = $evt->getMessage();
+ $this->_restoreMessage($message);
+ $to = array_keys($message->getTo());
+ $address = array_shift($to);
+ if ($replacements = $this->getReplacementsFor($address)) {
+ $body = $message->getBody();
+ $search = array_keys($replacements);
+ $replace = array_values($replacements);
+ $bodyReplaced = str_replace(
+ $search, $replace, $body
+ );
+ if ($body != $bodyReplaced) {
+ $this->_originalBody = $body;
+ $message->setBody($bodyReplaced);
+ }
+
+ foreach ($message->getHeaders()->getAll() as $header) {
+ $body = $header->getFieldBodyModel();
+ $count = 0;
+ if (is_array($body)) {
+ $bodyReplaced = array();
+ foreach ($body as $key => $value) {
+ $count1 = 0;
+ $count2 = 0;
+ $key = is_string($key) ? str_replace($search, $replace, $key, $count1) : $key;
+ $value = is_string($value) ? str_replace($search, $replace, $value, $count2) : $value;
+ $bodyReplaced[$key] = $value;
+
+ if (!$count && ($count1 || $count2)) {
+ $count = 1;
+ }
+ }
+ } else {
+ $bodyReplaced = str_replace($search, $replace, $body, $count);
+ }
+
+ if ($count) {
+ $this->_originalHeaders[$header->getFieldName()] = $body;
+ $header->setFieldBodyModel($bodyReplaced);
+ }
+ }
+
+ $children = (array) $message->getChildren();
+ foreach ($children as $child) {
+ list($type, ) = sscanf($child->getContentType(), '%[^/]/%s');
+ if ('text' == $type) {
+ $body = $child->getBody();
+ $bodyReplaced = str_replace(
+ $search, $replace, $body
+ );
+ if ($body != $bodyReplaced) {
+ $child->setBody($bodyReplaced);
+ $this->_originalChildBodies[$child->getId()] = $body;
+ }
+ }
+ }
+ $this->_lastMessage = $message;
+ }
+ }
+
+ /**
+ * Find a map of replacements for the address.
+ *
+ * If this plugin was provided with a delegate instance of
+ * {@link Swift_Plugins_Decorator_Replacements} then the call will be
+ * delegated to it. Otherwise, it will attempt to find the replacements
+ * from the array provided in the constructor.
+ *
+ * If no replacements can be found, an empty value (NULL) is returned.
+ *
+ * @param string $address
+ *
+ * @return array
+ */
+ public function getReplacementsFor($address)
+ {
+ if ($this->_replacements instanceof Swift_Plugins_Decorator_Replacements) {
+ return $this->_replacements->getReplacementsFor($address);
+ } else {
+ return isset($this->_replacements[$address])
+ ? $this->_replacements[$address]
+ : null
+ ;
+ }
+ }
+
+ /**
+ * Invoked immediately after the Message is sent.
+ *
+ * @param Swift_Events_SendEvent $evt
+ */
+ public function sendPerformed(Swift_Events_SendEvent $evt)
+ {
+ $this->_restoreMessage($evt->getMessage());
+ }
+
+ /** Restore a changed message back to its original state */
+ private function _restoreMessage(Swift_Mime_Message $message)
+ {
+ if ($this->_lastMessage === $message) {
+ if (isset($this->_originalBody)) {
+ $message->setBody($this->_originalBody);
+ $this->_originalBody = null;
+ }
+ if (!empty($this->_originalHeaders)) {
+ foreach ($message->getHeaders()->getAll() as $header) {
+ if (array_key_exists($header->getFieldName(), $this->_originalHeaders)) {
+ $header->setFieldBodyModel($this->_originalHeaders[$header->getFieldName()]);
+ }
+ }
+ $this->_originalHeaders = array();
+ }
+ if (!empty($this->_originalChildBodies)) {
+ $children = (array) $message->getChildren();
+ foreach ($children as $child) {
+ $id = $child->getId();
+ if (array_key_exists($id, $this->_originalChildBodies)) {
+ $child->setBody($this->_originalChildBodies[$id]);
+ }
+ }
+ $this->_originalChildBodies = array();
+ }
+ $this->_lastMessage = null;
+ }
+ }
+}
diff --git a/vendor/swiftmailer/classes/Swift/Plugins/ImpersonatePlugin.php b/vendor/swiftmailer/classes/Swift/Plugins/ImpersonatePlugin.php
new file mode 100644
index 000000000..e2999490a
--- /dev/null
+++ b/vendor/swiftmailer/classes/Swift/Plugins/ImpersonatePlugin.php
@@ -0,0 +1,68 @@
+_sender = $sender;
+ }
+
+ /**
+ * Invoked immediately before the Message is sent.
+ *
+ * @param Swift_Events_SendEvent $evt
+ */
+ public function beforeSendPerformed(Swift_Events_SendEvent $evt)
+ {
+ $message = $evt->getMessage();
+ $headers = $message->getHeaders();
+
+ // save current recipients
+ $headers->addPathHeader('X-Swift-Return-Path', $message->getReturnPath());
+
+ // replace them with the one to send to
+ $message->setReturnPath($this->_sender);
+ }
+
+ /**
+ * Invoked immediately after the Message is sent.
+ *
+ * @param Swift_Events_SendEvent $evt
+ */
+ public function sendPerformed(Swift_Events_SendEvent $evt)
+ {
+ $message = $evt->getMessage();
+
+ // restore original headers
+ $headers = $message->getHeaders();
+
+ if ($headers->has('X-Swift-Return-Path')) {
+ $message->setReturnPath($headers->get('X-Swift-Return-Path')->getAddress());
+ $headers->removeAll('X-Swift-Return-Path');
+ }
+ }
+}
diff --git a/vendor/swiftmailer/classes/Swift/Plugins/Logger.php b/vendor/swiftmailer/classes/Swift/Plugins/Logger.php
new file mode 100644
index 000000000..915e72066
--- /dev/null
+++ b/vendor/swiftmailer/classes/Swift/Plugins/Logger.php
@@ -0,0 +1,36 @@
+_logger = $logger;
+ }
+
+ /**
+ * Add a log entry.
+ *
+ * @param string $entry
+ */
+ public function add($entry)
+ {
+ $this->_logger->add($entry);
+ }
+
+ /**
+ * Clear the log contents.
+ */
+ public function clear()
+ {
+ $this->_logger->clear();
+ }
+
+ /**
+ * Get this log as a string.
+ *
+ * @return string
+ */
+ public function dump()
+ {
+ return $this->_logger->dump();
+ }
+
+ /**
+ * Invoked immediately following a command being sent.
+ *
+ * @param Swift_Events_CommandEvent $evt
+ */
+ public function commandSent(Swift_Events_CommandEvent $evt)
+ {
+ $command = $evt->getCommand();
+ $this->_logger->add(sprintf(">> %s", $command));
+ }
+
+ /**
+ * Invoked immediately following a response coming back.
+ *
+ * @param Swift_Events_ResponseEvent $evt
+ */
+ public function responseReceived(Swift_Events_ResponseEvent $evt)
+ {
+ $response = $evt->getResponse();
+ $this->_logger->add(sprintf("<< %s", $response));
+ }
+
+ /**
+ * Invoked just before a Transport is started.
+ *
+ * @param Swift_Events_TransportChangeEvent $evt
+ */
+ public function beforeTransportStarted(Swift_Events_TransportChangeEvent $evt)
+ {
+ $transportName = get_class($evt->getSource());
+ $this->_logger->add(sprintf("++ Starting %s", $transportName));
+ }
+
+ /**
+ * Invoked immediately after the Transport is started.
+ *
+ * @param Swift_Events_TransportChangeEvent $evt
+ */
+ public function transportStarted(Swift_Events_TransportChangeEvent $evt)
+ {
+ $transportName = get_class($evt->getSource());
+ $this->_logger->add(sprintf("++ %s started", $transportName));
+ }
+
+ /**
+ * Invoked just before a Transport is stopped.
+ *
+ * @param Swift_Events_TransportChangeEvent $evt
+ */
+ public function beforeTransportStopped(Swift_Events_TransportChangeEvent $evt)
+ {
+ $transportName = get_class($evt->getSource());
+ $this->_logger->add(sprintf("++ Stopping %s", $transportName));
+ }
+
+ /**
+ * Invoked immediately after the Transport is stopped.
+ *
+ * @param Swift_Events_TransportChangeEvent $evt
+ */
+ public function transportStopped(Swift_Events_TransportChangeEvent $evt)
+ {
+ $transportName = get_class($evt->getSource());
+ $this->_logger->add(sprintf("++ %s stopped", $transportName));
+ }
+
+ /**
+ * Invoked as a TransportException is thrown in the Transport system.
+ *
+ * @param Swift_Events_TransportExceptionEvent $evt
+ */
+ public function exceptionThrown(Swift_Events_TransportExceptionEvent $evt)
+ {
+ $e = $evt->getException();
+ $message = $e->getMessage();
+ $this->_logger->add(sprintf("!! %s", $message));
+ $message .= PHP_EOL;
+ $message .= 'Log data:' . PHP_EOL;
+ $message .= $this->_logger->dump();
+ $evt->cancelBubble();
+ throw new Swift_TransportException($message);
+ }
+}
diff --git a/vendor/swiftmailer/classes/Swift/Plugins/Loggers/ArrayLogger.php b/vendor/swiftmailer/classes/Swift/Plugins/Loggers/ArrayLogger.php
new file mode 100644
index 000000000..f1739e8e6
--- /dev/null
+++ b/vendor/swiftmailer/classes/Swift/Plugins/Loggers/ArrayLogger.php
@@ -0,0 +1,72 @@
+_size = $size;
+ }
+
+ /**
+ * Add a log entry.
+ *
+ * @param string $entry
+ */
+ public function add($entry)
+ {
+ $this->_log[] = $entry;
+ while (count($this->_log) > $this->_size) {
+ array_shift($this->_log);
+ }
+ }
+
+ /**
+ * Clear the log contents.
+ */
+ public function clear()
+ {
+ $this->_log = array();
+ }
+
+ /**
+ * Get this log as a string.
+ *
+ * @return string
+ */
+ public function dump()
+ {
+ return implode(PHP_EOL, $this->_log);
+ }
+}
diff --git a/vendor/swiftmailer/classes/Swift/Plugins/Loggers/EchoLogger.php b/vendor/swiftmailer/classes/Swift/Plugins/Loggers/EchoLogger.php
new file mode 100644
index 000000000..e8b6c18a3
--- /dev/null
+++ b/vendor/swiftmailer/classes/Swift/Plugins/Loggers/EchoLogger.php
@@ -0,0 +1,58 @@
+_isHtml = $isHtml;
+ }
+
+ /**
+ * Add a log entry.
+ *
+ * @param string $entry
+ */
+ public function add($entry)
+ {
+ if ($this->_isHtml) {
+ printf('%s%s%s', htmlspecialchars($entry, ENT_QUOTES), '