summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFrederic Guillot <fred@kanboard.net>2015-10-16 20:50:12 -0400
committerFrederic Guillot <fred@kanboard.net>2015-10-16 20:50:12 -0400
commitf99a3c501fd6ed7b4914b8d6e855489c2ce5b219 (patch)
tree976276d6acfff78923e4549b0ef9ea94c5e2cb0d
parent9c9ed02cd7ebc5dbbc99bcaed6f80988ce8a9677 (diff)
Make mail transports pluggable and move integrations to plugins
- Postmark: https://github.com/kanboard/plugin-postmark - Mailgun: https://github.com/kanboard/plugin-mailgun - Sendgrid: https://github.com/kanboard/plugin-sendgrid
-rw-r--r--ChangeLog4
-rw-r--r--app/Controller/Webhook.php33
-rw-r--r--app/Core/Base.php6
-rw-r--r--app/Core/EmailClient.php49
-rw-r--r--app/Core/Mail/Client.php96
-rw-r--r--app/Core/Mail/ClientInterface.php24
-rw-r--r--app/Core/Mail/Transport/Mail.php (renamed from app/Integration/Smtp.php)34
-rw-r--r--app/Core/Mail/Transport/Sendmail.php25
-rw-r--r--app/Core/Mail/Transport/Smtp.php30
-rw-r--r--app/Core/Plugin/Base.php2
-rw-r--r--app/Integration/Mailgun.php95
-rw-r--r--app/Integration/Postmark.php94
-rw-r--r--app/Integration/Sendgrid.php98
-rw-r--r--app/Locale/cs_CZ/translations.php7
-rw-r--r--app/Locale/da_DK/translations.php7
-rw-r--r--app/Locale/de_DE/translations.php7
-rw-r--r--app/Locale/es_ES/translations.php7
-rw-r--r--app/Locale/fi_FI/translations.php7
-rw-r--r--app/Locale/fr_FR/translations.php7
-rw-r--r--app/Locale/hu_HU/translations.php7
-rw-r--r--app/Locale/id_ID/translations.php7
-rw-r--r--app/Locale/it_IT/translations.php7
-rw-r--r--app/Locale/ja_JP/translations.php7
-rw-r--r--app/Locale/nb_NO/translations.php9
-rw-r--r--app/Locale/nl_NL/translations.php7
-rw-r--r--app/Locale/pl_PL/translations.php7
-rw-r--r--app/Locale/pt_BR/translations.php7
-rw-r--r--app/Locale/pt_PT/translations.php7
-rw-r--r--app/Locale/ru_RU/translations.php7
-rw-r--r--app/Locale/sr_Latn_RS/translations.php7
-rw-r--r--app/Locale/sv_SE/translations.php7
-rw-r--r--app/Locale/th_TH/translations.php7
-rw-r--r--app/Locale/tr_TR/translations.php7
-rw-r--r--app/Locale/zh_CN/translations.php7
-rw-r--r--app/ServiceProvider/ClassProvider.php14
-rw-r--r--app/Template/config/integrations.php20
-rw-r--r--app/constants.php5
-rw-r--r--assets/img/mailgun-icon.pngbin632 -> 0 bytes
-rw-r--r--assets/img/postmark-icon.pngbin474 -> 0 bytes
-rw-r--r--assets/img/sendgrid-icon.pngbin600 -> 0 bytes
-rw-r--r--doc/email-configuration.markdown61
-rw-r--r--doc/index.markdown3
-rw-r--r--doc/mailgun.markdown28
-rw-r--r--doc/plugin-hooks.markdown148
-rw-r--r--doc/plugin-overrides.markdown36
-rw-r--r--doc/plugin-registration.markdown200
-rw-r--r--doc/plugin-schema-migrations.markdown38
-rw-r--r--doc/plugins.markdown424
-rw-r--r--doc/postmark.markdown29
-rw-r--r--doc/sendgrid.markdown24
-rwxr-xr-xkanboard24
-rw-r--r--tests/units/Integration/MailgunTest.php71
-rw-r--r--tests/units/Integration/PostmarkTest.php106
-rw-r--r--tests/units/Integration/SendgridTest.php135
-rw-r--r--tests/units/Model/EmailNotificationTest.php4
55 files changed, 652 insertions, 1457 deletions
diff --git a/ChangeLog b/ChangeLog
index f43d97a1..21066286 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -3,7 +3,8 @@ Version 1.0.20 (unreleased)
Breaking changes:
-- Add namespace Kanboard
+- Add namespace Kanboard (update your plugins)
+- Move Mailgun, Sendgrid and Postmark to plugins
New features:
@@ -13,6 +14,7 @@ Improvements:
* Allow to change comments sorting
* Add the possibility to append or not custom filters
+* Make mail transports pluggable
Version 1.0.19
--------------
diff --git a/app/Controller/Webhook.php b/app/Controller/Webhook.php
index 1b3a659b..1186680d 100644
--- a/app/Controller/Webhook.php
+++ b/app/Controller/Webhook.php
@@ -92,37 +92,4 @@ class Webhook extends Base
echo $result ? 'PARSED' : 'IGNORED';
}
-
- /**
- * Handle Postmark webhooks
- *
- * @access public
- */
- public function postmark()
- {
- $this->checkWebhookToken();
- echo $this->postmark->receiveEmail($this->request->getJson()) ? 'PARSED' : 'IGNORED';
- }
-
- /**
- * Handle Mailgun webhooks
- *
- * @access public
- */
- public function mailgun()
- {
- $this->checkWebhookToken();
- echo $this->mailgun->receiveEmail($_POST) ? 'PARSED' : 'IGNORED';
- }
-
- /**
- * Handle Sendgrid webhooks
- *
- * @access public
- */
- public function sendgrid()
- {
- $this->checkWebhookToken();
- echo $this->sendgrid->receiveEmail($_POST) ? 'PARSED' : 'IGNORED';
- }
}
diff --git a/app/Core/Base.php b/app/Core/Base.php
index 331b67e3..76723e8f 100644
--- a/app/Core/Base.php
+++ b/app/Core/Base.php
@@ -11,7 +11,7 @@ use Pimple\Container;
* @author Frederic Guillot
*
* @property \Kanboard\Core\Helper $helper
- * @property \Kanboard\Core\EmailClient $emailClient
+ * @property \Kanboard\Core\Mail\Client $emailClient
* @property \Kanboard\Core\HttpClient $httpClient
* @property \Kanboard\Core\Paginator $paginator
* @property \Kanboard\Core\Request $request
@@ -29,11 +29,7 @@ use Pimple\Container;
* @property \Kanboard\Integration\GitlabWebhook $gitlabWebhook
* @property \Kanboard\Integration\HipchatWebhook $hipchatWebhook
* @property \Kanboard\Integration\Jabber $jabber
- * @property \Kanboard\Integration\Mailgun $mailgun
- * @property \Kanboard\Integration\Postmark $postmark
- * @property \Kanboard\Integration\Sendgrid $sendgrid
* @property \Kanboard\Integration\SlackWebhook $slackWebhook
- * @property \Kanboard\Integration\Smtp $smtp
* @property \Kanboard\Formatter\ProjectGanttFormatter $projectGanttFormatter
* @property \Kanboard\Formatter\TaskFilterGanttFormatter $taskFilterGanttFormatter
* @property \Kanboard\Formatter\TaskFilterAutoCompleteFormatter $taskFilterAutoCompleteFormatter
diff --git a/app/Core/EmailClient.php b/app/Core/EmailClient.php
deleted file mode 100644
index 38d6b3f1..00000000
--- a/app/Core/EmailClient.php
+++ /dev/null
@@ -1,49 +0,0 @@
-<?php
-
-namespace Kanboard\Core;
-
-/**
- * Mail client
- *
- * @package core
- * @author Frederic Guillot
- */
-class EmailClient extends Base
-{
- /**
- * Send a HTML email
- *
- * @access public
- * @param string $email
- * @param string $name
- * @param string $subject
- * @param string $html
- */
- public function send($email, $name, $subject, $html)
- {
- $this->container['logger']->debug('Sending email to '.$email.' ('.MAIL_TRANSPORT.')');
-
- $start_time = microtime(true);
- $author = 'Kanboard';
-
- if (Session::isOpen() && $this->userSession->isLogged()) {
- $author = e('%s via Kanboard', $this->user->getFullname($this->session['user']));
- }
-
- switch (MAIL_TRANSPORT) {
- case 'sendgrid':
- $this->sendgrid->sendEmail($email, $name, $subject, $html, $author);
- break;
- case 'mailgun':
- $this->mailgun->sendEmail($email, $name, $subject, $html, $author);
- break;
- case 'postmark':
- $this->postmark->sendEmail($email, $name, $subject, $html, $author);
- break;
- default:
- $this->smtp->sendEmail($email, $name, $subject, $html, $author);
- }
-
- $this->container['logger']->debug('Email sent in '.round(microtime(true) - $start_time, 6).' seconds');
- }
-}
diff --git a/app/Core/Mail/Client.php b/app/Core/Mail/Client.php
new file mode 100644
index 00000000..c7cfaab9
--- /dev/null
+++ b/app/Core/Mail/Client.php
@@ -0,0 +1,96 @@
+<?php
+
+namespace Kanboard\Core\Mail;
+
+use Pimple\Container;
+use Kanboard\Core\Base;
+
+/**
+ * Mail Client
+ *
+ * @package mail
+ * @author Frederic Guillot
+ */
+class Client extends Base
+{
+ /**
+ * Mail transport instances
+ *
+ * @access private
+ * @var \Pimple\Container
+ */
+ private $transports;
+
+ /**
+ * Constructor
+ *
+ * @access public
+ * @param \Pimple\Container $container
+ */
+ public function __construct(Container $container)
+ {
+ parent::__construct($container);
+ $this->transports = new Container;
+ }
+
+ /**
+ * Send a HTML email
+ *
+ * @access public
+ * @param string $email
+ * @param string $name
+ * @param string $subject
+ * @param string $html
+ * @return EmailClient
+ */
+ public function send($email, $name, $subject, $html)
+ {
+ $this->container['logger']->debug('Sending email to '.$email.' ('.MAIL_TRANSPORT.')');
+
+ $start_time = microtime(true);
+ $author = 'Kanboard';
+
+ if ($this->userSession->isLogged()) {
+ $author = e('%s via Kanboard', $this->user->getFullname($this->session['user']));
+ }
+
+ $this->getTransport(MAIL_TRANSPORT)->sendEmail($email, $name, $subject, $html, $author);
+
+ if (DEBUG) {
+ $this->logger->debug('Email sent in '.round(microtime(true) - $start_time, 6).' seconds');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get mail transport instance
+ *
+ * @access public
+ * @param string $transport
+ * @return EmailClientInterface
+ */
+ public function getTransport($transport)
+ {
+ return $this->transports[$transport];
+ }
+
+ /**
+ * Add a new mail transport
+ *
+ * @access public
+ * @param string $transport
+ * @param string $class
+ * @return EmailClient
+ */
+ public function setTransport($transport, $class)
+ {
+ $container = $this->container;
+
+ $this->transports[$transport] = function() use ($class, $container) {
+ return new $class($container);
+ };
+
+ return $this;
+ }
+}
diff --git a/app/Core/Mail/ClientInterface.php b/app/Core/Mail/ClientInterface.php
new file mode 100644
index 00000000..66263a98
--- /dev/null
+++ b/app/Core/Mail/ClientInterface.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace Kanboard\Core\Mail;
+
+/**
+ * Mail Client Interface
+ *
+ * @package mail
+ * @author Frederic Guillot
+ */
+interface ClientInterface
+{
+ /**
+ * Send a HTML email
+ *
+ * @access public
+ * @param string $email
+ * @param string $name
+ * @param string $subject
+ * @param string $html
+ * @param string $author
+ */
+ public function sendEmail($email, $name, $subject, $html, $author);
+}
diff --git a/app/Integration/Smtp.php b/app/Core/Mail/Transport/Mail.php
index 3ef6539d..ca06e208 100644
--- a/app/Integration/Smtp.php
+++ b/app/Core/Mail/Transport/Mail.php
@@ -1,21 +1,21 @@
<?php
-namespace Kanboard\Integration;
+namespace Kanboard\Core\Mail\Transport;
use Swift_Message;
use Swift_Mailer;
use Swift_MailTransport;
-use Swift_SendmailTransport;
-use Swift_SmtpTransport;
use Swift_TransportException;
+use Kanboard\Core\Base;
+use Kanboard\Core\Mail\ClientInterface;
/**
- * Smtp
+ * PHP Mail Handler
*
- * @package integration
+ * @package transport
* @author Frederic Guillot
*/
-class Smtp extends \Kanboard\Core\Base
+class Mail extends Base implements ClientInterface
{
/**
* Send a HTML email
@@ -40,32 +40,18 @@ class Smtp extends \Kanboard\Core\Base
Swift_Mailer::newInstance($this->getTransport())->send($message);
}
catch (Swift_TransportException $e) {
- $this->container['logger']->error($e->getMessage());
+ $this->logger->error($e->getMessage());
}
}
/**
* Get SwiftMailer transport
*
- * @access private
+ * @access protected
* @return \Swift_Transport
*/
- private function getTransport()
+ protected function getTransport()
{
- 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);
- $transport->setEncryption(MAIL_SMTP_ENCRYPTION);
- break;
- case 'sendmail':
- $transport = Swift_SendmailTransport::newInstance(MAIL_SENDMAIL_COMMAND);
- break;
- default:
- $transport = Swift_MailTransport::newInstance();
- }
-
- return $transport;
+ return Swift_MailTransport::newInstance();
}
}
diff --git a/app/Core/Mail/Transport/Sendmail.php b/app/Core/Mail/Transport/Sendmail.php
new file mode 100644
index 00000000..849e3385
--- /dev/null
+++ b/app/Core/Mail/Transport/Sendmail.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace Kanboard\Core\Mail\Transport;
+
+use Swift_SendmailTransport;
+
+/**
+ * PHP Mail Handler
+ *
+ * @package transport
+ * @author Frederic Guillot
+ */
+class Sendmail extends Mail
+{
+ /**
+ * Get SwiftMailer transport
+ *
+ * @access protected
+ * @return \Swift_Transport
+ */
+ protected function getTransport()
+ {
+ return Swift_SendmailTransport::newInstance(MAIL_SENDMAIL_COMMAND);
+ }
+}
diff --git a/app/Core/Mail/Transport/Smtp.php b/app/Core/Mail/Transport/Smtp.php
new file mode 100644
index 00000000..757408ea
--- /dev/null
+++ b/app/Core/Mail/Transport/Smtp.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace Kanboard\Core\Mail\Transport;
+
+use Swift_SmtpTransport;
+
+/**
+ * PHP Mail Handler
+ *
+ * @package transport
+ * @author Frederic Guillot
+ */
+class Smtp extends Mail
+{
+ /**
+ * Get SwiftMailer transport
+ *
+ * @access protected
+ * @return \Swift_Transport
+ */
+ protected function getTransport()
+ {
+ $transport = Swift_SmtpTransport::newInstance(MAIL_SMTP_HOSTNAME, MAIL_SMTP_PORT);
+ $transport->setUsername(MAIL_SMTP_USERNAME);
+ $transport->setPassword(MAIL_SMTP_PASSWORD);
+ $transport->setEncryption(MAIL_SMTP_ENCRYPTION);
+
+ return $transport;
+ }
+}
diff --git a/app/Core/Plugin/Base.php b/app/Core/Plugin/Base.php
index d8b196e1..e38ffee8 100644
--- a/app/Core/Plugin/Base.php
+++ b/app/Core/Plugin/Base.php
@@ -66,7 +66,7 @@ abstract class Base extends \Kanboard\Core\Base
*/
public function getPluginName()
{
- return ucfirst(substr(get_called_class(), 7, -7));
+ return ucfirst(substr(get_called_class(), 16, -7));
}
/**
diff --git a/app/Integration/Mailgun.php b/app/Integration/Mailgun.php
deleted file mode 100644
index 045284d2..00000000
--- a/app/Integration/Mailgun.php
+++ /dev/null
@@ -1,95 +0,0 @@
-<?php
-
-namespace Kanboard\Integration;
-
-use Kanboard\Core\Tool;
-
-/**
- * Mailgun Integration
- *
- * @package integration
- * @author Frederic Guillot
- */
-class Mailgun extends \Kanboard\Core\Base
-{
- /**
- * Send a HTML email
- *
- * @access public
- * @param string $email
- * @param string $name
- * @param string $subject
- * @param string $html
- * @param string $author
- */
- public function sendEmail($email, $name, $subject, $html, $author)
- {
- $headers = array(
- 'Authorization: Basic '.base64_encode('api:'.MAILGUN_API_TOKEN)
- );
-
- $payload = array(
- 'from' => sprintf('%s <%s>', $author, MAIL_FROM),
- 'to' => sprintf('%s <%s>', $name, $email),
- 'subject' => $subject,
- 'html' => $html,
- );
-
- $this->httpClient->postForm('https://api.mailgun.net/v3/'.MAILGUN_DOMAIN.'/messages', $payload, $headers);
- }
-
- /**
- * Parse incoming email
- *
- * @access public
- * @param array $payload Incoming email
- * @return boolean
- */
- public function receiveEmail(array $payload)
- {
- if (empty($payload['sender']) || empty($payload['subject']) || empty($payload['recipient'])) {
- return false;
- }
-
- // The user must exists in Kanboard
- $user = $this->user->getByEmail($payload['sender']);
-
- if (empty($user)) {
- $this->container['logger']->debug('Mailgun: ignored => user not found');
- return false;
- }
-
- // The project must have a short name
- $project = $this->project->getByIdentifier(Tool::getMailboxHash($payload['recipient']));
-
- if (empty($project)) {
- $this->container['logger']->debug('Mailgun: ignored => project not found');
- return false;
- }
-
- // The user must be member of the project
- if (! $this->projectPermission->isMember($project['id'], $user['id'])) {
- $this->container['logger']->debug('Mailgun: ignored => user is not member of the project');
- return false;
- }
-
- // Get the Markdown contents
- if (! empty($payload['stripped-html'])) {
- $description = $this->htmlConverter->convert($payload['stripped-html']);
- }
- else if (! empty($payload['stripped-text'])) {
- $description = $payload['stripped-text'];
- }
- else {
- $description = '';
- }
-
- // Finally, we create the task
- return (bool) $this->taskCreation->create(array(
- 'project_id' => $project['id'],
- 'title' => $payload['subject'],
- 'description' => $description,
- 'creator_id' => $user['id'],
- ));
- }
-}
diff --git a/app/Integration/Postmark.php b/app/Integration/Postmark.php
deleted file mode 100644
index b57f92d4..00000000
--- a/app/Integration/Postmark.php
+++ /dev/null
@@ -1,94 +0,0 @@
-<?php
-
-namespace Kanboard\Integration;
-
-/**
- * Postmark integration
- *
- * @package integration
- * @author Frederic Guillot
- */
-class Postmark extends \Kanboard\Core\Base
-{
- /**
- * Send a HTML email
- *
- * @access public
- * @param string $email
- * @param string $name
- * @param string $subject
- * @param string $html
- * @param string $author
- */
- public function sendEmail($email, $name, $subject, $html, $author)
- {
- $headers = array(
- 'Accept: application/json',
- 'X-Postmark-Server-Token: '.POSTMARK_API_TOKEN,
- );
-
- $payload = array(
- 'From' => sprintf('%s <%s>', $author, MAIL_FROM),
- 'To' => sprintf('%s <%s>', $name, $email),
- 'Subject' => $subject,
- 'HtmlBody' => $html,
- );
-
- $this->httpClient->postJson('https://api.postmarkapp.com/email', $payload, $headers);
- }
-
- /**
- * Parse incoming email
- *
- * @access public
- * @param array $payload Incoming email
- * @return boolean
- */
- public function receiveEmail(array $payload)
- {
- if (empty($payload['From']) || empty($payload['Subject']) || empty($payload['MailboxHash'])) {
- return false;
- }
-
- // The user must exists in Kanboard
- $user = $this->user->getByEmail($payload['From']);
-
- if (empty($user)) {
- $this->container['logger']->debug('Postmark: ignored => user not found');
- return false;
- }
-
- // The project must have a short name
- $project = $this->project->getByIdentifier($payload['MailboxHash']);
-
- if (empty($project)) {
- $this->container['logger']->debug('Postmark: ignored => project not found');
- return false;
- }
-
- // The user must be member of the project
- if (! $this->projectPermission->isMember($project['id'], $user['id'])) {
- $this->container['logger']->debug('Postmark: ignored => user is not member of the project');
- return false;
- }
-
- // Get the Markdown contents
- if (! empty($payload['HtmlBody'])) {
- $description = $this->htmlConverter->convert($payload['HtmlBody']);
- }
- else if (! empty($payload['TextBody'])) {
- $description = $payload['TextBody'];
- }
- else {
- $description = '';
- }
-
- // Finally, we create the task
- return (bool) $this->taskCreation->create(array(
- 'project_id' => $project['id'],
- 'title' => $payload['Subject'],
- 'description' => $description,
- 'creator_id' => $user['id'],
- ));
- }
-}
diff --git a/app/Integration/Sendgrid.php b/app/Integration/Sendgrid.php
deleted file mode 100644
index 2da87119..00000000
--- a/app/Integration/Sendgrid.php
+++ /dev/null
@@ -1,98 +0,0 @@
-<?php
-
-namespace Kanboard\Integration;
-
-use Kanboard\Core\Tool;
-
-/**
- * Sendgrid Integration
- *
- * @package integration
- * @author Frederic Guillot
- */
-class Sendgrid extends \Kanboard\Core\Base
-{
- /**
- * Send a HTML email
- *
- * @access public
- * @param string $email
- * @param string $name
- * @param string $subject
- * @param string $html
- * @param string $author
- */
- public function sendEmail($email, $name, $subject, $html, $author)
- {
- $payload = array(
- 'api_user' => SENDGRID_API_USER,
- 'api_key' => SENDGRID_API_KEY,
- 'to' => $email,
- 'toname' => $name,
- 'from' => MAIL_FROM,
- 'fromname' => $author,
- 'html' => $html,
- 'subject' => $subject,
- );
-
- $this->httpClient->postForm('https://api.sendgrid.com/api/mail.send.json', $payload);
- }
-
- /**
- * Parse incoming email
- *
- * @access public
- * @param array $payload Incoming email
- * @return boolean
- */
- public function receiveEmail(array $payload)
- {
- if (empty($payload['envelope']) || empty($payload['subject'])) {
- return false;
- }
-
- $envelope = json_decode($payload['envelope'], true);
- $sender = isset($envelope['to'][0]) ? $envelope['to'][0] : '';
-
- // The user must exists in Kanboard
- $user = $this->user->getByEmail($envelope['from']);
-
- if (empty($user)) {
- $this->container['logger']->debug('SendgridWebhook: ignored => user not found');
- return false;
- }
-
- // The project must have a short name
- $project = $this->project->getByIdentifier(Tool::getMailboxHash($sender));
-
- if (empty($project)) {
- $this->container['logger']->debug('SendgridWebhook: ignored => project not found');
- return false;
- }
-
- // The user must be member of the project
- if (! $this->projectPermission->isMember($project['id'], $user['id'])) {
- $this->container['logger']->debug('SendgridWebhook: ignored => user is not member of the project');
- return false;
- }
-
- // Get the Markdown contents
- if (! empty($payload['html'])) {
- $description = $this->htmlConverter->convert($payload['html']);
- }
- else if (! empty($payload['text'])) {
- $description = $payload['text'];
- }
- else {
- $description = '';
- }
-
- // Finally, we create the task
- return (bool) $this->taskCreation->create(array(
- 'project_id' => $project['id'],
- 'title' => $payload['subject'],
- 'description' => $description,
- 'creator_id' => $user['id'],
- ));
- }
-}
diff --git a/app/Locale/cs_CZ/translations.php b/app/Locale/cs_CZ/translations.php
index ba3d809f..0ea35a9b 100644
--- a/app/Locale/cs_CZ/translations.php
+++ b/app/Locale/cs_CZ/translations.php
@@ -353,7 +353,6 @@ return array(
'Clone' => 'Kopie',
'Project cloned successfully.' => 'Kopie projektu byla úspěšně vytvořena.',
'Unable to clone this project.' => 'Kopii projektu nelze vytvořit.',
- 'Email notifications' => 'Upozornění E-Mailem ',
'Enable email notifications' => 'Povolit upozornění pomocí e-mailů',
'Task position:' => 'Pořadí úkolu:',
'The task #%d have been opened.' => 'Úkol #%d byl znovu otevřen.',
@@ -735,12 +734,6 @@ return array(
'SEK - Swedish Krona' => 'SEK - Schwedische Kronen',
'The project identifier is an optional alphanumeric code used to identify your project.' => 'Identifikátor projektu je volitelný alfanumerický kód používaný k identifikaci vašeho projektu.',
'Identifier' => 'Identifikator',
- 'Postmark (incoming emails)' => 'Postmark (Eingehende E-Mails)',
- 'Help on Postmark integration' => 'Hilfe bei Postmark-Integration',
- 'Mailgun (incoming emails)' => 'Mailgun (Eingehende E-Mails)',
- 'Help on Mailgun integration' => 'Hilfe bei Mailgun-Integration',
- 'Sendgrid (incoming emails)' => 'Sendgrid (Eingehende E-Mails)',
- 'Help on Sendgrid integration' => 'Hilfe bei Sendgrid-Integration',
'Disable two factor authentication' => 'Zrušit dvou stupňovou autorizaci',
'Do you really want to disable the two factor authentication for this user: "%s"?' => 'Willst du wirklich für folgenden Nutzer die Zwei-Faktor-Authentifizierung deaktivieren: "%s"?',
// 'Edit link' => '',
diff --git a/app/Locale/da_DK/translations.php b/app/Locale/da_DK/translations.php
index fa45e581..a0578120 100644
--- a/app/Locale/da_DK/translations.php
+++ b/app/Locale/da_DK/translations.php
@@ -353,7 +353,6 @@ return array(
'Clone' => 'Kopier',
'Project cloned successfully.' => 'Projektet er kopieret.',
'Unable to clone this project.' => 'Projektet kunne ikke kopieres',
- 'Email notifications' => 'Email notifikationer',
'Enable email notifications' => 'Aktivér email notifikationer',
'Task position:' => 'Opgave position:',
'The task #%d have been opened.' => 'Opgaven #%d er blevet åbnet.',
@@ -735,12 +734,6 @@ return array(
// 'SEK - Swedish Krona' => '',
// 'The project identifier is an optional alphanumeric code used to identify your project.' => '',
// 'Identifier' => '',
- // 'Postmark (incoming emails)' => '',
- // 'Help on Postmark integration' => '',
- // 'Mailgun (incoming emails)' => '',
- // 'Help on Mailgun integration' => '',
- // 'Sendgrid (incoming emails)' => '',
- // 'Help on Sendgrid integration' => '',
// 'Disable two factor authentication' => '',
// 'Do you really want to disable the two factor authentication for this user: "%s"?' => '',
// 'Edit link' => '',
diff --git a/app/Locale/de_DE/translations.php b/app/Locale/de_DE/translations.php
index c15e4376..13d88b11 100644
--- a/app/Locale/de_DE/translations.php
+++ b/app/Locale/de_DE/translations.php
@@ -353,7 +353,6 @@ return array(
'Clone' => 'duplizieren',
'Project cloned successfully.' => 'Projekt wurde dupliziert.',
'Unable to clone this project.' => 'Duplizieren dieses Projekts schlug fehl.',
- 'Email notifications' => 'E-Mail-Benachrichtigungen',
'Enable email notifications' => 'E-Mail-Benachrichtigungen einschalten',
'Task position:' => 'Position der Aufgabe',
'The task #%d have been opened.' => 'Die Aufgabe #%d wurde geöffnet.',
@@ -735,12 +734,6 @@ return array(
'SEK - Swedish Krona' => 'SEK - Schwedische Kronen',
'The project identifier is an optional alphanumeric code used to identify your project.' => 'Der Projektidentifikator ist ein optionaler alphanumerischer Code, der das Projekt identifiziert.',
'Identifier' => 'Identifikator',
- 'Postmark (incoming emails)' => 'Postmark (Eingehende E-Mails)',
- 'Help on Postmark integration' => 'Hilfe bei Postmark-Integration',
- 'Mailgun (incoming emails)' => 'Mailgun (Eingehende E-Mails)',
- 'Help on Mailgun integration' => 'Hilfe bei Mailgun-Integration',
- 'Sendgrid (incoming emails)' => 'Sendgrid (Eingehende E-Mails)',
- 'Help on Sendgrid integration' => 'Hilfe bei Sendgrid-Integration',
'Disable two factor authentication' => 'Deaktiviere Zwei-Faktor-Authentifizierung',
'Do you really want to disable the two factor authentication for this user: "%s"?' => 'Willst du wirklich für folgenden Nutzer die Zwei-Faktor-Authentifizierung deaktivieren: "%s"?',
'Edit link' => 'Verbindung bearbeiten',
diff --git a/app/Locale/es_ES/translations.php b/app/Locale/es_ES/translations.php
index 152a617a..7e951471 100644
--- a/app/Locale/es_ES/translations.php
+++ b/app/Locale/es_ES/translations.php
@@ -353,7 +353,6 @@ return array(
'Clone' => 'Clonar',
'Project cloned successfully.' => 'Proyecto clonado correctamente',
'Unable to clone this project.' => 'Impsible clonar proyecto',
- 'Email notifications' => 'Notificaciones correo electrónico',
'Enable email notifications' => 'Habilitar notificaciones por correo electrónico',
'Task position:' => 'Posición de la tarea',
'The task #%d have been opened.' => 'La tarea #%d ha sido abierta',
@@ -735,12 +734,6 @@ return array(
'SEK - Swedish Krona' => 'SEK - Corona sueca',
'The project identifier is an optional alphanumeric code used to identify your project.' => 'El identificador del proyecto us un código opcional alfanumérico que se usa para identificar su proyecto.',
'Identifier' => 'Identificador',
- 'Postmark (incoming emails)' => 'Matasellos (emails entrantes)',
- 'Help on Postmark integration' => 'Ayuda sobre la integración de Matasellos',
- 'Mailgun (incoming emails)' => 'Mailgun (emails entrantes)',
- 'Help on Mailgun integration' => 'Ayuda sobre la integración con Mailgun',
- 'Sendgrid (incoming emails)' => 'Sendgrid (emails entrantes)',
- 'Help on Sendgrid integration' => 'Ayuda sobre la integración con Sendgrid',
'Disable two factor authentication' => 'Desactivar la autenticación de dos factores',
'Do you really want to disable the two factor authentication for this user: "%s"?' => '¿Realmentes quiere desactuvar la autenticación de dos factores para este usuario: "%s?"',
'Edit link' => 'Editar enlace',
diff --git a/app/Locale/fi_FI/translations.php b/app/Locale/fi_FI/translations.php
index 71844cc6..be4d2275 100644
--- a/app/Locale/fi_FI/translations.php
+++ b/app/Locale/fi_FI/translations.php
@@ -353,7 +353,6 @@ return array(
'Clone' => 'Kahdenna',
'Project cloned successfully.' => 'Projekti kahdennettu onnistuneesti',
'Unable to clone this project.' => 'Projektin kahdennus epäonnistui',
- 'Email notifications' => 'Sähköposti-ilmoitukset',
'Enable email notifications' => 'Ota käyttöön sähköposti-ilmoitukset',
'Task position:' => 'Tehtävän sijainti',
'The task #%d have been opened.' => 'Tehtävä #%d on avattu',
@@ -735,12 +734,6 @@ return array(
// 'SEK - Swedish Krona' => '',
// 'The project identifier is an optional alphanumeric code used to identify your project.' => '',
// 'Identifier' => '',
- // 'Postmark (incoming emails)' => '',
- // 'Help on Postmark integration' => '',
- // 'Mailgun (incoming emails)' => '',
- // 'Help on Mailgun integration' => '',
- // 'Sendgrid (incoming emails)' => '',
- // 'Help on Sendgrid integration' => '',
// 'Disable two factor authentication' => '',
// 'Do you really want to disable the two factor authentication for this user: "%s"?' => '',
// 'Edit link' => '',
diff --git a/app/Locale/fr_FR/translations.php b/app/Locale/fr_FR/translations.php
index ec32ecb1..1afdeab3 100644
--- a/app/Locale/fr_FR/translations.php
+++ b/app/Locale/fr_FR/translations.php
@@ -353,7 +353,6 @@ return array(
'Clone' => 'Clone',
'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.',
@@ -737,12 +736,6 @@ return array(
'SEK - Swedish Krona' => 'SEK - Couronne suédoise',
'The project identifier is an optional alphanumeric code used to identify your project.' => 'L\'identificateur du projet est un code alpha-numérique optionnel pour identifier votre projet.',
'Identifier' => 'Identificateur',
- 'Postmark (incoming emails)' => 'Postmark (emails entrants)',
- 'Help on Postmark integration' => 'Aide sur l\'intégration avec Postmark',
- 'Mailgun (incoming emails)' => 'Mailgun (emails entrants)',
- 'Help on Mailgun integration' => 'Aide sur l\'intégration avec Mailgun',
- 'Sendgrid (incoming emails)' => 'Sendgrid (emails entrants)',
- 'Help on Sendgrid integration' => 'Aide sur l\'intégration avec Sendgrid',
'Disable two factor authentication' => 'Désactiver l\'authentification à deux facteurs',
'Do you really want to disable the two factor authentication for this user: "%s"?' => 'Voulez-vous vraiment désactiver l\'authentification à deux facteurs pour cet utilisateur : « %s » ?',
'Edit link' => 'Modifier un lien',
diff --git a/app/Locale/hu_HU/translations.php b/app/Locale/hu_HU/translations.php
index bef65570..bd9ac9b5 100644
--- a/app/Locale/hu_HU/translations.php
+++ b/app/Locale/hu_HU/translations.php
@@ -353,7 +353,6 @@ return array(
'Clone' => 'Másolat',
'Project cloned successfully.' => 'A projekt sikeresen másolva.',
'Unable to clone this project.' => 'A projekt másolása sikertelen.',
- 'Email notifications' => 'E-mail értesítések',
'Enable email notifications' => 'E-mail értesítések engedélyezése',
'Task position:' => 'Feladat helye:',
'The task #%d have been opened.' => 'Feladat #%d megnyitva.',
@@ -735,12 +734,6 @@ return array(
// 'SEK - Swedish Krona' => '',
// 'The project identifier is an optional alphanumeric code used to identify your project.' => '',
// 'Identifier' => '',
- // 'Postmark (incoming emails)' => '',
- // 'Help on Postmark integration' => '',
- // 'Mailgun (incoming emails)' => '',
- // 'Help on Mailgun integration' => '',
- // 'Sendgrid (incoming emails)' => '',
- // 'Help on Sendgrid integration' => '',
// 'Disable two factor authentication' => '',
// 'Do you really want to disable the two factor authentication for this user: "%s"?' => '',
// 'Edit link' => '',
diff --git a/app/Locale/id_ID/translations.php b/app/Locale/id_ID/translations.php
index 03c96b83..ef4eefee 100644
--- a/app/Locale/id_ID/translations.php
+++ b/app/Locale/id_ID/translations.php
@@ -353,7 +353,6 @@ return array(
'Clone' => 'Klon',
'Project cloned successfully.' => 'Kloning proyek berhasil.',
'Unable to clone this project.' => 'Tidak dapat mengkloning proyek.',
- 'Email notifications' => 'Pemberitahuan email',
'Enable email notifications' => 'Aktifkan pemberitahuan dari email',
'Task position:' => 'Posisi tugas :',
'The task #%d have been opened.' => 'Tugas #%d telah dibuka.',
@@ -735,12 +734,6 @@ return array(
'SEK - Swedish Krona' => 'SEK - Krona Swedia',
'The project identifier is an optional alphanumeric code used to identify your project.' => 'Identifier proyek adalah kode alfanumerik opsional digunakan untuk mengidentifikasi proyek Anda.',
'Identifier' => 'Identifier',
- 'Postmark (incoming emails)' => 'Postmark (email masuk)',
- 'Help on Postmark integration' => 'Bantuan pada integrasi Postmark',
- 'Mailgun (incoming emails)' => 'Mailgun (email masuk)',
- 'Help on Mailgun integration' => 'Bantuan pada integrasi Mailgun',
- 'Sendgrid (incoming emails)' => 'Sendgrid (email masuk)',
- 'Help on Sendgrid integration' => 'Bantuan pada integrasi Sendgrid',
'Disable two factor authentication' => 'Matikan dua faktor otentifikasi',
'Do you really want to disable the two factor authentication for this user: "%s"?' => 'Apakah anda yakin akan mematikan dua faktor otentifikasi untuk pengguna ini : « %s » ?',
'Edit link' => 'Modifikasi tautan',
diff --git a/app/Locale/it_IT/translations.php b/app/Locale/it_IT/translations.php
index a9f19d84..e906888a 100644
--- a/app/Locale/it_IT/translations.php
+++ b/app/Locale/it_IT/translations.php
@@ -353,7 +353,6 @@ return array(
'Clone' => 'Clona',
'Project cloned successfully.' => 'Progetto clonato con successo.',
'Unable to clone this project.' => 'Impossibile clonare questo progetto',
- 'Email notifications' => 'Notifiche email',
'Enable email notifications' => 'Abilita le notifiche via email',
'Task position:' => 'Posizione del compito:',
'The task #%d have been opened.' => 'Il compito #%d è stato aperto.',
@@ -735,12 +734,6 @@ return array(
// 'SEK - Swedish Krona' => '',
// 'The project identifier is an optional alphanumeric code used to identify your project.' => '',
// 'Identifier' => '',
- // 'Postmark (incoming emails)' => '',
- // 'Help on Postmark integration' => '',
- // 'Mailgun (incoming emails)' => '',
- // 'Help on Mailgun integration' => '',
- // 'Sendgrid (incoming emails)' => '',
- // 'Help on Sendgrid integration' => '',
// 'Disable two factor authentication' => '',
// 'Do you really want to disable the two factor authentication for this user: "%s"?' => '',
// 'Edit link' => '',
diff --git a/app/Locale/ja_JP/translations.php b/app/Locale/ja_JP/translations.php
index 001fd014..7bf174ed 100644
--- a/app/Locale/ja_JP/translations.php
+++ b/app/Locale/ja_JP/translations.php
@@ -353,7 +353,6 @@ return array(
'Clone' => '複製',
'Project cloned successfully.' => 'プロジェクトを複製しました。',
'Unable to clone this project.' => 'プロジェクトの複製に失敗しました。',
- 'Email notifications' => 'メール通知',
'Enable email notifications' => 'メール通知を設定',
'Task position:' => 'タスクの位置:',
'The task #%d have been opened.' => 'タスク #%d をオープンしました。',
@@ -735,12 +734,6 @@ return array(
// 'SEK - Swedish Krona' => '',
// 'The project identifier is an optional alphanumeric code used to identify your project.' => '',
// 'Identifier' => '',
- // 'Postmark (incoming emails)' => '',
- // 'Help on Postmark integration' => '',
- // 'Mailgun (incoming emails)' => '',
- // 'Help on Mailgun integration' => '',
- // 'Sendgrid (incoming emails)' => '',
- // 'Help on Sendgrid integration' => '',
// 'Disable two factor authentication' => '',
// 'Do you really want to disable the two factor authentication for this user: "%s"?' => '',
// 'Edit link' => '',
diff --git a/app/Locale/nb_NO/translations.php b/app/Locale/nb_NO/translations.php
index b1aaf48a..ba4a06d5 100644
--- a/app/Locale/nb_NO/translations.php
+++ b/app/Locale/nb_NO/translations.php
@@ -353,7 +353,6 @@ return array(
'Clone' => 'Kopier',
'Project cloned successfully.' => 'Prosjektet er kopiert.',
'Unable to clone this project.' => 'Prosjektet kunne ikke kopieres',
- 'Email notifications' => 'Epostvarslinger',
'Enable email notifications' => 'Aktiver epostvarslinger',
'Task position:' => 'Oppgaveposisjon:',
'The task #%d have been opened.' => 'Oppgaven #%d er åpnet.',
@@ -730,17 +729,11 @@ return array(
// 'This chart show the task complexity over the time (Work Remaining).' => '',
// 'Screenshot taken %s' => '',
'Add a screenshot' => 'Legg til et skjermbilde',
- 'Take a screenshot and press CTRL+V or ⌘+V to paste here.' => 'Ta et skjermbilde og trykk CTRL+V for å lime det inn her.',
+ // 'Take a screenshot and press CTRL+V or ⌘+V to paste here.' => '',
'Screenshot uploaded successfully.' => 'Skjermbilde opplastet',
// 'SEK - Swedish Krona' => '',
'The project identifier is an optional alphanumeric code used to identify your project.' => 'Prosjektkoden er en alfanumerisk kode som kan brukes for å identifisere prosjektet',
'Identifier' => 'Prosjektkode',
- // 'Postmark (incoming emails)' => '',
- // 'Help on Postmark integration' => '',
- // 'Mailgun (incoming emails)' => '',
- // 'Help on Mailgun integration' => '',
- // 'Sendgrid (incoming emails)' => '',
- // 'Help on Sendgrid integration' => '',
// 'Disable two factor authentication' => '',
// 'Do you really want to disable the two factor authentication for this user: "%s"?' => '',
// 'Edit link' => '',
diff --git a/app/Locale/nl_NL/translations.php b/app/Locale/nl_NL/translations.php
index 5ee15c25..09076565 100644
--- a/app/Locale/nl_NL/translations.php
+++ b/app/Locale/nl_NL/translations.php
@@ -353,7 +353,6 @@ return array(
'Clone' => 'Kloon',
'Project cloned successfully.' => 'Project succesvol gekloond.',
'Unable to clone this project.' => 'Klonen van project niet gelukt.',
- 'Email notifications' => 'Email notificatie',
'Enable email notifications' => 'Email notificatie aanzetten',
'Task position:' => 'Taak positie :',
'The task #%d have been opened.' => 'Taak #%d is geopend.',
@@ -735,12 +734,6 @@ return array(
// 'SEK - Swedish Krona' => '',
// 'The project identifier is an optional alphanumeric code used to identify your project.' => '',
// 'Identifier' => '',
- // 'Postmark (incoming emails)' => '',
- // 'Help on Postmark integration' => '',
- // 'Mailgun (incoming emails)' => '',
- // 'Help on Mailgun integration' => '',
- // 'Sendgrid (incoming emails)' => '',
- // 'Help on Sendgrid integration' => '',
// 'Disable two factor authentication' => '',
// 'Do you really want to disable the two factor authentication for this user: "%s"?' => '',
// 'Edit link' => '',
diff --git a/app/Locale/pl_PL/translations.php b/app/Locale/pl_PL/translations.php
index f0d43ad5..92ad4c26 100644
--- a/app/Locale/pl_PL/translations.php
+++ b/app/Locale/pl_PL/translations.php
@@ -353,7 +353,6 @@ return array(
'Clone' => 'Sklonuj',
'Project cloned successfully.' => 'Projekt sklonowany pomyślnie.',
'Unable to clone this project.' => 'Nie można sklonować projektu.',
- 'Email notifications' => 'Powiadomienia email',
'Enable email notifications' => 'Włącz powiadomienia email',
'Task position:' => 'Pozycja zadania:',
'The task #%d have been opened.' => 'Zadania #%d zostały otwarte.',
@@ -735,12 +734,6 @@ return array(
'SEK - Swedish Krona' => 'SEK - Korona szwedzka',
'The project identifier is an optional alphanumeric code used to identify your project.' => 'Identyfikator projektu to opcjonalny kod alfanumeryczny do identyfikacji projektu.',
'Identifier' => 'Identyfikator',
- // 'Postmark (incoming emails)' => '',
- // 'Help on Postmark integration' => '',
- // 'Mailgun (incoming emails)' => '',
- // 'Help on Mailgun integration' => '',
- // 'Sendgrid (incoming emails)' => '',
- // 'Help on Sendgrid integration' => '',
'Disable two factor authentication' => 'Wyłącz uwierzytelnianie dwuetapowe',
'Do you really want to disable the two factor authentication for this user: "%s"?' => 'Czy na pewno chcesz wyłączyć uwierzytelnianie dwuetapowe dla tego użytkownika: "%s"?',
'Edit link' => 'Edytuj link',
diff --git a/app/Locale/pt_BR/translations.php b/app/Locale/pt_BR/translations.php
index 52cb14cc..6112f34c 100644
--- a/app/Locale/pt_BR/translations.php
+++ b/app/Locale/pt_BR/translations.php
@@ -353,7 +353,6 @@ return array(
'Clone' => 'Clonar',
'Project cloned successfully.' => 'Projeto clonado com sucesso.',
'Unable to clone this project.' => 'Não foi possível clonar este projeto.',
- 'Email notifications' => 'Notificações por email',
'Enable email notifications' => 'Habilitar notificações por email',
'Task position:' => 'Posição da tarefa:',
'The task #%d have been opened.' => 'A tarefa #%d foi aberta.',
@@ -735,12 +734,6 @@ return array(
'SEK - Swedish Krona' => 'SEK - Coroa sueca',
'The project identifier is an optional alphanumeric code used to identify your project.' => 'O identificador de projeto é um código alfanumérico opcional utilizado para identificar o seu projeto.',
'Identifier' => 'Identificador',
- 'Postmark (incoming emails)' => 'Postmark (e-mails recebidos)',
- 'Help on Postmark integration' => 'Ajuda na integração do Postmark',
- 'Mailgun (incoming emails)' => 'Mailgun (e-mails recebidos)',
- 'Help on Mailgun integration' => 'Ajuda na integração do Mailgun',
- 'Sendgrid (incoming emails)' => 'Sendgrid (e-mails recebidos)',
- 'Help on Sendgrid integration' => 'Ajuda na integração do Sendgrid',
'Disable two factor authentication' => 'Desativar autenticação à dois fatores',
'Do you really want to disable the two factor authentication for this user: "%s"?' => 'Você deseja realmente desativar a autenticação à dois fatores para esse usuário: "%s"?',
'Edit link' => 'Editar um link',
diff --git a/app/Locale/pt_PT/translations.php b/app/Locale/pt_PT/translations.php
index ac8961ab..800e5ee3 100644
--- a/app/Locale/pt_PT/translations.php
+++ b/app/Locale/pt_PT/translations.php
@@ -353,7 +353,6 @@ return array(
'Clone' => 'Clonar',
'Project cloned successfully.' => 'Projecto clonado com sucesso.',
'Unable to clone this project.' => 'Não foi possível clonar este projecto.',
- 'Email notifications' => 'Notificações por email',
'Enable email notifications' => 'Activar notificações por email',
'Task position:' => 'Posição da tarefa:',
'The task #%d have been opened.' => 'A tarefa #%d foi aberta.',
@@ -735,12 +734,6 @@ return array(
'SEK - Swedish Krona' => 'SEK - Coroa sueca',
'The project identifier is an optional alphanumeric code used to identify your project.' => 'O identificador de projecto é um código alfanumérico opcional utilizado para identificar o seu projecto.',
'Identifier' => 'Identificador',
- 'Postmark (incoming emails)' => 'Postmark (e-mails recebidos)',
- 'Help on Postmark integration' => 'Ajuda na integração do Postmark',
- 'Mailgun (incoming emails)' => 'Mailgun (e-mails recebidos)',
- 'Help on Mailgun integration' => 'Ajuda na integração do Mailgun',
- 'Sendgrid (incoming emails)' => 'Sendgrid (e-mails recebidos)',
- 'Help on Sendgrid integration' => 'Ajuda na integração do Sendgrid',
'Disable two factor authentication' => 'Desactivar autenticação com dois factores',
'Do you really want to disable the two factor authentication for this user: "%s"?' => 'Tem a certeza que quer desactivar a autenticação com dois factores para esse utilizador: "%s"?',
'Edit link' => 'Editar um link',
diff --git a/app/Locale/ru_RU/translations.php b/app/Locale/ru_RU/translations.php
index 4a64e066..fd0540e0 100644
--- a/app/Locale/ru_RU/translations.php
+++ b/app/Locale/ru_RU/translations.php
@@ -353,7 +353,6 @@ return array(
'Clone' => 'Клонировать',
'Project cloned successfully.' => 'Проект клонирован.',
'Unable to clone this project.' => 'Не удалось клонировать проект.',
- 'Email notifications' => 'Уведомления по e-mail',
'Enable email notifications' => 'Включить уведомления по e-mail',
'Task position:' => 'Позиция задачи:',
'The task #%d have been opened.' => 'Задача #%d была открыта.',
@@ -735,12 +734,6 @@ return array(
'SEK - Swedish Krona' => 'SEK - Шведская крона',
'The project identifier is an optional alphanumeric code used to identify your project.' => 'Идентификатор проекта - это опциональный буквенно-цифровой код использующийся для идентификации проекта',
'Identifier' => 'Идентификатор',
- 'Postmark (incoming emails)' => 'Postmark (входящие сообщения)',
- 'Help on Postmark integration' => 'Справка о Postmark интеграции',
- 'Mailgun (incoming emails)' => 'Mailgun (входящие сообщения)',
- 'Help on Mailgun integration' => 'Справка о Mailgun интеграции',
- 'Sendgrid (incoming emails)' => 'Sendgrid (входящие сообщения)',
- 'Help on Sendgrid integration' => 'Справка о Sendgrid интеграции',
'Disable two factor authentication' => 'Выключить двухфакторную авторизацию',
'Do you really want to disable the two factor authentication for this user: "%s"?' => 'Вы действительно хотите выключить двухфакторную авторизацию для пользователя "%s"?',
'Edit link' => 'Редактировать ссылку',
diff --git a/app/Locale/sr_Latn_RS/translations.php b/app/Locale/sr_Latn_RS/translations.php
index 0f80aa1e..a1eac165 100644
--- a/app/Locale/sr_Latn_RS/translations.php
+++ b/app/Locale/sr_Latn_RS/translations.php
@@ -353,7 +353,6 @@ return array(
'Clone' => 'Iskopiraj',
'Project cloned successfully.' => 'Projekat uspešno iskopiran.',
'Unable to clone this project.' => 'Nije moguće iskopirati projekat.',
- 'Email notifications' => 'Obaveštenje e-mailom',
'Enable email notifications' => 'Omogući obaveštenja e-mailom',
'Task position:' => 'Pozicija zadatka:',
'The task #%d have been opened.' => 'Zadatak #%d je otvoren.',
@@ -735,12 +734,6 @@ return array(
// 'SEK - Swedish Krona' => '',
// 'The project identifier is an optional alphanumeric code used to identify your project.' => '',
// 'Identifier' => '',
- // 'Postmark (incoming emails)' => '',
- // 'Help on Postmark integration' => '',
- // 'Mailgun (incoming emails)' => '',
- // 'Help on Mailgun integration' => '',
- // 'Sendgrid (incoming emails)' => '',
- // 'Help on Sendgrid integration' => '',
// 'Disable two factor authentication' => '',
// 'Do you really want to disable the two factor authentication for this user: "%s"?' => '',
// 'Edit link' => '',
diff --git a/app/Locale/sv_SE/translations.php b/app/Locale/sv_SE/translations.php
index f1994f81..6faf7f60 100644
--- a/app/Locale/sv_SE/translations.php
+++ b/app/Locale/sv_SE/translations.php
@@ -353,7 +353,6 @@ return array(
'Clone' => 'Klona',
'Project cloned successfully.' => 'Projektet har klonats.',
'Unable to clone this project.' => 'Kunde inte klona projektet.',
- 'Email notifications' => 'Epostnotiser',
'Enable email notifications' => 'Aktivera epostnotiser',
'Task position:' => 'Uppgiftsposition:',
'The task #%d have been opened.' => 'Uppgiften #%d har öppnats.',
@@ -735,12 +734,6 @@ return array(
'SEK - Swedish Krona' => 'SEK - Svensk Krona',
'The project identifier is an optional alphanumeric code used to identify your project.' => 'Projektidentifieraren är en valbar alfanumerisk kod som används för att identifiera ditt projekt.',
'Identifier' => 'Identifierare',
- 'Postmark (incoming emails)' => 'Postmark (inkommande e-post)',
- 'Help on Postmark integration' => 'Hjälp för Postmark integration',
- 'Mailgun (incoming emails)' => 'Mailgrun (inkommande e-post)',
- 'Help on Mailgun integration' => 'Hjälp för Mailgrun integration',
- 'Sendgrid (incoming emails)' => 'Sendgrid (inkommande e-post)',
- 'Help on Sendgrid integration' => 'Hjälp för Sendgrid integration',
'Disable two factor authentication' => 'Inaktivera två-faktors autentisering',
'Do you really want to disable the two factor authentication for this user: "%s"?' => 'Vill du verkligen inaktivera två-faktors autentisering för denna användare: "%s"?',
'Edit link' => 'Ändra länk',
diff --git a/app/Locale/th_TH/translations.php b/app/Locale/th_TH/translations.php
index 931bd87c..b2918ae2 100644
--- a/app/Locale/th_TH/translations.php
+++ b/app/Locale/th_TH/translations.php
@@ -353,7 +353,6 @@ return array(
'Clone' => 'เลียนแบบ',
'Project cloned successfully.' => 'เลียนแบบโปรเจคเรียบร้อยแล้ว',
'Unable to clone this project.' => 'ไม่สามารถเลียบแบบโปรเจคได้',
- 'Email notifications' => 'อีเมลแจ้งเตือน',
'Enable email notifications' => 'เปิดอีเมลแจ้งเตือน',
'Task position:' => 'ตำแหน่งงาน',
'The task #%d have been opened.' => 'งานที่ #%d ถุกเปิด',
@@ -735,12 +734,6 @@ return array(
// 'SEK - Swedish Krona' => '',
// 'The project identifier is an optional alphanumeric code used to identify your project.' => '',
// 'Identifier' => '',
- // 'Postmark (incoming emails)' => '',
- // 'Help on Postmark integration' => '',
- // 'Mailgun (incoming emails)' => '',
- // 'Help on Mailgun integration' => '',
- // 'Sendgrid (incoming emails)' => '',
- // 'Help on Sendgrid integration' => '',
// 'Disable two factor authentication' => '',
// 'Do you really want to disable the two factor authentication for this user: "%s"?' => '',
'Edit link' => 'แก้ไขลิงค์',
diff --git a/app/Locale/tr_TR/translations.php b/app/Locale/tr_TR/translations.php
index 53f8fb95..54cb165d 100644
--- a/app/Locale/tr_TR/translations.php
+++ b/app/Locale/tr_TR/translations.php
@@ -353,7 +353,6 @@ return array(
'Clone' => 'Kopya oluştur',
'Project cloned successfully.' => 'Proje kopyası başarıyla oluşturuldu.',
'Unable to clone this project.' => 'Proje kopyası oluşturulamadı.',
- 'Email notifications' => 'E-Posta bilgilendirmesi',
'Enable email notifications' => 'E-Posta bilgilendirmesini aç',
'Task position:' => 'Görev pozisyonu',
'The task #%d have been opened.' => '#%d numaralı görev açıldı.',
@@ -735,12 +734,6 @@ return array(
// 'SEK - Swedish Krona' => '',
// 'The project identifier is an optional alphanumeric code used to identify your project.' => '',
// 'Identifier' => '',
- // 'Postmark (incoming emails)' => '',
- // 'Help on Postmark integration' => '',
- // 'Mailgun (incoming emails)' => '',
- // 'Help on Mailgun integration' => '',
- // 'Sendgrid (incoming emails)' => '',
- // 'Help on Sendgrid integration' => '',
// 'Disable two factor authentication' => '',
// 'Do you really want to disable the two factor authentication for this user: "%s"?' => '',
// 'Edit link' => '',
diff --git a/app/Locale/zh_CN/translations.php b/app/Locale/zh_CN/translations.php
index 5863934a..02e0572c 100644
--- a/app/Locale/zh_CN/translations.php
+++ b/app/Locale/zh_CN/translations.php
@@ -353,7 +353,6 @@ return array(
'Clone' => '克隆',
'Project cloned successfully.' => '成功复制项目。',
'Unable to clone this project.' => '无法复制此项目',
- 'Email notifications' => '邮件通知',
'Enable email notifications' => '启用邮件通知',
'Task position:' => '任务位置:',
'The task #%d have been opened.' => '任务#%d已经被开启.',
@@ -735,12 +734,6 @@ return array(
// 'SEK - Swedish Krona' => '',
// 'The project identifier is an optional alphanumeric code used to identify your project.' => '',
// 'Identifier' => '',
- // 'Postmark (incoming emails)' => '',
- // 'Help on Postmark integration' => '',
- // 'Mailgun (incoming emails)' => '',
- // 'Help on Mailgun integration' => '',
- // 'Sendgrid (incoming emails)' => '',
- // 'Help on Sendgrid integration' => '',
// 'Disable two factor authentication' => '',
// 'Do you really want to disable the two factor authentication for this user: "%s"?' => '',
'Edit link' => '编辑链接',
diff --git a/app/ServiceProvider/ClassProvider.php b/app/ServiceProvider/ClassProvider.php
index d91bced7..4acebfeb 100644
--- a/app/ServiceProvider/ClassProvider.php
+++ b/app/ServiceProvider/ClassProvider.php
@@ -3,6 +3,7 @@
namespace Kanboard\ServiceProvider;
use Kanboard\Core\Plugin\Loader;
+use Kanboard\Core\Mail\Client as EmailClient;
use Kanboard\Core\ObjectStorage\FileStorage;
use Kanboard\Core\Paginator;
use Kanboard\Core\OAuth2;
@@ -78,7 +79,6 @@ class ClassProvider implements ServiceProviderInterface
'ProjectGanttFormatter',
),
'Core' => array(
- 'EmailClient',
'Helper',
'HttpClient',
'Lexer',
@@ -99,11 +99,7 @@ class ClassProvider implements ServiceProviderInterface
'GitlabWebhook',
'HipchatWebhook',
'Jabber',
- 'Mailgun',
- 'Postmark',
- 'Sendgrid',
'SlackWebhook',
- 'Smtp',
)
);
@@ -127,6 +123,14 @@ class ClassProvider implements ServiceProviderInterface
return new FileStorage(FILES_DIR);
};
+ $container['emailClient'] = function($container) {
+ $mailer = new EmailClient($container);
+ $mailer->setTransport('smtp', '\Kanboard\Core\Mail\Transport\Smtp');
+ $mailer->setTransport('sendmail', '\Kanboard\Core\Mail\Transport\Sendmail');
+ $mailer->setTransport('mail', '\Kanboard\Core\Mail\Transport\Mail');
+ return $mailer;
+ };
+
$container['pluginLoader'] = new Loader($container);
$container['cspRules'] = array('style-src' => "'self' 'unsafe-inline'", 'img-src' => '* data:');
diff --git a/app/Template/config/integrations.php b/app/Template/config/integrations.php
index 4e24a270..b454fa86 100644
--- a/app/Template/config/integrations.php
+++ b/app/Template/config/integrations.php
@@ -6,6 +6,8 @@
<?= $this->form->csrf() ?>
+ <?= $this->hook->render('template:config:integrations', array('values' => $values)) ?>
+
<h3><i class="fa fa-google"></i> <?= t('Google Authentication') ?></h3>
<div class="listing">
<input type="text" class="auto-select" readonly="readonly" value="<?= $this->url->href('oauth', 'google', array(), false, '', true) ?>"/><br/>
@@ -24,24 +26,6 @@
<p class="form-help"><?= $this->url->doc(t('Help on Gitlab authentication'), 'gitlab-authentication') ?></p>
</div>
- <h3><img src="<?= $this->url->dir() ?>assets/img/mailgun-icon.png"/>&nbsp;<?= t('Mailgun (incoming emails)') ?></h3>
- <div class="listing">
- <input type="text" class="auto-select" readonly="readonly" value="<?= $this->url->href('webhook', 'mailgun', array('token' => $values['webhook_token']), false, '', true) ?>"/><br/>
- <p class="form-help"><?= $this->url->doc(t('Help on Mailgun integration'), 'mailgun') ?></p>
- </div>
-
- <h3><img src="<?= $this->url->dir() ?>assets/img/sendgrid-icon.png"/>&nbsp;<?= t('Sendgrid (incoming emails)') ?></h3>
- <div class="listing">
- <input type="text" class="auto-select" readonly="readonly" value="<?= $this->url->href('webhook', 'sendgrid', array('token' => $values['webhook_token']), false, '', true) ?>"/><br/>
- <p class="form-help"><?= $this->url->doc(t('Help on Sendgrid integration'), 'sendgrid') ?></p>
- </div>
-
- <h3><img src="<?= $this->url->dir() ?>assets/img/postmark-icon.png"/>&nbsp;<?= t('Postmark (incoming emails)') ?></h3>
- <div class="listing">
- <input type="text" class="auto-select" readonly="readonly" value="<?= $this->url->href('webhook', 'postmark', array('token' => $values['webhook_token']), false, '', true) ?>"/><br/>
- <p class="form-help"><?= $this->url->doc(t('Help on Postmark integration'), 'postmark') ?></p>
- </div>
-
<h3><img src="<?= $this->url->dir() ?>assets/img/gravatar-icon.png"/>&nbsp;<?= t('Gravatar') ?></h3>
<div class="listing">
<?= $this->form->checkbox('integration_gravatar', t('Enable Gravatar images'), 1, $values['integration_gravatar'] == 1) ?>
diff --git a/app/constants.php b/app/constants.php
index 004f7cf7..5010dacd 100644
--- a/app/constants.php
+++ b/app/constants.php
@@ -82,11 +82,6 @@ defined('MAIL_SMTP_USERNAME') or define('MAIL_SMTP_USERNAME', '');
defined('MAIL_SMTP_PASSWORD') or define('MAIL_SMTP_PASSWORD', '');
defined('MAIL_SMTP_ENCRYPTION') or define('MAIL_SMTP_ENCRYPTION', null);
defined('MAIL_SENDMAIL_COMMAND') or define('MAIL_SENDMAIL_COMMAND', '/usr/sbin/sendmail -bs');
-defined('POSTMARK_API_TOKEN') or define('POSTMARK_API_TOKEN', '');
-defined('MAILGUN_API_TOKEN') or define('MAILGUN_API_TOKEN', '');
-defined('MAILGUN_DOMAIN') or define('MAILGUN_DOMAIN', '');
-defined('SENDGRID_API_USER') or define('SENDGRID_API_USER', '');
-defined('SENDGRID_API_KEY') or define('SENDGRID_API_KEY', '');
// Enable or disable "Strict-Transport-Security" HTTP header
defined('ENABLE_HSTS') or define('ENABLE_HSTS', true);
diff --git a/assets/img/mailgun-icon.png b/assets/img/mailgun-icon.png
deleted file mode 100644
index 41bb2783..00000000
--- a/assets/img/mailgun-icon.png
+++ /dev/null
Binary files differ
diff --git a/assets/img/postmark-icon.png b/assets/img/postmark-icon.png
deleted file mode 100644
index 87f07594..00000000
--- a/assets/img/postmark-icon.png
+++ /dev/null
Binary files differ
diff --git a/assets/img/sendgrid-icon.png b/assets/img/sendgrid-icon.png
deleted file mode 100644
index 4247e27b..00000000
--- a/assets/img/sendgrid-icon.png
+++ /dev/null
Binary files differ
diff --git a/doc/email-configuration.markdown b/doc/email-configuration.markdown
index c66996c6..40736c6a 100644
--- a/doc/email-configuration.markdown
+++ b/doc/email-configuration.markdown
@@ -20,9 +20,7 @@ There are several email transports available:
- SMTP
- Sendmail
- PHP native mail function
-- Mailgun
-- Postmark
-- Sendgrid
+- Other methods can be provided by external plugins: Postmark, Sendgrid and Mailgun
Server settings
---------------
@@ -77,63 +75,6 @@ This is the default configuration:
define('MAIL_TRANSPORT', 'mail');
```
-### Mailgun HTTP API
-
-You can use the HTTP API of Mailgun to send emails.
-
-Configuration:
-
-```php
-// We choose "mailgun" as mail transport
-define('MAIL_TRANSPORT', 'mailgun');
-
-// Mailgun API key
-define('MAILGUN_API_TOKEN', 'YOUR_API_KEY');
-
-// Mailgun domain name
-define('MAILGUN_DOMAIN', 'YOUR_DOMAIN_CONFIGURED_IN_MAILGUN');
-
-// Be sure to use the sender email address configured in Mailgun
-define('MAIL_FROM', 'sender-address-configured-in-mailgun@example.org');
-```
-
-### Postmark HTTP API
-
-Postmark is a third-party email service.
-If you already use the Postmark integration to receive emails in Kanboard you can use the same provider to send email too.
-
-This system use their HTTP API instead of the SMTP protocol.
-
-Here are the required settings for this configuration:
-
-```php
-// We choose "postmark" as mail transport
-define('MAIL_TRANSPORT', 'postmark');
-
-// Copy and paste your Postmark API token
-define('POSTMARK_API_TOKEN', 'COPY HERE YOUR POSTMARK API TOKEN');
-
-// Be sure to use the Postmark configured sender email address
-define('MAIL_FROM', 'sender-address-configured-in-postmark@example.org');
-```
-
-### Sendgrid HTTP API
-
-You can use the HTTP API of Sendgrid to send emails.
-
-Configuration:
-
-```php
-// We choose "sendgrid" as mail transport
-define('MAIL_TRANSPORT', 'sendgrid');
-
-// Sendgrid username
-define('SENDGRID_API_USER', 'YOUR_SENDGRID_USERNAME');
-
-// Sendgrid password
-define('SENDGRID_API_KEY', 'YOUR_SENDGRID_PASSWORD');
-```
-
### The sender email address
By default, emails will use the sender address `notifications@kanboard.local`.
diff --git a/doc/index.markdown b/doc/index.markdown
index 797c8fae..86b5b7fc 100644
--- a/doc/index.markdown
+++ b/doc/index.markdown
@@ -68,10 +68,7 @@ Using Kanboard
- [Gitlab webhooks](gitlab-webhooks.markdown)
- [Hipchat](hipchat.markdown)
- [Jabber](jabber.markdown)
-- [Mailgun](mailgun.markdown)
-- [Sendgrid](sendgrid.markdown)
- [Slack](slack.markdown)
-- [Postmark](postmark.markdown)
- [iCalendar subscriptions](ical.markdown)
- [RSS/Atom subscriptions](rss.markdown)
- [Json-RPC API](api-json-rpc.markdown)
diff --git a/doc/mailgun.markdown b/doc/mailgun.markdown
deleted file mode 100644
index 6465903a..00000000
--- a/doc/mailgun.markdown
+++ /dev/null
@@ -1,28 +0,0 @@
-Mailgun
-=======
-
-You can use the service [Mailgun](http://www.mailgun.com/) to create tasks directly by email.
-
-This integration works with the inbound email service of Mailgun (routes).
-Kanboard use a webhook to handle incoming emails.
-
-The [incoming email workflow is described here](create-tasks-by-email.markdown).
-
-Mailgun configuration
----------------------
-
-Create a new route in the web interface or via the API ([official documentation](https://documentation.mailgun.com/user_manual.html#routes)), here an example:
-
-```
-match_recipient("^kanboard\+(.*)@mydomain.tld$")
-forward("https://mykanboard/?controller=webhook&action=mailgun&token=mytoken")
-```
-
-The Kanboard webhook url is displayed in **Settings > Integrations > Mailgun**
-
-Kanboard configuration
-----------------------
-
-1. Be sure that your users have an email address in their profiles
-2. Assign a project identifier to the desired projects: **Project settings > Edit**
-3. Try to send an email to your project
diff --git a/doc/plugin-hooks.markdown b/doc/plugin-hooks.markdown
new file mode 100644
index 00000000..eca8c14a
--- /dev/null
+++ b/doc/plugin-hooks.markdown
@@ -0,0 +1,148 @@
+Plugin Hooks
+============
+
+Application Hooks
+-----------------
+
+Hooks can extend, replace, filter data or change the default behavior. Each hook is identified with a unique name, example: `controller:calendar:user:events`
+
+### Listen on hook events
+
+In your `initialize()` method you need to call the method `on()` of the class `Kanboard\Core\Plugin\Hook`:
+
+```php
+$this->hook->on('hook_name', $callable);
+```
+
+The first argument is the name of the hook and the second is a PHP callable.
+
+### Hooks executed only one time
+
+Some hooks can have only one listener:
+
+#### model:subtask-time-tracking:calculate:time-spent
+
+- Override time spent calculation when subtask timer is stopped
+- Arguments:
+ - `$user_id` (integer)
+ - `$start` (DateTime)
+ - `$end` (DateTime)
+
+#### model:subtask-time-tracking:calendar:events
+
+- Override subtask time tracking events to display the calendar
+- Arguments:
+ - `$user_id` (integer)
+ - `$events` (array)
+ - `$start` (string, ISO-8601 format)
+ - `$end` (string, ISO-8601 format)
+
+### Merge hooks
+
+"Merge hooks" act in the same way as the function `array_merge`. The hook callback must return an array. This array will be merged with the default one.
+
+Example to add events in the user calendar:
+
+```php
+class Plugin extends Base
+{
+ public function initialize()
+ {
+ $container = $this->container;
+
+ $this->hook->on('controller:calendar:user:events', function($user_id, $start, $end) use ($container) {
+ $model = new SubtaskForecast($container);
+ return $model->getCalendarEvents($user_id, $end); // Return new events
+ });
+ }
+}
+```
+
+List of merge hooks:
+
+#### controller:calendar:project:events
+
+- Add more events to the project calendar
+- Arguments:
+ - `$project_id` (integer)
+ - `$start` Calendar start date (string, ISO-8601 format)
+ - `$end` Calendar` end date (string, ISO-8601 format)
+
+#### controller:calendar:user:events
+
+- Add more events to the user calendar
+- Arguments:
+ - `$user_id` (integer)
+ - `$start` Calendar start date (string, ISO-8601 format)
+ - `$end` Calendar end date (string, ISO-8601 format)
+
+Asset Hooks
+-----------
+
+Asset hooks can be used to add easily a new stylesheet or a new javascript file in the layout. You can use this feature to create a theme and override all Kanboard default styles.
+
+Example to add a new stylesheet:
+
+```php
+<?php
+
+namespace Kanboard\Plugin\Css;
+
+use Kanboard\Core\Plugin\Base;
+
+class Plugin extends Base
+{
+ public function initialize()
+ {
+ $this->hook->on('template:layout:css', 'plugins/Css/skin.css');
+ }
+}
+```
+
+List of asset Hooks:
+
+- `template:layout:css`
+- `template:layout:js`
+
+Template Hooks
+--------------
+
+Template hooks allow to add new content in existing templates.
+
+Example to add new content in the dashboard sidebar:
+
+```php
+$this->template->hook->attach('template:dashboard:sidebar', 'myplugin:dashboard/sidebar');
+```
+
+This call is usually defined in the `initialize()` method.
+The first argument is name of the hook and the second argument is the template name.
+
+Template names prefixed with the plugin name and colon indicate the location of the template.
+
+Example with `myplugin:dashboard/sidebar`:
+
+- `myplugin` is the name of your plugin (lowercase)
+- `dashboard/sidebar` is the template name
+- On the filesystem, the plugin will be located here: `plugins\Myplugin\Template\dashboard\sidebar.php`
+- Templates are written in pure PHP (don't forget to escape data)
+
+Template name without prefix are core templates.
+
+List of template hooks:
+
+- `template:dashboard:sidebar`
+- `template:config:sidebar`
+- `template:config:integrations`
+- `template:export:sidebar`
+- `template:layout:head`
+- `template:layout:top`
+- `template:layout:bottom`
+- `template:project:dropdown`
+- `template:project-user:sidebar`
+- `template:task:sidebar:information`
+- `template:task:sidebar:actions`
+- `template:user:sidebar:information`
+- `template:user:sidebar:actions`
+
+Other template hooks can be added if necessary, just ask on the issue tracker.
diff --git a/doc/plugin-overrides.markdown b/doc/plugin-overrides.markdown
new file mode 100644
index 00000000..905808d5
--- /dev/null
+++ b/doc/plugin-overrides.markdown
@@ -0,0 +1,36 @@
+Plugin Overrides
+================
+
+Override HTTP Content Security Policy
+-------------------------------------
+
+If you would like to replace the default HTTP Content Security Policy header, you can use the method `setContentSecurityPolicy()`:
+
+```php
+<?php
+
+namespace Kanboard\Plugin\Csp;
+
+use Kanboard\Core\Plugin\Base;
+
+class Plugin extends Base
+{
+ public function initialize()
+ {
+ $this->setContentSecurityPolicy(array('script-src' => 'something'));
+ }
+}
+```
+
+Template Overrides
+------------------
+
+Any templates defined in the core can be overrided. By example, you can redefine the default layout or change email notifications.
+
+Example of template override:
+
+```php
+$this->template->setTemplateOverride('header', 'theme:layout/header');
+```
+
+The first argument is the original template name and the second argument the template to use as replacement.
diff --git a/doc/plugin-registration.markdown b/doc/plugin-registration.markdown
new file mode 100644
index 00000000..50bfaff2
--- /dev/null
+++ b/doc/plugin-registration.markdown
@@ -0,0 +1,200 @@
+Plugin Registration
+===================
+
+Directory structure
+-------------------
+
+Plugins are stored in the `plugins` subdirectory. An example of a plugin directory structure:
+
+```bash
+plugins
+└── Budget <= Plugin name
+ ├── Asset <= Javascript/CSS files
+ ├── Controller
+ ├── LICENSE <= Plugin license
+ ├── Locale
+ │ ├── fr_FR
+ │   ├── it_IT
+ │   ├── ja_JP
+ │   └── zh_CN
+ ├── Model
+ ├── Plugin.php <= Plugin registration file
+ ├── README.md
+ ├── Schema <= Database migrations
+ ├── Template
+ └── Test <= Unit tests
+```
+
+Only the registration file `Plugin.php` is required. Other folders are optionals.
+
+The first letter of the plugin name must be capitalized.
+
+Plugin Registration File
+------------------------
+
+Kanboard will scan the directory `plugins` and load automatically everything under this directory. The file `Plugin.php` is used to load and register the plugin.
+
+Example of `Plugin.php` file (`plugins/Foobar/Plugin.php`):
+
+```php
+<?php
+
+namespace Kanboard\Plugin\Foobar;
+
+use Kanboard\Core\Plugin\Base;
+
+class Plugin extends Plugin\Base
+{
+ public function initialize()
+ {
+ $this->template->hook->attach('template:layout:head', 'theme:layout/head');
+ }
+}
+```
+
+This file should contains a class `Plugin` defined under the namespace `Kanboard\Plugin\Yourplugin` and extends `Kanboard\Core\Plugin\Base`.
+
+The only required method is `initialize()`. This method is called for each request when the plugin is loaded.
+
+Plugin Methods
+--------------
+
+Available methods from `Kanboard\Core\Plugin\Base`:
+
+- `initialize()`: Executed when the plugin is loaded
+- `getClasses()`: Return all classes that should be stored in the dependency injection container
+- `on($event, $callback)`: Listen on internal events
+- `getPluginName()`: Should return plugin name
+- `getPluginAuthor()`: Should return plugin author
+- `getPluginVersion()`: Should return plugin version
+- `getPluginDescription()`: Should return plugin description
+- `getPluginHomepage()`: Should return plugin Homepage (link)
+- `setContentSecurityPolicy(array $rules)`: Override default HTTP CSP rules
+
+Your plugin registration class also inherit from `Kanboard\Core\Base`, that means you can access to all classes and methods of Kanboard easily.
+
+This example will fetch the user #123:
+
+```php
+$this->user->getById(123);
+```
+
+Plugin Translations
+-------------------
+
+Plugin can be translated in the same way the rest of the application. You must load the translations yourself when the session is created:
+
+```php
+$this->on('session.bootstrap', function($container) {
+ Translator::load($container['config']->getCurrentLanguage(), __DIR__.'/Locale');
+});
+```
+
+The translations must be stored in `plugins/Myplugin/Locale/xx_XX/translations.php`.
+
+Dependency Injection Container
+------------------------------
+
+Kanboard use Pimple, a simple PHP Dependency Injection Container. However, Kanboard can register any class in the container easily.
+
+Those classes are available everywhere in the application and only one instance is created.
+
+Here an example to register your own models in the container:
+
+```php
+public function getClasses()
+{
+ return array(
+ 'Plugin\Budget\Model' => array(
+ 'HourlyRate',
+ 'Budget',
+ )
+ );
+}
+```
+
+Now, if you use a class that extends from `Core\Base`, you can access directly to those class instance:
+
+```php
+$this->hourlyRate->remove(123);
+$this->budget->getDailyBudgetBreakdown(456);
+
+// It's the same thing as using the container:
+$this->container['hourlyRate']->getAll();
+```
+
+Keys of the containers are unique across the application. If you override an existing class you will change the default behavior.
+
+Event Listening
+----------------
+
+Kanboard use internal events and your plugin can listen and perform actions on these events.
+
+```php
+$this->on('session.bootstrap', function($container) {
+ // Do something
+});
+```
+
+- The first argument is the event name
+- The second argument is a PHP callable function (closure or class method)
+
+Extend Automatic Actions
+------------------------
+
+To define a new automatic action with a plugin, you just need to call the method `extendActions()` from the class `Kanboard\Model\Action`, here an example:
+
+```php
+<?php
+
+namespace Kanboard\Plugin\AutomaticAction;
+
+use Kanboard\Core\Plugin\Base;
+
+class Plugin extends Base
+{
+ public function initialize()
+ {
+ $this->action->extendActions(
+ '\Kanboard\Plugin\AutomaticAction\Action\SendSlackMessage', // Use absolute namespace
+ t('Send a message to Slack when the task color change')
+ );
+ }
+}
+```
+
+- The first argument of the method `extendActions()` is the action class with the complete namespace path. **The namespace path must starts with a backslash** otherwise Kanboard will not be able to load your class.
+- The second argument is the description of your automatic action.
+
+The automatic action class must inherits from the class `Kanboard\Action\Base` and implements all abstract methods:
+
+- `getCompatibleEvents()`
+- `getActionRequiredParameters()`
+- `getEventRequiredParameters()`
+- `doAction(array $data)`
+- `hasRequiredCondition(array $data)`
+
+For more details you should take a look to existing automatic actions or this [plugin example](https://github.com/kanboard/plugin-example-automatic-action).
+
+Extend ACL
+----------
+
+Kanboard use an access list for privilege separations. Your extension can add new rules:
+
+```php
+$this->acl->extend('project_manager_acl', array('mycontroller' => '*'));
+```
+
+- The first argument is the ACL name
+- The second argument are the new rules
+ + Syntax to include only some actions: `array('controller' => array('action1', 'action2'))`
+ + Syntax to include all actions of a controller: `array('controller' => '*')`
+ + Everything is lowercase
+
+List of ACL:
+
+- `public_acl`: Public access without authentication
+- `project_member_acl`: Project member access
+- `project_manager_acl`: Project manager access
+- `project_admin_acl`: Project Admins
+- `admin_acl`: Administrators
diff --git a/doc/plugin-schema-migrations.markdown b/doc/plugin-schema-migrations.markdown
new file mode 100644
index 00000000..d595605f
--- /dev/null
+++ b/doc/plugin-schema-migrations.markdown
@@ -0,0 +1,38 @@
+Plugin Schema Migrations
+========================
+
+Kanboard execute database migrations automatically for you. Migrations must be stored in a folder **Schema** and the filename must be the same as the database driver:
+
+```bash
+Schema
+├── Mysql.php
+├── Postgres.php
+└── Sqlite.php
+```
+
+Each file contains all migrations, here an example for Sqlite:
+
+```php
+<?php
+
+namespace Kanboard\Plugin\Something\Schema;
+
+const VERSION = 1;
+
+function version_1($pdo)
+{
+ $pdo->exec('CREATE TABLE IF NOT EXISTS something (
+ "id" INTEGER PRIMARY KEY,
+ "project_id" INTEGER NOT NULL,
+ "something" TEXT,
+ FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE
+ )');
+}
+```
+
+- The constant `VERSION` is the last version of your schema
+- Each function is a migration `version_1()`, `version_2()`, etc...
+- A `PDO` instance is passed as first argument
+- Everything is executed inside a transaction, if something doesn't work a rollback is performed and the error is displayed to the user
+
+Kanboard will compare the version defined in your schema and the version stored in the database. If the versions are different, Kanboard will execute one by one each migration until to reach the last version.
diff --git a/doc/plugins.markdown b/doc/plugins.markdown
index 0a0577f9..a0f389e0 100644
--- a/doc/plugins.markdown
+++ b/doc/plugins.markdown
@@ -1,430 +1,24 @@
Plugin Development
==================
-Note: The plugin API is considered alpha at the moment.
+Note: The plugin API is **considered alpha** at the moment.
Plugins are useful to extend the core functionalities of Kanboard, adding features, creating themes or changing the default behavior.
Plugin creators should specify explicitly the compatible versions of Kanboard. Internal code of Kanboard may change over the time and your plugin must be tested with new versions.
-Directory structure
--------------------
-
-Plugins are stored in the `plugins` subdirectory. An example of a plugin directory structure:
-
-```bash
-plugins
-└── Budget <= Plugin name
- ├── Asset <= Javascript/CSS files
- ├── Controller
- ├── LICENSE <= Plugin license
- ├── Locale
- │ ├── fr_FR
- │   ├── it_IT
- │   ├── ja_JP
- │   └── zh_CN
- ├── Model
- ├── Plugin.php <= Plugin registration file
- ├── README.md
- ├── Schema <= Database migrations
- ├── Template
- └── Test <= Unit tests
-```
-
-Only the registration file `Plugin.php` is required. Other folders are optionals.
-
-The first letter of the plugin name must be capitalized.
-
-Plugin registration file
-------------------------
-
-Kanboard will scan the directory `plugins` and load automatically everything under this directory. The file `Plugin.php` is used to load and register the plugin.
-
-Example of `Plugin.php` file (`plugins/Foobar/Plugin.php`):
-
-```php
-<?php
-
-namespace Kanboard\Plugin\Foobar;
-
-use Kanboard\Core\Plugin\Base;
-
-class Plugin extends Plugin\Base
-{
- public function initialize()
- {
- $this->template->hook->attach('template:layout:head', 'theme:layout/head');
- }
-}
-```
-
-This file should contains a class `Plugin` defined under the namespace `Kanboard\Plugin\Yourplugin` and extends `Kanboard\Core\Plugin\Base`.
-
-The only required method is `initialize()`. This method is called for each request when the plugin is loaded.
-
-Plugin methods
---------------
-
-Available methods from `Kanboard\Core\Plugin\Base`:
-
-- `initialize()`: Executed when the plugin is loaded
-- `getClasses()`: Return all classes that should be stored in the dependency injection container
-- `on($event, $callback)`: Listen on internal events
-- `getPluginName()`: Should return plugin name
-- `getPluginAuthor()`: Should return plugin author
-- `getPluginVersion()`: Should return plugin version
-- `getPluginDescription()`: Should return plugin description
-- `getPluginHomepage()`: Should return plugin Homepage (link)
-
-Your plugin registration class also inherit from `Kanboard\Core\Base`, that means you can access to all classes and methods of Kanboard easily.
-
-This example will fetch the user #123:
-
-```php
-$this->user->getById(123);
-```
-
-Application Hooks
------------------
-
-Hooks can extend, replace, filter data or change the default behavior. Each hook is identified with a unique name, example: `controller:calendar:user:events`
-
-### Listen on hook events
-
-In your `initialize()` method you need to call the method `on()` of the class `Kanboard\Core\Plugin\Hook`:
-
-```php
-$this->hook->on('hook_name', $callable);
-```
-
-The first argument is the name of the hook and the second is a PHP callable.
-
-### Hooks executed only one time
-
-Some hooks can have only one listener:
-
-#### model:subtask-time-tracking:calculate:time-spent
-
-- Override time spent calculation when subtask timer is stopped
-- Arguments:
- - `$user_id` (integer)
- - `$start` (DateTime)
- - `$end` (DateTime)
-
-#### model:subtask-time-tracking:calendar:events
-
-- Override subtask time tracking events to display the calendar
-- Arguments:
- - `$user_id` (integer)
- - `$events` (array)
- - `$start` (string, ISO-8601 format)
- - `$end` (string, ISO-8601 format)
-
-### Merge hooks
-
-"Merge hooks" act in the same way as the function `array_merge`. The hook callback must return an array. This array will be merged with the default one.
-
-Example to add events in the user calendar:
-
-```php
-class Plugin extends Base
-{
- public function initialize()
- {
- $container = $this->container;
-
- $this->hook->on('controller:calendar:user:events', function($user_id, $start, $end) use ($container) {
- $model = new SubtaskForecast($container);
- return $model->getCalendarEvents($user_id, $end); // Return new events
- });
- }
-}
-```
-
-List of merge hooks:
-
-#### controller:calendar:project:events
-
-- Add more events to the project calendar
-- Arguments:
- - `$project_id` (integer)
- - `$start` Calendar start date (string, ISO-8601 format)
- - `$end` Calendar` end date (string, ISO-8601 format)
-
-#### controller:calendar:user:events
-
-- Add more events to the user calendar
-- Arguments:
- - `$user_id` (integer)
- - `$start` Calendar start date (string, ISO-8601 format)
- - `$end` Calendar end date (string, ISO-8601 format)
-
-Asset Hooks
------------
-
-Asset hooks can be used to add easily a new stylesheet or a new javascript file in the layout. You can use this feature to create a theme and override all Kanboard default styles.
-
-Example to add a new stylesheet:
-
-```php
-<?php
-
-namespace Kanboard\Plugin\Css;
-
-use Kanboard\Core\Plugin\Base;
-
-class Plugin extends Base
-{
- public function initialize()
- {
- $this->hook->on('template:layout:css', 'plugins/Css/skin.css');
- }
-}
-```
-
-List of asset Hooks:
-
-- `template:layout:css`
-- `template:layout:js`
-
-Template hooks
---------------
-
-Template hooks allow to add new content in existing templates.
-
-Example to add new content in the dashboard sidebar:
-
-```php
-$this->template->hook->attach('template:dashboard:sidebar', 'myplugin:dashboard/sidebar');
-```
-
-This call is usually defined in the `initialize()` method.
-The first argument is name of the hook and the second argument is the template name.
-
-Template names prefixed with the plugin name and colon indicate the location of the template.
-
-Example with `myplugin:dashboard/sidebar`:
-
-- `myplugin` is the name of your plugin (lowercase)
-- `dashboard/sidebar` is the template name
-- On the filesystem, the plugin will be located here: `plugins\Myplugin\Template\dashboard\sidebar.php`
-- Templates are written in pure PHP (don't forget to escape data)
-
-Template name without prefix are core templates.
-
-List of template hooks:
-
-- `template:dashboard:sidebar`
-- `template:config:sidebar`
-- `template:export:sidebar`
-- `template:layout:head`
-- `template:layout:top`
-- `template:layout:bottom`
-- `template:project:dropdown`
-- `template:project-user:sidebar`
-- `template:task:sidebar:information`
-- `template:task:sidebar:actions`
-- `template:user:sidebar:information`
-- `template:user:sidebar:actions`
-
-Other template hooks can be added if necessary, just ask on the issue tracker.
-
-Template overrides
-------------------
-
-Any templates defined in the core can be overrided. By example, you can redefine the default layout or change email notifications.
-
-Example of template override:
-
-```php
-$this->template->setTemplateOverride('header', 'theme:layout/header');
-```
-
-The first argument is the original template name and the second argument the template to use as replacement.
-
-Listen on events
-----------------
-
-Kanboard use internal events and your plugin can listen and perform actions on these events.
-
-```php
-$this->on('session.bootstrap', function($container) {
- // Do something
-});
-```
-
-- The first argument is the event name
-- The second argument is a PHP callable function (closure or class method)
-
-Extend Automatic Actions
-------------------------
-
-To define a new automatic action with a plugin, you just need to call the method `extendActions()` from the class `Model\Action`, here an example:
-
-```php
-<?php
-
-namespace Kanboard\Plugin\AutomaticAction;
-
-use Kanboard\Core\Plugin\Base;
-
-class Plugin extends Base
-{
- public function initialize()
- {
- $this->action->extendActions(
- '\Kanboard\Plugin\AutomaticAction\Action\SendSlackMessage', // Use absolute namespace
- t('Send a message to Slack when the task color change')
- );
- }
-}
-```
-
-- The first argument of the method `extendActions()` is the action class with the complete namespace path. **The namespace path must starts with a backslash** otherwise Kanboard will not be able to load your class.
-- The second argument is the description of your automatic action.
-
-The automatic action class must inherits from the class `Action\Base` and implements all abstract methods:
-
-- `getCompatibleEvents()`
-- `getActionRequiredParameters()`
-- `getEventRequiredParameters()`
-- `doAction(array $data)`
-- `hasRequiredCondition(array $data)`
-
-For more details you should take a look to existing automatic actions or this [plugin example](https://github.com/kanboard/plugin-example-automatic-action).
-
-Extend ACL
-----------
-
-Kanboard use an access list for privilege separations. Your extension can add new rules:
-
-```php
-$this->acl->extend('project_manager_acl', array('mycontroller' => '*'));
-```
-
-- The first argument is the ACL name
-- The second argument are the new rules
- + Syntax to include only some actions: `array('controller' => array('action1', 'action2'))`
- + Syntax to include all actions of a controller: `array('controller' => '*')`
- + Everything is lowercase
-
-List of ACL:
-
-- `public_acl`: Public access without authentication
-- `project_member_acl`: Project member access
-- `project_manager_acl`: Project manager access
-- `project_admin_acl`: Project Admins
-- `admin_acl`: Administrators
-
-Plugin Translations
--------------------
-
-Plugin can be translated in the same way the rest of the application. You must load the translations yourself when the session is created:
-
-```php
-$this->on('session.bootstrap', function($container) {
- Translator::load($container['config']->getCurrentLanguage(), __DIR__.'/Locale');
-});
-```
-
-The translations must be stored in `plugins/Myplugin/Locale/xx_XX/translations.php`.
-
-Override HTTP Content Security Policy
--------------------------------------
-
-If you would like to replace the default HTTP Content Security Policy header, you can use the method `setContentSecurityPolicy()`:
-
-```php
-<?php
-
-namespace Kanboard\Plugin\Csp;
-
-use Kanboard\Core\Plugin\Base;
-
-class Plugin extends Base
-{
- public function initialize()
- {
- $this->setContentSecurityPolicy(array('script-src' => 'something'));
- }
-}
-```
-
-Dependency Injection Container
-------------------------------
-
-Kanboard use Pimple, a simple PHP Dependency Injection Container. However, Kanboard can register any class in the container easily.
-
-Those classes are available everywhere in the application and only one instance is created.
-
-Here an example to register your own models in the container:
-
-```php
-public function getClasses()
-{
- return array(
- 'Plugin\Budget\Model' => array(
- 'HourlyRate',
- 'Budget',
- )
- );
-}
-```
-
-Now, if you use a class that extends from `Core\Base`, you can access directly to those class instance:
-
-```php
-$this->hourlyRate->remove(123);
-$this->budget->getDailyBudgetBreakdown(456);
-
-// It's the same thing as using the container:
-$this->container['hourlyRate']->getAll();
-```
-
-Keys of the containers are unique across the application. If you override an existing class you will change the default behavior.
-
-Schema migrations
------------------
-
-Kanboard execute database migrations automatically for you. Migrations must be stored in a folder **Schema** and the filename must be the same as the database driver:
-
-```bash
-Schema
-├── Mysql.php
-├── Postgres.php
-└── Sqlite.php
-```
-
-Each file contains all migrations, here an example for Sqlite:
-
-```php
-<?php
-
-namespace Kanboard\Plugin\Something\Schema;
-
-const VERSION = 1;
-
-function version_1($pdo)
-{
- $pdo->exec('CREATE TABLE IF NOT EXISTS something (
- "id" INTEGER PRIMARY KEY,
- "project_id" INTEGER NOT NULL,
- "something" TEXT,
- FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE
- )');
-}
-```
-
-- The constant `VERSION` is the last version of your schema
-- Each function is a migration `version_1()`, `version_2()`, etc...
-- A `PDO` instance is passed as first argument
-- Everything is executed inside a transaction, if something doesn't work a rollback is performed and the error is displayed to the user
-
-Kanboard will compare the version defined in your schema and the version stored in the database. If the versions are different, Kanboard will execute one by one each migration until to reach the last version.
+- [Plugin Registration](plugin-registration.markdown)
+- [Plugin Hooks](plugin-hooks.markdown)
+- [Plugin Overrides](plugin-overrides.markdown)
+- [Plugin Schema Migrations](plugin-schema-migrations.markdown)
Examples of plugins
-------------------
+- [Sendgrid](https://github.com/kanboard/plugin-sendgrid)
+- [Mailgun](https://github.com/kanboard/plugin-mailgun)
+- [Postmark](https://github.com/kanboard/plugin-postmark)
+- [Amazon S3](https://github.com/kanboard/plugin-s3)
- [Budget planning](https://github.com/kanboard/plugin-budget)
- [User timetable](https://github.com/kanboard/plugin-timetable)
- [Subtask Forecast](https://github.com/kanboard/plugin-subtask-forecast)
diff --git a/doc/postmark.markdown b/doc/postmark.markdown
deleted file mode 100644
index 7c33ee63..00000000
--- a/doc/postmark.markdown
+++ /dev/null
@@ -1,29 +0,0 @@
-Postmark
-========
-
-You can use the service [Postmark](https://postmarkapp.com/) to create tasks directly by email.
-
-This integration works with the inbound email service of Postmark.
-Kanboard use a webhook to handle incoming emails.
-
-The [incoming email workflow is described here](create-tasks-by-email.markdown).
-
-Postmark configuration
-----------------------
-
-Just follow the [official documentation about inbound email processing](http://developer.postmarkapp.com/developer-process-configure.html).
-Basically, you have to forward your own domain or subdomain to a specific Postmark email address.
-
-The Kanboard webhook url is displayed in **Settings > Integrations > Postmark**
-
-Kanboard configuration
-----------------------
-
-1. Be sure that your users have an email address in their profiles
-2. Assign a project identifier to the desired projects: **Project settings > Edit**
-3. Try to send an email to your project
-
-Troubleshootings
-----------------
-
-- Test the webhook url from the Postmark console, you should have a status code `200 OK`
diff --git a/doc/sendgrid.markdown b/doc/sendgrid.markdown
deleted file mode 100644
index 0f307dac..00000000
--- a/doc/sendgrid.markdown
+++ /dev/null
@@ -1,24 +0,0 @@
-Sendgrid
-========
-
-You can use the service [Sendgrid](https://sendgrid.com/) to create tasks directly by email.
-
-This integration works with the [Parse API of Sendgrid](https://sendgrid.com/docs/API_Reference/Webhooks/parse.html).
-Kanboard use a webhook to handle incoming emails.
-
-The [incoming email workflow is described here](create-tasks-by-email.markdown).
-
-Sendgrid configuration
-----------------------
-
-1. Create a new domain or subdomain (by example **inbound.mydomain.tld**) with a MX record that point to **mx.sendgrid.net**
-2. Add your domain and the Kanboard webhook url to [the configuration page in Sendgrid](https://sendgrid.com/developer/reply)
-
-The Kanboard webhook url is displayed in **Settings > Integrations > Sendgrid**
-
-Kanboard configuration
-----------------------
-
-1. Be sure that your users have an email address in their profiles
-2. Assign a project identifier to the desired projects: **Project settings > Edit**
-3. Try to send an email to your project
diff --git a/kanboard b/kanboard
index 4d2c9ab1..2911a506 100755
--- a/kanboard
+++ b/kanboard
@@ -5,16 +5,24 @@ require __DIR__.'/app/common.php';
use Symfony\Component\Console\Application;
use Symfony\Component\EventDispatcher\Event;
+use Kanboard\Console\TaskOverdueNotification;
+use Kanboard\Console\SubtaskExport;
+use Kanboard\Console\TaskExport;
+use Kanboard\Console\ProjectDailyStatsCalculation;
+use Kanboard\Console\ProjectDailyColumnStatsExport;
+use Kanboard\Console\TransitionExport;
+use Kanboard\Console\LocaleSync;
+use Kanboard\Console\LocaleComparator;
$container['dispatcher']->dispatch('console.bootstrap', new Event);
$application = new Application('Kanboard', APP_VERSION);
-$application->add(new Console\TaskOverdueNotification($container));
-$application->add(new Console\SubtaskExport($container));
-$application->add(new Console\TaskExport($container));
-$application->add(new Console\ProjectDailyStatsCalculation($container));
-$application->add(new Console\ProjectDailyColumnStatsExport($container));
-$application->add(new Console\TransitionExport($container));
-$application->add(new Console\LocaleSync($container));
-$application->add(new Console\LocaleComparator($container));
+$application->add(new TaskOverdueNotification($container));
+$application->add(new SubtaskExport($container));
+$application->add(new TaskExport($container));
+$application->add(new ProjectDailyStatsCalculation($container));
+$application->add(new ProjectDailyColumnStatsExport($container));
+$application->add(new TransitionExport($container));
+$application->add(new LocaleSync($container));
+$application->add(new LocaleComparator($container));
$application->run();
diff --git a/tests/units/Integration/MailgunTest.php b/tests/units/Integration/MailgunTest.php
deleted file mode 100644
index 25599f8e..00000000
--- a/tests/units/Integration/MailgunTest.php
+++ /dev/null
@@ -1,71 +0,0 @@
-<?php
-
-require_once __DIR__.'/../Base.php';
-
-use Kanboard\Integration\Mailgun;
-use Kanboard\Model\TaskCreation;
-use Kanboard\Model\TaskFinder;
-use Kanboard\Model\Project;
-use Kanboard\Model\ProjectPermission;
-use Kanboard\Model\User;
-
-class MailgunTest extends Base
-{
- public function testSendEmail()
- {
- $pm = new Mailgun($this->container);
- $pm->sendEmail('test@localhost', 'Me', 'Test', 'Content', 'Bob');
-
- $this->assertStringStartsWith('https://api.mailgun.net/v3/', $this->container['httpClient']->getUrl());
-
- $data = $this->container['httpClient']->getData();
-
- $this->assertArrayHasKey('from', $data);
- $this->assertArrayHasKey('to', $data);
- $this->assertArrayHasKey('subject', $data);
- $this->assertArrayHasKey('html', $data);
-
- $this->assertEquals('Me <test@localhost>', $data['to']);
- $this->assertEquals('Bob <notifications@kanboard.local>', $data['from']);
- $this->assertEquals('Test', $data['subject']);
- $this->assertEquals('Content', $data['html']);
- }
-
- public function testHandlePayload()
- {
- $w = new Mailgun($this->container);
- $p = new Project($this->container);
- $pp = new ProjectPermission($this->container);
- $u = new User($this->container);
- $tc = new TaskCreation($this->container);
- $tf = new TaskFinder($this->container);
-
- $this->assertEquals(2, $u->create(array('username' => 'me', 'email' => 'me@localhost')));
-
- $this->assertEquals(1, $p->create(array('name' => 'test1')));
- $this->assertEquals(2, $p->create(array('name' => 'test2', 'identifier' => 'TEST1')));
-
- // Empty payload
- $this->assertFalse($w->receiveEmail(array()));
-
- // Unknown user
- $this->assertFalse($w->receiveEmail(array('sender' => 'a@b.c', 'subject' => 'Email task', 'recipient' => 'foobar', 'stripped-text' => 'boo')));
-
- // Project not found
- $this->assertFalse($w->receiveEmail(array('sender' => 'me@localhost', 'subject' => 'Email task', 'recipient' => 'foo+test@localhost', 'stripped-text' => 'boo')));
-
- // User is not member
- $this->assertFalse($w->receiveEmail(array('sender' => 'me@localhost', 'subject' => 'Email task', 'recipient' => 'foo+test1@localhost', 'stripped-text' => 'boo')));
- $this->assertTrue($pp->addMember(2, 2));
-
- // The task must be created
- $this->assertTrue($w->receiveEmail(array('sender' => 'me@localhost', 'subject' => 'Email task', 'recipient' => 'foo+test1@localhost', 'stripped-text' => 'boo')));
-
- $task = $tf->getById(1);
- $this->assertNotEmpty($task);
- $this->assertEquals(2, $task['project_id']);
- $this->assertEquals('Email task', $task['title']);
- $this->assertEquals('boo', $task['description']);
- $this->assertEquals(2, $task['creator_id']);
- }
-}
diff --git a/tests/units/Integration/PostmarkTest.php b/tests/units/Integration/PostmarkTest.php
deleted file mode 100644
index fba6dbba..00000000
--- a/tests/units/Integration/PostmarkTest.php
+++ /dev/null
@@ -1,106 +0,0 @@
-<?php
-
-require_once __DIR__.'/../Base.php';
-
-use Kanboard\Integration\Postmark;
-use Kanboard\Model\TaskCreation;
-use Kanboard\Model\TaskFinder;
-use Kanboard\Model\Project;
-use Kanboard\Model\ProjectPermission;
-use Kanboard\Model\User;
-
-class PostmarkTest extends Base
-{
- public function testSendEmail()
- {
- $pm = new Postmark($this->container);
- $pm->sendEmail('test@localhost', 'Me', 'Test', 'Content', 'Bob');
-
- $this->assertEquals('https://api.postmarkapp.com/email', $this->container['httpClient']->getUrl());
-
- $data = $this->container['httpClient']->getData();
-
- $this->assertArrayHasKey('From', $data);
- $this->assertArrayHasKey('To', $data);
- $this->assertArrayHasKey('Subject', $data);
- $this->assertArrayHasKey('HtmlBody', $data);
-
- $this->assertEquals('Me <test@localhost>', $data['To']);
- $this->assertEquals('Bob <notifications@kanboard.local>', $data['From']);
- $this->assertEquals('Test', $data['Subject']);
- $this->assertEquals('Content', $data['HtmlBody']);
-
- $this->assertContains('Accept: application/json', $this->container['httpClient']->getHeaders());
- $this->assertContains('X-Postmark-Server-Token: ', $this->container['httpClient']->getHeaders());
- }
-
- public function testHandlePayload()
- {
- $w = new Postmark($this->container);
- $p = new Project($this->container);
- $pp = new ProjectPermission($this->container);
- $u = new User($this->container);
- $tc = new TaskCreation($this->container);
- $tf = new TaskFinder($this->container);
-
- $this->assertEquals(2, $u->create(array('username' => 'me', 'email' => 'me@localhost')));
-
- $this->assertEquals(1, $p->create(array('name' => 'test1')));
- $this->assertEquals(2, $p->create(array('name' => 'test2', 'identifier' => 'TEST1')));
-
- // Empty payload
- $this->assertFalse($w->receiveEmail(array()));
-
- // Unknown user
- $this->assertFalse($w->receiveEmail(array('From' => 'a@b.c', 'Subject' => 'Email task', 'MailboxHash' => 'foobar', 'TextBody' => 'boo')));
-
- // Project not found
- $this->assertFalse($w->receiveEmail(array('From' => 'me@localhost', 'Subject' => 'Email task', 'MailboxHash' => 'test', 'TextBody' => 'boo')));
-
- // User is not member
- $this->assertFalse($w->receiveEmail(array('From' => 'me@localhost', 'Subject' => 'Email task', 'MailboxHash' => 'test1', 'TextBody' => 'boo')));
- $this->assertTrue($pp->addMember(2, 2));
-
- // The task must be created
- $this->assertTrue($w->receiveEmail(array('From' => 'me@localhost', 'Subject' => 'Email task', 'MailboxHash' => 'test1', 'TextBody' => 'boo')));
-
- $task = $tf->getById(1);
- $this->assertNotEmpty($task);
- $this->assertEquals(2, $task['project_id']);
- $this->assertEquals('Email task', $task['title']);
- $this->assertEquals('boo', $task['description']);
- $this->assertEquals(2, $task['creator_id']);
- }
-
- public function testHtml2Markdown()
- {
- $w = new Postmark($this->container);
- $p = new Project($this->container);
- $pp = new ProjectPermission($this->container);
- $u = new User($this->container);
- $tc = new TaskCreation($this->container);
- $tf = new TaskFinder($this->container);
-
- $this->assertEquals(2, $u->create(array('username' => 'me', 'email' => 'me@localhost')));
- $this->assertEquals(1, $p->create(array('name' => 'test2', 'identifier' => 'TEST1')));
- $this->assertTrue($pp->addMember(1, 2));
-
- $this->assertTrue($w->receiveEmail(array('From' => 'me@localhost', 'Subject' => 'Email task', 'MailboxHash' => 'test1', 'TextBody' => 'boo', 'HtmlBody' => '<p><strong>boo</strong></p>')));
-
- $task = $tf->getById(1);
- $this->assertNotEmpty($task);
- $this->assertEquals(1, $task['project_id']);
- $this->assertEquals('Email task', $task['title']);
- $this->assertEquals('**boo**', $task['description']);
- $this->assertEquals(2, $task['creator_id']);
-
- $this->assertTrue($w->receiveEmail(array('From' => 'me@localhost', 'Subject' => 'Email task', 'MailboxHash' => 'test1', 'TextBody' => '**boo**', 'HtmlBody' => '')));
-
- $task = $tf->getById(2);
- $this->assertNotEmpty($task);
- $this->assertEquals(1, $task['project_id']);
- $this->assertEquals('Email task', $task['title']);
- $this->assertEquals('**boo**', $task['description']);
- $this->assertEquals(2, $task['creator_id']);
- }
-}
diff --git a/tests/units/Integration/SendgridTest.php b/tests/units/Integration/SendgridTest.php
deleted file mode 100644
index 85303ce7..00000000
--- a/tests/units/Integration/SendgridTest.php
+++ /dev/null
@@ -1,135 +0,0 @@
-<?php
-
-require_once __DIR__.'/../Base.php';
-
-use Kanboard\Integration\Sendgrid;
-use Kanboard\Model\TaskCreation;
-use Kanboard\Model\TaskFinder;
-use Kanboard\Model\Project;
-use Kanboard\Model\ProjectPermission;
-use Kanboard\Model\User;
-
-class SendgridTest extends Base
-{
- public function testSendEmail()
- {
- $pm = new Sendgrid($this->container);
- $pm->sendEmail('test@localhost', 'Me', 'Test', 'Content', 'Bob');
-
- $this->assertEquals('https://api.sendgrid.com/api/mail.send.json', $this->container['httpClient']->getUrl());
-
- $data = $this->container['httpClient']->getData();
-
- $this->assertArrayHasKey('api_user', $data);
- $this->assertArrayHasKey('api_key', $data);
- $this->assertArrayHasKey('from', $data);
- $this->assertArrayHasKey('fromname', $data);
- $this->assertArrayHasKey('to', $data);
- $this->assertArrayHasKey('toname', $data);
- $this->assertArrayHasKey('subject', $data);
- $this->assertArrayHasKey('html', $data);
-
- $this->assertEquals('test@localhost', $data['to']);
- $this->assertEquals('Me', $data['toname']);
- $this->assertEquals('notifications@kanboard.local', $data['from']);
- $this->assertEquals('Bob', $data['fromname']);
- $this->assertEquals('Test', $data['subject']);
- $this->assertEquals('Content', $data['html']);
- $this->assertEquals('', $data['api_key']);
- $this->assertEquals('', $data['api_user']);
- }
-
- public function testHandlePayload()
- {
- $w = new Sendgrid($this->container);
- $p = new Project($this->container);
- $pp = new ProjectPermission($this->container);
- $u = new User($this->container);
- $tc = new TaskCreation($this->container);
- $tf = new TaskFinder($this->container);
-
- $this->assertEquals(2, $u->create(array('username' => 'me', 'email' => 'me@localhost')));
-
- $this->assertEquals(1, $p->create(array('name' => 'test1')));
- $this->assertEquals(2, $p->create(array('name' => 'test2', 'identifier' => 'TEST1')));
-
- // Empty payload
- $this->assertFalse($w->receiveEmail(array()));
-
- // Unknown user
- $this->assertFalse($w->receiveEmail(array(
- 'envelope' => '{"to":["a@b.c"],"from":"a.b.c"}',
- 'subject' => 'Email task'
- )));
-
- // Project not found
- $this->assertFalse($w->receiveEmail(array(
- 'envelope' => '{"to":["a@b.c"],"from":"me@localhost"}',
- 'subject' => 'Email task'
- )));
-
- // User is not member
- $this->assertFalse($w->receiveEmail(array(
- 'envelope' => '{"to":["something+test1@localhost"],"from":"me@localhost"}',
- 'subject' => 'Email task'
- )));
-
- $this->assertTrue($pp->addMember(2, 2));
-
- // The task must be created
- $this->assertTrue($w->receiveEmail(array(
- 'envelope' => '{"to":["something+test1@localhost"],"from":"me@localhost"}',
- 'subject' => 'Email task'
- )));
-
- $task = $tf->getById(1);
- $this->assertNotEmpty($task);
- $this->assertEquals(2, $task['project_id']);
- $this->assertEquals('Email task', $task['title']);
- $this->assertEquals('', $task['description']);
- $this->assertEquals(2, $task['creator_id']);
-
- // Html content
- $this->assertTrue($w->receiveEmail(array(
- 'envelope' => '{"to":["something+test1@localhost"],"from":"me@localhost"}',
- 'subject' => 'Email task',
- 'html' => '<strong>bold</strong> text',
- )));
-
- $task = $tf->getById(2);
- $this->assertNotEmpty($task);
- $this->assertEquals(2, $task['project_id']);
- $this->assertEquals('Email task', $task['title']);
- $this->assertEquals('**bold** text', $task['description']);
- $this->assertEquals(2, $task['creator_id']);
-
- // Text content
- $this->assertTrue($w->receiveEmail(array(
- 'envelope' => '{"to":["something+test1@localhost"],"from":"me@localhost"}',
- 'subject' => 'Email task',
- 'text' => '**bold** text',
- )));
-
- $task = $tf->getById(3);
- $this->assertNotEmpty($task);
- $this->assertEquals(2, $task['project_id']);
- $this->assertEquals('Email task', $task['title']);
- $this->assertEquals('**bold** text', $task['description']);
- $this->assertEquals(2, $task['creator_id']);
-
- // Text + html content
- $this->assertTrue($w->receiveEmail(array(
- 'envelope' => '{"to":["something+test1@localhost"],"from":"me@localhost"}',
- 'subject' => 'Email task',
- 'html' => '<strong>bold</strong> html',
- 'text' => '**bold** text',
- )));
-
- $task = $tf->getById(4);
- $this->assertNotEmpty($task);
- $this->assertEquals(2, $task['project_id']);
- $this->assertEquals('Email task', $task['title']);
- $this->assertEquals('**bold** html', $task['description']);
- $this->assertEquals(2, $task['creator_id']);
- }
-}
diff --git a/tests/units/Model/EmailNotificationTest.php b/tests/units/Model/EmailNotificationTest.php
index afe8d196..342f0aa9 100644
--- a/tests/units/Model/EmailNotificationTest.php
+++ b/tests/units/Model/EmailNotificationTest.php
@@ -74,7 +74,7 @@ class EmailNotificationTest extends Base
$this->assertTrue($u->update(array('id' => 1, 'email' => 'test@localhost')));
$this->container['emailClient'] = $this
- ->getMockBuilder('\Kanboard\Core\EmailClient')
+ ->getMockBuilder('\Kanboard\Core\Mail\Client')
->setConstructorArgs(array($this->container))
->setMethods(array('send'))
->getMock();
@@ -104,7 +104,7 @@ class EmailNotificationTest extends Base
$this->assertEquals(1, $tc->create(array('title' => 'test', 'project_id' => 1)));
$this->container['emailClient'] = $this
- ->getMockBuilder('\Kanboard\Core\EmailClient')
+ ->getMockBuilder('\Kanboard\Core\Mail\Client')
->setConstructorArgs(array($this->container))
->setMethods(array('send'))
->getMock();