Move slack, hipchat and jabber integrations to plugins

This commit is contained in:
Frederic Guillot
2015-10-17 22:19:49 -04:00
parent 9283fb88d8
commit 09da289c2f
68 changed files with 544 additions and 1644 deletions

View File

@@ -44,7 +44,7 @@ class Config extends Base
$values += array('subtask_restriction' => 0, 'subtask_time_tracking' => 0, 'cfd_include_closed_tasks' => 0);
break;
case 'integrations':
$values += array('integration_slack_webhook' => 0, 'integration_hipchat' => 0, 'integration_gravatar' => 0, 'integration_jabber' => 0);
$values += array('integration_gravatar' => 0);
break;
case 'calendar':
$values += array('calendar_user_subtasks_time_tracking' => 0);

View File

@@ -89,28 +89,49 @@ class Project extends Base
*
* @access public
*/
public function integration()
public function integrations()
{
$project = $this->getProject();
if ($this->request->isPost()) {
$params = $this->request->getValues();
$params += array('hipchat' => 0, 'slack' => 0, 'jabber' => 0);
$this->projectIntegration->saveParameters($project['id'], $params);
$this->projectMetadata->save($project['id'], $this->request->getValues());
$this->session->flash(t('Project updated successfully.'));
$this->response->redirect($this->helper->url->to('project', 'integrations', array('project_id' => $project['id'])));
}
$values = $this->projectIntegration->getParameters($project['id']);
$values += array('hipchat_api_url' => 'https://api.hipchat.com');
$this->response->html($this->projectLayout('project/integrations', array(
'project' => $project,
'title' => t('Integrations'),
'webhook_token' => $this->config->get('webhook_token'),
'values' => $values,
'values' => $this->projectMetadata->getAll($project['id']),
'errors' => array(),
)));
}
/**
* Display project notifications
*
* @access public
*/
public function notifications()
{
$project = $this->getProject();
if ($this->request->isPost()) {
$values = $this->request->getValues();
$this->projectNotification->saveSettings($project['id'], $values);
$this->session->flash(t('Project updated successfully.'));
$this->response->redirect($this->helper->url->to('project', 'notifications', array('project_id' => $project['id'])));
}
$this->response->html($this->projectLayout('project/notifications', array(
'notifications' => $this->projectNotification->readSettings($project['id']),
'types' => $this->projectNotificationType->getTypes(),
'project' => $project,
'title' => t('Notifications'),
)));
}
/**
* Display a form to edit a project
*

View File

@@ -213,6 +213,28 @@ class User extends Base
)));
}
/**
* Display user integrations
*
* @access public
*/
public function integrations()
{
$user = $this->getUser();
if ($this->request->isPost()) {
$values = $this->request->getValues();
$this->userMetadata->save($user['id'], $values);
$this->session->flash(t('User updated successfully.'));
$this->response->redirect($this->helper->url->to('user', 'integrations', array('user_id' => $user['id'])));
}
$this->response->html($this->layout('user/integrations', array(
'user' => $user,
'values' => $this->userMetadata->getall($user['id']),
)));
}
/**
* Display external accounts
*

View File

@@ -27,9 +27,6 @@ use Pimple\Container;
* @property \Kanboard\Integration\BitbucketWebhook $bitbucketWebhook
* @property \Kanboard\Integration\GithubWebhook $githubWebhook
* @property \Kanboard\Integration\GitlabWebhook $gitlabWebhook
* @property \Kanboard\Integration\HipchatWebhook $hipchatWebhook
* @property \Kanboard\Integration\Jabber $jabber
* @property \Kanboard\Integration\SlackWebhook $slackWebhook
* @property \Kanboard\Formatter\ProjectGanttFormatter $projectGanttFormatter
* @property \Kanboard\Formatter\TaskFilterGanttFormatter $taskFilterGanttFormatter
* @property \Kanboard\Formatter\TaskFilterAutoCompleteFormatter $taskFilterAutoCompleteFormatter
@@ -49,6 +46,7 @@ use Pimple\Container;
* @property \Kanboard\Model\File $file
* @property \Kanboard\Model\LastLogin $lastLogin
* @property \Kanboard\Model\Link $link
* @property \Kanboard\Model\Notification $notification
* @property \Kanboard\Model\OverdueNotification $overdueNotification
* @property \Kanboard\Model\Project $project
* @property \Kanboard\Model\ProjectActivity $projectActivity
@@ -56,7 +54,7 @@ use Pimple\Container;
* @property \Kanboard\Model\ProjectDuplication $projectDuplication
* @property \Kanboard\Model\ProjectDailyColumnStats $projectDailyColumnStats
* @property \Kanboard\Model\ProjectDailyStats $projectDailyStats
* @property \Kanboard\Model\ProjectIntegration $projectIntegration
* @property \Kanboard\Model\ProjectMetadata $projectMetadata
* @property \Kanboard\Model\ProjectPermission $projectPermission
* @property \Kanboard\Model\Subtask $subtask
* @property \Kanboard\Model\SubtaskExport $subtaskExport
@@ -76,6 +74,7 @@ use Pimple\Container;
* @property \Kanboard\Model\TaskPosition $taskPosition
* @property \Kanboard\Model\TaskStatus $taskStatus
* @property \Kanboard\Model\TaskValidator $taskValidator
* @property \Kanboard\Model\TaskMetadata $taskMetadata
* @property \Kanboard\Model\Transition $transition
* @property \Kanboard\Model\User $user
* @property \Kanboard\Model\UserImport $userImport
@@ -84,6 +83,7 @@ use Pimple\Container;
* @property \Kanboard\Model\UserNotificationFilter $userNotificationFilter
* @property \Kanboard\Model\UserUnreadNotification $userUnreadNotification
* @property \Kanboard\Model\UserSession $userSession
* @property \Kanboard\Model\UserMetadata $userMetadata
* @property \Kanboard\Model\Webhook $webhook
* @property \Psr\Log\LoggerInterface $logger
* @property \League\HTMLToMarkdown\HtmlConverter $htmlConverter

View File

@@ -1,93 +0,0 @@
<?php
namespace Kanboard\Integration;
/**
* Hipchat webhook
*
* @package integration
* @author Frederic Guillot
*/
class HipchatWebhook extends \Kanboard\Core\Base
{
/**
* Return true if Hipchat is enabled for this project or globally
*
* @access public
* @param integer $project_id
* @return boolean
*/
public function isActivated($project_id)
{
return $this->config->get('integration_hipchat') == 1 || $this->projectIntegration->hasValue($project_id, 'hipchat', 1);
}
/**
* Get API parameters
*
* @access public
* @param integer $project_id
* @return array
*/
public function getParameters($project_id)
{
if ($this->config->get('integration_hipchat') == 1) {
return array(
'api_url' => $this->config->get('integration_hipchat_api_url'),
'room_id' => $this->config->get('integration_hipchat_room_id'),
'room_token' => $this->config->get('integration_hipchat_room_token'),
);
}
$options = $this->projectIntegration->getParameters($project_id);
return array(
'api_url' => $options['hipchat_api_url'],
'room_id' => $options['hipchat_room_id'],
'room_token' => $options['hipchat_room_token'],
);
}
/**
* Send the notification if activated
*
* @access public
* @param integer $project_id Project id
* @param integer $task_id Task id
* @param string $event_name Event name
* @param array $event Event data
*/
public function notify($project_id, $task_id, $event_name, array $event)
{
if ($this->isActivated($project_id)) {
$params = $this->getParameters($project_id);
$project = $this->project->getbyId($project_id);
$event['event_name'] = $event_name;
$event['author'] = $this->user->getFullname($this->session['user']);
$html = '<img src="http://kanboard.net/assets/img/favicon-32x32.png"/>';
$html .= '<strong>'.$project['name'].'</strong>'.(isset($event['task']['title']) ? '<br/>'.$event['task']['title'] : '').'<br/>';
$html .= $this->projectActivity->getTitle($event);
if ($this->config->get('application_url')) {
$html .= '<br/><a href="'.$this->helper->url->href('task', 'show', array('task_id' => $task_id, 'project_id' => $project_id), false, '', true).'">';
$html .= t('view the task on Kanboard').'</a>';
}
$payload = array(
'message' => $html,
'color' => 'yellow',
);
$url = sprintf(
'%s/v2/room/%s/notification?auth_token=%s',
$params['api_url'],
$params['room_id'],
$params['room_token']
);
$this->httpClient->postJson($url, $payload);
}
}
}

View File

@@ -1,126 +0,0 @@
<?php
namespace Kanboard\Integration;
use Exception;
use Fabiang\Xmpp\Options;
use Fabiang\Xmpp\Client;
use Fabiang\Xmpp\Protocol\Message;
use Fabiang\Xmpp\Protocol\Presence;
/**
* Jabber
*
* @package integration
* @author Frederic Guillot
*/
class Jabber extends \Kanboard\Core\Base
{
/**
* Return true if Jabber is enabled for this project or globally
*
* @access public
* @param integer $project_id
* @return boolean
*/
public function isActivated($project_id)
{
return $this->config->get('integration_jabber') == 1 || $this->projectIntegration->hasValue($project_id, 'jabber', 1);
}
/**
* Get connection parameters
*
* @access public
* @param integer $project_id
* @return array
*/
public function getParameters($project_id)
{
if ($this->config->get('integration_jabber') == 1) {
return array(
'server' => $this->config->get('integration_jabber_server'),
'domain' => $this->config->get('integration_jabber_domain'),
'username' => $this->config->get('integration_jabber_username'),
'password' => $this->config->get('integration_jabber_password'),
'nickname' => $this->config->get('integration_jabber_nickname'),
'room' => $this->config->get('integration_jabber_room'),
);
}
$options = $this->projectIntegration->getParameters($project_id);
return array(
'server' => $options['jabber_server'],
'domain' => $options['jabber_domain'],
'username' => $options['jabber_username'],
'password' => $options['jabber_password'],
'nickname' => $options['jabber_nickname'],
'room' => $options['jabber_room'],
);
}
/**
* Build and send the message
*
* @access public
* @param integer $project_id Project id
* @param integer $task_id Task id
* @param string $event_name Event name
* @param array $event Event data
*/
public function notify($project_id, $task_id, $event_name, array $event)
{
if ($this->isActivated($project_id)) {
$project = $this->project->getbyId($project_id);
$event['event_name'] = $event_name;
$event['author'] = $this->user->getFullname($this->session['user']);
$payload = '['.$project['name'].'] '.str_replace('&quot;', '"', $this->projectActivity->getTitle($event)).(isset($event['task']['title']) ? ' ('.$event['task']['title'].')' : '');
if ($this->config->get('application_url')) {
$payload .= ' '.$this->helper->url->to('task', 'show', array('task_id' => $task_id, 'project_id' => $project_id), '', true);
}
$this->sendMessage($project_id, $payload);
}
}
/**
* Send message to the XMPP server
*
* @access public
* @param integer $project_id
* @param string $payload
*/
public function sendMessage($project_id, $payload)
{
try {
$params = $this->getParameters($project_id);
$options = new Options($params['server']);
$options->setUsername($params['username']);
$options->setPassword($params['password']);
$options->setTo($params['domain']);
$options->setLogger($this->container['logger']);
$client = new Client($options);
$channel = new Presence;
$channel->setTo($params['room'])->setNickName($params['nickname']);
$client->send($channel);
$message = new Message;
$message->setMessage($payload)
->setTo($params['room'])
->setType(Message::TYPE_GROUPCHAT);
$client->send($message);
$client->disconnect();
} catch (Exception $e) {
$this->container['logger']->error('Jabber error: '.$e->getMessage());
}
}
}

View File

@@ -1,126 +0,0 @@
<?php
namespace Kanboard\Integration;
/**
* Slack Webhook
*
* @package integration
* @author Frederic Guillot
*/
class SlackWebhook extends \Kanboard\Core\Base
{
/**
* Return true if Slack is enabled for this project or globally
*
* @access public
* @param integer $project_id
* @return boolean
*/
public function isActivated($project_id)
{
return $this->config->get('integration_slack_webhook') == 1 || $this->projectIntegration->hasValue($project_id, 'slack', 1);
}
/**
* Get wehbook url
*
* @access public
* @param integer $project_id
* @return string
*/
public function getWebhookUrl($project_id)
{
if ($this->config->get('integration_slack_webhook') == 1) {
return $this->config->get('integration_slack_webhook_url');
}
$options = $this->projectIntegration->getParameters($project_id);
return isset($options['slack_webhook_url']) ? $options['slack_webhook_url'] : '';
}
/**
* Get optional Slack channel
*
* @access public
* @param integer $project_id
* @return string
*/
public function getChannel($project_id)
{
$channel = $this->config->get('integration_slack_webhook_channel');
if (! empty($channel)) {
return $channel;
}
$options = $this->projectIntegration->getParameters($project_id);
return isset($options['slack_webhook_channel']) ? $options['slack_webhook_channel'] : '';
}
/**
* Send notification to Slack
*
* @access public
* @param integer $project_id Project id
* @param integer $task_id Task id
* @param string $event_name Event name
* @param array $event Event data
*/
public function notify($project_id, $task_id, $event_name, array $event)
{
if ($this->isActivated($project_id)) {
$project = $this->project->getbyId($project_id);
$event['event_name'] = $event_name;
$event['author'] = $this->user->getFullname($this->session['user']);
$message = '*['.$project['name'].']* ';
$message .= str_replace('&quot;', '"', $this->projectActivity->getTitle($event));
$message .= isset($event['task']['title']) ? ' ('.$event['task']['title'].')' : '';
if ($this->config->get('application_url')) {
$message .= ' - <'.$this->helper->url->href('task', 'show', array('task_id' => $task_id, 'project_id' => $project_id), false, '', true);
$message .= '|'.t('view the task on Kanboard').'>';
}
$this->sendMessage($project_id, $message);
}
}
/**
* Send message to Slack
*
* @access public
* @param integer $project_id
* @param string $message
*/
public function sendMessage($project_id, $message)
{
$payload = array(
'text' => $message,
'username' => 'Kanboard',
'icon_url' => 'http://kanboard.net/assets/img/favicon.png',
);
$this->sendPayload($project_id, $payload);
}
/**
* Send payload to Slack
*
* @access public
* @param integer $project_id
* @param array $payload
*/
public function sendPayload($project_id, array $payload)
{
$channel = $this->getChannel($project_id);
if (! empty($channel)) {
$payload['channel'] = $channel;
}
$this->httpClient->postJson($this->getWebhookUrl($project_id), $payload);
}
}

View File

@@ -697,15 +697,9 @@ return array(
'Reference currency' => 'Referenční měna',
'The currency rate have been added successfully.' => 'Směnný kurz byl úspěšně přidán.',
'Unable to add this currency rate.' => 'Nelze přidat tento směnný kurz',
'Send notifications to a Slack channel' => 'Zaslání upozornění do Slack kanálu',
'Webhook URL' => 'Webhook URL',
'Help on Slack integration' => 'Nápověda pro Slack integraci.',
'%s remove the assignee of the task %s' => '%s odstranil přiřazení úkolu %s ',
'Send notifications to Hipchat' => 'Odeslat upozornění přes Hipchat',
'API URL' => 'API URL',
'Room API ID or name' => 'Raum API ID oder Name',
'Room notification token' => 'Raum Benachrichtigungstoken',
'Help on Hipchat integration' => 'Hilfe bei Hipchat Integration',
'Enable Gravatar images' => 'Aktiviere Gravatar Bilder',
'Information' => 'Informace',
'Check two factor authentication code' => 'Prüfe Zwei-Faktor-Authentifizierungscode',
@@ -768,14 +762,6 @@ return array(
// 'When task is moved from first column' => '',
// 'When task is moved to last column' => '',
// 'Year(s)' => '',
// 'Jabber (XMPP)' => '',
// 'Send notifications to Jabber' => '',
// 'XMPP server address' => '',
// 'Jabber domain' => '',
// 'Jabber nickname' => '',
// 'Multi-user chat room' => '',
// 'Help on Jabber integration' => '',
// 'The server address must use this format: "tcp://hostname:5222"' => '',
'Calendar settings' => 'Nastavení kalendáře',
// 'Project calendar view' => '',
'Project settings' => 'Nastavení projektu',
@@ -820,8 +806,6 @@ return array(
'%s moved the task #%d to the swimlane "%s"' => '%s hat die Aufgabe #%d in die Swimlane "%s" verschoben',
// 'Swimlane' => '',
// 'Gravatar' => '',
// 'Hipchat' => '',
// 'Slack' => '',
'%s moved the task %s to the first swimlane' => '%s hat die Aufgabe %s in die erste Swimlane verschoben',
'%s moved the task %s to the swimlane "%s"' => '%s hat die Aufgaben %s in die Swimlane "%s" verschoben',
'This report contains all subtasks information for the given date range.' => 'Report obsahuje všechny informace o dílčích úkolech pro daný časový úsek',
@@ -918,7 +902,6 @@ return array(
'Help on Google authentication' => 'Nápověda k ověřování pomocí služby Google',
'Github Authentication' => 'Ověřování pomocí služby Github',
'Help on Github authentication' => 'Nápověda k ověřování pomocí služby Github',
'Channel/Group/User (Optional)' => 'Kanál/Skupina/Uživatel (volitelně)',
'Lead time: ' => 'Dodací lhůta: ',
'Cycle time: ' => 'Doba cyklu: ',
'Time spent into each column' => 'Čas strávený v každé fázi',

View File

@@ -697,15 +697,9 @@ return array(
// 'Reference currency' => '',
// 'The currency rate have been added successfully.' => '',
// 'Unable to add this currency rate.' => '',
// 'Send notifications to a Slack channel' => '',
// 'Webhook URL' => '',
// 'Help on Slack integration' => '',
// '%s remove the assignee of the task %s' => '',
// 'Send notifications to Hipchat' => '',
// 'API URL' => '',
// 'Room API ID or name' => '',
// 'Room notification token' => '',
// 'Help on Hipchat integration' => '',
// 'Enable Gravatar images' => '',
// 'Information' => '',
// 'Check two factor authentication code' => '',
@@ -768,14 +762,6 @@ return array(
// 'When task is moved from first column' => '',
// 'When task is moved to last column' => '',
// 'Year(s)' => '',
// 'Jabber (XMPP)' => '',
// 'Send notifications to Jabber' => '',
// 'XMPP server address' => '',
// 'Jabber domain' => '',
// 'Jabber nickname' => '',
// 'Multi-user chat room' => '',
// 'Help on Jabber integration' => '',
// 'The server address must use this format: "tcp://hostname:5222"' => '',
// 'Calendar settings' => '',
// 'Project calendar view' => '',
// 'Project settings' => '',
@@ -820,8 +806,6 @@ return array(
// '%s moved the task #%d to the swimlane "%s"' => '',
// 'Swimlane' => '',
// 'Gravatar' => '',
// 'Hipchat' => '',
// 'Slack' => '',
// '%s moved the task %s to the first swimlane' => '',
// '%s moved the task %s to the swimlane "%s"' => '',
// 'This report contains all subtasks information for the given date range.' => '',
@@ -918,7 +902,6 @@ return array(
// 'Help on Google authentication' => '',
// 'Github Authentication' => '',
// 'Help on Github authentication' => '',
// 'Channel/Group/User (Optional)' => '',
// 'Lead time: ' => '',
// 'Cycle time: ' => '',
// 'Time spent into each column' => '',

View File

@@ -697,15 +697,9 @@ return array(
'Reference currency' => 'Referenzwährung',
'The currency rate have been added successfully.' => 'Der Währungskurs wurde erfolgreich hinzugefügt.',
'Unable to add this currency rate.' => 'Währungskurs konnte nicht hinzugefügt werden',
'Send notifications to a Slack channel' => 'Benachrichtigung an einen Slack-Kanal senden',
'Webhook URL' => 'Webhook-URL',
'Help on Slack integration' => 'Hilfe für Slack-Integration.',
'%s remove the assignee of the task %s' => '%s Zuordnung für die Aufgabe %s entfernen',
'Send notifications to Hipchat' => 'Sende Benachrichtigung an Hipchat',
'API URL' => 'API-URL',
'Room API ID or name' => 'Raum-API-ID oder -Name',
'Room notification token' => 'Raum-Benachrichtigungstoken',
'Help on Hipchat integration' => 'Hilfe bei Hipchat-Integration',
'Enable Gravatar images' => 'Aktiviere Gravatar-Bilder',
'Information' => 'Information',
'Check two factor authentication code' => 'Prüfe Zwei-Faktor-Authentifizierungscode',
@@ -768,14 +762,6 @@ return array(
'When task is moved from first column' => 'Wenn Aufgabe von erster Spalte verschoben wird',
'When task is moved to last column' => 'Wenn Aufgabe in letzte Spalte verschoben wird',
'Year(s)' => 'Jahr(e)',
'Jabber (XMPP)' => 'Jabber (XMPP)',
'Send notifications to Jabber' => 'Benachrichtigungen an Jabber senden',
'XMPP server address' => 'XMPP-Server-Adresse',
'Jabber domain' => 'Jabber-Domain',
'Jabber nickname' => 'Jabber-Nickname',
'Multi-user chat room' => 'Multi-User-Chatroom',
'Help on Jabber integration' => 'Hilfe zur Jabber-Integration',
'The server address must use this format: "tcp://hostname:5222"' => 'Die Server-Adresse muss in diesem Format sein: "tcp://hostname:5222"',
'Calendar settings' => 'Kalender-Einstellungen',
'Project calendar view' => 'Projekt-Kalendarsicht',
'Project settings' => 'Projekteinstellungen',
@@ -820,8 +806,6 @@ return array(
'%s moved the task #%d to the swimlane "%s"' => '%s hat die Aufgabe #%d in die Swimlane "%s" verschoben',
'Swimlane' => 'Swimlane',
'Gravatar' => 'Gravatar',
'Hipchat' => 'Hipchat',
'Slack' => 'Slack',
'%s moved the task %s to the first swimlane' => '%s hat die Aufgabe %s in die erste Swimlane verschoben',
'%s moved the task %s to the swimlane "%s"' => '%s hat die Aufgaben %s in die Swimlane "%s" verschoben',
'This report contains all subtasks information for the given date range.' => 'Der Bericht beinhaltet alle Teilaufgaben im gewählten Zeitraum',
@@ -918,7 +902,6 @@ return array(
'Help on Google authentication' => 'Hilfe bei Google-Authentifizierung',
'Github Authentication' => 'Github-Authentifizierung',
'Help on Github authentication' => 'Hilfe bei Github-Authentifizierung',
'Channel/Group/User (Optional)' => 'Kanal/Gruppe/Benutzer (optional)',
'Lead time: ' => 'Durchlaufzeit:',
'Cycle time: ' => 'Zykluszeit:',
'Time spent into each column' => 'zeit verbracht in jeder Spalte',

View File

@@ -697,15 +697,9 @@ return array(
'Reference currency' => 'Moneda de referencia',
'The currency rate have been added successfully.' => 'Se ha añadido el cambio de moneda con éxito',
'Unable to add this currency rate.' => 'No pude añadir este cambio de moneda.',
'Send notifications to a Slack channel' => 'Enviar notificaciones a un canal Desatendido',
'Webhook URL' => 'URL de Disparador Web (webhook)',
'Help on Slack integration' => 'Ayuda sobre integración Desatendida',
'%s remove the assignee of the task %s' => '%s quita el concesionario de la tarea %s',
'Send notifications to Hipchat' => 'Enviar notificaciones a Hipchat',
'API URL' => 'URL de API',
'Room API ID or name' => 'ID de API de habitación o nombre',
'Room notification token' => 'Notificación de ficha de Habitación',
'Help on Hipchat integration' => 'Ayuda sobre integración de Hipchat',
'Enable Gravatar images' => 'Activar imágenes Gravatar',
'Information' => 'Información',
'Check two factor authentication code' => 'Revisar código de autenticación de dos factores',
@@ -768,14 +762,6 @@ return array(
'When task is moved from first column' => 'Cuando la tarea es movida desde la primera columna',
'When task is moved to last column' => 'Cuando la tarea es movida a la última columna',
'Year(s)' => 'Año(s)',
'Jabber (XMPP)' => 'Jabber (XMPP)',
'Send notifications to Jabber' => 'Enviar notificaciones a Jabber',
'XMPP server address' => 'Dirección del servidor XMPP',
'Jabber domain' => 'Dominio Jabber',
'Jabber nickname' => 'Apodo Jabber',
'Multi-user chat room' => 'Sala de chat multiusuario',
'Help on Jabber integration' => 'Ayuda para la integración con Jabber',
'The server address must use this format: "tcp://hostname:5222"' => 'La dirección del servidor debe usar este formato: "tcp://hostname:5222"',
'Calendar settings' => 'Parámetros del Calendario',
'Project calendar view' => 'Vista de Calendario para el Proyecto',
'Project settings' => 'Parámetros del Proyecto',
@@ -820,8 +806,6 @@ return array(
'%s moved the task #%d to the swimlane "%s"' => '%s movió la tarea #%d a la calle "%s"',
'Swimlane' => 'Calle',
'Gravatar' => 'Gravatar',
'Hipchat' => 'Hipchat',
'Slack' => 'Desatendida',
'%s moved the task %s to the first swimlane' => '%s movió la tarea %s a la primera calle',
'%s moved the task %s to the swimlane "%s"' => '%s movió la tarea %s a la calle "%s"',
'This report contains all subtasks information for the given date range.' => 'Este informe contiene todas la información de las subtareas para el rango proporcionado de fechas.',
@@ -918,7 +902,6 @@ return array(
'Help on Google authentication' => 'Ayuda con la aAutenticación de Google',
'Github Authentication' => 'Autenticación de Github',
'Help on Github authentication' => 'Ayuda con la autenticación de Github',
'Channel/Group/User (Optional)' => 'Canal/Grupo/Usuario (Opcional)',
'Lead time: ' => 'Plazo de entrega: ',
'Cycle time: ' => 'Tiempo de Ciclo: ',
'Time spent into each column' => 'Tiempo empleado en cada columna',

View File

@@ -697,15 +697,9 @@ return array(
// 'Reference currency' => '',
// 'The currency rate have been added successfully.' => '',
// 'Unable to add this currency rate.' => '',
// 'Send notifications to a Slack channel' => '',
// 'Webhook URL' => '',
// 'Help on Slack integration' => '',
// '%s remove the assignee of the task %s' => '',
// 'Send notifications to Hipchat' => '',
// 'API URL' => '',
// 'Room API ID or name' => '',
// 'Room notification token' => '',
// 'Help on Hipchat integration' => '',
// 'Enable Gravatar images' => '',
// 'Information' => '',
// 'Check two factor authentication code' => '',
@@ -768,14 +762,6 @@ return array(
// 'When task is moved from first column' => '',
// 'When task is moved to last column' => '',
// 'Year(s)' => '',
// 'Jabber (XMPP)' => '',
// 'Send notifications to Jabber' => '',
// 'XMPP server address' => '',
// 'Jabber domain' => '',
// 'Jabber nickname' => '',
// 'Multi-user chat room' => '',
// 'Help on Jabber integration' => '',
// 'The server address must use this format: "tcp://hostname:5222"' => '',
// 'Calendar settings' => '',
// 'Project calendar view' => '',
// 'Project settings' => '',
@@ -820,8 +806,6 @@ return array(
// '%s moved the task #%d to the swimlane "%s"' => '',
// 'Swimlane' => '',
// 'Gravatar' => '',
// 'Hipchat' => '',
// 'Slack' => '',
// '%s moved the task %s to the first swimlane' => '',
// '%s moved the task %s to the swimlane "%s"' => '',
// 'This report contains all subtasks information for the given date range.' => '',
@@ -918,7 +902,6 @@ return array(
// 'Help on Google authentication' => '',
// 'Github Authentication' => '',
// 'Help on Github authentication' => '',
// 'Channel/Group/User (Optional)' => '',
// 'Lead time: ' => '',
// 'Cycle time: ' => '',
// 'Time spent into each column' => '',

View File

@@ -699,15 +699,9 @@ return array(
'Reference currency' => 'Devise de référence',
'The currency rate have been added successfully.' => 'Le taux de change a été ajouté avec succès.',
'Unable to add this currency rate.' => 'Impossible d\'ajouter ce taux de change',
'Send notifications to a Slack channel' => 'Envoyer les notifications sur un salon de discussion Slack',
'Webhook URL' => 'URL du webhook',
'Help on Slack integration' => 'Aide sur l\'intégration avec Slack',
'%s remove the assignee of the task %s' => '%s a enlevé la personne assignée à la tâche %s',
'Send notifications to Hipchat' => 'Envoyer les notifications vers Hipchat',
'API URL' => 'URL de l\'api',
'Room API ID or name' => 'Nom ou identifiant du salon de discussion',
'Room notification token' => 'Jeton de sécurité du salon de discussion',
'Help on Hipchat integration' => 'Aide sur l\'intégration avec Hipchat',
'Enable Gravatar images' => 'Activer les images Gravatar',
'Information' => 'Informations',
'Check two factor authentication code' => 'Vérification du code pour l\'authentification à deux-facteurs',
@@ -770,14 +764,6 @@ return array(
'When task is moved from first column' => 'Lorsque la tâche est déplacée en dehors de la première colonne',
'When task is moved to last column' => 'Lorsque la tâche est déplacée dans la dernière colonne',
'Year(s)' => 'Année(s)',
'Jabber (XMPP)' => 'Jabber (XMPP)',
'Send notifications to Jabber' => 'Envoyer les notifications vers Jabber',
'XMPP server address' => 'Adresse du serveur XMPP',
'Jabber domain' => 'Nom de domaine Jabber',
'Jabber nickname' => 'Pseudonyme Jabber',
'Multi-user chat room' => 'Salon de discussion multi-utilisateurs',
'Help on Jabber integration' => 'Aide sur l\'intégration avec Jabber',
'The server address must use this format: "tcp://hostname:5222"' => 'L\'adresse du serveur doit utiliser le format suivant : « tcp://hostname:5222 »',
'Calendar settings' => 'Paramètres du calendrier',
'Project calendar view' => 'Vue en mode projet du calendrier',
'Project settings' => 'Paramètres du projet',
@@ -822,8 +808,6 @@ return array(
'%s moved the task #%d to the swimlane "%s"' => '%s a déplacé la tâche n°%d dans la swimlane « %s »',
'Swimlane' => 'Swimlane',
'Gravatar' => 'Gravatar',
'Hipchat' => 'Hipchat',
'Slack' => 'Slack',
'%s moved the task %s to the first swimlane' => '%s a déplacé la tâche %s dans la première swimlane',
'%s moved the task %s to the swimlane "%s"' => '%s a déplacé la tâche %s dans la swimlane « %s »',
'This report contains all subtasks information for the given date range.' => 'Ce rapport contient les informations de toutes les sous-tâches pour la période sélectionnée.',
@@ -920,7 +904,6 @@ return array(
'Help on Google authentication' => 'Aide sur l\'authentification Google',
'Github Authentication' => 'Authentification Github',
'Help on Github authentication' => 'Aide sur l\'authentification Github',
'Channel/Group/User (Optional)' => 'Canal/Groupe/Utilisateur (Optionnel)',
'Lead time: ' => 'Lead time : ',
'Cycle time: ' => 'Temps de cycle : ',
'Time spent into each column' => 'Temps passé dans chaque colonne',

View File

@@ -697,15 +697,9 @@ return array(
// 'Reference currency' => '',
// 'The currency rate have been added successfully.' => '',
// 'Unable to add this currency rate.' => '',
// 'Send notifications to a Slack channel' => '',
// 'Webhook URL' => '',
// 'Help on Slack integration' => '',
// '%s remove the assignee of the task %s' => '',
// 'Send notifications to Hipchat' => '',
// 'API URL' => '',
// 'Room API ID or name' => '',
// 'Room notification token' => '',
// 'Help on Hipchat integration' => '',
// 'Enable Gravatar images' => '',
// 'Information' => '',
// 'Check two factor authentication code' => '',
@@ -768,14 +762,6 @@ return array(
// 'When task is moved from first column' => '',
// 'When task is moved to last column' => '',
// 'Year(s)' => '',
// 'Jabber (XMPP)' => '',
// 'Send notifications to Jabber' => '',
// 'XMPP server address' => '',
// 'Jabber domain' => '',
// 'Jabber nickname' => '',
// 'Multi-user chat room' => '',
// 'Help on Jabber integration' => '',
// 'The server address must use this format: "tcp://hostname:5222"' => '',
// 'Calendar settings' => '',
// 'Project calendar view' => '',
// 'Project settings' => '',
@@ -820,8 +806,6 @@ return array(
// '%s moved the task #%d to the swimlane "%s"' => '',
// 'Swimlane' => '',
// 'Gravatar' => '',
// 'Hipchat' => '',
// 'Slack' => '',
// '%s moved the task %s to the first swimlane' => '',
// '%s moved the task %s to the swimlane "%s"' => '',
// 'This report contains all subtasks information for the given date range.' => '',
@@ -918,7 +902,6 @@ return array(
// 'Help on Google authentication' => '',
// 'Github Authentication' => '',
// 'Help on Github authentication' => '',
// 'Channel/Group/User (Optional)' => '',
// 'Lead time: ' => '',
// 'Cycle time: ' => '',
// 'Time spent into each column' => '',

View File

@@ -697,15 +697,9 @@ return array(
'Reference currency' => 'Referensi mata uang',
'The currency rate have been added successfully.' => 'Nilai tukar mata uang berhasil ditambahkan.',
'Unable to add this currency rate.' => 'Tidak dapat menambahkan nilai tukar mata uang',
'Send notifications to a Slack channel' => 'Kirim pemberitahuan ke saluran Slack',
'Webhook URL' => 'URL webhook',
'Help on Slack integration' => 'Bantuan pada integrasi Slack',
'%s remove the assignee of the task %s' => '%s menghapus penugasan dari tugas %s',
'Send notifications to Hipchat' => 'Kirim pemberitahuan ke Hipchat',
'API URL' => 'API URL',
'Room API ID or name' => 'Id kamar API atau nama ',
'Room notification token' => 'Token notifikasi kamar',
'Help on Hipchat integration' => 'Bantuan pada integrasi Hipchat',
'Enable Gravatar images' => 'Mengaktifkan gambar Gravatar',
'Information' => 'Informasi',
'Check two factor authentication code' => 'Cek dua faktor kode otentifikasi',
@@ -768,14 +762,6 @@ return array(
'When task is moved from first column' => 'Ketika tugas dipindahkan dari kolom pertama',
'When task is moved to last column' => 'Ketika tugas dipindahkan ke kolom terakhir',
'Year(s)' => 'Tahun',
'Jabber (XMPP)' => 'Jabber (XMPP)',
'Send notifications to Jabber' => 'Kirim pemberitahuan ke Jabber',
'XMPP server address' => 'alamat server XMPP',
'Jabber domain' => 'Domain Jabber',
'Jabber nickname' => 'Nickname Jabber',
'Multi-user chat room' => 'Multi-pengguna kamar obrolan',
'Help on Jabber integration' => 'Bantuan pada integrasi Jabber',
'The server address must use this format: "tcp://hostname:5222"' => 'Alamat server harus menggunakan format ini : « tcp://hostname:5222 »',
'Calendar settings' => 'Pengaturan kalender',
'Project calendar view' => 'Tampilan kalender proyek',
'Project settings' => 'Pengaturan proyek',
@@ -820,8 +806,6 @@ return array(
'%s moved the task #%d to the swimlane "%s"' => '%s memindahkan tugas n°%d ke swimlane « %s »',
'Swimlane' => 'Swimlane',
'Gravatar' => 'Gravatar',
'Hipchat' => 'Hipchat',
'Slack' => 'Slack',
'%s moved the task %s to the first swimlane' => '%s memindahkan tugas %s ke swimlane pertama',
'%s moved the task %s to the swimlane "%s"' => '%s memindahkan tugas %s ke swimlane « %s »',
'This report contains all subtasks information for the given date range.' => 'Laporan ini berisi semua informasi subtugas untuk rentang tanggal tertentu.',
@@ -918,7 +902,6 @@ return array(
'Help on Google authentication' => 'Bantuan pada otentifikasi Google',
'Github Authentication' => 'Otentifikasi Github',
'Help on Github authentication' => 'Bantuan pada otentifikasi Github',
'Channel/Group/User (Optional)' => 'Kanal/Grup/Pengguna (pilihan)',
'Lead time: ' => 'Lead time : ',
'Cycle time: ' => 'Siklus waktu : ',
'Time spent into each column' => 'Waktu yang dihabiskan di setiap kolom',

View File

@@ -697,15 +697,9 @@ return array(
'Reference currency' => 'Valuta di riferimento',
'The currency rate have been added successfully.' => 'Il tasso di cambio è stato aggiunto con successo.',
'Unable to add this currency rate.' => 'Impossibile aggiungere questo tasso di cambio.',
'Send notifications to a Slack channel' => 'Invia notifiche al canale Slack',
'Webhook URL' => 'URL Webhook',
'Help on Slack integration' => 'Guida all\'integrazione con Slack',
'%s remove the assignee of the task %s' => '%s rimuove l\'assegnatario del compito %s',
'Send notifications to Hipchat' => 'Invia notifiche a Hipchat',
'API URL' => 'URL API',
'Room API ID or name' => 'Nome o ID API della Room',
'Room notification token' => 'Token per le notifiche della Room',
'Help on Hipchat integration' => 'Guida all\'integrazione di Hipchat',
'Enable Gravatar images' => 'Abilita immagini Gravatar',
'Information' => 'Informazioni',
'Check two factor authentication code' => 'Controlla il codice di autenticazione a due fattori',
@@ -768,14 +762,6 @@ return array(
// 'When task is moved from first column' => '',
// 'When task is moved to last column' => '',
// 'Year(s)' => '',
// 'Jabber (XMPP)' => '',
// 'Send notifications to Jabber' => '',
// 'XMPP server address' => '',
// 'Jabber domain' => '',
// 'Jabber nickname' => '',
// 'Multi-user chat room' => '',
// 'Help on Jabber integration' => '',
// 'The server address must use this format: "tcp://hostname:5222"' => '',
// 'Calendar settings' => '',
// 'Project calendar view' => '',
// 'Project settings' => '',
@@ -820,8 +806,6 @@ return array(
// '%s moved the task #%d to the swimlane "%s"' => '',
// 'Swimlane' => '',
// 'Gravatar' => '',
// 'Hipchat' => '',
// 'Slack' => '',
// '%s moved the task %s to the first swimlane' => '',
// '%s moved the task %s to the swimlane "%s"' => '',
// 'This report contains all subtasks information for the given date range.' => '',
@@ -918,7 +902,6 @@ return array(
// 'Help on Google authentication' => '',
// 'Github Authentication' => '',
// 'Help on Github authentication' => '',
// 'Channel/Group/User (Optional)' => '',
// 'Lead time: ' => '',
// 'Cycle time: ' => '',
// 'Time spent into each column' => '',

View File

@@ -697,15 +697,9 @@ return array(
'Reference currency' => '基軸通貨',
// 'The currency rate have been added successfully.' => '',
'Unable to add this currency rate.' => 'この通貨レートを追加できません。',
'Send notifications to a Slack channel' => 'Slack チャンネルに通知を送信',
'Webhook URL' => 'Webhook URL',
'Help on Slack integration' => 'Slack 連携のヘルプ',
'%s remove the assignee of the task %s' => '%s がタスク「%s」の担当を解除しました。',
'Send notifications to Hipchat' => 'Hipchat に通知を送信',
'API URL' => 'API URL',
'Room API ID or name' => 'Room API ID または名前',
'Room notification token' => 'Room 通知トークン',
'Help on Hipchat integration' => 'Hipchat 連携のヘルプ',
'Enable Gravatar images' => 'Gravatar イメージを有効化',
'Information' => '情報 ',
'Check two factor authentication code' => '2 段認証をチェックする',
@@ -768,14 +762,6 @@ return array(
// 'When task is moved from first column' => '',
// 'When task is moved to last column' => '',
// 'Year(s)' => '',
// 'Jabber (XMPP)' => '',
// 'Send notifications to Jabber' => '',
// 'XMPP server address' => '',
// 'Jabber domain' => '',
// 'Jabber nickname' => '',
// 'Multi-user chat room' => '',
// 'Help on Jabber integration' => '',
// 'The server address must use this format: "tcp://hostname:5222"' => '',
// 'Calendar settings' => '',
// 'Project calendar view' => '',
// 'Project settings' => '',
@@ -820,8 +806,6 @@ return array(
// '%s moved the task #%d to the swimlane "%s"' => '',
// 'Swimlane' => '',
// 'Gravatar' => '',
// 'Hipchat' => '',
// 'Slack' => '',
// '%s moved the task %s to the first swimlane' => '',
// '%s moved the task %s to the swimlane "%s"' => '',
// 'This report contains all subtasks information for the given date range.' => '',
@@ -918,7 +902,6 @@ return array(
// 'Help on Google authentication' => '',
// 'Github Authentication' => '',
// 'Help on Github authentication' => '',
// 'Channel/Group/User (Optional)' => '',
// 'Lead time: ' => '',
// 'Cycle time: ' => '',
// 'Time spent into each column' => '',

View File

@@ -697,15 +697,9 @@ return array(
// 'Reference currency' => '',
// 'The currency rate have been added successfully.' => '',
// 'Unable to add this currency rate.' => '',
// 'Send notifications to a Slack channel' => '',
// 'Webhook URL' => '',
// 'Help on Slack integration' => '',
// '%s remove the assignee of the task %s' => '',
// 'Send notifications to Hipchat' => '',
// 'API URL' => '',
// 'Room API ID or name' => '',
// 'Room notification token' => '',
// 'Help on Hipchat integration' => '',
// 'Enable Gravatar images' => '',
// 'Information' => '',
// 'Check two factor authentication code' => '',
@@ -768,14 +762,6 @@ return array(
'When task is moved from first column' => 'Når oppgaven er flyttet fra første kolon',
'When task is moved to last column' => 'Når oppgaven er flyttet til siste kolonne',
'Year(s)' => 'år',
// 'Jabber (XMPP)' => '',
// 'Send notifications to Jabber' => '',
// 'XMPP server address' => '',
// 'Jabber domain' => '',
// 'Jabber nickname' => '',
// 'Multi-user chat room' => '',
// 'Help on Jabber integration' => '',
// 'The server address must use this format: "tcp://hostname:5222"' => '',
'Calendar settings' => 'Kalenderinstillinger',
'Project calendar view' => 'Visning prosjektkalender',
'Project settings' => 'Prosjektinnstillinger',
@@ -820,8 +806,6 @@ return array(
// '%s moved the task #%d to the swimlane "%s"' => '',
'Swimlane' => 'Svømmebane',
// 'Gravatar' => '',
// 'Hipchat' => '',
// 'Slack' => '',
// '%s moved the task %s to the first swimlane' => '',
// '%s moved the task %s to the swimlane "%s"' => '',
// 'This report contains all subtasks information for the given date range.' => '',
@@ -918,7 +902,6 @@ return array(
// 'Help on Google authentication' => '',
// 'Github Authentication' => '',
// 'Help on Github authentication' => '',
// 'Channel/Group/User (Optional)' => '',
// 'Lead time: ' => '',
// 'Cycle time: ' => '',
// 'Time spent into each column' => '',

View File

@@ -697,15 +697,9 @@ return array(
// 'Reference currency' => '',
// 'The currency rate have been added successfully.' => '',
// 'Unable to add this currency rate.' => '',
// 'Send notifications to a Slack channel' => '',
// 'Webhook URL' => '',
// 'Help on Slack integration' => '',
// '%s remove the assignee of the task %s' => '',
// 'Send notifications to Hipchat' => '',
// 'API URL' => '',
// 'Room API ID or name' => '',
// 'Room notification token' => '',
// 'Help on Hipchat integration' => '',
// 'Enable Gravatar images' => '',
// 'Information' => '',
// 'Check two factor authentication code' => '',
@@ -768,14 +762,6 @@ return array(
// 'When task is moved from first column' => '',
// 'When task is moved to last column' => '',
// 'Year(s)' => '',
// 'Jabber (XMPP)' => '',
// 'Send notifications to Jabber' => '',
// 'XMPP server address' => '',
// 'Jabber domain' => '',
// 'Jabber nickname' => '',
// 'Multi-user chat room' => '',
// 'Help on Jabber integration' => '',
// 'The server address must use this format: "tcp://hostname:5222"' => '',
// 'Calendar settings' => '',
// 'Project calendar view' => '',
// 'Project settings' => '',
@@ -820,8 +806,6 @@ return array(
// '%s moved the task #%d to the swimlane "%s"' => '',
// 'Swimlane' => '',
// 'Gravatar' => '',
// 'Hipchat' => '',
// 'Slack' => '',
// '%s moved the task %s to the first swimlane' => '',
// '%s moved the task %s to the swimlane "%s"' => '',
// 'This report contains all subtasks information for the given date range.' => '',
@@ -918,7 +902,6 @@ return array(
// 'Help on Google authentication' => '',
// 'Github Authentication' => '',
// 'Help on Github authentication' => '',
// 'Channel/Group/User (Optional)' => '',
// 'Lead time: ' => '',
// 'Cycle time: ' => '',
// 'Time spent into each column' => '',

View File

@@ -697,15 +697,9 @@ return array(
'Reference currency' => 'Waluta referencyjna',
'The currency rate have been added successfully.' => 'Dodano kurs waluty',
'Unable to add this currency rate.' => 'Nie można dodać kursu waluty',
// 'Send notifications to a Slack channel' => '',
// 'Webhook URL' => '',
// 'Help on Slack integration' => '',
'%s remove the assignee of the task %s' => '%s usunął osobę przypisaną do zadania %s',
// 'Send notifications to Hipchat' => '',
// 'API URL' => '',
// 'Room API ID or name' => '',
// 'Room notification token' => '',
// 'Help on Hipchat integration' => '',
// 'Enable Gravatar images' => '',
'Information' => 'Informacje',
'Check two factor authentication code' => 'Sprawdź kod weryfikujący',
@@ -768,14 +762,6 @@ return array(
'When task is moved from first column' => 'Przeniesienie zadania z pierwszej kolumny',
'When task is moved to last column' => 'Przeniesienie zadania do ostatniej kolumny',
'Year(s)' => 'Lat',
// 'Jabber (XMPP)' => '',
// 'Send notifications to Jabber' => '',
// 'XMPP server address' => '',
// 'Jabber domain' => '',
// 'Jabber nickname' => '',
// 'Multi-user chat room' => '',
// 'Help on Jabber integration' => '',
// 'The server address must use this format: "tcp://hostname:5222"' => '',
'Calendar settings' => 'Ustawienia kalendarza',
'Project calendar view' => 'Widok kalendarza projektu',
'Project settings' => 'Ustawienia Projektu',
@@ -820,8 +806,6 @@ return array(
// '%s moved the task #%d to the swimlane "%s"' => '',
// 'Swimlane' => '',
// 'Gravatar' => '',
// 'Hipchat' => '',
// 'Slack' => '',
// '%s moved the task %s to the first swimlane' => '',
// '%s moved the task %s to the swimlane "%s"' => '',
// 'This report contains all subtasks information for the given date range.' => '',
@@ -918,7 +902,6 @@ return array(
// 'Help on Google authentication' => '',
// 'Github Authentication' => '',
// 'Help on Github authentication' => '',
// 'Channel/Group/User (Optional)' => '',
// 'Lead time: ' => '',
// 'Cycle time: ' => '',
// 'Time spent into each column' => '',

View File

@@ -697,15 +697,9 @@ return array(
'Reference currency' => 'Moeda de Referência',
'The currency rate have been added successfully.' => 'A taxa de câmbio foi adicionada com sucesso.',
'Unable to add this currency rate.' => 'Impossível de adicionar essa taxa de câmbio.',
'Send notifications to a Slack channel' => 'Enviar as notificações em um canal Slack',
'Webhook URL' => 'URL do webhook',
'Help on Slack integration' => 'Ajuda sobre integração com o Slack',
'%s remove the assignee of the task %s' => '%s removeu a pessoa designada para a tarefa %s',
'Send notifications to Hipchat' => 'Enviar as notificações para o Hipchat',
'API URL' => 'URL da API',
'Room API ID or name' => 'Nome ou ID da sala de discussão',
'Room notification token' => 'Código de segurança da sala de discussão',
'Help on Hipchat integration' => 'Ajuda sobre integração com o Hipchat',
'Enable Gravatar images' => 'Ativar imagens do Gravatar',
'Information' => 'Informações',
'Check two factor authentication code' => 'Verificação do código de autenticação à fator duplo',
@@ -768,14 +762,6 @@ return array(
'When task is moved from first column' => 'Quando a tarefa é movida fora da primeira coluna',
'When task is moved to last column' => 'Quando a tarefa é movida para a última coluna',
'Year(s)' => 'Ano(s)',
'Jabber (XMPP)' => 'Jabber (XMPP)',
'Send notifications to Jabber' => 'Enviar notificações para o Jabber',
'XMPP server address' => 'Endereço do servidor XMPP',
'Jabber domain' => 'Nome de domínio Jabber',
'Jabber nickname' => 'Apelido Jabber',
'Multi-user chat room' => 'Sala de chat multi-usuário',
'Help on Jabber integration' => 'Ajuda sobre integração com o Jabber',
'The server address must use this format: "tcp://hostname:5222"' => 'O endereço do servidor deve usar o seguinte formato: "tcp://hostname:5222"',
'Calendar settings' => 'Configurações do calendário',
'Project calendar view' => 'Vista em modo projeto do calendário',
'Project settings' => 'Configurações dos projetos',
@@ -820,8 +806,6 @@ return array(
'%s moved the task #%d to the swimlane "%s"' => '%s moveu a tarefa #%d para a swimlane "%s"',
'Swimlane' => 'Swimlane',
'Gravatar' => 'Gravatar',
'Hipchat' => 'Hipchat',
'Slack' => 'Slack',
'%s moved the task %s to the first swimlane' => '%s moveu a tarefa %s para a primeira swimlane',
'%s moved the task %s to the swimlane "%s"' => '%s moveu a tarefa %s para a swimlane "%s"',
'This report contains all subtasks information for the given date range.' => 'Este relatório contém informações de todas as sub-tarefas para o período selecionado.',
@@ -918,7 +902,6 @@ return array(
'Help on Google authentication' => 'Ajuda com a autenticação Google',
'Github Authentication' => 'Autenticação Github',
'Help on Github authentication' => 'Ajuda com a autenticação Github',
'Channel/Group/User (Optional)' => 'Canal/Grupo/Utilizador (Opcional)',
'Lead time: ' => 'Lead time: ',
'Cycle time: ' => 'Cycle time: ',
'Time spent into each column' => 'Tempo gasto em cada coluna',

View File

@@ -697,15 +697,9 @@ return array(
'Reference currency' => 'Moeda de Referência',
'The currency rate have been added successfully.' => 'A taxa de câmbio foi adicionada com sucesso.',
'Unable to add this currency rate.' => 'Impossível adicionar essa taxa de câmbio.',
'Send notifications to a Slack channel' => 'Enviar as notificações por canal Slack',
'Webhook URL' => 'URL do webhook',
'Help on Slack integration' => 'Ajuda na integração com o Slack',
'%s remove the assignee of the task %s' => '%s removeu a pessoa assignada à tarefa %s',
'Send notifications to Hipchat' => 'Enviar as notificações para o Hipchat',
'API URL' => 'URL da API',
'Room API ID or name' => 'Nome ou ID da sala de discussão',
'Room notification token' => 'Código de segurança da sala de discussão',
'Help on Hipchat integration' => 'Ajuda na integração com o Hipchat',
'Enable Gravatar images' => 'Activar imagem Gravatar',
'Information' => 'Informações',
'Check two factor authentication code' => 'Verificação do código de autenticação com factor duplo',
@@ -768,14 +762,6 @@ return array(
'When task is moved from first column' => 'Quando a tarefa é movida fora da primeira coluna',
'When task is moved to last column' => 'Quando a tarefa é movida para a última coluna',
'Year(s)' => 'Ano(s)',
'Jabber (XMPP)' => 'Jabber (XMPP)',
'Send notifications to Jabber' => 'Enviar notificações para o Jabber',
'XMPP server address' => 'Endereço do servidor XMPP',
'Jabber domain' => 'Nome de domínio Jabber',
'Jabber nickname' => 'Apelido Jabber',
'Multi-user chat room' => 'Sala de chat multi-utilizador',
'Help on Jabber integration' => 'Ajuda na integração com Jabber',
'The server address must use this format: "tcp://hostname:5222"' => 'O endereço do servidor deve usar o seguinte formato: "tcp://hostname:5222"',
'Calendar settings' => 'Configurações do calendário',
'Project calendar view' => 'Vista em modo projecto do calendário',
'Project settings' => 'Configurações dos projectos',
@@ -820,8 +806,6 @@ return array(
'%s moved the task #%d to the swimlane "%s"' => '%s moveu a tarefa n° %d no swimlane "%s"',
'Swimlane' => 'Swimlane',
'Gravatar' => 'Gravatar',
'Hipchat' => 'Hipchat',
'Slack' => 'Slack',
'%s moved the task %s to the first swimlane' => '%s moveu a tarefa %s no primeiro swimlane',
'%s moved the task %s to the swimlane "%s"' => '%s moveu a tarefa %s no swimlane "%s"',
'This report contains all subtasks information for the given date range.' => 'Este relatório contém informações de todas as sub-tarefas para o período selecionado.',
@@ -918,7 +902,6 @@ return array(
'Help on Google authentication' => 'Ajuda com autenticação Google',
'Github Authentication' => 'Autenticação Github',
'Help on Github authentication' => 'Ajuda com autenticação Github',
'Channel/Group/User (Optional)' => 'Canal/Grupo/Utilizador (Opcional)',
'Lead time: ' => 'Tempo de Espera: ',
'Cycle time: ' => 'Tempo de Ciclo: ',
'Time spent into each column' => 'Tempo gasto em cada coluna',

View File

@@ -697,15 +697,9 @@ return array(
'Reference currency' => 'Справочник валют',
'The currency rate have been added successfully.' => 'Курс валюты был успешно добавлен.',
'Unable to add this currency rate.' => 'Невозможно добавить этот курс валюты.',
'Send notifications to a Slack channel' => 'Отправлять уведомления в канал Slack',
'Webhook URL' => 'Webhook URL',
'Help on Slack integration' => 'Помощь по интеграции Slack',
'%s remove the assignee of the task %s' => '%s удалить назначенную задачу %s',
'Send notifications to Hipchat' => 'Отправлять уведомления в Hipchat',
'API URL' => 'API URL',
'Room API ID or name' => 'API ID комнаты или имя',
'Room notification token' => 'Ключь комнаты для уведомлений',
'Help on Hipchat integration' => 'Помощь по интеграции Hipchat',
'Enable Gravatar images' => 'Включить Gravatar изображения',
'Information' => 'Информация',
'Check two factor authentication code' => 'Проверка кода двухфакторной авторизации',
@@ -768,14 +762,6 @@ return array(
'When task is moved from first column' => 'Когда задача перемещается из первой колонки',
'When task is moved to last column' => 'Когда задача перемещается в последнюю колонку',
'Year(s)' => 'Год(а)',
'Jabber (XMPP)' => 'Jabber (XMPP)',
'Send notifications to Jabber' => 'Отправлять уведомления в Jabber',
'XMPP server address' => 'Адрес Jabber сервера',
'Jabber domain' => 'Домен Jabber',
'Jabber nickname' => 'Имя пользователя Jabber',
'Multi-user chat room' => 'Многопользовательский чат',
'Help on Jabber integration' => 'Помощь по интеграции Jabber',
'The server address must use this format: "tcp://hostname:5222"' => 'Адрес сервера должен быть в формате: tcp://hostname:5222',
'Calendar settings' => 'Настройки календаря',
'Project calendar view' => 'Вид календаря проекта',
'Project settings' => 'Настройки проекта',
@@ -820,8 +806,6 @@ return array(
'%s moved the task #%d to the swimlane "%s"' => '%s задач перемещено #%d в дорожке "%s"',
'Swimlane' => 'Дорожки',
'Gravatar' => 'Граватар',
'Hipchat' => 'Hipchat',
'Slack' => 'Slack',
// '%s moved the task %s to the first swimlane' => '',
// '%s moved the task %s to the swimlane "%s"' => '',
'This report contains all subtasks information for the given date range.' => 'Этот отчет содержит всю информацию подзадач в заданном диапазоне дат.',
@@ -918,7 +902,6 @@ return array(
'Help on Google authentication' => 'Помощь в авторизации Google',
'Github Authentication' => 'Авторизация Github',
'Help on Github authentication' => 'Помощь в авторизации Github',
'Channel/Group/User (Optional)' => 'Канал/Группа/Пользователь (опционально)',
'Lead time: ' => 'Время выполнения:',
'Cycle time: ' => 'Время цикла:',
'Time spent into each column' => 'Время, проведенное в каждой колонке',

View File

@@ -697,15 +697,9 @@ return array(
// 'Reference currency' => '',
// 'The currency rate have been added successfully.' => '',
// 'Unable to add this currency rate.' => '',
// 'Send notifications to a Slack channel' => '',
// 'Webhook URL' => '',
// 'Help on Slack integration' => '',
// '%s remove the assignee of the task %s' => '',
// 'Send notifications to Hipchat' => '',
// 'API URL' => '',
// 'Room API ID or name' => '',
// 'Room notification token' => '',
// 'Help on Hipchat integration' => '',
// 'Enable Gravatar images' => '',
// 'Information' => '',
// 'Check two factor authentication code' => '',
@@ -768,14 +762,6 @@ return array(
// 'When task is moved from first column' => '',
// 'When task is moved to last column' => '',
// 'Year(s)' => '',
// 'Jabber (XMPP)' => '',
// 'Send notifications to Jabber' => '',
// 'XMPP server address' => '',
// 'Jabber domain' => '',
// 'Jabber nickname' => '',
// 'Multi-user chat room' => '',
// 'Help on Jabber integration' => '',
// 'The server address must use this format: "tcp://hostname:5222"' => '',
// 'Calendar settings' => '',
// 'Project calendar view' => '',
// 'Project settings' => '',
@@ -820,8 +806,6 @@ return array(
// '%s moved the task #%d to the swimlane "%s"' => '',
// 'Swimlane' => '',
// 'Gravatar' => '',
// 'Hipchat' => '',
// 'Slack' => '',
// '%s moved the task %s to the first swimlane' => '',
// '%s moved the task %s to the swimlane "%s"' => '',
// 'This report contains all subtasks information for the given date range.' => '',
@@ -918,7 +902,6 @@ return array(
// 'Help on Google authentication' => '',
// 'Github Authentication' => '',
// 'Help on Github authentication' => '',
// 'Channel/Group/User (Optional)' => '',
// 'Lead time: ' => '',
// 'Cycle time: ' => '',
// 'Time spent into each column' => '',

View File

@@ -697,15 +697,9 @@ return array(
'Reference currency' => 'Referensvaluta',
'The currency rate have been added successfully.' => 'Valutakursen har lagts till.',
'Unable to add this currency rate.' => 'Kunde inte lägga till valutakursen.',
'Send notifications to a Slack channel' => 'Skicka notiser till en Slack kanal',
'Webhook URL' => 'Webhook URL',
'Help on Slack integration' => 'Hjälp för Slack integration',
'%s remove the assignee of the task %s' => '%s ta bort tilldelningen av uppgiften %s',
'Send notifications to Hipchat' => 'Skicka notiser till Hipchat',
'API URL' => 'API URL',
'Room API ID or name' => 'Room API ID eller namn',
'Room notification token' => 'Room notistoken',
'Help on Hipchat integration' => 'Hjälp för Hipchat integration',
'Enable Gravatar images' => 'Aktivera Gravatar bilder',
'Information' => 'Information',
'Check two factor authentication code' => 'Kolla tvåfaktorsverifieringskod',
@@ -768,14 +762,6 @@ return array(
'When task is moved from first column' => 'När uppgiften flyttas från första kolumnen',
'When task is moved to last column' => 'När uppgiften flyttas till sista kolumnen',
'Year(s)' => 'År',
'Jabber (XMPP)' => 'Jabber (XMPP)',
'Send notifications to Jabber' => 'Skicka notiser till Jabber',
'XMPP server address' => 'XMPP serveradress',
'Jabber domain' => 'Jabber domän',
'Jabber nickname' => 'Jabber smeknamn',
'Multi-user chat room' => 'Multi-user chatrum',
'Help on Jabber integration' => 'Hjälp för Jabber integration',
'The server address must use this format: "tcp://hostname:5222"' => 'Serveradressen måste använda detta format: "tcp://hostname:5222"',
'Calendar settings' => 'Inställningar för kalendern',
'Project calendar view' => 'Projektkalendervy',
'Project settings' => 'Projektinställningar',
@@ -820,8 +806,6 @@ return array(
'%s moved the task #%d to the swimlane "%s"' => '%s flyttade uppgiften #%d till swimlane "%s"',
'Swimlane' => 'Swimlane',
'Gravatar' => 'Gravatar',
'Hipchat' => 'Hipchat',
'Slack' => 'Slack',
'%s moved the task %s to the first swimlane' => '%s flyttade uppgiften %s till första swimlane',
'%s moved the task %s to the swimlane "%s"' => '%s flyttade uppgiften %s till swimlane "%s"',
'This report contains all subtasks information for the given date range.' => 'Denna rapport innehåller all deluppgiftsinformation för det givna datumintervallet.',
@@ -918,7 +902,6 @@ return array(
'Help on Google authentication' => 'Hjälp för Google autentisering',
'Github Authentication' => 'Github autentisering',
'Help on Github authentication' => 'Hjälp för Github autentisering',
'Channel/Group/User (Optional)' => 'Kanal/Grupp/Användare (valfri)',
'Lead time: ' => 'Ledtid',
'Cycle time: ' => 'Cykeltid',
'Time spent into each column' => 'Tidsåtgång per kolumn',

View File

@@ -697,15 +697,9 @@ return array(
// 'Reference currency' => '',
// 'The currency rate have been added successfully.' => '',
// 'Unable to add this currency rate.' => '',
'Send notifications to a Slack channel' => 'ส่งการแจ้งเตือนไปทาง Slack channel',
// 'Webhook URL' => '',
// 'Help on Slack integration' => '',
// '%s remove the assignee of the task %s' => '',
// 'Send notifications to Hipchat' => '',
// 'API URL' => '',
// 'Room API ID or name' => '',
// 'Room notification token' => '',
// 'Help on Hipchat integration' => '',
'Enable Gravatar images' => 'สามารถใช้งานภาพ Gravatar',
'Information' => 'ข้อมูลสารสนเทศ',
// 'Check two factor authentication code' => '',
@@ -768,14 +762,6 @@ return array(
'When task is moved from first column' => 'เมื่องานถูกย้ายจากคอลัมน์แรก',
'When task is moved to last column' => 'เมื่องานถูกย้ายไปคอลัมน์สุดท้าย',
'Year(s)' => 'ปี',
// 'Jabber (XMPP)' => '',
// 'Send notifications to Jabber' => '',
// 'XMPP server address' => '',
// 'Jabber domain' => '',
// 'Jabber nickname' => '',
// 'Multi-user chat room' => '',
// 'Help on Jabber integration' => '',
// 'The server address must use this format: "tcp://hostname:5222"' => '',
'Calendar settings' => 'ตั้งค่าปฏิทิน',
'Project calendar view' => 'มุมมองปฏิทินของโปรเจค',
'Project settings' => 'ตั้งค่าโปรเจค',
@@ -820,8 +806,6 @@ return array(
// '%s moved the task #%d to the swimlane "%s"' => '',
// 'Swimlane' => '',
// 'Gravatar' => '',
// 'Hipchat' => '',
// 'Slack' => '',
// '%s moved the task %s to the first swimlane' => '',
// '%s moved the task %s to the swimlane "%s"' => '',
// 'This report contains all subtasks information for the given date range.' => '',
@@ -918,7 +902,6 @@ return array(
// 'Help on Google authentication' => '',
// 'Github Authentication' => '',
// 'Help on Github authentication' => '',
// 'Channel/Group/User (Optional)' => '',
// 'Lead time: ' => '',
// 'Cycle time: ' => '',
// 'Time spent into each column' => '',

View File

@@ -697,15 +697,9 @@ return array(
// 'Reference currency' => '',
// 'The currency rate have been added successfully.' => '',
// 'Unable to add this currency rate.' => '',
// 'Send notifications to a Slack channel' => '',
// 'Webhook URL' => '',
// 'Help on Slack integration' => '',
// '%s remove the assignee of the task %s' => '',
// 'Send notifications to Hipchat' => '',
// 'API URL' => '',
// 'Room API ID or name' => '',
// 'Room notification token' => '',
// 'Help on Hipchat integration' => '',
// 'Enable Gravatar images' => '',
// 'Information' => '',
// 'Check two factor authentication code' => '',
@@ -768,14 +762,6 @@ return array(
// 'When task is moved from first column' => '',
// 'When task is moved to last column' => '',
// 'Year(s)' => '',
// 'Jabber (XMPP)' => '',
// 'Send notifications to Jabber' => '',
// 'XMPP server address' => '',
// 'Jabber domain' => '',
// 'Jabber nickname' => '',
// 'Multi-user chat room' => '',
// 'Help on Jabber integration' => '',
// 'The server address must use this format: "tcp://hostname:5222"' => '',
// 'Calendar settings' => '',
// 'Project calendar view' => '',
// 'Project settings' => '',
@@ -820,8 +806,6 @@ return array(
// '%s moved the task #%d to the swimlane "%s"' => '',
// 'Swimlane' => '',
// 'Gravatar' => '',
// 'Hipchat' => '',
// 'Slack' => '',
// '%s moved the task %s to the first swimlane' => '',
// '%s moved the task %s to the swimlane "%s"' => '',
// 'This report contains all subtasks information for the given date range.' => '',
@@ -918,7 +902,6 @@ return array(
// 'Help on Google authentication' => '',
// 'Github Authentication' => '',
// 'Help on Github authentication' => '',
// 'Channel/Group/User (Optional)' => '',
// 'Lead time: ' => '',
// 'Cycle time: ' => '',
// 'Time spent into each column' => '',

View File

@@ -697,15 +697,9 @@ return array(
'Reference currency' => '参考货币',
'The currency rate have been added successfully.' => '成功添加汇率。',
'Unable to add this currency rate.' => '无法添加此汇率',
'Send notifications to a Slack channel' => '发送通知到 Slack 频道',
'Webhook URL' => '网络钩子 URL',
'Help on Slack integration' => 'Slack 整合帮助',
'%s remove the assignee of the task %s' => '%s删除了任务%s的负责人',
'Send notifications to Hipchat' => '发送通知到 Hipchat',
'API URL' => 'API URL',
'Room API ID or name' => '房间 API ID 或名称',
'Room notification token' => '房间通知令牌',
'Help on Hipchat integration' => 'Hipchat 整合帮助',
'Enable Gravatar images' => '启用 Gravatar 图像',
'Information' => '信息',
'Check two factor authentication code' => '检查双重认证码',
@@ -768,14 +762,6 @@ return array(
// 'When task is moved from first column' => '',
// 'When task is moved to last column' => '',
// 'Year(s)' => '',
// 'Jabber (XMPP)' => '',
// 'Send notifications to Jabber' => '',
// 'XMPP server address' => '',
// 'Jabber domain' => '',
// 'Jabber nickname' => '',
'Multi-user chat room' => '多用户聊天室',
// 'Help on Jabber integration' => '',
// 'The server address must use this format: "tcp://hostname:5222"' => '',
'Calendar settings' => '日程设置',
// 'Project calendar view' => '',
'Project settings' => '项目设置',
@@ -820,8 +806,6 @@ return array(
// '%s moved the task #%d to the swimlane "%s"' => '',
// 'Swimlane' => '',
// 'Gravatar' => '',
// 'Hipchat' => '',
// 'Slack' => '',
// '%s moved the task %s to the first swimlane' => '',
// '%s moved the task %s to the swimlane "%s"' => '',
// 'This report contains all subtasks information for the given date range.' => '',
@@ -918,7 +902,6 @@ return array(
// 'Help on Google authentication' => '',
// 'Github Authentication' => '',
// 'Help on Github authentication' => '',
// 'Channel/Group/User (Optional)' => '',
// 'Lead time: ' => '',
// 'Cycle time: ' => '',
// 'Time spent into each column' => '',

View File

@@ -64,7 +64,7 @@ class Acl extends Base
'column' => '*',
'export' => '*',
'taskimport' => '*',
'project' => array('edit', 'update', 'share', 'integration', 'users', 'alloweverybody', 'allow', 'setowner', 'revoke', 'duplicate', 'disable', 'enable'),
'project' => array('edit', 'update', 'share', 'integrations', 'notifications', 'users', 'alloweverybody', 'allow', 'setowner', 'revoke', 'duplicate', 'disable', 'enable'),
'swimlane' => '*',
'gantt' => array('project', 'savetaskdate', 'task', 'savetask'),
);

142
app/Model/Notification.php Normal file
View File

@@ -0,0 +1,142 @@
<?php
namespace Kanboard\Model;
/**
* Notification
*
* @package model
* @author Frederic Guillot
*/
class Notification extends Base
{
/**
* Get the event title with author
*
* @access public
* @param string $event_author
* @param string $event_name
* @param array $event_data
* @return string
*/
public function getTitleWithAuthor($event_author, $event_name, array $event_data)
{
switch ($event_name) {
case Task::EVENT_ASSIGNEE_CHANGE:
$assignee = $event_data['task']['assignee_name'] ?: $event_data['task']['assignee_username'];
if (! empty($assignee)) {
return e('%s change the assignee of the task #%d to %s', $event_author, $event_data['task']['id'], $assignee);
}
return e('%s remove the assignee of the task %s', $event_author, e('#%d', $event_data['task']['id']));
case Task::EVENT_UPDATE:
return e('%s updated the task #%d', $event_author, $event_data['task']['id']);
case Task::EVENT_CREATE:
return e('%s created the task #%d', $event_author, $event_data['task']['id']);
case Task::EVENT_CLOSE:
return e('%s closed the task #%d', $event_author, $event_data['task']['id']);
case Task::EVENT_OPEN:
return e('%s open the task #%d', $event_author, $event_data['task']['id']);
case Task::EVENT_MOVE_COLUMN:
return e(
'%s moved the task #%d to the column "%s"',
$event_author,
$event_data['task']['id'],
$event_data['task']['column_title']
);
case Task::EVENT_MOVE_POSITION:
return e(
'%s moved the task #%d to the position %d in the column "%s"',
$event_author,
$event_data['task']['id'],
$event_data['task']['position'],
$event_data['task']['column_title']
);
case Task::EVENT_MOVE_SWIMLANE:
if ($event_data['task']['swimlane_id'] == 0) {
return e('%s moved the task #%d to the first swimlane', $event_author, $event_data['task']['id']);
}
return e(
'%s moved the task #%d to the swimlane "%s"',
$event_author,
$event_data['task']['id'],
$event_data['task']['swimlane_name']
);
case Subtask::EVENT_UPDATE:
return e('%s updated a subtask for the task #%d', $event_author, $event_data['task']['id']);
case Subtask::EVENT_CREATE:
return e('%s created a subtask for the task #%d', $event_author, $event_data['task']['id']);
case Comment::EVENT_UPDATE:
return e('%s updated a comment on the task #%d', $event_author, $event_data['task']['id']);
case Comment::EVENT_CREATE:
return e('%s commented on the task #%d', $event_author, $event_data['task']['id']);
case File::EVENT_CREATE:
return e('%s attached a file to the task #%d', $event_author, $event_data['task']['id']);
default:
return e('Notification');
}
}
/**
* Get the event title without author
*
* @access public
* @param string $event_name
* @param array $event_data
* @return string
*/
public function getTitleWithoutAuthor($event_name, array $event_data)
{
switch ($event_name) {
case File::EVENT_CREATE:
$title = e('New attachment on task #%d: %s', $event_data['file']['task_id'], $event_data['file']['name']);
break;
case Comment::EVENT_CREATE:
$title = e('New comment on task #%d', $event_data['comment']['task_id']);
break;
case Comment::EVENT_UPDATE:
$title = e('Comment updated on task #%d', $event_data['comment']['task_id']);
break;
case Subtask::EVENT_CREATE:
$title = e('New subtask on task #%d', $event_data['subtask']['task_id']);
break;
case Subtask::EVENT_UPDATE:
$title = e('Subtask updated on task #%d', $event_data['subtask']['task_id']);
break;
case Task::EVENT_CREATE:
$title = e('New task #%d: %s', $event_data['task']['id'], $event_data['task']['title']);
break;
case Task::EVENT_UPDATE:
$title = e('Task updated #%d', $event_data['task']['id']);
break;
case Task::EVENT_CLOSE:
$title = e('Task #%d closed', $event_data['task']['id']);
break;
case Task::EVENT_OPEN:
$title = e('Task #%d opened', $event_data['task']['id']);
break;
case Task::EVENT_MOVE_COLUMN:
$title = e('Column changed for task #%d', $event_data['task']['id']);
break;
case Task::EVENT_MOVE_POSITION:
$title = e('New position for task #%d', $event_data['task']['id']);
break;
case Task::EVENT_MOVE_SWIMLANE:
$title = e('Swimlane changed for task #%d', $event_data['task']['id']);
break;
case Task::EVENT_ASSIGNEE_CHANGE:
$title = e('Assignee changed on task #%d', $event_data['task']['id']);
break;
case Task::EVENT_OVERDUE:
$nb = count($event_data['tasks']);
$title = $nb > 1 ? e('%d overdue tasks', $nb) : e('Task #%d is overdue', $event_data['tasks'][0]['id']);
break;
default:
$title = e('Notification');
}
return $title;
}
}

View File

@@ -108,4 +108,20 @@ abstract class NotificationType extends Base
{
return $this->hiddens;
}
/**
* Keep only loaded notification types
*
* @access public
* @param string[] $types
* @return array
*/
public function filterTypes(array $types)
{
$classes = $this->classes;
return array_filter($types, function($type) use ($classes) {
return isset($classes[$type]);
});
}
}

View File

@@ -153,7 +153,7 @@ class ProjectActivity extends Base
unset($event['data']);
$event['author'] = $event['author_name'] ?: $event['author_username'];
$event['event_title'] = $this->getTitle($event);
$event['event_title'] = $this->notification->getTitleWithAuthor($event['author'], $event['event_name'], $event);
$event['event_content'] = $this->getContent($event);
}
@@ -195,56 +195,6 @@ class ProjectActivity extends Base
);
}
/**
* Get the event title (translated)
*
* @access public
* @param array $event Event properties
* @return string
*/
public function getTitle(array $event)
{
switch ($event['event_name']) {
case Task::EVENT_ASSIGNEE_CHANGE:
$assignee = $event['task']['assignee_name'] ?: $event['task']['assignee_username'];
if (! empty($assignee)) {
return t('%s change the assignee of the task #%d to %s', $event['author'], $event['task']['id'], $assignee);
}
return t('%s remove the assignee of the task %s', $event['author'], e('#%d', $event['task']['id']));
case Task::EVENT_UPDATE:
return t('%s updated the task #%d', $event['author'], $event['task']['id']);
case Task::EVENT_CREATE:
return t('%s created the task #%d', $event['author'], $event['task']['id']);
case Task::EVENT_CLOSE:
return t('%s closed the task #%d', $event['author'], $event['task']['id']);
case Task::EVENT_OPEN:
return t('%s open the task #%d', $event['author'], $event['task']['id']);
case Task::EVENT_MOVE_COLUMN:
return t('%s moved the task #%d to the column "%s"', $event['author'], $event['task']['id'], $event['task']['column_title']);
case Task::EVENT_MOVE_POSITION:
return t('%s moved the task #%d to the position %d in the column "%s"', $event['author'], $event['task']['id'], $event['task']['position'], $event['task']['column_title']);
case Task::EVENT_MOVE_SWIMLANE:
if ($event['task']['swimlane_id'] == 0) {
return t('%s moved the task #%d to the first swimlane', $event['author'], $event['task']['id']);
}
return t('%s moved the task #%d to the swimlane "%s"', $event['author'], $event['task']['id'], $event['task']['swimlane_name']);
case Subtask::EVENT_UPDATE:
return t('%s updated a subtask for the task #%d', $event['author'], $event['task']['id']);
case Subtask::EVENT_CREATE:
return t('%s created a subtask for the task #%d', $event['author'], $event['task']['id']);
case Comment::EVENT_UPDATE:
return t('%s updated a comment on the task #%d', $event['author'], $event['task']['id']);
case Comment::EVENT_CREATE:
return t('%s commented on the task #%d', $event['author'], $event['task']['id']);
case File::EVENT_CREATE:
return t('%s attached a file to the task #%d', $event['author'], $event['task']['id']);
default:
return '';
}
}
/**
* Decode event data, supports unserialize() and json_decode()
*

View File

@@ -1,66 +0,0 @@
<?php
namespace Kanboard\Model;
/**
* Project integration
*
* @package model
* @author Frederic Guillot
*/
class ProjectIntegration extends Base
{
/**
* SQL table name
*
* @var string
*/
const TABLE = 'project_integrations';
/**
* Get all parameters for a project
*
* @access public
* @param integer $project_id
* @return array
*/
public function getParameters($project_id)
{
return $this->db->table(self::TABLE)->eq('project_id', $project_id)->findOne() ?: array();
}
/**
* Save parameters for a project
*
* @access public
* @param integer $project_id
* @param array $values
* @return boolean
*/
public function saveParameters($project_id, array $values)
{
if ($this->db->table(self::TABLE)->eq('project_id', $project_id)->exists()) {
return $this->db->table(self::TABLE)->eq('project_id', $project_id)->update($values);
}
return $this->db->table(self::TABLE)->insert($values + array('project_id' => $project_id));
}
/**
* Check if a project has the given parameter/value
*
* @access public
* @param integer $project_id
* @param string $option
* @param string $value
* @return boolean
*/
public function hasValue($project_id, $option, $value)
{
return $this->db
->table(self::TABLE)
->eq('project_id', $project_id)
->eq($option, $value)
->exists();
}
}

View File

@@ -22,8 +22,44 @@ class ProjectNotification extends Base
{
$project = $this->project->getById($project_id);
foreach ($this->projectNotificationType->getSelectedTypes($project_id) as $type) {
$types = array_merge(
$this->projectNotificationType->getHiddenTypes(),
$this->projectNotificationType->getSelectedTypes($project_id)
);
foreach ($types as $type) {
$this->projectNotificationType->getType($type)->notifyProject($project, $event_name, $event_data);
}
}
/**
* Save settings for the given project
*
* @access public
* @param integer $project_id
* @param array $values
*/
public function saveSettings($project_id, array $values)
{
$this->db->startTransaction();
$types = empty($values['notification_types']) ? array() : array_keys($values['notification_types']);
$this->projectNotificationType->saveSelectedTypes($project_id, $types);
$this->db->closeTransaction();
}
/**
* Read user settings to display the form
*
* @access public
* @param integer $project_id
* @return array
*/
public function readSettings($project_id)
{
return array(
'notification_types' => $this->projectNotificationType->getSelectedTypes($project_id),
);
}
}

View File

@@ -26,13 +26,13 @@ class ProjectNotificationType extends NotificationType
*/
public function getSelectedTypes($project_id)
{
$selectedTypes = $this->db
$types = $this->db
->table(self::TABLE)
->eq('project_id', $project_id)
->asc('notification_type')
->findAllByColumn('notification_type');
return array_merge($this->getHiddenTypes(), $selectedTypes);
return $this->filterTypes($types);
}
/**
@@ -52,6 +52,6 @@ class ProjectNotificationType extends NotificationType
$results[] = $this->db->table(self::TABLE)->insert(array('project_id' => $project_id, 'notification_type' => $type));
}
return ! in_array(false, $results);
return ! in_array(false, $results, true);
}
}

View File

@@ -26,7 +26,8 @@ class UserNotificationType extends NotificationType
*/
public function getSelectedTypes($user_id)
{
return $this->db->table(self::TABLE)->eq('user_id', $user_id)->asc('notification_type')->findAllByColumn('notification_type');
$types = $this->db->table(self::TABLE)->eq('user_id', $user_id)->asc('notification_type')->findAllByColumn('notification_type');
return $this->filterTypes($types);
}
/**
@@ -46,6 +47,6 @@ class UserNotificationType extends NotificationType
$results[] = $this->db->table(self::TABLE)->insert(array('user_id' => $user_id, 'notification_type' => $type));
}
return ! in_array(false, $results);
return ! in_array(false, $results, true);
}
}

View File

@@ -48,7 +48,7 @@ class UserUnreadNotification extends Base
foreach ($events as &$event) {
$event['event_data'] = json_decode($event['event_data'], true);
$event['title'] = $this->getTitleFromEvent($event['event_name'], $event['event_data']);
$event['title'] = $this->notification->getTitleWithoutAuthor($event['event_name'], $event['event_data']);
}
return $events;
@@ -90,65 +90,4 @@ class UserUnreadNotification extends Base
{
return $this->db->table(self::TABLE)->eq('user_id', $user_id)->exists();
}
/**
* Get title from event
*
* @access public
* @param string $event_name
* @param array $event_data
* @return string
*/
public function getTitleFromEvent($event_name, array $event_data)
{
switch ($event_name) {
case File::EVENT_CREATE:
$title = t('New attachment on task #%d: %s', $event_data['file']['task_id'], $event_data['file']['name']);
break;
case Comment::EVENT_CREATE:
$title = t('New comment on task #%d', $event_data['comment']['task_id']);
break;
case Comment::EVENT_UPDATE:
$title = t('Comment updated on task #%d', $event_data['comment']['task_id']);
break;
case Subtask::EVENT_CREATE:
$title = t('New subtask on task #%d', $event_data['subtask']['task_id']);
break;
case Subtask::EVENT_UPDATE:
$title = t('Subtask updated on task #%d', $event_data['subtask']['task_id']);
break;
case Task::EVENT_CREATE:
$title = t('New task #%d: %s', $event_data['task']['id'], $event_data['task']['title']);
break;
case Task::EVENT_UPDATE:
$title = t('Task updated #%d', $event_data['task']['id']);
break;
case Task::EVENT_CLOSE:
$title = t('Task #%d closed', $event_data['task']['id']);
break;
case Task::EVENT_OPEN:
$title = t('Task #%d opened', $event_data['task']['id']);
break;
case Task::EVENT_MOVE_COLUMN:
$title = t('Column changed for task #%d', $event_data['task']['id']);
break;
case Task::EVENT_MOVE_POSITION:
$title = t('New position for task #%d', $event_data['task']['id']);
break;
case Task::EVENT_MOVE_SWIMLANE:
$title = t('Swimlane changed for task #%d', $event_data['task']['id']);
break;
case Task::EVENT_ASSIGNEE_CHANGE:
$title = t('Assignee changed on task #%d', $event_data['task']['id']);
break;
case Task::EVENT_OVERDUE:
$nb = count($event_data['tasks']);
$title = $nb > 1 ? t('%d overdue tasks', $nb) : t('Task #%d is overdue', $event_data['tasks'][0]['id']);
break;
default:
$title = e('Notification');
}
return $title;
}
}

View File

@@ -35,7 +35,6 @@ class ActivityStream extends Base implements NotificationInterface
public function notifyProject(array $project, $event_name, array $event_data)
{
if ($this->userSession->isLogged()) {
$this->projectActivity->createEvent(
$project['id'],
$event_data['task']['id'],
@@ -43,16 +42,6 @@ class ActivityStream extends Base implements NotificationInterface
$event_name,
$event_data
);
// TODO: need to be moved to external plugins
foreach (array('slackWebhook', 'hipchatWebhook', 'jabber') as $model) {
$this->$model->notify(
$project['id'],
$event_data['task']['id'],
$event_name,
$event_data
);
}
}
}
}

View File

@@ -39,6 +39,23 @@ function version_93($pdo)
UNIQUE(task_id, name)
) ENGINE=InnoDB CHARSET=utf8
");
$pdo->exec("DROP TABLE project_integrations");
$pdo->exec("DELETE FROM settings WHERE `option`='integration_jabber'");
$pdo->exec("DELETE FROM settings WHERE `option`='integration_jabber_server'");
$pdo->exec("DELETE FROM settings WHERE `option`='integration_jabber_domain'");
$pdo->exec("DELETE FROM settings WHERE `option`='integration_jabber_username'");
$pdo->exec("DELETE FROM settings WHERE `option`='integration_jabber_password'");
$pdo->exec("DELETE FROM settings WHERE `option`='integration_jabber_nickname'");
$pdo->exec("DELETE FROM settings WHERE `option`='integration_jabber_room'");
$pdo->exec("DELETE FROM settings WHERE `option`='integration_hipchat'");
$pdo->exec("DELETE FROM settings WHERE `option`='integration_hipchat_api_url'");
$pdo->exec("DELETE FROM settings WHERE `option`='integration_hipchat_room_id'");
$pdo->exec("DELETE FROM settings WHERE `option`='integration_hipchat_room_token'");
$pdo->exec("DELETE FROM settings WHERE `option`='integration_slack_webhook'");
$pdo->exec("DELETE FROM settings WHERE `option`='integration_slack_webhook_url'");
$pdo->exec("DELETE FROM settings WHERE `option`='integration_slack_webhook_channel'");
}
function version_92($pdo)

View File

@@ -39,6 +39,23 @@ function version_73($pdo)
UNIQUE(task_id, name)
)
");
$pdo->exec("DROP TABLE project_integrations");
$pdo->exec("DELETE FROM settings WHERE \"option\"='integration_jabber'");
$pdo->exec("DELETE FROM settings WHERE \"option\"='integration_jabber_server'");
$pdo->exec("DELETE FROM settings WHERE \"option\"='integration_jabber_domain'");
$pdo->exec("DELETE FROM settings WHERE \"option\"='integration_jabber_username'");
$pdo->exec("DELETE FROM settings WHERE \"option\"='integration_jabber_password'");
$pdo->exec("DELETE FROM settings WHERE \"option\"='integration_jabber_nickname'");
$pdo->exec("DELETE FROM settings WHERE \"option\"='integration_jabber_room'");
$pdo->exec("DELETE FROM settings WHERE \"option\"='integration_hipchat'");
$pdo->exec("DELETE FROM settings WHERE \"option\"='integration_hipchat_api_url'");
$pdo->exec("DELETE FROM settings WHERE \"option\"='integration_hipchat_room_id'");
$pdo->exec("DELETE FROM settings WHERE \"option\"='integration_hipchat_room_token'");
$pdo->exec("DELETE FROM settings WHERE \"option\"='integration_slack_webhook'");
$pdo->exec("DELETE FROM settings WHERE \"option\"='integration_slack_webhook_url'");
$pdo->exec("DELETE FROM settings WHERE \"option\"='integration_slack_webhook_channel'");
}
function version_72($pdo)

View File

@@ -39,6 +39,23 @@ function version_88($pdo)
UNIQUE(task_id, name)
)
");
$pdo->exec("DROP TABLE project_integrations");
$pdo->exec("DELETE FROM settings WHERE \"option\"='integration_jabber'");
$pdo->exec("DELETE FROM settings WHERE \"option\"='integration_jabber_server'");
$pdo->exec("DELETE FROM settings WHERE \"option\"='integration_jabber_domain'");
$pdo->exec("DELETE FROM settings WHERE \"option\"='integration_jabber_username'");
$pdo->exec("DELETE FROM settings WHERE \"option\"='integration_jabber_password'");
$pdo->exec("DELETE FROM settings WHERE \"option\"='integration_jabber_nickname'");
$pdo->exec("DELETE FROM settings WHERE \"option\"='integration_jabber_room'");
$pdo->exec("DELETE FROM settings WHERE \"option\"='integration_hipchat'");
$pdo->exec("DELETE FROM settings WHERE \"option\"='integration_hipchat_api_url'");
$pdo->exec("DELETE FROM settings WHERE \"option\"='integration_hipchat_room_id'");
$pdo->exec("DELETE FROM settings WHERE \"option\"='integration_hipchat_room_token'");
$pdo->exec("DELETE FROM settings WHERE \"option\"='integration_slack_webhook'");
$pdo->exec("DELETE FROM settings WHERE \"option\"='integration_slack_webhook_url'");
$pdo->exec("DELETE FROM settings WHERE \"option\"='integration_slack_webhook_channel'");
}
function version_87($pdo)

View File

@@ -31,6 +31,7 @@ class ClassProvider implements ServiceProviderInterface
'File',
'LastLogin',
'Link',
'Notification',
'OverdueNotification',
'Project',
'ProjectActivity',
@@ -38,7 +39,6 @@ class ClassProvider implements ServiceProviderInterface
'ProjectDuplication',
'ProjectDailyColumnStats',
'ProjectDailyStats',
'ProjectIntegration',
'ProjectPermission',
'ProjectNotification',
'ProjectMetadata',
@@ -98,9 +98,6 @@ class ClassProvider implements ServiceProviderInterface
'BitbucketWebhook',
'GithubWebhook',
'GitlabWebhook',
'HipchatWebhook',
'Jabber',
'SlackWebhook',
)
);

View File

@@ -31,60 +31,6 @@
<?= $this->form->checkbox('integration_gravatar', t('Enable Gravatar images'), 1, $values['integration_gravatar'] == 1) ?>
</div>
<h3><img src="<?= $this->url->dir() ?>assets/img/jabber-icon.png"/> <?= t('Jabber (XMPP)') ?></h3>
<div class="listing">
<?= $this->form->checkbox('integration_jabber', t('Send notifications to Jabber'), 1, $values['integration_jabber'] == 1) ?>
<?= $this->form->label(t('XMPP server address'), 'integration_jabber_server') ?>
<?= $this->form->text('integration_jabber_server', $values, $errors, array('placeholder="tcp://myserver:5222"')) ?>
<p class="form-help"><?= t('The server address must use this format: "tcp://hostname:5222"') ?></p>
<?= $this->form->label(t('Jabber domain'), 'integration_jabber_domain') ?>
<?= $this->form->text('integration_jabber_domain', $values, $errors, array('placeholder="example.com"')) ?>
<?= $this->form->label(t('Username'), 'integration_jabber_username') ?>
<?= $this->form->text('integration_jabber_username', $values, $errors) ?>
<?= $this->form->label(t('Password'), 'integration_jabber_password') ?>
<?= $this->form->password('integration_jabber_password', $values, $errors) ?>
<?= $this->form->label(t('Jabber nickname'), 'integration_jabber_nickname') ?>
<?= $this->form->text('integration_jabber_nickname', $values, $errors) ?>
<?= $this->form->label(t('Multi-user chat room'), 'integration_jabber_room') ?>
<?= $this->form->text('integration_jabber_room', $values, $errors, array('placeholder="myroom@conference.example.com"')) ?>
<p class="form-help"><?= $this->url->doc(t('Help on Jabber integration'), 'jabber') ?></p>
</div>
<h3><img src="<?= $this->url->dir() ?>assets/img/hipchat-icon.png"/> <?= t('Hipchat') ?></h3>
<div class="listing">
<?= $this->form->checkbox('integration_hipchat', t('Send notifications to Hipchat'), 1, $values['integration_hipchat'] == 1) ?>
<?= $this->form->label(t('API URL'), 'integration_hipchat_api_url') ?>
<?= $this->form->text('integration_hipchat_api_url', $values, $errors) ?>
<?= $this->form->label(t('Room API ID or name'), 'integration_hipchat_room_id') ?>
<?= $this->form->text('integration_hipchat_room_id', $values, $errors) ?>
<?= $this->form->label(t('Room notification token'), 'integration_hipchat_room_token') ?>
<?= $this->form->text('integration_hipchat_room_token', $values, $errors) ?>
<p class="form-help"><?= $this->url->doc(t('Help on Hipchat integration'), 'hipchat') ?></p>
</div>
<h3><i class="fa fa-slack fa-fw"></i>&nbsp;<?= t('Slack') ?></h3>
<div class="listing">
<?= $this->form->checkbox('integration_slack_webhook', t('Send notifications to a Slack channel'), 1, $values['integration_slack_webhook'] == 1) ?>
<?= $this->form->label(t('Webhook URL'), 'integration_slack_webhook_url') ?>
<?= $this->form->text('integration_slack_webhook_url', $values, $errors) ?>
<?= $this->form->label(t('Channel/Group/User (Optional)'), 'integration_slack_webhook_channel') ?>
<?= $this->form->text('integration_slack_webhook_channel', $values, $errors) ?>
<p class="form-help"><?= $this->url->doc(t('Help on Slack integration'), 'slack') ?></p>
</div>
<div class="form-actions">
<input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
</div>

View File

@@ -2,9 +2,10 @@
<h2><?= t('Integration with third-party services') ?></h2>
</div>
<form method="post" action="<?= $this->url->href('project', 'integration', array('project_id' => $project['id'])) ?>" autocomplete="off">
<form method="post" action="<?= $this->url->href('project', 'integrations', array('project_id' => $project['id'])) ?>" autocomplete="off">
<?= $this->form->csrf() ?>
<?= $this->hook->render('template:project:integrations', array('values' => $values)) ?>
<h3><i class="fa fa-github fa-fw"></i>&nbsp;<?= t('Github webhooks') ?></h3>
<div class="listing">
@@ -12,86 +13,15 @@
<p class="form-help"><?= $this->url->doc(t('Help on Github webhooks'), 'github-webhooks') ?></p>
</div>
<h3><img src="<?= $this->url->dir() ?>assets/img/gitlab-icon.png"/>&nbsp;<?= t('Gitlab webhooks') ?></h3>
<div class="listing">
<input type="text" class="auto-select" readonly="readonly" value="<?= $this->url->href('webhook', 'gitlab', array('token' => $webhook_token, 'project_id' => $project['id']), false, '', true) ?>"/><br/>
<p class="form-help"><?= $this->url->doc(t('Help on Gitlab webhooks'), 'gitlab-webhooks') ?></p>
</div>
<h3><i class="fa fa-bitbucket fa-fw"></i>&nbsp;<?= t('Bitbucket webhooks') ?></h3>
<div class="listing">
<input type="text" class="auto-select" readonly="readonly" value="<?= $this->url->href('webhook', 'bitbucket', array('token' => $webhook_token, 'project_id' => $project['id']), false, '', true) ?>"/><br/>
<p class="form-help"><?= $this->url->doc(t('Help on Bitbucket webhooks'), 'bitbucket-webhooks') ?></p>
</div>
<h3><img src="<?= $this->url->dir() ?>assets/img/jabber-icon.png"/> <?= t('Jabber (XMPP)') ?></h3>
<div class="listing">
<?= $this->form->checkbox('jabber', t('Send notifications to Jabber'), 1, isset($values['jabber']) && $values['jabber'] == 1) ?>
<?= $this->form->label(t('XMPP server address'), 'jabber_server') ?>
<?= $this->form->text('jabber_server', $values, $errors, array('placeholder="tcp://myserver:5222"')) ?>
<p class="form-help"><?= t('The server address must use this format: "tcp://hostname:5222"') ?></p>
<?= $this->form->label(t('Jabber domain'), 'jabber_domain') ?>
<?= $this->form->text('jabber_domain', $values, $errors, array('placeholder="example.com"')) ?>
<?= $this->form->label(t('Username'), 'jabber_username') ?>
<?= $this->form->text('jabber_username', $values, $errors) ?>
<?= $this->form->label(t('Password'), 'jabber_password') ?>
<?= $this->form->password('jabber_password', $values, $errors) ?>
<?= $this->form->label(t('Jabber nickname'), 'jabber_nickname') ?>
<?= $this->form->text('jabber_nickname', $values, $errors) ?>
<?= $this->form->label(t('Multi-user chat room'), 'jabber_room') ?>
<?= $this->form->text('jabber_room', $values, $errors, array('placeholder="myroom@conference.example.com"')) ?>
<p class="form-help"><?= $this->url->doc(t('Help on Jabber integration'), 'jabber') ?></p>
<div class="form-actions">
<input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
</div>
</div>
<h3><img src="<?= $this->url->dir() ?>assets/img/hipchat-icon.png"/> <?= t('Hipchat') ?></h3>
<div class="listing">
<?= $this->form->checkbox('hipchat', t('Send notifications to Hipchat'), 1, isset($values['hipchat']) && $values['hipchat'] == 1) ?>
<?= $this->form->label(t('API URL'), 'hipchat_api_url') ?>
<?= $this->form->text('hipchat_api_url', $values, $errors) ?>
<?= $this->form->label(t('Room API ID or name'), 'hipchat_room_id') ?>
<?= $this->form->text('hipchat_room_id', $values, $errors) ?>
<?= $this->form->label(t('Room notification token'), 'hipchat_room_token') ?>
<?= $this->form->text('hipchat_room_token', $values, $errors) ?>
<p class="form-help"><?= $this->url->doc(t('Help on Hipchat integration'), 'hipchat') ?></a></p>
<div class="form-actions">
<input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
</div>
</div>
<h3><i class="fa fa-slack fa-fw"></i>&nbsp;<?= t('Slack') ?></h3>
<div class="listing">
<?= $this->form->checkbox('slack', t('Send notifications to a Slack channel'), 1, isset($values['slack']) && $values['slack'] == 1) ?>
<?= $this->form->label(t('Webhook URL'), 'slack_webhook_url') ?>
<?= $this->form->text('slack_webhook_url', $values, $errors) ?>
<?= $this->form->label(t('Channel/Group/User (Optional)'), 'slack_webhook_channel') ?>
<?= $this->form->text('slack_webhook_channel', $values, $errors) ?>
<p class="form-help"><?= $this->url->doc(t('Help on Slack integration'), 'slack') ?></p>
<div class="form-actions">
<input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
</div>
</div>
</form>

View File

@@ -0,0 +1,20 @@
<div class="page-header">
<h2><?= t('Notifications') ?></h2>
</div>
<?php if (empty($types)): ?>
<p class="alert"><?= t('There is no notification method registered.') ?></p>
<?php else: ?>
<form method="post" action="<?= $this->url->href('project', 'notifications', array('project_id' => $project['id'])) ?>" autocomplete="off">
<?= $this->form->csrf() ?>
<h4><?= t('Notification methods:') ?></h4>
<?= $this->form->checkboxes('notification_types', $types, $notifications) ?>
<div class="form-actions">
<input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
<?= t('or') ?>
<?= $this->url->link(t('cancel'), 'project', 'show', array('project_id' => $project['id'])) ?>
</div>
</form>
<?php endif ?>

View File

@@ -12,8 +12,11 @@
<li <?= $this->app->getRouterController() === 'project' && $this->app->getRouterAction() === 'share' ? 'class="active"' : '' ?>>
<?= $this->url->link(t('Public access'), 'project', 'share', array('project_id' => $project['id'])) ?>
</li>
<li <?= $this->app->getRouterController() === 'project' && $this->app->getRouterAction() === 'integration' ? 'class="active"' : '' ?>>
<?= $this->url->link(t('Integrations'), 'project', 'integration', array('project_id' => $project['id'])) ?>
<li <?= $this->app->getRouterController() === 'project' && $this->app->getRouterAction() === 'notifications' ? 'class="active"' : '' ?>>
<?= $this->url->link(t('Notifications'), 'project', 'notifications', array('project_id' => $project['id'])) ?>
</li>
<li <?= $this->app->getRouterController() === 'project' && $this->app->getRouterAction() === 'integrations' ? 'class="active"' : '' ?>>
<?= $this->url->link(t('Integrations'), 'project', 'integrations', array('project_id' => $project['id'])) ?>
</li>
<li <?= $this->app->getRouterController() === 'project' && $this->app->getRouterAction() === 'edit' ? 'class="active"' : '' ?>>
<?= $this->url->link(t('Edit project'), 'project', 'edit', array('project_id' => $project['id'])) ?>

View File

@@ -0,0 +1,13 @@
<div class="page-header">
<h2><?= t('Integrations') ?></h2>
</div>
<form method="post" action="<?= $this->url->href('user', 'integrations', array('user_id' => $user['id'])) ?>" autocomplete="off">
<?= $this->form->csrf() ?>
<?php $hooks = $this->hook->render('template:user:integrations', array('values' => $values)) ?>
<?php if (! empty($hooks)): ?>
<?= $hooks ?>
<?php else: ?>
<p class="alert"><?= t('No external integration registered.') ?></p>
<?php endif ?>
</form>

View File

@@ -56,6 +56,9 @@
<li <?= $this->app->getRouterController() === 'user' && $this->app->getRouterAction() === 'external' ? 'class="active"' : '' ?>>
<?= $this->url->link(t('External accounts'), 'user', 'external', array('user_id' => $user['id'])) ?>
</li>
<li <?= $this->app->getRouterController() === 'user' && $this->app->getRouterAction() === 'integrations' ? 'class="active"' : '' ?>>
<?= $this->url->link(t('Integrations'), 'user', 'integrations', array('user_id' => $user['id'])) ?>
</li>
<?php endif ?>
<?php if ($this->user->isAdmin()): ?>