summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml1
-rw-r--r--README.markdown4
-rw-r--r--app/Controller/Action.php8
-rw-r--r--app/Controller/Base.php1
-rw-r--r--app/Controller/Task.php65
-rw-r--r--app/Controller/User.php64
-rw-r--r--app/Locales/de_DE/translations.php108
-rw-r--r--app/Locales/es_ES/translations.php8
-rw-r--r--app/Locales/fr_FR/translations.php8
-rw-r--r--app/Locales/pl_PL/translations.php8
-rw-r--r--app/Locales/pt_BR/translations.php8
-rw-r--r--app/Locales/sv_SE/translations.php8
-rw-r--r--app/Locales/zh_CN/translations.php8
-rw-r--r--app/Model/Acl.php4
-rw-r--r--app/Model/GitHub.php178
-rw-r--r--app/Model/LastLogin.php1
-rw-r--r--app/Model/User.php12
-rw-r--r--app/Schema/Mysql.php7
-rw-r--r--app/Schema/Sqlite.php7
-rw-r--r--app/Templates/board_task.php10
-rw-r--r--app/Templates/task_edit.php11
-rw-r--r--app/Templates/task_edit_description.php7
-rw-r--r--app/Templates/user_edit.php13
-rw-r--r--app/Templates/user_login.php12
-rw-r--r--app/common.php5
-rw-r--r--assets/css/app.css10
-rw-r--r--assets/js/board.js6
-rw-r--r--config.default.php9
-rw-r--r--docs/github-authentication.markdown62
-rw-r--r--jsonrpc.php12
-rwxr-xr-xscripts/make-archive.sh2
31 files changed, 572 insertions, 95 deletions
diff --git a/.travis.yml b/.travis.yml
index cc70a9d7..edd10551 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,6 +1,7 @@
language: php
php:
+ - "5.6"
- "5.5"
- "5.4"
- "5.3"
diff --git a/README.markdown b/README.markdown
index bada2914..2f8138b7 100644
--- a/README.markdown
+++ b/README.markdown
@@ -24,7 +24,7 @@ Features
- Tasks with different colors, categories, sub-tasks, attachments, Markdown support for the description
- Automatic actions
- Users management with a basic privileges separation (administrator or regular user)
-- External authentication: Google Account and LDAP/ActiveDirectory
+- External authentication: Google and GitHub accounts as well as LDAP/ActiveDirectory
- Webhooks to create tasks from an external software
- Host anywhere (shared hosting, VPS, Raspberry Pi or localhost)
- No external dependencies
@@ -62,6 +62,7 @@ Contributors:
- Mathgl67: https://github.com/mathgl67
- Matthieu Keller: https://github.com/maggick
- Maxime: https://github.com/EpocDotFr
+- Moraxy: https://github.com/moraxy
- Nala Ginrut: https://github.com/NalaGinrut
- Nekohayo: https://github.com/nekohayo
- Olivier Maridat: https://github.com/oliviermaridat
@@ -103,6 +104,7 @@ Documentation
- [LDAP authentication](docs/ldap-authentication.markdown)
- [Google authentication](docs/google-authentication.markdown)
+- [GitHub authentication](docs/github-authentication.markdown)
#### Developers
diff --git a/app/Controller/Action.php b/app/Controller/Action.php
index 11dc3b29..797bbfa2 100644
--- a/app/Controller/Action.php
+++ b/app/Controller/Action.php
@@ -33,10 +33,10 @@ class Action extends Base
'available_events' => $this->action->getAvailableEvents(),
'available_params' => $this->action->getAllActionParameters(),
'columns_list' => $this->board->getColumnsList($project['id']),
- 'users_list' => $this->project->getUsersList($project['id'], false),
+ 'users_list' => $this->project->getUsersList($project['id']),
'projects_list' => $this->project->getList(false),
'colors_list' => $this->task->getColors(),
- 'categories_list' => $this->category->getList($project['id'], false),
+ 'categories_list' => $this->category->getList($project['id']),
'menu' => 'projects',
'title' => t('Automatic actions')
)));
@@ -64,10 +64,10 @@ class Action extends Base
'values' => $values,
'action_params' => $action->getActionRequiredParameters(),
'columns_list' => $this->board->getColumnsList($project['id']),
- 'users_list' => $this->project->getUsersList($project['id'], false),
+ 'users_list' => $this->project->getUsersList($project['id']),
'projects_list' => $this->project->getList(false),
'colors_list' => $this->task->getColors(),
- 'categories_list' => $this->category->getList($project['id'], false),
+ 'categories_list' => $this->category->getList($project['id']),
'project' => $project,
'menu' => 'projects',
'title' => t('Automatic actions')
diff --git a/app/Controller/Base.php b/app/Controller/Base.php
index 13fb9b91..25a72f15 100644
--- a/app/Controller/Base.php
+++ b/app/Controller/Base.php
@@ -20,6 +20,7 @@ use Model\LastLogin;
* @property \Model\Config $config
* @property \Model\File $file
* @property \Model\Google $google
+ * @property \Model\GitHub $gitHub
* @property \Model\LastLogin $lastLogin
* @property \Model\Ldap $ldap
* @property \Model\Project $project
diff --git a/app/Controller/Task.php b/app/Controller/Task.php
index d44ba268..15482afc 100644
--- a/app/Controller/Task.php
+++ b/app/Controller/Task.php
@@ -162,17 +162,24 @@ class Task extends Base
$task['score'] = $task['score'] ?: '';
- $this->response->html($this->template->layout('task_edit', array(
- 'values' => $task,
- 'errors' => array(),
- 'task' => $task,
- 'columns_list' => $this->board->getColumnsList($task['project_id']),
- 'users_list' => $this->project->getUsersList($task['project_id']),
- 'colors_list' => $this->task->getColors(),
- 'categories_list' => $this->category->getList($task['project_id']),
- 'menu' => 'tasks',
- 'title' => t('Edit a task')
- )));
+ $params = array(
+ 'values' => $task,
+ 'errors' => array(),
+ 'task' => $task,
+ 'columns_list' => $this->board->getColumnsList($task['project_id']),
+ 'users_list' => $this->project->getUsersList($task['project_id']),
+ 'colors_list' => $this->task->getColors(),
+ 'categories_list' => $this->category->getList($task['project_id']),
+ 'ajax' => $this->request->isAjax(),
+ 'menu' => 'tasks',
+ 'title' => t('Edit a task')
+ );
+ if ($this->request->isAjax()) {
+ $this->response->html($this->template->load('task_edit', $params));
+ }
+ else {
+ $this->response->html($this->template->layout('task_edit', $params));
+ }
}
/**
@@ -191,7 +198,13 @@ class Task extends Base
if ($this->task->update($values)) {
$this->session->flash(t('Task updated successfully.'));
- $this->response->redirect('?controller=task&action=show&task_id='.$values['id']);
+
+ if ($this->request->getIntegerParam('ajax')) {
+ $this->response->redirect('?controller=board&action=show&project_id='.$task['project_id']);
+ }
+ else {
+ $this->response->redirect('?controller=task&action=show&task_id='.$values['id']);
+ }
}
else {
$this->session->flashError(t('Unable to update your task.'));
@@ -357,13 +370,20 @@ class Task extends Base
{
$task = $this->getTask();
- $this->response->html($this->taskLayout('task_edit_description', array(
- 'values' => $task,
- 'errors' => array(),
- 'task' => $task,
- 'menu' => 'tasks',
- 'title' => t('Edit the description')
- )));
+ $params = array(
+ 'values' => $task,
+ 'errors' => array(),
+ 'task' => $task,
+ 'ajax' => $this->request->isAjax(),
+ 'menu' => 'tasks',
+ 'title' => t('Edit the description')
+ );
+ if ($this->request->isAjax()) {
+ $this->response->html($this->template->load('task_edit_description', $params));
+ }
+ else {
+ $this->response->html($this->taskLayout('task_edit_description', $params));
+ }
}
/**
@@ -387,7 +407,12 @@ class Task extends Base
$this->session->flashError(t('Unable to update your task.'));
}
- $this->response->redirect('?controller=task&action=show&task_id='.$task['id']);
+ if ($this->request->getIntegerParam('ajax')) {
+ $this->response->redirect('?controller=board&action=show&project_id='.$task['project_id']);
+ }
+ else {
+ $this->response->redirect('?controller=task&action=show&task_id='.$task['id']);
+ }
}
$this->response->html($this->taskLayout('task_edit_description', array(
diff --git a/app/Controller/User.php b/app/Controller/User.php
index fca33b28..d30c6fd2 100644
--- a/app/Controller/User.php
+++ b/app/Controller/User.php
@@ -299,4 +299,68 @@ class User extends Base
$this->response->redirect('?controller=user');
}
+
+ /**
+ * GitHub authentication
+ *
+ * @access public
+ */
+ public function gitHub()
+ {
+ $code = $this->request->getStringParam('code');
+
+ if ($code) {
+ $profile = $this->gitHub->getGitHubProfile($code);
+
+ if (is_array($profile)) {
+
+ // If the user is already logged, link the account otherwise authenticate
+ if ($this->acl->isLogged()) {
+
+ if ($this->gitHub->updateUser($this->acl->getUserId(), $profile)) {
+ $this->session->flash(t('Your GitHub account was successfully linked to your profile.'));
+ }
+ else {
+ $this->session->flashError(t('Unable to link your GitHub Account.'));
+ }
+
+ $this->response->redirect('?controller=user');
+ }
+ else if ($this->gitHub->authenticate($profile['id'])) {
+ $this->response->redirect('?controller=app');
+ }
+ else {
+ $this->response->html($this->template->layout('user_login', array(
+ 'errors' => array('login' => t('GitHub authentication failed')),
+ 'values' => array(),
+ 'no_layout' => true,
+ 'title' => t('Login')
+ )));
+ }
+ }
+ }
+
+ $this->response->redirect($this->gitHub->getAuthorizationUrl());
+ }
+
+ /**
+ * Unlink a GitHub account
+ *
+ * @access public
+ */
+ public function unlinkGitHub()
+ {
+ $this->checkCSRFParam();
+
+ $this->gitHub->revokeGitHubAccess();
+
+ if ($this->gitHub->unlink($this->acl->getUserId())) {
+ $this->session->flash(t('Your GitHub account is no longer linked to your profile.'));
+ }
+ else {
+ $this->session->flashError(t('Unable to unlink your GitHub Account.'));
+ }
+
+ $this->response->redirect('?controller=user');
+ }
}
diff --git a/app/Locales/de_DE/translations.php b/app/Locales/de_DE/translations.php
index 48c7ee16..734c3437 100644
--- a/app/Locales/de_DE/translations.php
+++ b/app/Locales/de_DE/translations.php
@@ -5,9 +5,9 @@ return array(
'German' => 'Deutsch',
'French' => 'Französisch',
'Polish' => 'Polnisch',
- 'Portuguese (Brazilian)' => 'Portugisisch (Brasilien)',
+ 'Portuguese (Brazilian)' => 'Portugiesisch (Brasilien)',
'Spanish' => 'Spanisch',
- 'Chinese (Simplified)' => 'Chinesisch',
+ 'Chinese (Simplified)' => 'Chinesisch (vereinfacht)',
'None' => 'Kein',
'edit' => 'bearbeiten',
'Edit' => 'Bearbeiten',
@@ -27,16 +27,16 @@ return array(
'Grey' => 'Grau',
'Save' => 'Speichern',
'Login' => 'Anmelden',
- 'Official website:' => 'Offizielle Website :',
+ 'Official website:' => 'Offizielle Website:',
'Unassigned' => 'Nicht zugeordnet',
'View this task' => 'Aufgabe ansehen',
'Remove user' => 'Benutzer löschen',
- 'Do you really want to remove this user: "%s"?' => 'Willst Du diesen Benutzer wirklich löschen : « %s » ?',
+ 'Do you really want to remove this user: "%s"?' => 'Willst Du diesen Benutzer wirklich löschen: «%s»?',
'New user' => 'Neuer Benutzer',
'All users' => 'Alle Benutzer',
'Username' => 'Benutzername',
'Password' => 'Passwort',
- 'Default Project' => 'Standard Projekt',
+ 'Default Project' => 'Standardprojekt',
'Administrator' => 'Administrator',
'Sign in' => 'Anmelden',
'Users' => 'Benutzer',
@@ -70,37 +70,37 @@ return array(
'Disable' => 'Deaktivieren',
'Enable' => 'Aktivieren',
'New project' => 'Neues Projekt',
- 'Do you really want to remove this project: "%s"?' => 'Möchtest Du dieses Projekt wirklich löschen : « %s » ?',
+ 'Do you really want to remove this project: "%s"?' => 'Möchtest Du dieses Projekt wirklich löschen: «%s»?',
'Remove project' => 'Projekt löschen',
'Boards' => 'Tafel',
- 'Edit the board for "%s"' => 'Modifier le tableau pour « %s »',
+ 'Edit the board for "%s"' => 'Tafel für «%s» bearbeiten',
'All projects' => 'Alle Projekte',
'Change columns' => 'Spalten ändern',
'Add a new column' => 'Neue Spalte hinzufügen',
'Title' => 'Titel',
'Add Column' => 'Neue Spalte',
- 'Project "%s"' => 'Projekt « %s »',
+ 'Project "%s"' => 'Projekt «%s»',
'Nobody assigned' => 'Kein Zuständiger zugeordnet',
'Assigned to %s' => 'Zuständiger: %s',
'Remove a column' => 'Spalte löschen',
'Remove a column from a board' => 'Eine Spalte von einer Tafel löschen',
'Unable to remove this column.' => 'Löschen dieser Spalte nicht möglich.',
- 'Do you really want to remove this column: "%s"?' => 'Willst Du diese Spalte wirklich löschen : « %s » ?',
- 'This action will REMOVE ALL TASKS associated to this column!' => 'Diese Aktion wird ALLE AUFGABEN löschen !',
+ 'Do you really want to remove this column: "%s"?' => 'Willst Du diese Spalte wirklich löschen: «%s»?',
+ 'This action will REMOVE ALL TASKS associated to this column!' => 'Diese Aktion wird ALLE AUFGABEN löschen!',
'Settings' => 'Einstellungen',
'Application settings' => 'Applikationseinstellungen',
'Language' => 'Sprache',
'Webhooks token:' => 'Webhooks token:',
'More information' => 'Mehr Informationen',
- 'Database size:' => 'Datenbank Größe :',
+ 'Database size:' => 'Datenbankgröße:',
'Download the database' => 'Download der Datenbank',
'Optimize the database' => 'Optimieren der Datenbank',
'(VACUUM command)' => '(VACUUM command)',
'(Gzip compressed Sqlite file)' => '(Gzip komprimierte sqlite Datei)',
- 'User settings' => 'Benutzer Eintellungen',
- 'My default project:' => 'Mein Standard Projekt : ',
+ 'User settings' => 'Benutzereinstellungen',
+ 'My default project:' => 'Mein Standardprojekt:',
'Close a task' => 'Aufgabe schließen',
- 'Do you really want to close this task: "%s"?' => 'Willst Du diese Aufgabe wirklich schließen : « %s » ?',
+ 'Do you really want to close this task: "%s"?' => 'Willst Du diese Aufgabe wirklich schließen: «%s»?',
'Edit a task' => 'Aufgabe bearbeiten',
'Column' => 'Spalte',
'Color' => 'Farbe',
@@ -108,18 +108,18 @@ return array(
'Create another task' => 'Neue Aufgabe erstellen',
'New task' => 'Neue Aufgabe',
'Open a task' => 'Öffne eine Aufgabe',
- 'Do you really want to open this task: "%s"?' => 'Willst Du diese Aufgabe wirklich öffnen : « %s » ?',
+ 'Do you really want to open this task: "%s"?' => 'Willst Du diese Aufgabe wirklich öffnen: «%s»?',
'Back to the board' => 'Zurück zur Tafel',
'Created on %B %e, %G at %k:%M %p' => 'Erstellt am %d.%m.%Y um %H:%M',
'There is nobody assigned' => 'Ein gibt keinen Zuständigen',
- 'Column on the board:' => 'Spalten auf diesem Tafel : ',
+ 'Column on the board:' => 'Spalten auf diesem Tafel:',
'Status is open' => 'Status ist geöffnet',
'Status is closed' => 'Status ist geschlossen',
'close this task' => 'Aufgabe schließen',
'open this task' => 'Aufgabe öffnen',
- 'There is no description.' => 'Es gibt keine Erklärung.',
+ 'There is no description.' => 'Es gibt keine Beschreibung.',
'Add a new task' => 'Eine neue Aufgabe hinzufügen',
- 'The username is required' => 'Der Butzutzername wird benötigt',
+ 'The username is required' => 'Der Benutzername wird benötigt',
'The maximum length is %d characters' => 'Die maximale Länge sind %d Zeichen',
'The minimum length is %d characters' => 'Die minimale Länge sind %d Zeichen',
'The password is required' => 'Das Passwort wird benötigt',
@@ -133,12 +133,12 @@ return array(
'The project is required' => 'Das Projekt wird benötigt',
'The color is required' => 'Die Farbe wird benötigt',
'The id is required' => 'Die ID wird benötigt',
- 'The project id is required' => 'Die Projekt ID wird benötogt',
- 'The project name is required' => 'Der Projekt Name wird benötigt',
- 'This project must be unique' => 'Der Projekt Name muss eindeutig sein',
+ 'The project id is required' => 'Die Projekt ID wird benötigt',
+ 'The project name is required' => 'Der Projektname wird benötigt',
+ 'This project must be unique' => 'Der Projektname muss eindeutig sein',
'The title is required' => 'Der Titel wird benötigt',
'The language is required' => 'Die Sprache wird benötigt',
- 'There is no active project, the first step is to create a new project.' => 'Es gibt kein aktives Projekt. Der erste Schritt ist ein Projekt erstellen.',
+ 'There is no active project, the first step is to create a new project.' => 'Es gibt kein aktives Projekt. Der erste Schritt ist ein Projekt zu erstellen.',
'Settings saved successfully.' => 'Die Einstellungen wurden erfolgreich gespeichert.',
'Unable to save your settings.' => 'Speicher der Einstellungen nicht möglich.',
'Database optimization done.' => 'Optimieren der Datenbank abgeschlossen.',
@@ -148,7 +148,7 @@ return array(
'Unable to update this project.' => 'Ändern des Projekts nicht möglich.',
'Unable to remove this project.' => 'Löschen des Projekts nicht möglich.',
'Project removed successfully.' => 'Löschen des Projekts erfolgreich.',
- 'Project activated successfully.' => 'Aktivieren des Projekts erolgreich.',
+ 'Project activated successfully.' => 'Aktivieren des Projekts erfolgreich.',
'Unable to activate this project.' => 'Aktivieren des Projekts nicht möglich.',
'Project disabled successfully.' => 'Deaktivieren des Projekts erfolgreich.',
'Unable to disable this project.' => 'Deaktivieren des Projekts nicht möglich.',
@@ -171,24 +171,24 @@ return array(
'Backlog' => 'Ideen',
'Work in progress' => 'In Arbeit',
'Done' => 'Erledigt',
- 'Application version:' => 'Programmversion :',
- 'Completed on %B %e, %G at %k:%M %p' => 'Erledigt am %d.%m.%Y à %H:%M',
+ 'Application version:' => 'Programmversion:',
+ 'Completed on %B %e, %G at %k:%M %p' => 'Erledigt am %d.%m.%Y um %H:%M',
'%B %e, %G at %k:%M %p' => '%d.%m.%Y um %H:%M',
- 'Date created' => 'Erstellung am',
+ 'Date created' => 'Erstellt am',
'Date completed' => 'Erledigt am',
'Id' => 'ID',
'No task' => 'Keine Aufgabe',
'Completed tasks' => 'Erledigte Aufgaben',
'List of projects' => 'Liste der Projekte',
- 'Completed tasks for "%s"' => 'Erledigte Aufaben für « %s »',
- '%d closed tasks' => '%d erledigte Augaben',
+ 'Completed tasks for "%s"' => 'Erledigte Aufgaben für «%s»',
+ '%d closed tasks' => '%d erledigte Aufgaben',
'no task for this project' => 'Keine Aufgaben in diesem Projekt',
'Public link' => 'Öffentlicher Link',
- 'There is no column in your project!' => 'Es gibt keine Spalte in Deinem Projekt!',
+ 'There is no column in your project!' => 'Es gibt keine Spalte in deinem Projekt!',
'Change assignee' => 'Zuständigkeit ändern',
- 'Change assignee for the task "%s"' => 'Zuständigkeit für diese Aufgabe ändern: « %s »',
+ 'Change assignee for the task "%s"' => 'Zuständigkeit für diese Aufgabe ändern: «%s»',
'Timezone' => 'Zeitzone',
- 'Sorry, I didn\'t found this information in my database!' => 'Diese Information wurde in der Datenbank nicht gefunden !',
+ 'Sorry, I didn\'t found this information in my database!' => 'Diese Information wurde in der Datenbank nicht gefunden!',
'Page not found' => 'Seite nicht gefunden',
'Story Points' => 'Umfang',
'limit' => 'Limit',
@@ -197,17 +197,17 @@ return array(
'Edit project access list' => 'Projekt Zugangsberechtigungen bearbeiten',
'Edit users access' => 'Benutzerzugang bearbeiten',
'Allow this user' => 'Diesen Benutzer authorisieren',
- 'Project access list for "%s"' => 'Projekt Zugangsliste für « %s »',
- 'Only those users have access to this project:' => 'Nur diese Benutzer haben Zugang zum Projekt :',
+ 'Project access list for "%s"' => 'Projekt Zugangsliste für «%s»',
+ 'Only those users have access to this project:' => 'Nur diese Benutzer haben Zugang zum Projekt:',
'Don\'t forget that administrators have access to everything.' => 'Vergiss nicht, dass Administratoren überall Zugang haben.',
'revoke' => 'verbieten',
- 'List of authorized users' => 'Liste der authorisieren Benutzer',
+ 'List of authorized users' => 'Liste der authorisierten Benutzer',
'User' => 'Benutzer',
- 'Everybody have access to this project.' => 'Jedem hat Zugang zu diesem Projekt.',
+ 'Everybody have access to this project.' => 'Jeder hat Zugang zu diesem Projekt.',
'You are not allowed to access to this project.' => 'Du hast keinen Zugang zu diesem Projekt.',
'Comments' => 'Kommentare',
'Post comment' => 'kommentieren',
- 'Write your text in Markdown' => 'Schreibe Deinen Text in Markdown',
+ 'Write your text in Markdown' => 'Schreibe deinen Text in Markdown',
'Leave a comment' => 'Kommentar abgeben',
'Comment is required' => 'Ein Kommentar wird benötigt',
'Leave a description' => 'Beschreibung',
@@ -226,8 +226,8 @@ return array(
'Unable to create your automatic action.' => 'Deine automatische Aktion konnte nicht erstellt werden.',
'Remove an action' => 'Aktion löschen',
'Unable to remove this action.' => 'Aktion konnte nicht gelöscht werden',
- 'Action removed successfully.' => 'Action wurde erfolgreich gelöscht.',
- 'Automatic actions for the project "%s"' => 'Automatische Aktionen für dieses Projekt « %s »',
+ 'Action removed successfully.' => 'Aktion wurde erfolgreich gelöscht.',
+ 'Automatic actions for the project "%s"' => 'Automatische Aktionen für das Projekt «%s»',
'Defined actions' => 'Definierte Aktionen',
'Add an action' => 'Aktion hinzufügen',
'Event name' => 'Ereignis',
@@ -235,15 +235,15 @@ return array(
'Action parameters' => 'Aktionsparameter',
'Action' => 'Aktion',
'Event' => 'Ereignis',
- 'When the selected event occurs execute the corresponding action.' => 'Wenn des gewählte Ereignis eintritt, führe die passende Aktion aus.',
+ 'When the selected event occurs execute the corresponding action.' => 'Wenn des gewählte Ereignis eintritt, führe die zugehörige Aktion aus.',
'Next step' => 'Nächster Schritt',
'Define action parameters' => 'Aktionsparameter definieren',
'Save this action' => 'Aktion speichern',
- 'Do you really want to remove this action: "%s"?' => 'Willst Du diese Aktion wirklich löschen « %s » ?',
+ 'Do you really want to remove this action: "%s"?' => 'Willst Du diese Aktion wirklich löschen «%s»?',
'Remove an automatic action' => 'Löschen einer automatischen Aktion',
- 'Close the task' => 'Aufgabe schießen',
+ 'Close the task' => 'Aufgabe schließen',
'Assign the task to a specific user' => 'Aufgabe einem Benutzer zuordnen',
- 'Assign the task to the person who does the action' => 'Aufgabe dem Benutzer zuordnen der die Aktion aufgeführt hat',
+ 'Assign the task to the person who does the action' => 'Aufgabe dem Benutzer zuordnen der die Aktion ausgeführt hat',
'Duplicate the task to another project' => 'Aufgabe in ein anderes Projekt kopieren',
'Move a task to another column' => 'Aufgabe in eine andere Spalte verschieben',
'Move a task to another position in the same column' => 'Aufgabe an eine andere Position in der gleichen Spalte verschieben',
@@ -268,7 +268,7 @@ return array(
'Do you really want to remove this comment?' => 'Willst Du diesen Kommentar wirklich löschen?',
'Only administrators or the creator of the comment can access to this page.' => 'Nur Administratoren und der Ersteller des Kommentars könne diese Seite verwenden.',
'Details' => 'Details',
- 'Current password for the user "%s"' => 'Aktuelles Passwort für den Benutzer « %s »',
+ 'Current password for the user "%s"' => 'Aktuelles Passwort für den Benutzer «%s»',
'The current password is required' => 'Das aktuelle Passwort wird benötigt',
'Wrong password' => 'Falsches Passwort',
'Reset all tokens' => 'Alle Tokens zurücksetzten',
@@ -278,8 +278,8 @@ return array(
'Login date' => 'Anmeldedatum',
'Authentication method' => 'Anmeldemethode',
'IP address' => 'IP Adresse',
- 'User agent' => 'Benutzer Agent',
- 'Persistent connections' => 'Bestende Verbindungen',
+ 'User agent' => 'User Agent',
+ 'Persistent connections' => 'Bestehende Verbindungen',
'No session' => 'Keine Session',
'Expiration date' => 'Ablaufdatum',
'Remember Me' => 'Angemeldet bleiben',
@@ -291,23 +291,31 @@ return array(
'Closed' => 'Geschlossen',
'Search' => 'Suchen',
'Nothing found.' => 'Nichts gefunden.',
- 'Search in the project "%s"' => 'Suche im Projekt « %s »',
+ 'Search in the project "%s"' => 'Suche im Projekt «%s»',
'Due date' => 'Fälligkeitsdatum',
'Others formats accepted: %s and %s' => 'Andere akzepierte Formate : %s und %s',
'Description' => 'Beschreibung',
'%d comments' => '%d Kommentare',
'%d comment' => '%d Kommentar',
'Email address invalid' => 'EMail Adresse ungültig',
- 'Your Google Account is not linked anymore to your profile.' => 'Dein Google Accout ist nicht mit den Profil verbunden.',
- 'Unable to unlink your Google Account.' => 'Trennung der Verbindung mit dem Google Accout nicht möglich.',
+ 'Your Google Account is not linked anymore to your profile.' => 'Dein Google Account ist nicht mehr mit deinem Profil verbunden.',
+ 'Unable to unlink your Google Account.' => 'Trennung der Verbindung mit deinem Google Account nicht möglich.',
'Google authentication failed' => 'Zugang mit Google nicht möglich',
- 'Unable to link your Google Account.' => 'Verbindung mit dem Google Accout nicht möglich.',
- 'Your Google Account is linked to your profile successfully.' => 'Der Google Account wurde erfolgreich verbunden.',
+ 'Unable to link your Google Account.' => 'Verbindung mit deinem Google Account nicht möglich.',
+ 'Your Google Account is linked to your profile successfully.' => 'Dein Google Account wurde erfolgreich verbunden.',
'Email' => 'Email',
'Link my Google Account' => 'Verbinde meinen Google Account',
'Unlink my Google Account' => 'Verbindung mit meinem Google Account trennen',
'Login with my Google Account' => 'Anmelden mit meinem Google Account',
'Project not found.' => 'Das Projekt wurde nicht gefunden.',
+ 'Your GitHub account was successfully linked to your profile.' => 'Dein GitHub Account wurde erfolgreich mit deinem Profil verbunden.',
+ 'Unable to link your GitHub Account.' => 'Verbindung mit deinem GitHub Account nicht möglich.',
+ 'GitHub authentication failed' => 'Zugang mit GitHub nicht möglich',
+ 'Your GitHub account is no longer linked to your profile.' => 'Dein GitHub Account ist nicht mehr mit deinem Profil verbunden.',
+ 'Unable to unlink your GitHub Account.' => 'Trennung der Verbindung mit deinem GitHub Account nicht möglich.',
+ 'Login with my GitHub Account' => 'Anmelden mit meinem GitHub Account',
+ 'Link my GitHub Account' => 'Mit meinem GitHub Account verbinden',
+ 'Unlink my GitHub Account' => 'Verbindung mit meinem GitHub Account trennen',
// 'Task #%d' => '',
// 'Task removed successfully.' => '',
// 'Unable to remove this task.' => '',
diff --git a/app/Locales/es_ES/translations.php b/app/Locales/es_ES/translations.php
index 70bb9ffa..98ab30ca 100644
--- a/app/Locales/es_ES/translations.php
+++ b/app/Locales/es_ES/translations.php
@@ -376,4 +376,12 @@ return array(
'Unable to upload the file.' => 'No pude cargar el fichero.',
'Actions' => 'Acciones',
// 'Display another project' => '',
+ // 'Your GitHub account was successfully linked to your profile.' => '',
+ // 'Unable to link your GitHub Account.' => '',
+ // 'GitHub authentication failed' => '',
+ // 'Your GitHub account is no longer linked to your profile.' => '',
+ // 'Unable to unlink your GitHub Account.' => '',
+ // 'Login with my GitHub Account' => '',
+ // 'Link my GitHub Account' => '',
+ // 'Unlink my GitHub Account' => '',
);
diff --git a/app/Locales/fr_FR/translations.php b/app/Locales/fr_FR/translations.php
index 37189680..4405a9a3 100644
--- a/app/Locales/fr_FR/translations.php
+++ b/app/Locales/fr_FR/translations.php
@@ -374,4 +374,12 @@ return array(
'Maximum size: ' => 'Taille maximum : ',
'Unable to upload the file.' => 'Impossible de transférer le fichier.',
'Display another project' => 'Afficher un autre projet',
+ 'Your GitHub account was successfully linked to your profile.' => 'Votre compte Github est désormais lié avec votre profile.',
+ 'Unable to link your GitHub Account.' => 'Impossible de lier votre compte Github.',
+ 'GitHub authentication failed' => 'L\'authentification avec Github à échouée',
+ 'Your GitHub account is no longer linked to your profile.' => 'Votre compte Github n\'est plus relié avec votre profile.',
+ 'Unable to unlink your GitHub Account.' => 'Impossible de déconnecter votre compte Github.',
+ 'Login with my GitHub Account' => 'Se connecter avec mon compte Github',
+ 'Link my GitHub Account' => 'Lier mon compte Github',
+ 'Unlink my GitHub Account' => 'Ne plus utiliser mon compte Github',
);
diff --git a/app/Locales/pl_PL/translations.php b/app/Locales/pl_PL/translations.php
index 1253be23..7e552d6a 100644
--- a/app/Locales/pl_PL/translations.php
+++ b/app/Locales/pl_PL/translations.php
@@ -377,4 +377,12 @@ return array(
// 'Maximum size: ' => '',
// 'Unable to upload the file.' => '',
// 'Display another project' => '',
+ // 'Your GitHub account was successfully linked to your profile.' => '',
+ // 'Unable to link your GitHub Account.' => '',
+ // 'GitHub authentication failed' => '',
+ // 'Your GitHub account is no longer linked to your profile.' => '',
+ // 'Unable to unlink your GitHub Account.' => '',
+ // 'Login with my GitHub Account' => '',
+ // 'Link my GitHub Account' => '',
+ // 'Unlink my GitHub Account' => '',
);
diff --git a/app/Locales/pt_BR/translations.php b/app/Locales/pt_BR/translations.php
index 19fd412d..9a571bad 100644
--- a/app/Locales/pt_BR/translations.php
+++ b/app/Locales/pt_BR/translations.php
@@ -374,4 +374,12 @@ return array(
// 'Maximum size: ' => '',
// 'Unable to upload the file.' => '',
// 'Display another project' => '',
+ // 'Your GitHub account was successfully linked to your profile.' => '',
+ // 'Unable to link your GitHub Account.' => '',
+ // 'GitHub authentication failed' => '',
+ // 'Your GitHub account is no longer linked to your profile.' => '',
+ // 'Unable to unlink your GitHub Account.' => '',
+ // 'Login with my GitHub Account' => '',
+ // 'Link my GitHub Account' => '',
+ // 'Unlink my GitHub Account' => '',
);
diff --git a/app/Locales/sv_SE/translations.php b/app/Locales/sv_SE/translations.php
index 79be2132..b59217a1 100644
--- a/app/Locales/sv_SE/translations.php
+++ b/app/Locales/sv_SE/translations.php
@@ -376,4 +376,12 @@ return array(
'Maximum size: ' => 'Maxstorlek: ',
'Unable to upload the file.' => 'Kunde inte ladda upp filen.',
// 'Display another project' => '',
+ // 'Your GitHub account was successfully linked to your profile.' => '',
+ // 'Unable to link your GitHub Account.' => '',
+ // 'GitHub authentication failed' => '',
+ // 'Your GitHub account is no longer linked to your profile.' => '',
+ // 'Unable to unlink your GitHub Account.' => '',
+ // 'Login with my GitHub Account' => '',
+ // 'Link my GitHub Account' => '',
+ // 'Unlink my GitHub Account' => '',
);
diff --git a/app/Locales/zh_CN/translations.php b/app/Locales/zh_CN/translations.php
index 2144fcef..86628756 100644
--- a/app/Locales/zh_CN/translations.php
+++ b/app/Locales/zh_CN/translations.php
@@ -382,4 +382,12 @@ return array(
// 'Maximum size: ' => '',
// 'Unable to upload the file.' => '',
// 'Display another project' => '',
+ // 'Your GitHub account was successfully linked to your profile.' => '',
+ // 'Unable to link your GitHub Account.' => '',
+ // 'GitHub authentication failed' => '',
+ // 'Your GitHub account is no longer linked to your profile.' => '',
+ // 'Unable to unlink your GitHub Account.' => '',
+ // 'Login with my GitHub Account' => '',
+ // 'Link my GitHub Account' => '',
+ // 'Unlink my GitHub Account' => '',
);
diff --git a/app/Model/Acl.php b/app/Model/Acl.php
index 035fd7c3..8a87a6b2 100644
--- a/app/Model/Acl.php
+++ b/app/Model/Acl.php
@@ -17,7 +17,7 @@ class Acl extends Base
* @var array
*/
private $public_actions = array(
- 'user' => array('login', 'check', 'google'),
+ 'user' => array('login', 'check', 'google', 'github'),
'task' => array('add'),
'board' => array('readonly'),
);
@@ -32,7 +32,7 @@ class Acl extends Base
'app' => array('index'),
'board' => array('index', 'show', 'assign', 'assigntask', 'save', 'check'),
'project' => array('tasks', 'index', 'forbidden', 'search'),
- 'user' => array('index', 'edit', 'update', 'forbidden', 'logout', 'index', 'unlinkgoogle'),
+ 'user' => array('index', 'edit', 'update', 'forbidden', 'logout', 'index', 'unlinkgoogle', 'unlinkgithub'),
'config' => array('index', 'removeremembermetoken'),
'comment' => array('create', 'save', 'confirm', 'remove', 'update', 'edit', 'forbidden'),
'file' => array('create', 'save', 'download', 'confirm', 'remove', 'open', 'image'),
diff --git a/app/Model/GitHub.php b/app/Model/GitHub.php
new file mode 100644
index 00000000..3380218d
--- /dev/null
+++ b/app/Model/GitHub.php
@@ -0,0 +1,178 @@
+<?php
+
+namespace Model;
+
+require __DIR__.'/../../vendor/OAuth/bootstrap.php';
+
+use OAuth\Common\Storage\Session;
+use OAuth\Common\Consumer\Credentials;
+use OAuth\Common\Http\Uri\UriFactory;
+use OAuth\ServiceFactory;
+use OAuth\Common\Http\Exception\TokenResponseException;
+
+/**
+ * GitHub model
+ *
+ * @package model
+ */
+class GitHub extends Base
+{
+ /**
+ * Authenticate a GitHub user
+ *
+ * @access public
+ * @param string $github_id GitHub user id
+ * @return boolean
+ */
+ public function authenticate($github_id)
+ {
+ $userModel = new User($this->db, $this->event);
+
+ $user = $userModel->getByGitHubId($github_id);
+
+ if ($user) {
+
+ // Create the user session
+ $userModel->updateSession($user);
+
+ // Update login history
+ $lastLogin = new LastLogin($this->db, $this->event);
+ $lastLogin->create(
+ LastLogin::AUTH_GITHUB,
+ $user['id'],
+ $userModel->getIpAddress(),
+ $userModel->getUserAgent()
+ );
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Unlink a GitHub account for a given user
+ *
+ * @access public
+ * @param integer $user_id User id
+ * @return boolean
+ */
+ public function unlink($user_id)
+ {
+ $userModel = new User($this->db, $this->event);
+
+ return $userModel->update(array(
+ 'id' => $user_id,
+ 'github_id' => '',
+ ));
+ }
+
+ /**
+ * Update the user table based on the GitHub profile information
+ *
+ * @access public
+ * @param integer $user_id User id
+ * @param array $profile GitHub profile
+ * @return boolean
+ * @todo Don't overwrite existing email/name with empty GitHub data
+ */
+ public function updateUser($user_id, array $profile)
+ {
+ $userModel = new User($this->db, $this->event);
+
+ return $userModel->update(array(
+ 'id' => $user_id,
+ 'github_id' => $profile['id'],
+ 'email' => $profile['email'],
+ 'name' => $profile['name'],
+ ));
+ }
+
+ /**
+ * Get the GitHub service instance
+ *
+ * @access public
+ * @return \OAuth\OAuth2\Service\GitHub
+ */
+ public function getService()
+ {
+ $uriFactory = new UriFactory();
+ $currentUri = $uriFactory->createFromSuperGlobalArray($_SERVER);
+ $currentUri->setQuery('controller=user&action=gitHub');
+
+ $storage = new Session(false);
+
+ $credentials = new Credentials(
+ GITHUB_CLIENT_ID,
+ GITHUB_CLIENT_SECRET,
+ $currentUri->getAbsoluteUri()
+ );
+
+ $serviceFactory = new ServiceFactory();
+
+ return $serviceFactory->createService(
+ 'gitHub',
+ $credentials,
+ $storage,
+ array('')
+ );
+ }
+
+ /**
+ * Get the authorization URL
+ *
+ * @access public
+ * @return \OAuth\Common\Http\Uri\Uri
+ */
+ public function getAuthorizationUrl()
+ {
+ return $this->getService()->getAuthorizationUri();
+ }
+
+ /**
+ * Get GitHub profile information from the API
+ *
+ * @access public
+ * @param string $code GitHub authorization code
+ * @return bool|array
+ */
+ public function getGitHubProfile($code)
+ {
+ try {
+ $gitHubService = $this->getService();
+ $gitHubService->requestAccessToken($code);
+
+ return json_decode($gitHubService->request('user'), true);
+ }
+ catch (TokenResponseException $e) {
+ return false;
+ }
+
+ return false;
+ }
+
+ /**
+ * Revokes this user's GitHub tokens for Kanboard
+ *
+ * @access public
+ * @return bool|array
+ * @todo Currently this simply removes all our tokens for this user, ideally it should
+ * restrict itself to the one in question
+ */
+ public function revokeGitHubAccess()
+ {
+ try {
+ $gitHubService = $this->getService();
+
+ $basicAuthHeader = array('Authorization' => 'Basic ' .
+ base64_encode(GITHUB_CLIENT_ID.':'.GITHUB_CLIENT_SECRET));
+
+ return json_decode($gitHubService->request('/applications/'.GITHUB_CLIENT_ID.'/tokens', 'DELETE', null, $basicAuthHeader), true);
+ }
+ catch (TokenResponseException $e) {
+ return false;
+ }
+
+ return false;
+ }
+}
diff --git a/app/Model/LastLogin.php b/app/Model/LastLogin.php
index 56739b48..db4c4a57 100644
--- a/app/Model/LastLogin.php
+++ b/app/Model/LastLogin.php
@@ -33,6 +33,7 @@ class LastLogin extends Base
const AUTH_REMEMBER_ME = 'remember_me';
const AUTH_LDAP = 'ldap';
const AUTH_GOOGLE = 'google';
+ const AUTH_GITHUB = 'github';
/**
* Create a new record
diff --git a/app/Model/User.php b/app/Model/User.php
index 8769d69a..ba1acb90 100644
--- a/app/Model/User.php
+++ b/app/Model/User.php
@@ -52,6 +52,18 @@ class User extends Base
}
/**
+ * Get a specific user by the GitHub id
+ *
+ * @access public
+ * @param string $github_id GitHub user id
+ * @return array
+ */
+ public function getByGitHubId($github_id)
+ {
+ return $this->db->table(self::TABLE)->eq('github_id', $github_id)->findOne();
+ }
+
+ /**
* Get a specific user by the username
*
* @access public
diff --git a/app/Schema/Mysql.php b/app/Schema/Mysql.php
index 7ff130cf..a30b0989 100644
--- a/app/Schema/Mysql.php
+++ b/app/Schema/Mysql.php
@@ -3,7 +3,12 @@
namespace Schema;
use Core\Security;
-const VERSION = 19;
+const VERSION = 20;
+
+function version_20($pdo)
+{
+ $pdo->exec("ALTER TABLE users ADD COLUMN github_id VARCHAR(30)");
+}
function version_19($pdo)
{
diff --git a/app/Schema/Sqlite.php b/app/Schema/Sqlite.php
index 8f8e498a..6ea0541c 100644
--- a/app/Schema/Sqlite.php
+++ b/app/Schema/Sqlite.php
@@ -3,7 +3,12 @@
namespace Schema;
use Core\Security;
-const VERSION = 19;
+const VERSION = 20;
+
+function version_20($pdo)
+{
+ $pdo->exec("ALTER TABLE users ADD COLUMN github_id TEXT");
+}
function version_19($pdo)
{
diff --git a/app/Templates/board_task.php b/app/Templates/board_task.php
index fa745ac1..89f768e0 100644
--- a/app/Templates/board_task.php
+++ b/app/Templates/board_task.php
@@ -20,13 +20,13 @@
<?php else: ?>
- <a href="?controller=task&amp;action=edit&amp;task_id=<?= $task['id'] ?>" title="<?= t('Edit this task') ?>">#<?= $task['id'] ?></a> -
+ <a class="task-board-popover" href="?controller=task&amp;action=edit&amp;task_id=<?= $task['id'] ?>" title="<?= t('Edit this task') ?>">#<?= $task['id'] ?></a> -
<span class="task-board-user">
<?php if (! empty($task['owner_id'])): ?>
- <a href="?controller=board&amp;action=assign&amp;task_id=<?= $task['id'] ?>" title="<?= t('Change assignee') ?>"><?= t('Assigned to %s', $task['username']) ?></a>
+ <a class="task-board-popover" href="?controller=board&amp;action=assign&amp;task_id=<?= $task['id'] ?>" title="<?= t('Change assignee') ?>"><?= t('Assigned to %s', $task['username']) ?></a>
<?php else: ?>
- <a href="?controller=board&amp;action=assign&amp;task_id=<?= $task['id'] ?>" title="<?= t('Change assignee') ?>" class="task-board-nobody"><?= t('Nobody assigned') ?></a>
+ <a class="task-board-popover" href="?controller=board&amp;action=assign&amp;task_id=<?= $task['id'] ?>" title="<?= t('Change assignee') ?>" class="task-board-nobody"><?= t('Nobody assigned') ?></a>
<?php endif ?>
</span>
@@ -69,8 +69,8 @@
<?php endif ?>
<?php if (! empty($task['description'])): ?>
- <i class="fa fa-file-text-o" title="<?= t('Description') ?>"></i>
+ <a class="task-board-popover" href='?controller=task&amp;action=editDescription&amp;task_id=<?= $task['id'] ?>'><i class="fa fa-file-text-o" title="<?= t('Description') ?>"></i></a>
<?php endif ?>
</div>
</div>
-<?php endif ?> \ No newline at end of file
+<?php endif ?>
diff --git a/app/Templates/task_edit.php b/app/Templates/task_edit.php
index 015f746d..07c3539b 100644
--- a/app/Templates/task_edit.php
+++ b/app/Templates/task_edit.php
@@ -1,12 +1,14 @@
<section id="main">
<div class="page-header">
<h2><?= t('Edit a task') ?></h2>
+<?php if (!$ajax): ?>
<ul>
<li><a href="?controller=board&amp;action=show&amp;project_id=<?= $task['project_id'] ?>"><?= t('Back to the board') ?></a></li>
</ul>
+<?php endif ?>
</div>
<section>
- <form method="post" action="?controller=task&amp;action=update&amp;task_id=<?= $task['id'] ?>" autocomplete="off">
+ <form method="post" action="?controller=task&amp;action=update&amp;task_id=<?= $task['id'] ?>&amp;ajax=<?= $ajax ?>" autocomplete="off">
<?= Helper\form_csrf() ?>
@@ -47,7 +49,12 @@
<div class="form-actions">
<input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
- <?= t('or') ?> <a href="?controller=task&amp;action=show&amp;task_id=<?= $task['id'] ?>"><?= t('cancel') ?></a>
+ <?= t('or') ?>
+<?php if ($ajax): ?>
+ <a href="?controller=board&amp;action=show&amp;project_id=<?= $task['project_id'] ?>"><?= t('cancel') ?></a>
+<?php else: ?>
+ <a href="?controller=task&amp;action=show&amp;task_id=<?= $task['id'] ?>"><?= t('cancel') ?></a>
+<?php endif ?>
</div>
</form>
</section>
diff --git a/app/Templates/task_edit_description.php b/app/Templates/task_edit_description.php
index 550dac73..ba0d3887 100644
--- a/app/Templates/task_edit_description.php
+++ b/app/Templates/task_edit_description.php
@@ -2,7 +2,7 @@
<h2><?= t('Edit the description') ?></h2>
</div>
-<form method="post" action="?controller=task&amp;action=saveDescription&amp;task_id=<?= $task['id'] ?>" autocomplete="off">
+<form method="post" action="?controller=task&amp;action=saveDescription&amp;task_id=<?= $task['id'] ?>&amp;ajax=<?= $ajax ?>" autocomplete="off">
<?= Helper\form_csrf() ?>
@@ -13,6 +13,11 @@
<div class="form-actions">
<input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
<?= t('or') ?>
+<?php if ($ajax): ?>
+ <a href="?controller=board&amp;action=show&amp;project_id=<?= $task['project_id'] ?>"><?= t('cancel') ?></a>
+<?php else: ?>
<a href="?controller=task&amp;action=show&amp;task_id=<?= $task['id'] ?>"><?= t('cancel') ?></a>
+<?php endif ?>
</div>
</form>
+
diff --git a/app/Templates/user_edit.php b/app/Templates/user_edit.php
index 6b83f748..8fba922f 100644
--- a/app/Templates/user_edit.php
+++ b/app/Templates/user_edit.php
@@ -48,14 +48,27 @@
<?= Helper\form_checkbox('is_admin', t('Administrator'), 1, isset($values['is_admin']) && $values['is_admin'] == 1 ? true : false) ?><br/>
<?php endif ?>
+ <ul>
<?php if (GOOGLE_AUTH && Helper\is_current_user($values['id'])): ?>
+ <li>
<?php if (empty($values['google_id'])): ?>
<a href="?controller=user&amp;action=google<?= Helper\param_csrf() ?>"><?= t('Link my Google Account') ?></a>
<?php else: ?>
<a href="?controller=user&amp;action=unlinkGoogle<?= Helper\param_csrf() ?>"><?= t('Unlink my Google Account') ?></a>
<?php endif ?>
+ </li>
<?php endif ?>
+ <?php if (GITHUB_AUTH && Helper\is_current_user($values['id'])): ?>
+ <li>
+ <?php if (empty($values['github_id'])): ?>
+ <a href="?controller=user&amp;action=gitHub<?= Helper\param_csrf() ?>"><?= t('Link my GitHub Account') ?></a>
+ <?php else: ?>
+ <a href="?controller=user&amp;action=unlinkGitHub<?= Helper\param_csrf() ?>"><?= t('Unlink my GitHub Account') ?></a>
+ <?php endif ?>
+ </li>
+ <?php endif ?>
+ </ul>
</div>
<div class="form-actions">
diff --git a/app/Templates/user_login.php b/app/Templates/user_login.php
index 49902ebb..948a19e7 100644
--- a/app/Templates/user_login.php
+++ b/app/Templates/user_login.php
@@ -18,12 +18,20 @@
<?= Helper\form_checkbox('remember_me', t('Remember Me'), 1) ?><br/>
+ <ul>
<?php if (GOOGLE_AUTH): ?>
- <p>
+ <li>
<a href="?controller=user&amp;action=google"><?= t('Login with my Google Account') ?></a>
- </p>
+ </li>
<?php endif ?>
+ <?php if (GITHUB_AUTH): ?>
+ <li>
+ <a href="?controller=user&amp;action=gitHub"><?= t('Login with my GitHub Account') ?></a>
+ </li>
+ <?php endif ?>
+ </ul>
+
<div class="form-actions">
<input type="submit" value="<?= t('Sign in') ?>" class="btn btn-blue"/>
</div>
diff --git a/app/common.php b/app/common.php
index c5fb34e2..d607cf92 100644
--- a/app/common.php
+++ b/app/common.php
@@ -53,6 +53,11 @@ defined('GOOGLE_AUTH') or define('GOOGLE_AUTH', false);
defined('GOOGLE_CLIENT_ID') or define('GOOGLE_CLIENT_ID', '');
defined('GOOGLE_CLIENT_SECRET') or define('GOOGLE_CLIENT_SECRET', '');
+// GitHub authentication
+defined('GITHUB_AUTH') or define('GITHUB_AUTH', false);
+defined('GITHUB_CLIENT_ID') or define('GITHUB_CLIENT_ID', '');
+defined('GITHUB_CLIENT_SECRET') or define('GITHUB_CLIENT_SECRET', '');
+
$loader = new Loader;
$loader->execute();
diff --git a/assets/css/app.css b/assets/css/app.css
index 60d37abc..b35ce05e 100644
--- a/assets/css/app.css
+++ b/assets/css/app.css
@@ -249,11 +249,21 @@ input.form-date {
padding-left: 0;
}
+.form-column ul {
+ margin-top: 15px;
+}
+
.form-login {
padding-left: 20px;
width: 430px;
}
+.form-column li,
+.form-login li {
+ margin-left: 25px;
+ line-height: 25px;
+}
+
/* alerts */
.alert {
padding: 8px 35px 8px 14px;
diff --git a/assets/js/board.js b/assets/js/board.js
index f43f3f57..0487005d 100644
--- a/assets/js/board.js
+++ b/assets/js/board.js
@@ -15,14 +15,14 @@
});
// Open assignee popover
- $(".task-board-user a").click(function(e) {
+ $(".task-board-popover").click(function(e) {
e.preventDefault();
e.stopPropagation();
- var taskId = $(this).parent().parent().attr("data-task-id");
+ var href = $(this).attr('href');
- $.get("?controller=board&action=assign&task_id=" + taskId, function(data) {
+ $.get(href, function(data) {
popover_show(data);
});
});
diff --git a/config.default.php b/config.default.php
index e3551994..027d8417 100644
--- a/config.default.php
+++ b/config.default.php
@@ -62,3 +62,12 @@ define('GOOGLE_CLIENT_ID', '');
// Google client secret key (Get this value from the Google developer console)
define('GOOGLE_CLIENT_SECRET', '');
+
+// Enable/disable GitHub authentication
+define('GITHUB_AUTH', false);
+
+// GitHub client id (Copy it from your settings -> Applications -> Developer applications)
+define('GITHUB_CLIENT_ID', '');
+
+// GitHub client secret key (Copy it from your settings -> Applications -> Developer applications)
+define('GITHUB_CLIENT_SECRET', '');
diff --git a/docs/github-authentication.markdown b/docs/github-authentication.markdown
new file mode 100644
index 00000000..ef99bb80
--- /dev/null
+++ b/docs/github-authentication.markdown
@@ -0,0 +1,62 @@
+GitHub Authentication
+=====================
+
+Requirements
+------------
+
+- OAuth GitHub API credentials (available in your [Settings > Applications > Developer applications](https://github.com/settings/applications))
+
+How does this work?
+-------------------
+
+The GitHub authentication in Kanboard uses the [OAuth 2.0](http://oauth.net/2/) protocol, so any user of Kanboard can be linked to a GitHub account. When that is done, they no longer need to manually login with their Kanboard account, but can simply automatically login with their GitHub account.
+
+How to link a GitHub account
+----------------------------------
+
+1. Login to Kanboard with the desired user
+2. Go to the **Edit user** page and click on the link **Link my GitHub Account**
+3. You are redirected to the GitHub **Authorize application** form, authorize Kanboard by clicking on the button **Accept**
+4. Finally, you are redirected to Kanboard and now your user account is linked to your GitHub account
+5. During the process, Kanboard has updated your full name and your email address based on your GitHub profile, if either of those are publically available
+6. Log out of Kanboard and you should be able to login directly with GitHub by clicking on the link **Login with my GitHub Account**
+
+Installation instructions
+-------------------------
+
+### Setting up OAuth 2.0
+
+If you know what you're doing, you can directly go to the ["Register a new OAuth application"](https://github.com/settings/applications/new) site, set everything up and skip to [Setting up Kanboard](#setting-up-kanboard) below.
+
+Summarizing the [official GitHub documentation](https://developer.github.com/guides/basics-of-authentication/#registering-your-app):
+
+- Open your [**Settings**](https://github.com/settings), select [**Applications**](https://github.com/settings/applications) from the sidebar and click on [**Register new application**](https://github.com/settings/applications/new) on the top, next to where it says **Developer applications**
+- Fill out the form with whatever values you like, only the **Authorization callback URL** _must_ be: **http://YOUR_SERVER/?controller=user&action=gitHub**
+
+### Setting up Kanboard
+
+Either create a new `config.php` file or copy and rename the `config.default.php` file and set the following values:
+
+```php
+// Enable/disable GitHub authentication
+define('GITHUB_AUTH', true);
+
+// GitHub client id (Copy it from your settings -> Applications -> Developer applications)
+define('GITHUB_CLIENT_ID', 'YOUR_GITHUB_CLIENT_ID');
+
+// GitHub client secret key (Copy it from your settings -> Applications -> Developer applications)
+define('GITHUB_CLIENT_SECRET', 'YOUR_GITHUB_CLIENT_SECRET');
+
+```
+
+Notes
+-----
+**Important:** _*Never*_ store your GITHUB_CLIENT_ID or GITHUB_CLIENT_SECRET in GitHub or somewhere with full public access in general!
+
+Kanboard uses these information from your public GitHub profile:
+
+- Full name
+- Public email address
+- GitHub unique id
+
+The GitHub unique id is used to link the local user account and the GitHub account.
diff --git a/jsonrpc.php b/jsonrpc.php
index 981fefa2..186e454d 100644
--- a/jsonrpc.php
+++ b/jsonrpc.php
@@ -10,7 +10,7 @@ use Model\User;
use Model\Config;
use Model\Category;
use Model\Comment;
-use Model\Subtask;
+use Model\SubTask;
use Model\Board;
use Model\Action;
@@ -20,7 +20,7 @@ $task = new Task($registry->shared('db'), $registry->shared('event'));
$user = new User($registry->shared('db'), $registry->shared('event'));
$category = new Category($registry->shared('db'), $registry->shared('event'));
$comment = new Comment($registry->shared('db'), $registry->shared('event'));
-$subtask = new Subtask($registry->shared('db'), $registry->shared('event'));
+$subtask = new SubTask($registry->shared('db'), $registry->shared('event'));
$board = new Board($registry->shared('db'), $registry->shared('event'));
$action = new Action($registry->shared('db'), $registry->shared('event'));
@@ -123,6 +123,14 @@ $server->register('updateTask', function($values) use ($task) {
return $valid && $task->update($values);
});
+$server->register('openTask', function($task_id) use ($task) {
+ return $task->open($task_id);
+});
+
+$server->register('closeTask', function($task_id) use ($task) {
+ return $task->close($task_id);
+});
+
$server->register('removeTask', function($task_id) use ($task) {
return $task->remove($task_id);
});
diff --git a/scripts/make-archive.sh b/scripts/make-archive.sh
index 0c1d8fee..7edb90c7 100755
--- a/scripts/make-archive.sh
+++ b/scripts/make-archive.sh
@@ -5,7 +5,7 @@ APP="kanboard"
cd /tmp
rm -rf /tmp/$APP /tmp/$APP-*.zip 2>/dev/null
-git clone git@github.com:fguillot/$APP.git
+git clone https://github.com/fguillot/$APP.git
rm -rf $APP/data/*.sqlite $APP/.git $APP/.gitignore $APP/scripts $APP/tests $APP/Vagrantfile $APP/.*.yml $APP/phpunit.xml $APP/README.markdown $APP/docs
sed -i.bak s/master/$VERSION/g $APP/app/common.php && rm -f $APP/app/*.bak
zip -r $APP-$VERSION.zip $APP