mirror of
https://github.com/YunoHost-Apps/kanboard_ynh.git
synced 2024-09-03 19:36:17 +02:00
Merge pull request #22 from mbugeia/Dev
Update to Kanboard v1.0.24 with reverse-proxy-ldap plugin
This commit is contained in:
commit
69ae0e4f34
754 changed files with 31659 additions and 23546 deletions
|
@ -33,7 +33,8 @@ From command line:
|
|||
|
||||
Infos
|
||||
-----
|
||||
Kanboard v1.0.21
|
||||
Kanboard v1.0.24
|
||||
Reverse-Proxy Authentication with LDAP user provider plugin v1.0.0 https://github.com/kanboard/plugin-reverse-proxy-ldap
|
||||
|
||||
Yunohost forum thread: <https://forum.yunohost.org/t/kanboard-package/78>
|
||||
|
||||
|
|
|
@ -28,19 +28,6 @@ define('MAIL_SMTP_ENCRYPTION', null); // Valid values are "null", "ssl" or "tls"
|
|||
// Sendmail command to use when the transport is "sendmail"
|
||||
define('MAIL_SENDMAIL_COMMAND', '/usr/sbin/sendmail -bs');
|
||||
|
||||
// Postmark API token (used to send emails through their API)
|
||||
define('POSTMARK_API_TOKEN', '');
|
||||
|
||||
// Mailgun API key (used to send emails through their API)
|
||||
define('MAILGUN_API_TOKEN', '');
|
||||
|
||||
// Mailgun domain name
|
||||
define('MAILGUN_DOMAIN', '');
|
||||
|
||||
// Sendgrid API configuration
|
||||
define('SENDGRID_API_USER', '');
|
||||
define('SENDGRID_API_KEY', '');
|
||||
|
||||
// Database driver: sqlite, mysql or postgres (sqlite by default)
|
||||
define('DB_DRIVER', 'mysql');
|
||||
|
||||
|
@ -63,7 +50,7 @@ define('DB_PORT', null);
|
|||
define('LDAP_AUTH', false);
|
||||
|
||||
// LDAP server hostname
|
||||
define('LDAP_SERVER', '');
|
||||
define('LDAP_SERVER', 'localhost');
|
||||
|
||||
// LDAP server port (389 by default)
|
||||
define('LDAP_PORT', 389);
|
||||
|
@ -74,6 +61,10 @@ define('LDAP_SSL_VERIFY', true);
|
|||
// Enable LDAP START_TLS
|
||||
define('LDAP_START_TLS', false);
|
||||
|
||||
// By default Kanboard lowercase the ldap username to avoid duplicate users (the database is case sensitive)
|
||||
// Set to true if you want to preserve the case
|
||||
define('LDAP_USERNAME_CASE_SENSITIVE', false);
|
||||
|
||||
// LDAP bind type: "anonymous", "user" or "proxy"
|
||||
define('LDAP_BIND_TYPE', 'anonymous');
|
||||
|
||||
|
@ -84,43 +75,56 @@ define('LDAP_USERNAME', null);
|
|||
// LDAP password to use for proxy mode
|
||||
define('LDAP_PASSWORD', null);
|
||||
|
||||
// LDAP account base, i.e. root of all user account
|
||||
// Example: ou=People,dc=example,dc=com
|
||||
define('LDAP_ACCOUNT_BASE', '');
|
||||
// LDAP DN for users
|
||||
// Example for ActiveDirectory: CN=Users,DC=kanboard,DC=local
|
||||
// Example for OpenLDAP: ou=People,dc=example,dc=com
|
||||
define('LDAP_USER_BASE_DN', 'ou=users,dc=yunohost,dc=org');
|
||||
|
||||
// LDAP query pattern to use when searching for a user account
|
||||
// LDAP pattern to use when searching for a user account
|
||||
// Example for ActiveDirectory: '(&(objectClass=user)(sAMAccountName=%s))'
|
||||
// Example for OpenLDAP: 'uid=%s'
|
||||
define('LDAP_USER_PATTERN', '');
|
||||
define('LDAP_USER_FILTER', 'uid=%s');
|
||||
|
||||
// Name of an attribute of the user account object which should be used as the full name of the user
|
||||
define('LDAP_ACCOUNT_FULLNAME', 'displayname');
|
||||
|
||||
// Name of an attribute of the user account object which should be used as the email of the user
|
||||
define('LDAP_ACCOUNT_EMAIL', 'mail');
|
||||
|
||||
// Name of an attribute of the user account object which should be used as the id of the user. (optional)
|
||||
// LDAP attribute for username
|
||||
// Example for ActiveDirectory: 'samaccountname'
|
||||
// Example for OpenLDAP: 'uid'
|
||||
define('LDAP_ACCOUNT_ID', '');
|
||||
define('LDAP_USER_ATTRIBUTE_USERNAME', 'uid');
|
||||
|
||||
// LDAP Attribute for group membership
|
||||
define('LDAP_ACCOUNT_MEMBEROF', 'memberof');
|
||||
// LDAP attribute for user full name
|
||||
// Example for ActiveDirectory: 'displayname'
|
||||
// Example for OpenLDAP: 'cn'
|
||||
define('LDAP_USER_ATTRIBUTE_FULLNAME', 'displayname');
|
||||
|
||||
// DN for administrators
|
||||
// Example: CN=Kanboard Admins,CN=Users,DC=kanboard,DC=local
|
||||
// LDAP attribute for user email
|
||||
define('LDAP_USER_ATTRIBUTE_EMAIL', 'mail');
|
||||
|
||||
// LDAP attribute to find groups in user profile
|
||||
define('LDAP_USER_ATTRIBUTE_GROUPS', 'memberof');
|
||||
|
||||
// Allow automatic LDAP user creation
|
||||
define('LDAP_USER_CREATION', true);
|
||||
|
||||
// LDAP DN for administrators
|
||||
// Example: CN=Kanboard-Admins,CN=Users,DC=kanboard,DC=local
|
||||
define('LDAP_GROUP_ADMIN_DN', '');
|
||||
|
||||
// DN for project administrators
|
||||
// Example: CN=Kanboard Project Admins,CN=Users,DC=kanboard,DC=local
|
||||
define('LDAP_GROUP_PROJECT_ADMIN_DN', '');
|
||||
// LDAP DN for managers
|
||||
// Example: CN=Kanboard Managers,CN=Users,DC=kanboard,DC=local
|
||||
define('LDAP_GROUP_MANAGER_DN', '');
|
||||
|
||||
// By default Kanboard lowercase the ldap username to avoid duplicate users (the database is case sensitive)
|
||||
// Set to true if you want to preserve the case
|
||||
define('LDAP_USERNAME_CASE_SENSITIVE', false);
|
||||
// Enable LDAP group provider for project permissions
|
||||
// The end-user will be able to browse LDAP groups from the user interface and allow access to specified projects
|
||||
define('LDAP_GROUP_PROVIDER', false);
|
||||
|
||||
// Automatically create user account
|
||||
define('LDAP_ACCOUNT_CREATION', true);
|
||||
// LDAP Base DN for groups
|
||||
define('LDAP_GROUP_BASE_DN', '');
|
||||
|
||||
// LDAP group filter
|
||||
// Example for ActiveDirectory: (&(objectClass=group)(sAMAccountName=%s*))
|
||||
define('LDAP_GROUP_FILTER', '');
|
||||
|
||||
// LDAP attribute for the group name
|
||||
define('LDAP_GROUP_ATTRIBUTE_NAME', 'cn');
|
||||
|
||||
// Enable/disable Google authentication
|
||||
define('GOOGLE_AUTH', false);
|
||||
|
@ -168,7 +172,7 @@ define('GITLAB_OAUTH_TOKEN_URL', 'https://gitlab.com/oauth/token');
|
|||
define('GITLAB_API_URL', 'https://gitlab.com/api/v3/');
|
||||
|
||||
// Enable/disable the reverse proxy authentication
|
||||
define('REVERSE_PROXY_AUTH', true);
|
||||
define('REVERSE_PROXY_AUTH', false);
|
||||
|
||||
// Header name to use for the username
|
||||
define('REVERSE_PROXY_USER_HEADER', 'REMOTE_USER');
|
||||
|
|
|
@ -10,12 +10,19 @@ admin=$(sudo yunohost app setting $app adminusername)
|
|||
email=$(sudo yunohost user info $admin | grep mail: | sed "s/mail: //g")
|
||||
db_pwd=$(sudo yunohost app setting $app mysqlpwd)
|
||||
|
||||
# flush php sessions before upgrade
|
||||
sudo rm -rf /var/lib/php5/session/*
|
||||
|
||||
final_path=/var/www/$app
|
||||
sudo mv $final_path $final_path.old
|
||||
|
||||
sudo mkdir -p $final_path
|
||||
sudo cp -a ../sources/. $final_path
|
||||
sudo cp -a $final_path.old/data $final_path/data
|
||||
# restore data
|
||||
sudo cp -a $final_path.old/data $final_path
|
||||
# restore plugins
|
||||
sudo cp -a $final_path.old/plugins $final_path
|
||||
# delete temp directory
|
||||
sudo rm -Rf $final_path.old
|
||||
|
||||
# Copy and edit config.php
|
||||
|
|
|
@ -1,11 +1,118 @@
|
|||
Version 1.0.24
|
||||
--------------
|
||||
|
||||
New features:
|
||||
|
||||
* Forgot Password
|
||||
* Add drop-down menu on each board column title to close all tasks
|
||||
* Add Malay language
|
||||
* Add new API procedures for groups, roles, project permissions and to move/duplicate tasks to another project
|
||||
|
||||
Improvements:
|
||||
|
||||
* Avoid to send XHR request when a task has not moved after a drag and drop
|
||||
* Set maximum dropzone height when the individual column scrolling is disabled
|
||||
* Always show the search box in board selector
|
||||
* Replace logout link by a drop-down menu
|
||||
* Handle notification for group members attached to a project
|
||||
* Return the highest role for a project when a user is member of multiple groups
|
||||
* Show in user interface the saving state of the task
|
||||
* Add drop-down menu for subtasks, categories, swimlanes, columns, custom filters, task links and groups
|
||||
* Add new template hooks
|
||||
* Application settings are not cached anymore in the session
|
||||
* Do not check board status during task move
|
||||
* Move validators to a separate namespace
|
||||
* Improve and write unit tests for reports
|
||||
* Reduce the number of SQL queries for project daily column stats
|
||||
* Remove event subscriber to update date_moved field
|
||||
* Make sure that some event subscribers are not executed multiple times
|
||||
* Show rendering time of individual templates when debug mode is enabled
|
||||
* Make sure that no events are fired if nothing has been modified in the task
|
||||
* Make dashboard section title clickable
|
||||
* Add unit tests for LastLogin
|
||||
|
||||
Bug fixes:
|
||||
|
||||
* Automatic action listeners were using the same instance
|
||||
* Fix wrong link for category in task footer
|
||||
* Unable to set currency rate with Postgres database
|
||||
* Avoid automatic actions that change the color to fire subsequent events
|
||||
* Unable to unassign a task from the API
|
||||
* Revert back previous optimizations of TaskPosition (incompatibility with some environment)
|
||||
|
||||
Version 1.0.23
|
||||
--------------
|
||||
|
||||
Breaking changes:
|
||||
|
||||
* Plugin API changes for Automatic Actions
|
||||
* Automatic Action to close a task doesn't have the column parameter anymore (use the action "Close a task in a specific column")
|
||||
* Action name stored in the database is now the absolute class name
|
||||
* Core functionalities moved to external plugins:
|
||||
- Github Webhook: https://github.com/kanboard/plugin-github-webhook
|
||||
- Gitlab Webhook: https://github.com/kanboard/plugin-gitlab-webhook
|
||||
- Bitbucket Webhook: https://github.com/kanboard/plugin-bitbucket-webhook
|
||||
|
||||
New features:
|
||||
|
||||
* Added support of user mentions (@username)
|
||||
* Added report to compare working hours between open and closed tasks
|
||||
* Added the possibility to define custom routes from plugins
|
||||
* Added new method to remove metadata
|
||||
|
||||
Improvements:
|
||||
|
||||
* Improve Two-Factor activation and plugin API
|
||||
* Improving performance during task position change (SQL queries are 3 times faster than before)
|
||||
* Do not show window scrollbars when individual column scrolling is enabled
|
||||
* Automatic Actions code improvements and unit tests
|
||||
* Increase action name column length in actions table
|
||||
|
||||
Bug fixes:
|
||||
|
||||
* Fix compatibility issue with FreeBSD for session.hash_function parameter
|
||||
* Fix wrong constant name that causes a PHP error in project management section
|
||||
* Fix pagination in group members listing
|
||||
* Avoid PHP error when enabling LDAP group provider with PHP < 5.5
|
||||
|
||||
Version 1.0.22
|
||||
--------------
|
||||
|
||||
Breaking changes:
|
||||
|
||||
* LDAP configuration parameters changes (See documentation)
|
||||
* SQL table changes:
|
||||
- "users" table: added new column "role" and removed columns "is_admin" and "is_project_admin"
|
||||
- "project_has_users" table: replaced column "is_owner" with column "role"
|
||||
- Sqlite does not support alter table, old columns still there but unused
|
||||
* API procedure changes:
|
||||
- createUser
|
||||
- createLdapUser
|
||||
- updateUser
|
||||
- updateTask
|
||||
* Event removed: "session.bootstrap", use "app.boostrap" instead
|
||||
|
||||
New features:
|
||||
|
||||
* Add pluggable authentication and authorization system (complete rewrite)
|
||||
* Add groups (teams/organization)
|
||||
* Add LDAP groups synchronization
|
||||
* Add project group permissions
|
||||
* Add new project role Viewer
|
||||
* Add generic LDAP client library
|
||||
* Add search query attribute for task link
|
||||
* Add the possibility to define API token in config file
|
||||
* Add capability to reopen Gitlab issues
|
||||
* Try to load config.php from /data if not available
|
||||
|
||||
Version 1.0.21
|
||||
--------------
|
||||
|
||||
Breaking changes:
|
||||
|
||||
* Projects with duplicate name are now allowed:
|
||||
For Postgres and Mysql the unique constraint is removed by database migration
|
||||
However Sqlite does not support alter table, only new databases will have the unique constraint removed
|
||||
* Projects with duplicate names are now allowed:
|
||||
- For Postgres and Mysql the unique constraint is removed by database migration
|
||||
- However Sqlite does not support alter table, only new databases will have the unique constraint removed
|
||||
|
||||
New features:
|
||||
|
||||
|
@ -14,7 +121,7 @@ New features:
|
|||
|
||||
Improvements:
|
||||
|
||||
* Dropdown menu entry are now clickable outside of the html link
|
||||
* Dropdown menu entries are now clickable outside of the html link
|
||||
* Improve error handling of plugins
|
||||
* Use PHP7 function random_bytes() to generate tokens if available
|
||||
* CSV task export show the assignee name in addition to the assignee username
|
||||
|
@ -62,7 +169,7 @@ Improvements:
|
|||
Bug fixes:
|
||||
|
||||
* People should not see any tasks during a search when they are not associated to a project
|
||||
* Avoid to disable the default swimlane during renaming when there is no other activated swimlane
|
||||
* Avoid disabling the default swimlane during renaming when there is no other activated swimlane
|
||||
|
||||
Version 1.0.19
|
||||
--------------
|
||||
|
@ -94,15 +201,15 @@ Improvements:
|
|||
* Offer alternative method to create Mysql and Postgres databases (import sql dump)
|
||||
* Make sure there is always a trailing slash for application_url
|
||||
* Do not show the checkbox "Show default swimlane" when there is no active swimlanes
|
||||
* Append filters instead of replacing value for users and categories dropdowns
|
||||
* Append filters instead of replacing value for users and categories drop-downs
|
||||
* Do not show empty swimlanes in public view
|
||||
* Change swimlane layout to save space on the screen
|
||||
* Add the possibility to set/unset max column height (column scrolling)
|
||||
* Show "Open this task" in dropdown menu for closed tasks
|
||||
* Show "Open this task" in drop-down menu for closed tasks
|
||||
* Show assignee on card only when someone is assigned (hide nobody text)
|
||||
* Highlight selected item in dropdown menus
|
||||
* Highlight selected item in drop-down menus
|
||||
* Gantt chart: change bar color according to task progress
|
||||
* Replace color dropdown by color picker in task forms
|
||||
* Replace color drop-down by color picker in task forms
|
||||
* Creating another task stay in the popover (no full page refresh anymore)
|
||||
* Avoid scrollbar in Gantt chart for row title on Windows platform
|
||||
* Remove unnecessary margin for calendar header
|
||||
|
@ -114,14 +221,14 @@ Improvements:
|
|||
|
||||
Others:
|
||||
|
||||
* Data directory permissions are not checked anymore
|
||||
* Data directory permission are not checked anymore
|
||||
* Data directory is not mandatory anymore for people that use a remote database and remote object storage
|
||||
|
||||
Bug fixes:
|
||||
|
||||
* Fix typo in template that prevent the Gitlab OAuth link to be displayed
|
||||
* Fix typo in template that prevents Gitlab OAuth link to be displayed
|
||||
* Fix Markdown preview links focus
|
||||
* Avoid dropdown menu to be truncated inside a column with scrolling
|
||||
* Avoid drop-down menu to be truncated inside a column with scrolling
|
||||
* Deleting subtask doesn't update task time tracking
|
||||
* Fix Mysql error about gitlab_id when creating remote user
|
||||
* Fix subtask timer bug (event called recursively)
|
||||
|
@ -139,7 +246,7 @@ New features:
|
|||
* Add hide/show columns
|
||||
* Add Gantt chart for projects and tasks
|
||||
* Add new role "Project Administrator"
|
||||
* Add login bruteforce protection with captcha and account lockdown
|
||||
* Add login brute force protection with captcha and account lockdown
|
||||
* Add new api procedures: getDefaultTaskColor(), getDefaultTaskColors() and getColorList()
|
||||
* Add user api access
|
||||
* Add config parameter to define session duration
|
||||
|
@ -147,7 +254,7 @@ New features:
|
|||
* Add start/end date for projects
|
||||
* Add new automated action to change task color based on the task link
|
||||
* Add milestone marker in board task
|
||||
* Add search in task title when using an integer only input
|
||||
* Add search for task title when using an integer only input
|
||||
* Add Portuguese (European) translation
|
||||
* Add Norwegian translation
|
||||
|
||||
|
@ -164,16 +271,16 @@ Improvements:
|
|||
* Improve sidebar menus
|
||||
* Add no referrer policy in meta tags
|
||||
* Run automated unit tests with Sqlite/Mysql/Postgres on Travis-ci
|
||||
* Add Makefile and remove the scripts directory
|
||||
* Add Makefile and remove the "scripts" directory
|
||||
|
||||
Bug fixes:
|
||||
|
||||
* Wrong template name for subtasks tooltip due to previous refactoring
|
||||
* Fix broken url for closed tasks in project view
|
||||
* Fix permission issue when changing the url manually
|
||||
* Fix bug task estimate is reseted when using subtask timer
|
||||
* Fix bug task estimate is reset when using subtask timer
|
||||
* Fix screenshot feature with Firefox 40
|
||||
* Fix bug when uploading files with cyrilic characters
|
||||
* Fix bug when uploading files with Cyrilic characters
|
||||
|
||||
Version 1.0.17
|
||||
--------------
|
||||
|
@ -187,14 +294,14 @@ New features:
|
|||
* Added new dashboard layout
|
||||
* Added new layout for board/calendar/list views
|
||||
* Added filters helper for search forms
|
||||
* Added settings option to disable subtask timer
|
||||
* Added settings option to include or exclude closed tasks into CFD
|
||||
* Added settings option to define the default task color
|
||||
* Added setting option to disable subtask timer
|
||||
* Added setting option to include or exclude closed tasks into CFD
|
||||
* Added setting option to define the default task color
|
||||
* Added new config option to disable automatic creation of LDAP accounts
|
||||
* Added loading icon on board view
|
||||
* Prompt user when moving or duplicate a task to another project
|
||||
* Added current values when moving/duplicate a task to another project and add a loading icon
|
||||
* Added memory consumption in debug log
|
||||
* Added memory consumption to debug log
|
||||
* Added form to create remote user
|
||||
* Added edit form for user authentication
|
||||
* Added config option to hide login form
|
||||
|
@ -205,7 +312,7 @@ New features:
|
|||
* Added new report: Lead and cycle time for projects
|
||||
* Added new report: Average time spent into each column
|
||||
* Added task analytics
|
||||
* Added icon to set automatically the start date
|
||||
* Added icon to set the start date automatically
|
||||
* Added datetime picker for start date
|
||||
|
||||
Improvements:
|
||||
|
@ -214,8 +321,8 @@ Improvements:
|
|||
* Display user initials when tasks are in collapsed mode
|
||||
* Show title in tooltip for collapsed tasks
|
||||
* Improve alert box fadeout to avoid an empty space
|
||||
* Set focus on the dropdown for category popover
|
||||
* Make escape keyboard shorcut global
|
||||
* Set focus on the drop-down for category popover
|
||||
* Make escape keyboard shortcut global
|
||||
* Check the box remember me by default
|
||||
* Store redirect login url in session instead of using url parameter
|
||||
* Update Gitlab webhook
|
||||
|
@ -242,7 +349,7 @@ Translations:
|
|||
|
||||
Bug fixes:
|
||||
|
||||
* Screenshot dropdown: unexpected scroll down on the board view and focus lost when clicking on the drop zone
|
||||
* Screenshot drop-down: unexpected scroll down on the board view and focus lost when clicking on the drop zone
|
||||
* No creator when duplicating a task
|
||||
* Avoid the creation of multiple subtask timer for the same task and user
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014-2015 Frédéric Guillot
|
||||
Copyright (c) 2014-2016 Frédéric Guillot
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
namespace Kanboard\Action;
|
||||
|
||||
use Kanboard\Event\GenericEvent;
|
||||
use Pimple\Container;
|
||||
|
||||
/**
|
||||
* Base class for automatic actions
|
||||
|
@ -13,6 +12,14 @@ use Pimple\Container;
|
|||
*/
|
||||
abstract class Base extends \Kanboard\Core\Base
|
||||
{
|
||||
/**
|
||||
* Extended events
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private $compatibleEvents = array();
|
||||
|
||||
/**
|
||||
* Flag for called listener
|
||||
*
|
||||
|
@ -27,7 +34,7 @@ abstract class Base extends \Kanboard\Core\Base
|
|||
* @access private
|
||||
* @var integer
|
||||
*/
|
||||
private $project_id = 0;
|
||||
private $projectId = 0;
|
||||
|
||||
/**
|
||||
* User parameters
|
||||
|
@ -38,20 +45,25 @@ abstract class Base extends \Kanboard\Core\Base
|
|||
private $params = array();
|
||||
|
||||
/**
|
||||
* Attached event name
|
||||
* Get automatic action name
|
||||
*
|
||||
* @access protected
|
||||
* @var string
|
||||
* @final
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
protected $event_name = '';
|
||||
final public function getName()
|
||||
{
|
||||
return '\\'.get_called_class();
|
||||
}
|
||||
|
||||
/**
|
||||
* Container instance
|
||||
* Get automatic action description
|
||||
*
|
||||
* @access protected
|
||||
* @var \Pimple\Container
|
||||
* @abstract
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
protected $container;
|
||||
abstract public function getDescription();
|
||||
|
||||
/**
|
||||
* Execute the action
|
||||
|
@ -99,22 +111,6 @@ abstract class Base extends \Kanboard\Core\Base
|
|||
*/
|
||||
abstract public function hasRequiredCondition(array $data);
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @access public
|
||||
* @param \Pimple\Container $container Container
|
||||
* @param integer $project_id Project id
|
||||
* @param string $event_name Attached event name
|
||||
*/
|
||||
public function __construct(Container $container, $project_id, $event_name)
|
||||
{
|
||||
$this->container = $container;
|
||||
$this->project_id = $project_id;
|
||||
$this->event_name = $event_name;
|
||||
$this->called = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return class information
|
||||
*
|
||||
|
@ -123,7 +119,25 @@ abstract class Base extends \Kanboard\Core\Base
|
|||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return get_called_class();
|
||||
$params = array();
|
||||
|
||||
foreach ($this->params as $key => $value) {
|
||||
$params[] = $key.'='.var_export($value, true);
|
||||
}
|
||||
|
||||
return $this->getName().'('.implode('|', $params).'])';
|
||||
}
|
||||
|
||||
/**
|
||||
* Set project id
|
||||
*
|
||||
* @access public
|
||||
* @return Base
|
||||
*/
|
||||
public function setProjectId($project_id)
|
||||
{
|
||||
$this->projectId = $project_id;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -134,7 +148,7 @@ abstract class Base extends \Kanboard\Core\Base
|
|||
*/
|
||||
public function getProjectId()
|
||||
{
|
||||
return $this->project_id;
|
||||
return $this->projectId;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -143,10 +157,12 @@ abstract class Base extends \Kanboard\Core\Base
|
|||
* @access public
|
||||
* @param string $name Parameter name
|
||||
* @param mixed $value Value
|
||||
* @param Base
|
||||
*/
|
||||
public function setParam($name, $value)
|
||||
{
|
||||
$this->params[$name] = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -154,24 +170,25 @@ abstract class Base extends \Kanboard\Core\Base
|
|||
*
|
||||
* @access public
|
||||
* @param string $name Parameter name
|
||||
* @param mixed $default_value Default value
|
||||
* @param mixed $default Default value
|
||||
* @return mixed
|
||||
*/
|
||||
public function getParam($name, $default_value = null)
|
||||
public function getParam($name, $default = null)
|
||||
{
|
||||
return isset($this->params[$name]) ? $this->params[$name] : $default_value;
|
||||
return isset($this->params[$name]) ? $this->params[$name] : $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an action is executable (right project and required parameters)
|
||||
*
|
||||
* @access public
|
||||
* @param array $data Event data dictionary
|
||||
* @return bool True if the action is executable
|
||||
* @param array $data
|
||||
* @param string $eventName
|
||||
* @return bool
|
||||
*/
|
||||
public function isExecutable(array $data)
|
||||
public function isExecutable(array $data, $eventName)
|
||||
{
|
||||
return $this->hasCompatibleEvent() &&
|
||||
return $this->hasCompatibleEvent($eventName) &&
|
||||
$this->hasRequiredProject($data) &&
|
||||
$this->hasRequiredParameters($data) &&
|
||||
$this->hasRequiredCondition($data);
|
||||
|
@ -181,11 +198,12 @@ abstract class Base extends \Kanboard\Core\Base
|
|||
* Check if the event is compatible with the action
|
||||
*
|
||||
* @access public
|
||||
* @param string $eventName
|
||||
* @return bool
|
||||
*/
|
||||
public function hasCompatibleEvent()
|
||||
public function hasCompatibleEvent($eventName)
|
||||
{
|
||||
return in_array($this->event_name, $this->getCompatibleEvents());
|
||||
return in_array($eventName, $this->getEvents());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -197,7 +215,7 @@ abstract class Base extends \Kanboard\Core\Base
|
|||
*/
|
||||
public function hasRequiredProject(array $data)
|
||||
{
|
||||
return isset($data['project_id']) && $data['project_id'] == $this->project_id;
|
||||
return isset($data['project_id']) && $data['project_id'] == $this->getProjectId();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -222,10 +240,11 @@ abstract class Base extends \Kanboard\Core\Base
|
|||
* Execute the action
|
||||
*
|
||||
* @access public
|
||||
* @param \Event\GenericEvent $event Event data dictionary
|
||||
* @return bool True if the action was executed or false when not executed
|
||||
* @param \Kanboard\Event\GenericEvent $event
|
||||
* @param string $eventName
|
||||
* @return bool
|
||||
*/
|
||||
public function execute(GenericEvent $event)
|
||||
public function execute(GenericEvent $event, $eventName)
|
||||
{
|
||||
// Avoid infinite loop, a listener instance can be called only one time
|
||||
if ($this->called) {
|
||||
|
@ -233,17 +252,44 @@ abstract class Base extends \Kanboard\Core\Base
|
|||
}
|
||||
|
||||
$data = $event->getAll();
|
||||
$result = false;
|
||||
$executable = $this->isExecutable($data, $eventName);
|
||||
$executed = false;
|
||||
|
||||
if ($this->isExecutable($data)) {
|
||||
if ($executable) {
|
||||
$this->called = true;
|
||||
$result = $this->doAction($data);
|
||||
$executed = $this->doAction($data);
|
||||
}
|
||||
|
||||
if (DEBUG) {
|
||||
$this->logger->debug(get_called_class().' => '.($result ? 'true' : 'false'));
|
||||
$this->logger->debug($this.' ['.$eventName.'] => executable='.var_export($executable, true).' exec_success='.var_export($executed, true));
|
||||
|
||||
return $executed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a new event for the automatic action
|
||||
*
|
||||
* @access public
|
||||
* @param string $event
|
||||
* @param string $description
|
||||
*/
|
||||
public function addEvent($event, $description = '')
|
||||
{
|
||||
if ($description !== '') {
|
||||
$this->eventManager->register($event, $description);
|
||||
}
|
||||
|
||||
return $result;
|
||||
$this->compatibleEvents[] = $event;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all compatible events of an automatic action
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getEvents()
|
||||
{
|
||||
return array_unique(array_merge($this->getCompatibleEvents(), $this->compatibleEvents));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,10 +2,6 @@
|
|||
|
||||
namespace Kanboard\Action;
|
||||
|
||||
use Kanboard\Integration\BitbucketWebhook;
|
||||
use Kanboard\Integration\GithubWebhook;
|
||||
use Kanboard\Integration\GitlabWebhook;
|
||||
|
||||
/**
|
||||
* Create automatically a comment from a webhook
|
||||
*
|
||||
|
@ -14,6 +10,17 @@ use Kanboard\Integration\GitlabWebhook;
|
|||
*/
|
||||
class CommentCreation extends Base
|
||||
{
|
||||
/**
|
||||
* Get automatic action description
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription()
|
||||
{
|
||||
return t('Create a comment from an external provider');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of compatible events
|
||||
*
|
||||
|
@ -22,14 +29,7 @@ class CommentCreation extends Base
|
|||
*/
|
||||
public function getCompatibleEvents()
|
||||
{
|
||||
return array(
|
||||
GithubWebhook::EVENT_ISSUE_COMMENT,
|
||||
GithubWebhook::EVENT_COMMIT,
|
||||
BitbucketWebhook::EVENT_ISSUE_COMMENT,
|
||||
BitbucketWebhook::EVENT_COMMIT,
|
||||
GitlabWebhook::EVENT_COMMIT,
|
||||
GitlabWebhook::EVENT_ISSUE_COMMENT,
|
||||
);
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -67,9 +67,9 @@ class CommentCreation extends Base
|
|||
{
|
||||
return (bool) $this->comment->create(array(
|
||||
'reference' => isset($data['reference']) ? $data['reference'] : '',
|
||||
'comment' => empty($data['comment']) ? $data['commit_comment'] : $data['comment'],
|
||||
'comment' => $data['comment'],
|
||||
'task_id' => $data['task_id'],
|
||||
'user_id' => empty($data['user_id']) ? 0 : $data['user_id'],
|
||||
'user_id' => isset($data['user_id']) && $this->projectPermission->isAssignable($this->getProjectId(), $data['user_id']) ? $data['user_id'] : 0,
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -82,6 +82,6 @@ class CommentCreation extends Base
|
|||
*/
|
||||
public function hasRequiredCondition(array $data)
|
||||
{
|
||||
return ! empty($data['comment']) || ! empty($data['commit_comment']);
|
||||
return ! empty($data['comment']);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,13 +5,24 @@ namespace Kanboard\Action;
|
|||
use Kanboard\Model\Task;
|
||||
|
||||
/**
|
||||
* Add a log of the triggering event to the task description.
|
||||
* Add a comment of the triggering event to the task description.
|
||||
*
|
||||
* @package action
|
||||
* @author Oren Ben-Kiki
|
||||
*/
|
||||
class TaskLogMoveAnotherColumn extends Base
|
||||
class CommentCreationMoveTaskColumn extends Base
|
||||
{
|
||||
/**
|
||||
* Get automatic action description
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription()
|
||||
{
|
||||
return t('Add a comment log when moving the task between columns');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of compatible events
|
||||
*
|
|
@ -12,6 +12,17 @@ use Kanboard\Model\Task;
|
|||
*/
|
||||
class TaskAssignCategoryColor extends Base
|
||||
{
|
||||
/**
|
||||
* Get automatic action description
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription()
|
||||
{
|
||||
return t('Assign automatically a category based on a color');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of compatible events
|
||||
*
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
|
||||
namespace Kanboard\Action;
|
||||
|
||||
use Kanboard\Integration\GithubWebhook;
|
||||
|
||||
/**
|
||||
* Set a category automatically according to a label
|
||||
*
|
||||
|
@ -12,6 +10,17 @@ use Kanboard\Integration\GithubWebhook;
|
|||
*/
|
||||
class TaskAssignCategoryLabel extends Base
|
||||
{
|
||||
/**
|
||||
* Get automatic action description
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription()
|
||||
{
|
||||
return t('Change the category based on an external label');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of compatible events
|
||||
*
|
||||
|
@ -20,9 +29,7 @@ class TaskAssignCategoryLabel extends Base
|
|||
*/
|
||||
public function getCompatibleEvents()
|
||||
{
|
||||
return array(
|
||||
GithubWebhook::EVENT_ISSUE_LABEL_CHANGE,
|
||||
);
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -64,7 +71,7 @@ class TaskAssignCategoryLabel extends Base
|
|||
{
|
||||
$values = array(
|
||||
'id' => $data['task_id'],
|
||||
'category_id' => isset($data['category_id']) ? $data['category_id'] : $this->getParam('category_id'),
|
||||
'category_id' => $this->getParam('category_id'),
|
||||
);
|
||||
|
||||
return $this->taskModification->update($values);
|
||||
|
@ -79,6 +86,6 @@ class TaskAssignCategoryLabel extends Base
|
|||
*/
|
||||
public function hasRequiredCondition(array $data)
|
||||
{
|
||||
return $data['label'] == $this->getParam('label');
|
||||
return $data['label'] == $this->getParam('label') && empty($data['category_id']);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,17 @@ use Kanboard\Model\TaskLink;
|
|||
*/
|
||||
class TaskAssignCategoryLink extends Base
|
||||
{
|
||||
/**
|
||||
* Get automatic action description
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription()
|
||||
{
|
||||
return t('Assign automatically a category based on a link');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of compatible events
|
||||
*
|
||||
|
@ -65,7 +76,7 @@ class TaskAssignCategoryLink extends Base
|
|||
{
|
||||
$values = array(
|
||||
'id' => $data['task_id'],
|
||||
'category_id' => isset($data['category_id']) ? $data['category_id'] : $this->getParam('category_id'),
|
||||
'category_id' => $this->getParam('category_id'),
|
||||
);
|
||||
|
||||
return $this->taskModification->update($values);
|
||||
|
|
|
@ -12,6 +12,17 @@ use Kanboard\Model\Task;
|
|||
*/
|
||||
class TaskAssignColorCategory extends Base
|
||||
{
|
||||
/**
|
||||
* Get automatic action description
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription()
|
||||
{
|
||||
return t('Assign automatically a color based on a category');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of compatible events
|
||||
*
|
||||
|
@ -67,7 +78,7 @@ class TaskAssignColorCategory extends Base
|
|||
'color_id' => $this->getParam('color_id'),
|
||||
);
|
||||
|
||||
return $this->taskModification->update($values);
|
||||
return $this->taskModification->update($values, false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -12,6 +12,17 @@ use Kanboard\Model\Task;
|
|||
*/
|
||||
class TaskAssignColorColumn extends Base
|
||||
{
|
||||
/**
|
||||
* Get automatic action description
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription()
|
||||
{
|
||||
return t('Assign a color when the task is moved to a specific column');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of compatible events
|
||||
*
|
||||
|
@ -68,7 +79,7 @@ class TaskAssignColorColumn extends Base
|
|||
'color_id' => $this->getParam('color_id'),
|
||||
);
|
||||
|
||||
return $this->taskModification->update($values);
|
||||
return $this->taskModification->update($values, false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -12,6 +12,17 @@ use Kanboard\Model\TaskLink;
|
|||
*/
|
||||
class TaskAssignColorLink extends Base
|
||||
{
|
||||
/**
|
||||
* Get automatic action description
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription()
|
||||
{
|
||||
return t('Change task color when using a specific task link');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of compatible events
|
||||
*
|
||||
|
@ -67,7 +78,7 @@ class TaskAssignColorLink extends Base
|
|||
'color_id' => $this->getParam('color_id'),
|
||||
);
|
||||
|
||||
return $this->taskModification->update($values);
|
||||
return $this->taskModification->update($values, false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -12,6 +12,17 @@ use Kanboard\Model\Task;
|
|||
*/
|
||||
class TaskAssignColorUser extends Base
|
||||
{
|
||||
/**
|
||||
* Get automatic action description
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription()
|
||||
{
|
||||
return t('Assign a color to a specific user');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of compatible events
|
||||
*
|
||||
|
@ -68,7 +79,7 @@ class TaskAssignColorUser extends Base
|
|||
'color_id' => $this->getParam('color_id'),
|
||||
);
|
||||
|
||||
return $this->taskModification->update($values);
|
||||
return $this->taskModification->update($values, false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -12,6 +12,17 @@ use Kanboard\Model\Task;
|
|||
*/
|
||||
class TaskAssignCurrentUser extends Base
|
||||
{
|
||||
/**
|
||||
* Get automatic action description
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription()
|
||||
{
|
||||
return t('Assign the task to the person who does the action');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of compatible events
|
||||
*
|
||||
|
@ -22,7 +33,6 @@ class TaskAssignCurrentUser extends Base
|
|||
{
|
||||
return array(
|
||||
Task::EVENT_CREATE,
|
||||
Task::EVENT_MOVE_COLUMN,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -34,9 +44,7 @@ class TaskAssignCurrentUser extends Base
|
|||
*/
|
||||
public function getActionRequiredParameters()
|
||||
{
|
||||
return array(
|
||||
'column_id' => t('Column'),
|
||||
);
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -49,7 +57,6 @@ class TaskAssignCurrentUser extends Base
|
|||
{
|
||||
return array(
|
||||
'task_id',
|
||||
'column_id',
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -83,6 +90,6 @@ class TaskAssignCurrentUser extends Base
|
|||
*/
|
||||
public function hasRequiredCondition(array $data)
|
||||
{
|
||||
return $data['column_id'] == $this->getParam('column_id');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
98
sources/app/Action/TaskAssignCurrentUserColumn.php
Normal file
98
sources/app/Action/TaskAssignCurrentUserColumn.php
Normal file
|
@ -0,0 +1,98 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Action;
|
||||
|
||||
use Kanboard\Model\Task;
|
||||
|
||||
/**
|
||||
* Assign a task to the logged user on column change
|
||||
*
|
||||
* @package action
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class TaskAssignCurrentUserColumn extends Base
|
||||
{
|
||||
/**
|
||||
* Get automatic action description
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription()
|
||||
{
|
||||
return t('Assign the task to the person who does the action when the column is changed');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of compatible events
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getCompatibleEvents()
|
||||
{
|
||||
return array(
|
||||
Task::EVENT_MOVE_COLUMN,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the required parameter for the action (defined by the user)
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getActionRequiredParameters()
|
||||
{
|
||||
return array(
|
||||
'column_id' => t('Column'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the required parameter for the event
|
||||
*
|
||||
* @access public
|
||||
* @return string[]
|
||||
*/
|
||||
public function getEventRequiredParameters()
|
||||
{
|
||||
return array(
|
||||
'task_id',
|
||||
'column_id',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the action
|
||||
*
|
||||
* @access public
|
||||
* @param array $data Event data dictionary
|
||||
* @return bool True if the action was executed or false when not executed
|
||||
*/
|
||||
public function doAction(array $data)
|
||||
{
|
||||
if (! $this->userSession->isLogged()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$values = array(
|
||||
'id' => $data['task_id'],
|
||||
'owner_id' => $this->userSession->getId(),
|
||||
);
|
||||
|
||||
return $this->taskModification->update($values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the event data meet the action condition
|
||||
*
|
||||
* @access public
|
||||
* @param array $data Event data dictionary
|
||||
* @return bool
|
||||
*/
|
||||
public function hasRequiredCondition(array $data)
|
||||
{
|
||||
return $data['column_id'] == $this->getParam('column_id');
|
||||
}
|
||||
}
|
|
@ -12,6 +12,17 @@ use Kanboard\Model\Task;
|
|||
*/
|
||||
class TaskAssignSpecificUser extends Base
|
||||
{
|
||||
/**
|
||||
* Get automatic action description
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription()
|
||||
{
|
||||
return t('Assign the task to a specific user');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of compatible events
|
||||
*
|
||||
|
|
|
@ -2,9 +2,6 @@
|
|||
|
||||
namespace Kanboard\Action;
|
||||
|
||||
use Kanboard\Integration\GithubWebhook;
|
||||
use Kanboard\Integration\BitbucketWebhook;
|
||||
|
||||
/**
|
||||
* Assign a task to someone
|
||||
*
|
||||
|
@ -13,6 +10,17 @@ use Kanboard\Integration\BitbucketWebhook;
|
|||
*/
|
||||
class TaskAssignUser extends Base
|
||||
{
|
||||
/**
|
||||
* Get automatic action description
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription()
|
||||
{
|
||||
return t('Change the assignee based on an external username');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of compatible events
|
||||
*
|
||||
|
@ -21,10 +29,7 @@ class TaskAssignUser extends Base
|
|||
*/
|
||||
public function getCompatibleEvents()
|
||||
{
|
||||
return array(
|
||||
GithubWebhook::EVENT_ISSUE_ASSIGNEE_CHANGE,
|
||||
BitbucketWebhook::EVENT_ISSUE_ASSIGNEE_CHANGE,
|
||||
);
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -78,6 +83,6 @@ class TaskAssignUser extends Base
|
|||
*/
|
||||
public function hasRequiredCondition(array $data)
|
||||
{
|
||||
return true;
|
||||
return $this->projectPermission->isAssignable($this->getProjectId(), $data['owner_id']);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,11 +2,6 @@
|
|||
|
||||
namespace Kanboard\Action;
|
||||
|
||||
use Kanboard\Integration\GitlabWebhook;
|
||||
use Kanboard\Integration\GithubWebhook;
|
||||
use Kanboard\Integration\BitbucketWebhook;
|
||||
use Kanboard\Model\Task;
|
||||
|
||||
/**
|
||||
* Close automatically a task
|
||||
*
|
||||
|
@ -15,6 +10,17 @@ use Kanboard\Model\Task;
|
|||
*/
|
||||
class TaskClose extends Base
|
||||
{
|
||||
/**
|
||||
* Get automatic action description
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription()
|
||||
{
|
||||
return t('Close a task');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of compatible events
|
||||
*
|
||||
|
@ -23,15 +29,7 @@ class TaskClose extends Base
|
|||
*/
|
||||
public function getCompatibleEvents()
|
||||
{
|
||||
return array(
|
||||
Task::EVENT_MOVE_COLUMN,
|
||||
GithubWebhook::EVENT_COMMIT,
|
||||
GithubWebhook::EVENT_ISSUE_CLOSED,
|
||||
GitlabWebhook::EVENT_COMMIT,
|
||||
GitlabWebhook::EVENT_ISSUE_CLOSED,
|
||||
BitbucketWebhook::EVENT_COMMIT,
|
||||
BitbucketWebhook::EVENT_ISSUE_CLOSED,
|
||||
);
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -42,17 +40,7 @@ class TaskClose extends Base
|
|||
*/
|
||||
public function getActionRequiredParameters()
|
||||
{
|
||||
switch ($this->event_name) {
|
||||
case GithubWebhook::EVENT_COMMIT:
|
||||
case GithubWebhook::EVENT_ISSUE_CLOSED:
|
||||
case GitlabWebhook::EVENT_COMMIT:
|
||||
case GitlabWebhook::EVENT_ISSUE_CLOSED:
|
||||
case BitbucketWebhook::EVENT_COMMIT:
|
||||
case BitbucketWebhook::EVENT_ISSUE_CLOSED:
|
||||
return array();
|
||||
default:
|
||||
return array('column_id' => t('Column'));
|
||||
}
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -63,17 +51,7 @@ class TaskClose extends Base
|
|||
*/
|
||||
public function getEventRequiredParameters()
|
||||
{
|
||||
switch ($this->event_name) {
|
||||
case GithubWebhook::EVENT_COMMIT:
|
||||
case GithubWebhook::EVENT_ISSUE_CLOSED:
|
||||
case GitlabWebhook::EVENT_COMMIT:
|
||||
case GitlabWebhook::EVENT_ISSUE_CLOSED:
|
||||
case BitbucketWebhook::EVENT_COMMIT:
|
||||
case BitbucketWebhook::EVENT_ISSUE_CLOSED:
|
||||
return array('task_id');
|
||||
default:
|
||||
return array('task_id', 'column_id');
|
||||
}
|
||||
return array('task_id');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -97,16 +75,6 @@ class TaskClose extends Base
|
|||
*/
|
||||
public function hasRequiredCondition(array $data)
|
||||
{
|
||||
switch ($this->event_name) {
|
||||
case GithubWebhook::EVENT_COMMIT:
|
||||
case GithubWebhook::EVENT_ISSUE_CLOSED:
|
||||
case GitlabWebhook::EVENT_COMMIT:
|
||||
case GitlabWebhook::EVENT_ISSUE_CLOSED:
|
||||
case BitbucketWebhook::EVENT_COMMIT:
|
||||
case BitbucketWebhook::EVENT_ISSUE_CLOSED:
|
||||
return true;
|
||||
default:
|
||||
return $data['column_id'] == $this->getParam('column_id');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
84
sources/app/Action/TaskCloseColumn.php
Normal file
84
sources/app/Action/TaskCloseColumn.php
Normal file
|
@ -0,0 +1,84 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Action;
|
||||
|
||||
use Kanboard\Model\Task;
|
||||
|
||||
/**
|
||||
* Close automatically a task in a specific column
|
||||
*
|
||||
* @package action
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class TaskCloseColumn extends Base
|
||||
{
|
||||
/**
|
||||
* Get automatic action description
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription()
|
||||
{
|
||||
return t('Close a task in a specific column');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of compatible events
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getCompatibleEvents()
|
||||
{
|
||||
return array(
|
||||
Task::EVENT_MOVE_COLUMN,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the required parameter for the action (defined by the user)
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getActionRequiredParameters()
|
||||
{
|
||||
return array('column_id' => t('Column'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the required parameter for the event
|
||||
*
|
||||
* @access public
|
||||
* @return string[]
|
||||
*/
|
||||
public function getEventRequiredParameters()
|
||||
{
|
||||
return array('task_id', 'column_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the action (close the task)
|
||||
*
|
||||
* @access public
|
||||
* @param array $data Event data dictionary
|
||||
* @return bool True if the action was executed or false when not executed
|
||||
*/
|
||||
public function doAction(array $data)
|
||||
{
|
||||
return $this->taskStatus->close($data['task_id']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the event data meet the action condition
|
||||
*
|
||||
* @access public
|
||||
* @param array $data Event data dictionary
|
||||
* @return bool
|
||||
*/
|
||||
public function hasRequiredCondition(array $data)
|
||||
{
|
||||
return $data['column_id'] == $this->getParam('column_id');
|
||||
}
|
||||
}
|
|
@ -2,10 +2,6 @@
|
|||
|
||||
namespace Kanboard\Action;
|
||||
|
||||
use Kanboard\Integration\GithubWebhook;
|
||||
use Kanboard\Integration\GitlabWebhook;
|
||||
use Kanboard\Integration\BitbucketWebhook;
|
||||
|
||||
/**
|
||||
* Create automatically a task from a webhook
|
||||
*
|
||||
|
@ -14,6 +10,17 @@ use Kanboard\Integration\BitbucketWebhook;
|
|||
*/
|
||||
class TaskCreation extends Base
|
||||
{
|
||||
/**
|
||||
* Get automatic action description
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription()
|
||||
{
|
||||
return t('Create a task from an external provider');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of compatible events
|
||||
*
|
||||
|
@ -22,11 +29,7 @@ class TaskCreation extends Base
|
|||
*/
|
||||
public function getCompatibleEvents()
|
||||
{
|
||||
return array(
|
||||
GithubWebhook::EVENT_ISSUE_OPENED,
|
||||
GitlabWebhook::EVENT_ISSUE_OPENED,
|
||||
BitbucketWebhook::EVENT_ISSUE_OPENED,
|
||||
);
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -12,6 +12,17 @@ use Kanboard\Model\Task;
|
|||
*/
|
||||
class TaskDuplicateAnotherProject extends Base
|
||||
{
|
||||
/**
|
||||
* Get automatic action description
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription()
|
||||
{
|
||||
return t('Duplicate the task to another project');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of compatible events
|
||||
*
|
||||
|
@ -51,7 +62,6 @@ class TaskDuplicateAnotherProject extends Base
|
|||
return array(
|
||||
'task_id',
|
||||
'column_id',
|
||||
'project_id',
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -65,7 +75,6 @@ class TaskDuplicateAnotherProject extends Base
|
|||
public function doAction(array $data)
|
||||
{
|
||||
$destination_column_id = $this->board->getFirstColumn($this->getParam('project_id'));
|
||||
|
||||
return (bool) $this->taskDuplication->duplicateToProject($data['task_id'], $this->getParam('project_id'), null, $destination_column_id);
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,17 @@ use Kanboard\Model\Task;
|
|||
*/
|
||||
class TaskEmail extends Base
|
||||
{
|
||||
/**
|
||||
* Get automatic action description
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription()
|
||||
{
|
||||
return t('Send a task by email to someone');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of compatible events
|
||||
*
|
||||
|
|
|
@ -12,6 +12,17 @@ use Kanboard\Model\Task;
|
|||
*/
|
||||
class TaskMoveAnotherProject extends Base
|
||||
{
|
||||
/**
|
||||
* Get automatic action description
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription()
|
||||
{
|
||||
return t('Move the task to another project');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of compatible events
|
||||
*
|
||||
|
|
|
@ -12,6 +12,17 @@ use Kanboard\Model\Task;
|
|||
*/
|
||||
class TaskMoveColumnAssigned extends Base
|
||||
{
|
||||
/**
|
||||
* Get automatic action description
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription()
|
||||
{
|
||||
return t('Move the task to another column when assigned to a user');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of compatible events
|
||||
*
|
||||
|
@ -51,7 +62,6 @@ class TaskMoveColumnAssigned extends Base
|
|||
return array(
|
||||
'task_id',
|
||||
'column_id',
|
||||
'project_id',
|
||||
'owner_id'
|
||||
);
|
||||
}
|
||||
|
|
|
@ -12,6 +12,17 @@ use Kanboard\Model\Task;
|
|||
*/
|
||||
class TaskMoveColumnCategoryChange extends Base
|
||||
{
|
||||
/**
|
||||
* Get automatic action description
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription()
|
||||
{
|
||||
return t('Move the task to another column when the category is changed');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of compatible events
|
||||
*
|
||||
|
@ -50,7 +61,6 @@ class TaskMoveColumnCategoryChange extends Base
|
|||
return array(
|
||||
'task_id',
|
||||
'column_id',
|
||||
'project_id',
|
||||
'category_id',
|
||||
);
|
||||
}
|
||||
|
@ -71,7 +81,8 @@ class TaskMoveColumnCategoryChange extends Base
|
|||
$data['task_id'],
|
||||
$this->getParam('dest_column_id'),
|
||||
$original_task['position'],
|
||||
$original_task['swimlane_id']
|
||||
$original_task['swimlane_id'],
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,17 @@ use Kanboard\Model\Task;
|
|||
*/
|
||||
class TaskMoveColumnUnAssigned extends Base
|
||||
{
|
||||
/**
|
||||
* Get automatic action description
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription()
|
||||
{
|
||||
return t('Move the task to another column when assignee is cleared');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of compatible events
|
||||
*
|
||||
|
@ -51,7 +62,6 @@ class TaskMoveColumnUnAssigned extends Base
|
|||
return array(
|
||||
'task_id',
|
||||
'column_id',
|
||||
'project_id',
|
||||
'owner_id'
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,9 +2,6 @@
|
|||
|
||||
namespace Kanboard\Action;
|
||||
|
||||
use Kanboard\Integration\GithubWebhook;
|
||||
use Kanboard\Integration\BitbucketWebhook;
|
||||
|
||||
/**
|
||||
* Open automatically a task
|
||||
*
|
||||
|
@ -13,6 +10,17 @@ use Kanboard\Integration\BitbucketWebhook;
|
|||
*/
|
||||
class TaskOpen extends Base
|
||||
{
|
||||
/**
|
||||
* Get automatic action description
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription()
|
||||
{
|
||||
return t('Open a task');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of compatible events
|
||||
*
|
||||
|
@ -21,10 +29,7 @@ class TaskOpen extends Base
|
|||
*/
|
||||
public function getCompatibleEvents()
|
||||
{
|
||||
return array(
|
||||
GithubWebhook::EVENT_ISSUE_REOPENED,
|
||||
BitbucketWebhook::EVENT_ISSUE_REOPENED,
|
||||
);
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -12,6 +12,17 @@ use Kanboard\Model\Task;
|
|||
*/
|
||||
class TaskUpdateStartDate extends Base
|
||||
{
|
||||
/**
|
||||
* Get automatic action description
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription()
|
||||
{
|
||||
return t('Automatically update the start date');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of compatible events
|
||||
*
|
||||
|
@ -66,7 +77,7 @@ class TaskUpdateStartDate extends Base
|
|||
'date_started' => time(),
|
||||
);
|
||||
|
||||
return $this->taskModification->update($values);
|
||||
return $this->taskModification->update($values, false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
114
sources/app/Analytic/AverageLeadCycleTimeAnalytic.php
Normal file
114
sources/app/Analytic/AverageLeadCycleTimeAnalytic.php
Normal file
|
@ -0,0 +1,114 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Analytic;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
use Kanboard\Model\Task;
|
||||
|
||||
/**
|
||||
* Average Lead and Cycle Time
|
||||
*
|
||||
* @package analytic
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class AverageLeadCycleTimeAnalytic extends Base
|
||||
{
|
||||
/**
|
||||
* Build report
|
||||
*
|
||||
* @access public
|
||||
* @param integer $project_id Project id
|
||||
* @return array
|
||||
*/
|
||||
public function build($project_id)
|
||||
{
|
||||
$stats = array(
|
||||
'count' => 0,
|
||||
'total_lead_time' => 0,
|
||||
'total_cycle_time' => 0,
|
||||
'avg_lead_time' => 0,
|
||||
'avg_cycle_time' => 0,
|
||||
);
|
||||
|
||||
$tasks = $this->getTasks($project_id);
|
||||
|
||||
foreach ($tasks as &$task) {
|
||||
$stats['count']++;
|
||||
$stats['total_lead_time'] += $this->calculateLeadTime($task);
|
||||
$stats['total_cycle_time'] += $this->calculateCycleTime($task);
|
||||
}
|
||||
|
||||
$stats['avg_lead_time'] = $this->calculateAverage($stats, 'total_lead_time');
|
||||
$stats['avg_cycle_time'] = $this->calculateAverage($stats, 'total_cycle_time');
|
||||
|
||||
return $stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate average
|
||||
*
|
||||
* @access private
|
||||
* @param array &$stats
|
||||
* @param string $field
|
||||
* @return float
|
||||
*/
|
||||
private function calculateAverage(array &$stats, $field)
|
||||
{
|
||||
if ($stats['count'] > 0) {
|
||||
return (int) ($stats[$field] / $stats['count']);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate lead time
|
||||
*
|
||||
* @access private
|
||||
* @param array &$task
|
||||
* @return integer
|
||||
*/
|
||||
private function calculateLeadTime(array &$task)
|
||||
{
|
||||
$end = $task['date_completed'] ?: time();
|
||||
$start = $task['date_creation'];
|
||||
|
||||
return $end - $start;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate cycle time
|
||||
*
|
||||
* @access private
|
||||
* @param array &$task
|
||||
* @return integer
|
||||
*/
|
||||
private function calculateCycleTime(array &$task)
|
||||
{
|
||||
if (empty($task['date_started'])) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$end = $task['date_completed'] ?: time();
|
||||
$start = $task['date_started'];
|
||||
|
||||
return $end - $start;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the 1000 last created tasks
|
||||
*
|
||||
* @access private
|
||||
* @return array
|
||||
*/
|
||||
private function getTasks($project_id)
|
||||
{
|
||||
return $this->db
|
||||
->table(Task::TABLE)
|
||||
->columns('date_completed', 'date_creation', 'date_started')
|
||||
->eq('project_id', $project_id)
|
||||
->desc('id')
|
||||
->limit(1000)
|
||||
->findAll();
|
||||
}
|
||||
}
|
153
sources/app/Analytic/AverageTimeSpentColumnAnalytic.php
Normal file
153
sources/app/Analytic/AverageTimeSpentColumnAnalytic.php
Normal file
|
@ -0,0 +1,153 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Analytic;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
use Kanboard\Model\Task;
|
||||
|
||||
/**
|
||||
* Average Time Spent by Column
|
||||
*
|
||||
* @package analytic
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class AverageTimeSpentColumnAnalytic extends Base
|
||||
{
|
||||
/**
|
||||
* Build report
|
||||
*
|
||||
* @access public
|
||||
* @param integer $project_id Project id
|
||||
* @return array
|
||||
*/
|
||||
public function build($project_id)
|
||||
{
|
||||
$stats = $this->initialize($project_id);
|
||||
|
||||
$this->processTasks($stats, $project_id);
|
||||
$this->calculateAverage($stats);
|
||||
|
||||
return $stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize default values for each column
|
||||
*
|
||||
* @access private
|
||||
* @param integer $project_id
|
||||
* @return array
|
||||
*/
|
||||
private function initialize($project_id)
|
||||
{
|
||||
$stats = array();
|
||||
$columns = $this->board->getColumnsList($project_id);
|
||||
|
||||
foreach ($columns as $column_id => $column_title) {
|
||||
$stats[$column_id] = array(
|
||||
'count' => 0,
|
||||
'time_spent' => 0,
|
||||
'average' => 0,
|
||||
'title' => $column_title,
|
||||
);
|
||||
}
|
||||
|
||||
return $stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate time spent for each tasks for each columns
|
||||
*
|
||||
* @access private
|
||||
* @param array $stats
|
||||
* @param integer $project_id
|
||||
*/
|
||||
private function processTasks(array &$stats, $project_id)
|
||||
{
|
||||
$tasks = $this->getTasks($project_id);
|
||||
|
||||
foreach ($tasks as &$task) {
|
||||
foreach ($this->getTaskTimeByColumns($task) as $column_id => $time_spent) {
|
||||
if (isset($stats[$column_id])) {
|
||||
$stats[$column_id]['count']++;
|
||||
$stats[$column_id]['time_spent'] += $time_spent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate averages
|
||||
*
|
||||
* @access private
|
||||
* @param array $stats
|
||||
*/
|
||||
private function calculateAverage(array &$stats)
|
||||
{
|
||||
foreach ($stats as &$column) {
|
||||
$this->calculateColumnAverage($column);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate column average
|
||||
*
|
||||
* @access private
|
||||
* @param array $column
|
||||
*/
|
||||
private function calculateColumnAverage(array &$column)
|
||||
{
|
||||
if ($column['count'] > 0) {
|
||||
$column['average'] = (int) ($column['time_spent'] / $column['count']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get time spent for each column for a given task
|
||||
*
|
||||
* @access private
|
||||
* @param array $task
|
||||
* @return array
|
||||
*/
|
||||
private function getTaskTimeByColumns(array &$task)
|
||||
{
|
||||
$columns = $this->transition->getTimeSpentByTask($task['id']);
|
||||
|
||||
if (! isset($columns[$task['column_id']])) {
|
||||
$columns[$task['column_id']] = 0;
|
||||
}
|
||||
|
||||
$columns[$task['column_id']] += $this->getTaskTimeSpentInCurrentColumn($task);
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate time spent of a task in the current column
|
||||
*
|
||||
* @access private
|
||||
* @param array $task
|
||||
*/
|
||||
private function getTaskTimeSpentInCurrentColumn(array &$task)
|
||||
{
|
||||
$end = $task['date_completed'] ?: time();
|
||||
return $end - $task['date_moved'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the last 1000 tasks
|
||||
*
|
||||
* @access private
|
||||
* @param integer $project_id
|
||||
* @return array
|
||||
*/
|
||||
private function getTasks($project_id)
|
||||
{
|
||||
return $this->db
|
||||
->table(Task::TABLE)
|
||||
->columns('id', 'date_completed', 'date_moved', 'column_id')
|
||||
->eq('project_id', $project_id)
|
||||
->desc('id')
|
||||
->limit(1000)
|
||||
->findAll();
|
||||
}
|
||||
}
|
50
sources/app/Analytic/EstimatedTimeComparisonAnalytic.php
Normal file
50
sources/app/Analytic/EstimatedTimeComparisonAnalytic.php
Normal file
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Analytic;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
use Kanboard\Model\Task;
|
||||
|
||||
/**
|
||||
* Estimated/Spent Time Comparison
|
||||
*
|
||||
* @package analytic
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class EstimatedTimeComparisonAnalytic extends Base
|
||||
{
|
||||
/**
|
||||
* Build report
|
||||
*
|
||||
* @access public
|
||||
* @param integer $project_id Project id
|
||||
* @return array
|
||||
*/
|
||||
public function build($project_id)
|
||||
{
|
||||
$rows = $this->db->table(Task::TABLE)
|
||||
->columns('SUM(time_estimated) AS time_estimated', 'SUM(time_spent) AS time_spent', 'is_active')
|
||||
->eq('project_id', $project_id)
|
||||
->groupBy('is_active')
|
||||
->findAll();
|
||||
|
||||
$metrics = array(
|
||||
'open' => array(
|
||||
'time_spent' => 0,
|
||||
'time_estimated' => 0,
|
||||
),
|
||||
'closed' => array(
|
||||
'time_spent' => 0,
|
||||
'time_estimated' => 0,
|
||||
),
|
||||
);
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$key = $row['is_active'] == Task::STATUS_OPEN ? 'open' : 'closed';
|
||||
$metrics[$key]['time_spent'] = $row['time_spent'];
|
||||
$metrics[$key]['time_estimated'] = $row['time_estimated'];
|
||||
}
|
||||
|
||||
return $metrics;
|
||||
}
|
||||
}
|
48
sources/app/Analytic/TaskDistributionAnalytic.php
Normal file
48
sources/app/Analytic/TaskDistributionAnalytic.php
Normal file
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Analytic;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
|
||||
/**
|
||||
* Task Distribution
|
||||
*
|
||||
* @package analytic
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class TaskDistributionAnalytic extends Base
|
||||
{
|
||||
/**
|
||||
* Build report
|
||||
*
|
||||
* @access public
|
||||
* @param integer $project_id Project id
|
||||
* @return array
|
||||
*/
|
||||
public function build($project_id)
|
||||
{
|
||||
$metrics = array();
|
||||
$total = 0;
|
||||
$columns = $this->board->getColumns($project_id);
|
||||
|
||||
foreach ($columns as $column) {
|
||||
$nb_tasks = $this->taskFinder->countByColumnId($project_id, $column['id']);
|
||||
$total += $nb_tasks;
|
||||
|
||||
$metrics[] = array(
|
||||
'column_title' => $column['title'],
|
||||
'nb_tasks' => $nb_tasks,
|
||||
);
|
||||
}
|
||||
|
||||
if ($total === 0) {
|
||||
return array();
|
||||
}
|
||||
|
||||
foreach ($metrics as &$metric) {
|
||||
$metric['percentage'] = round(($metric['nb_tasks'] * 100) / $total, 2);
|
||||
}
|
||||
|
||||
return $metrics;
|
||||
}
|
||||
}
|
56
sources/app/Analytic/UserDistributionAnalytic.php
Normal file
56
sources/app/Analytic/UserDistributionAnalytic.php
Normal file
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Analytic;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
|
||||
/**
|
||||
* User Distribution
|
||||
*
|
||||
* @package analytic
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class UserDistributionAnalytic extends Base
|
||||
{
|
||||
/**
|
||||
* Build Report
|
||||
*
|
||||
* @access public
|
||||
* @param integer $project_id
|
||||
* @return array
|
||||
*/
|
||||
public function build($project_id)
|
||||
{
|
||||
$metrics = array();
|
||||
$total = 0;
|
||||
$tasks = $this->taskFinder->getAll($project_id);
|
||||
$users = $this->projectUserRole->getAssignableUsersList($project_id);
|
||||
|
||||
foreach ($tasks as $task) {
|
||||
$user = isset($users[$task['owner_id']]) ? $users[$task['owner_id']] : $users[0];
|
||||
$total++;
|
||||
|
||||
if (! isset($metrics[$user])) {
|
||||
$metrics[$user] = array(
|
||||
'nb_tasks' => 0,
|
||||
'percentage' => 0,
|
||||
'user' => $user,
|
||||
);
|
||||
}
|
||||
|
||||
$metrics[$user]['nb_tasks']++;
|
||||
}
|
||||
|
||||
if ($total === 0) {
|
||||
return array();
|
||||
}
|
||||
|
||||
foreach ($metrics as &$metric) {
|
||||
$metric['percentage'] = round(($metric['nb_tasks'] * 100) / $total, 2);
|
||||
}
|
||||
|
||||
ksort($metrics);
|
||||
|
||||
return array_values($metrics);
|
||||
}
|
||||
}
|
|
@ -12,17 +12,17 @@ class Action extends \Kanboard\Core\Base
|
|||
{
|
||||
public function getAvailableActions()
|
||||
{
|
||||
return $this->action->getAvailableActions();
|
||||
return $this->actionManager->getAvailableActions();
|
||||
}
|
||||
|
||||
public function getAvailableActionEvents()
|
||||
{
|
||||
return $this->action->getAvailableEvents();
|
||||
return $this->eventManager->getAll();
|
||||
}
|
||||
|
||||
public function getCompatibleActionEvents($action_name)
|
||||
{
|
||||
return $this->action->getCompatibleEvents($action_name);
|
||||
return $this->actionManager->getCompatibleEvents($action_name);
|
||||
}
|
||||
|
||||
public function removeAction($action_id)
|
||||
|
@ -32,22 +32,10 @@ class Action extends \Kanboard\Core\Base
|
|||
|
||||
public function getActions($project_id)
|
||||
{
|
||||
$actions = $this->action->getAllByProject($project_id);
|
||||
|
||||
foreach ($actions as $index => $action) {
|
||||
$params = array();
|
||||
|
||||
foreach ($action['params'] as $param) {
|
||||
$params[$param['name']] = $param['value'];
|
||||
}
|
||||
|
||||
$actions[$index]['params'] = $params;
|
||||
}
|
||||
|
||||
return $actions;
|
||||
return $this->action->getAllByProject($project_id);
|
||||
}
|
||||
|
||||
public function createAction($project_id, $event_name, $action_name, $params)
|
||||
public function createAction($project_id, $event_name, $action_name, array $params)
|
||||
{
|
||||
$values = array(
|
||||
'project_id' => $project_id,
|
||||
|
@ -56,23 +44,23 @@ class Action extends \Kanboard\Core\Base
|
|||
'params' => $params,
|
||||
);
|
||||
|
||||
list($valid, ) = $this->action->validateCreation($values);
|
||||
list($valid, ) = $this->actionValidator->validateCreation($values);
|
||||
|
||||
if (! $valid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the action exists
|
||||
$actions = $this->action->getAvailableActions();
|
||||
$actions = $this->actionManager->getAvailableActions();
|
||||
|
||||
if (! isset($actions[$action_name])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check the event
|
||||
$action = $this->action->load($action_name, $project_id, $event_name);
|
||||
$action = $this->actionManager->getAction($action_name);
|
||||
|
||||
if (! in_array($event_name, $action->getCompatibleEvents())) {
|
||||
if (! in_array($event_name, $action->getEvents())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -34,4 +34,14 @@ class App extends \Kanboard\Core\Base
|
|||
{
|
||||
return $this->color->getList();
|
||||
}
|
||||
|
||||
public function getApplicationRoles()
|
||||
{
|
||||
return $this->role->getApplicationRoles();
|
||||
}
|
||||
|
||||
public function getProjectRoles()
|
||||
{
|
||||
return $this->role->getProjectRoles();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
namespace Kanboard\Api;
|
||||
|
||||
use JsonRPC\AuthenticationFailure;
|
||||
use Symfony\Component\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* Base class
|
||||
|
@ -24,15 +23,58 @@ class Auth extends Base
|
|||
*/
|
||||
public function checkCredentials($username, $password, $class, $method)
|
||||
{
|
||||
$this->container['dispatcher']->dispatch('api.bootstrap', new Event);
|
||||
$this->dispatcher->dispatch('app.bootstrap');
|
||||
|
||||
if ($username !== 'jsonrpc' && ! $this->authentication->hasCaptcha($username) && $this->authentication->authenticate($username, $password)) {
|
||||
if ($this->isUserAuthenticated($username, $password)) {
|
||||
$this->checkProcedurePermission(true, $method);
|
||||
$this->userSession->initialize($this->user->getByUsername($username));
|
||||
} elseif ($username === 'jsonrpc' && $password === $this->config->get('api_token')) {
|
||||
} elseif ($this->isAppAuthenticated($username, $password)) {
|
||||
$this->checkProcedurePermission(false, $method);
|
||||
} else {
|
||||
throw new AuthenticationFailure('Wrong credentials');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check user credentials
|
||||
*
|
||||
* @access public
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @return boolean
|
||||
*/
|
||||
private function isUserAuthenticated($username, $password)
|
||||
{
|
||||
return $username !== 'jsonrpc' &&
|
||||
! $this->userLocking->isLocked($username) &&
|
||||
$this->authenticationManager->passwordAuthentication($username, $password);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check administrative credentials
|
||||
*
|
||||
* @access public
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @return boolean
|
||||
*/
|
||||
private function isAppAuthenticated($username, $password)
|
||||
{
|
||||
return $username === 'jsonrpc' && $password === $this->getApiToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get API Token
|
||||
*
|
||||
* @access private
|
||||
* @return string
|
||||
*/
|
||||
private function getApiToken()
|
||||
{
|
||||
if (defined('API_AUTHENTICATION_TOKEN')) {
|
||||
return API_AUTHENTICATION_TOKEN;
|
||||
}
|
||||
|
||||
return $this->config->get('api_token');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ class Category extends \Kanboard\Core\Base
|
|||
'name' => $name,
|
||||
);
|
||||
|
||||
list($valid, ) = $this->category->validateCreation($values);
|
||||
list($valid, ) = $this->categoryValidator->validateCreation($values);
|
||||
return $valid ? $this->category->create($values) : false;
|
||||
}
|
||||
|
||||
|
@ -43,7 +43,7 @@ class Category extends \Kanboard\Core\Base
|
|||
'name' => $name,
|
||||
);
|
||||
|
||||
list($valid, ) = $this->category->validateModification($values);
|
||||
list($valid, ) = $this->categoryValidator->validateModification($values);
|
||||
return $valid && $this->category->update($values);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,15 +25,16 @@ class Comment extends \Kanboard\Core\Base
|
|||
return $this->comment->remove($comment_id);
|
||||
}
|
||||
|
||||
public function createComment($task_id, $user_id, $content)
|
||||
public function createComment($task_id, $user_id, $content, $reference = '')
|
||||
{
|
||||
$values = array(
|
||||
'task_id' => $task_id,
|
||||
'user_id' => $user_id,
|
||||
'comment' => $content,
|
||||
'reference' => $reference,
|
||||
);
|
||||
|
||||
list($valid, ) = $this->comment->validateCreation($values);
|
||||
list($valid, ) = $this->commentValidator->validateCreation($values);
|
||||
|
||||
return $valid ? $this->comment->create($values) : false;
|
||||
}
|
||||
|
@ -45,7 +46,7 @@ class Comment extends \Kanboard\Core\Base
|
|||
'comment' => $content,
|
||||
);
|
||||
|
||||
list($valid, ) = $this->comment->validateModification($values);
|
||||
list($valid, ) = $this->commentValidator->validateModification($values);
|
||||
return $valid && $this->comment->update($values);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,14 +32,18 @@ class File extends \Kanboard\Core\Base
|
|||
}
|
||||
} catch (ObjectStorageException $e) {
|
||||
$this->logger->error($e->getMessage());
|
||||
return '';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
public function createFile($project_id, $task_id, $filename, $blob)
|
||||
{
|
||||
return $this->file->uploadContent($project_id, $task_id, $filename, $blob);
|
||||
try {
|
||||
return $this->file->uploadContent($project_id, $task_id, $filename, $blob);
|
||||
} catch (ObjectStorageException $e) {
|
||||
$this->logger->error($e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function removeFile($file_id)
|
||||
|
|
49
sources/app/Api/Group.php
Normal file
49
sources/app/Api/Group.php
Normal file
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Api;
|
||||
|
||||
/**
|
||||
* Group API controller
|
||||
*
|
||||
* @package api
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Group extends \Kanboard\Core\Base
|
||||
{
|
||||
public function createGroup($name, $external_id = '')
|
||||
{
|
||||
return $this->group->create($name, $external_id);
|
||||
}
|
||||
|
||||
public function updateGroup($group_id, $name = null, $external_id = null)
|
||||
{
|
||||
$values = array(
|
||||
'id' => $group_id,
|
||||
'name' => $name,
|
||||
'external_id' => $external_id,
|
||||
);
|
||||
|
||||
foreach ($values as $key => $value) {
|
||||
if (is_null($value)) {
|
||||
unset($values[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->group->update($values);
|
||||
}
|
||||
|
||||
public function removeGroup($group_id)
|
||||
{
|
||||
return $this->group->remove($group_id);
|
||||
}
|
||||
|
||||
public function getGroup($group_id)
|
||||
{
|
||||
return $this->group->getById($group_id);
|
||||
}
|
||||
|
||||
public function getAllGroups()
|
||||
{
|
||||
return $this->group->getAll();
|
||||
}
|
||||
}
|
32
sources/app/Api/GroupMember.php
Normal file
32
sources/app/Api/GroupMember.php
Normal file
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Api;
|
||||
|
||||
/**
|
||||
* Group Member API controller
|
||||
*
|
||||
* @package api
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class GroupMember extends \Kanboard\Core\Base
|
||||
{
|
||||
public function getGroupMembers($group_id)
|
||||
{
|
||||
return $this->groupMember->getMembers($group_id);
|
||||
}
|
||||
|
||||
public function addGroupMember($group_id, $user_id)
|
||||
{
|
||||
return $this->groupMember->addUser($group_id, $user_id);
|
||||
}
|
||||
|
||||
public function removeGroupMember($group_id, $user_id)
|
||||
{
|
||||
return $this->groupMember->removeUser($group_id, $user_id);
|
||||
}
|
||||
|
||||
public function isGroupMember($group_id, $user_id)
|
||||
{
|
||||
return $this->groupMember->isMember($group_id, $user_id);
|
||||
}
|
||||
}
|
|
@ -72,7 +72,7 @@ class Link extends \Kanboard\Core\Base
|
|||
'opposite_label' => $opposite_label,
|
||||
);
|
||||
|
||||
list($valid, ) = $this->link->validateCreation($values);
|
||||
list($valid, ) = $this->linkValidator->validateCreation($values);
|
||||
return $valid ? $this->link->create($label, $opposite_label) : false;
|
||||
}
|
||||
|
||||
|
@ -93,7 +93,7 @@ class Link extends \Kanboard\Core\Base
|
|||
'label' => $label,
|
||||
);
|
||||
|
||||
list($valid, ) = $this->link->validateModification($values);
|
||||
list($valid, ) = $this->linkValidator->validateModification($values);
|
||||
return $valid && $this->link->update($values);
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ class Me extends Base
|
|||
public function getMyDashboard()
|
||||
{
|
||||
$user_id = $this->userSession->getId();
|
||||
$projects = $this->project->getQueryColumnStats($this->projectPermission->getActiveMemberProjectIds($user_id))->findAll();
|
||||
$projects = $this->project->getQueryColumnStats($this->projectPermission->getActiveProjectIds($user_id))->findAll();
|
||||
$tasks = $this->taskFinder->getUserQuery($user_id)->findAll();
|
||||
|
||||
return array(
|
||||
|
@ -32,7 +32,7 @@ class Me extends Base
|
|||
|
||||
public function getMyActivityStream()
|
||||
{
|
||||
$project_ids = $this->projectPermission->getActiveMemberProjectIds($this->userSession->getId());
|
||||
$project_ids = $this->projectPermission->getActiveProjectIds($this->userSession->getId());
|
||||
return $this->projectActivity->getProjects($project_ids, 100);
|
||||
}
|
||||
|
||||
|
@ -44,13 +44,13 @@ class Me extends Base
|
|||
'is_private' => 1,
|
||||
);
|
||||
|
||||
list($valid, ) = $this->project->validateCreation($values);
|
||||
list($valid, ) = $this->projectValidator->validateCreation($values);
|
||||
return $valid ? $this->project->create($values, $this->userSession->getId(), true) : false;
|
||||
}
|
||||
|
||||
public function getMyProjectsList()
|
||||
{
|
||||
return $this->projectPermission->getMemberProjects($this->userSession->getId());
|
||||
return $this->projectUserRole->getProjectsByUser($this->userSession->getId());
|
||||
}
|
||||
|
||||
public function getMyOverdueTasks()
|
||||
|
@ -60,7 +60,7 @@ class Me extends Base
|
|||
|
||||
public function getMyProjects()
|
||||
{
|
||||
$project_ids = $this->projectPermission->getActiveMemberProjectIds($this->userSession->getId());
|
||||
$project_ids = $this->projectPermission->getActiveProjectIds($this->userSession->getId());
|
||||
$projects = $this->project->getAllByIds($project_ids);
|
||||
|
||||
return $this->formatProjects($projects);
|
||||
|
|
|
@ -69,7 +69,7 @@ class Project extends Base
|
|||
'description' => $description
|
||||
);
|
||||
|
||||
list($valid, ) = $this->project->validateCreation($values);
|
||||
list($valid, ) = $this->projectValidator->validateCreation($values);
|
||||
return $valid ? $this->project->create($values) : false;
|
||||
}
|
||||
|
||||
|
@ -81,7 +81,7 @@ class Project extends Base
|
|||
'description' => $description
|
||||
);
|
||||
|
||||
list($valid, ) = $this->project->validateModification($values);
|
||||
list($valid, ) = $this->projectValidator->validateModification($values);
|
||||
return $valid && $this->project->update($values);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,26 +2,71 @@
|
|||
|
||||
namespace Kanboard\Api;
|
||||
|
||||
use Kanboard\Core\Security\Role;
|
||||
|
||||
/**
|
||||
* ProjectPermission API controller
|
||||
* Project Permission API controller
|
||||
*
|
||||
* @package api
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class ProjectPermission extends \Kanboard\Core\Base
|
||||
{
|
||||
public function getProjectUsers($project_id)
|
||||
{
|
||||
return $this->projectUserRole->getAllUsers($project_id);
|
||||
}
|
||||
|
||||
public function getAssignableUsers($project_id, $prepend_unassigned = false)
|
||||
{
|
||||
return $this->projectUserRole->getAssignableUsersList($project_id, $prepend_unassigned);
|
||||
}
|
||||
|
||||
public function addProjectUser($project_id, $user_id, $role = Role::PROJECT_MEMBER)
|
||||
{
|
||||
return $this->projectUserRole->addUser($project_id, $user_id, $role);
|
||||
}
|
||||
|
||||
public function addProjectGroup($project_id, $group_id, $role = Role::PROJECT_MEMBER)
|
||||
{
|
||||
return $this->projectGroupRole->addGroup($project_id, $group_id, $role);
|
||||
}
|
||||
|
||||
public function removeProjectUser($project_id, $user_id)
|
||||
{
|
||||
return $this->projectUserRole->removeUser($project_id, $user_id);
|
||||
}
|
||||
|
||||
public function removeProjectGroup($project_id, $group_id)
|
||||
{
|
||||
return $this->projectGroupRole->removeGroup($project_id, $group_id);
|
||||
}
|
||||
|
||||
public function changeProjectUserRole($project_id, $user_id, $role)
|
||||
{
|
||||
return $this->projectUserRole->changeUserRole($project_id, $user_id, $role);
|
||||
}
|
||||
|
||||
public function changeProjectGroupRole($project_id, $group_id, $role)
|
||||
{
|
||||
return $this->projectGroupRole->changeGroupRole($project_id, $group_id, $role);
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
public function getMembers($project_id)
|
||||
{
|
||||
return $this->projectPermission->getMembers($project_id);
|
||||
return $this->getProjectUsers($project_id);
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
public function revokeUser($project_id, $user_id)
|
||||
{
|
||||
return $this->projectPermission->revokeMember($project_id, $user_id);
|
||||
return $this->removeProjectUser($project_id, $user_id);
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
public function allowUser($project_id, $user_id)
|
||||
{
|
||||
return $this->projectPermission->addMember($project_id, $user_id);
|
||||
return $this->addProjectUser($project_id, $user_id);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ class Subtask extends \Kanboard\Core\Base
|
|||
'status' => $status,
|
||||
);
|
||||
|
||||
list($valid, ) = $this->subtask->validateCreation($values);
|
||||
list($valid, ) = $this->subtaskValidator->validateCreation($values);
|
||||
return $valid ? $this->subtask->create($values) : false;
|
||||
}
|
||||
|
||||
|
@ -58,7 +58,7 @@ class Subtask extends \Kanboard\Core\Base
|
|||
}
|
||||
}
|
||||
|
||||
list($valid, ) = $this->subtask->validateApiModification($values);
|
||||
list($valid, ) = $this->subtaskValidator->validateApiModification($values);
|
||||
return $valid && $this->subtask->update($values);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,6 +64,16 @@ class Task extends Base
|
|||
return $this->taskPosition->movePosition($project_id, $task_id, $column_id, $position, $swimlane_id);
|
||||
}
|
||||
|
||||
public function moveTaskToProject($task_id, $project_id, $swimlane_id = null, $column_id = null, $category_id = null, $owner_id = null)
|
||||
{
|
||||
return $this->taskDuplication->moveToProject($task_id, $project_id, $swimlane_id, $column_id, $category_id, $owner_id);
|
||||
}
|
||||
|
||||
public function duplicateTaskToProject($task_id, $project_id, $swimlane_id = null, $column_id = null, $category_id = null, $owner_id = null)
|
||||
{
|
||||
return $this->taskDuplication->duplicateToProject($task_id, $project_id, $swimlane_id, $column_id, $category_id, $owner_id);
|
||||
}
|
||||
|
||||
public function createTask($title, $project_id, $color_id = '', $column_id = 0, $owner_id = 0, $creator_id = 0,
|
||||
$date_due = '', $description = '', $category_id = 0, $score = 0, $swimlane_id = 0,
|
||||
$recurrence_status = 0, $recurrence_trigger = 0, $recurrence_factor = 0, $recurrence_timeframe = 0,
|
||||
|
@ -71,6 +81,14 @@ class Task extends Base
|
|||
{
|
||||
$this->checkProjectPermission($project_id);
|
||||
|
||||
if ($owner_id !== 0 && ! $this->projectPermission->isAssignable($project_id, $owner_id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->userSession->isLogged()) {
|
||||
$creator_id = $this->userSession->getId();
|
||||
}
|
||||
|
||||
$values = array(
|
||||
'title' => $title,
|
||||
'project_id' => $project_id,
|
||||
|
@ -96,20 +114,28 @@ class Task extends Base
|
|||
return $valid ? $this->taskCreation->create($values) : false;
|
||||
}
|
||||
|
||||
public function updateTask($id, $title = null, $project_id = null, $color_id = null, $owner_id = null,
|
||||
$creator_id = null, $date_due = null, $description = null, $category_id = null, $score = null,
|
||||
public function updateTask($id, $title = null, $color_id = null, $owner_id = null,
|
||||
$date_due = null, $description = null, $category_id = null, $score = null,
|
||||
$recurrence_status = null, $recurrence_trigger = null, $recurrence_factor = null,
|
||||
$recurrence_timeframe = null, $recurrence_basedate = null, $reference = null)
|
||||
{
|
||||
$this->checkTaskPermission($id);
|
||||
|
||||
$project_id = $this->taskFinder->getProjectId($id);
|
||||
|
||||
if ($project_id === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($owner_id !== null && $owner_id != 0 && ! $this->projectPermission->isAssignable($project_id, $owner_id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$values = array(
|
||||
'id' => $id,
|
||||
'title' => $title,
|
||||
'project_id' => $project_id,
|
||||
'color_id' => $color_id,
|
||||
'owner_id' => $owner_id,
|
||||
'creator_id' => $creator_id,
|
||||
'date_due' => $date_due,
|
||||
'description' => $description,
|
||||
'category_id' => $category_id,
|
||||
|
|
|
@ -2,7 +2,11 @@
|
|||
|
||||
namespace Kanboard\Api;
|
||||
|
||||
use Kanboard\Auth\Ldap;
|
||||
use LogicException;
|
||||
use Kanboard\Core\Security\Role;
|
||||
use Kanboard\Core\Ldap\Client as LdapClient;
|
||||
use Kanboard\Core\Ldap\ClientException as LdapException;
|
||||
use Kanboard\Core\Ldap\User as LdapUser;
|
||||
|
||||
/**
|
||||
* User API controller
|
||||
|
@ -27,7 +31,7 @@ class User extends \Kanboard\Core\Base
|
|||
return $this->user->remove($user_id);
|
||||
}
|
||||
|
||||
public function createUser($username, $password, $name = '', $email = '', $is_admin = 0, $is_project_admin = 0)
|
||||
public function createUser($username, $password, $name = '', $email = '', $role = Role::APP_USER)
|
||||
{
|
||||
$values = array(
|
||||
'username' => $username,
|
||||
|
@ -35,44 +39,53 @@ class User extends \Kanboard\Core\Base
|
|||
'confirmation' => $password,
|
||||
'name' => $name,
|
||||
'email' => $email,
|
||||
'is_admin' => $is_admin,
|
||||
'is_project_admin' => $is_project_admin,
|
||||
'role' => $role,
|
||||
);
|
||||
|
||||
list($valid, ) = $this->user->validateCreation($values);
|
||||
list($valid, ) = $this->userValidator->validateCreation($values);
|
||||
return $valid ? $this->user->create($values) : false;
|
||||
}
|
||||
|
||||
public function createLdapUser($username = '', $email = '', $is_admin = 0, $is_project_admin = 0)
|
||||
public function createLdapUser($username)
|
||||
{
|
||||
$ldap = new Ldap($this->container);
|
||||
$user = $ldap->lookup($username, $email);
|
||||
try {
|
||||
|
||||
if (! $user) {
|
||||
$ldap = LdapClient::connect();
|
||||
$user = LdapUser::getUser($ldap, sprintf(LDAP_USER_FILTER, $username));
|
||||
|
||||
if ($user === null) {
|
||||
$this->logger->info('User not found in LDAP server');
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($user->getUsername() === '') {
|
||||
throw new LogicException('Username not found in LDAP profile, check the parameter LDAP_USER_ATTRIBUTE_USERNAME');
|
||||
}
|
||||
|
||||
$values = array(
|
||||
'username' => $user->getUsername(),
|
||||
'name' => $user->getName(),
|
||||
'email' => $user->getEmail(),
|
||||
'role' => $user->getRole(),
|
||||
'is_ldap_user' => 1,
|
||||
);
|
||||
|
||||
return $this->user->create($values);
|
||||
|
||||
} catch (LdapException $e) {
|
||||
$this->logger->error($e->getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
$values = array(
|
||||
'username' => $user['username'],
|
||||
'name' => $user['name'],
|
||||
'email' => $user['email'],
|
||||
'is_ldap_user' => 1,
|
||||
'is_admin' => $is_admin,
|
||||
'is_project_admin' => $is_project_admin,
|
||||
);
|
||||
|
||||
return $this->user->create($values);
|
||||
}
|
||||
|
||||
public function updateUser($id, $username = null, $name = null, $email = null, $is_admin = null, $is_project_admin = null)
|
||||
public function updateUser($id, $username = null, $name = null, $email = null, $role = null)
|
||||
{
|
||||
$values = array(
|
||||
'id' => $id,
|
||||
'username' => $username,
|
||||
'name' => $name,
|
||||
'email' => $email,
|
||||
'is_admin' => $is_admin,
|
||||
'is_project_admin' => $is_project_admin,
|
||||
'role' => $role,
|
||||
);
|
||||
|
||||
foreach ($values as $key => $value) {
|
||||
|
@ -81,7 +94,7 @@ class User extends \Kanboard\Core\Base
|
|||
}
|
||||
}
|
||||
|
||||
list($valid, ) = $this->user->validateApiModification($values);
|
||||
list($valid, ) = $this->userValidator->validateApiModification($values);
|
||||
return $valid && $this->user->update($values);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,49 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Auth;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
use Kanboard\Model\User;
|
||||
use Kanboard\Event\AuthEvent;
|
||||
|
||||
/**
|
||||
* Database authentication
|
||||
*
|
||||
* @package auth
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Database extends Base
|
||||
{
|
||||
/**
|
||||
* Backend name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const AUTH_NAME = 'Database';
|
||||
|
||||
/**
|
||||
* Authenticate a user
|
||||
*
|
||||
* @access public
|
||||
* @param string $username Username
|
||||
* @param string $password Password
|
||||
* @return boolean
|
||||
*/
|
||||
public function authenticate($username, $password)
|
||||
{
|
||||
$user = $this->db
|
||||
->table(User::TABLE)
|
||||
->eq('username', $username)
|
||||
->eq('disable_login_form', 0)
|
||||
->eq('is_ldap_user', 0)
|
||||
->findOne();
|
||||
|
||||
if (is_array($user) && password_verify($password, $user['password'])) {
|
||||
$this->userSession->initialize($user);
|
||||
$this->container['dispatcher']->dispatch('auth.success', new AuthEvent(self::AUTH_NAME, $user['id']));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
125
sources/app/Auth/DatabaseAuth.php
Normal file
125
sources/app/Auth/DatabaseAuth.php
Normal file
|
@ -0,0 +1,125 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Auth;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
use Kanboard\Core\Security\PasswordAuthenticationProviderInterface;
|
||||
use Kanboard\Core\Security\SessionCheckProviderInterface;
|
||||
use Kanboard\Model\User;
|
||||
use Kanboard\User\DatabaseUserProvider;
|
||||
|
||||
/**
|
||||
* Database Authentication Provider
|
||||
*
|
||||
* @package auth
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class DatabaseAuth extends Base implements PasswordAuthenticationProviderInterface, SessionCheckProviderInterface
|
||||
{
|
||||
/**
|
||||
* User properties
|
||||
*
|
||||
* @access protected
|
||||
* @var array
|
||||
*/
|
||||
protected $userInfo = array();
|
||||
|
||||
/**
|
||||
* Username
|
||||
*
|
||||
* @access protected
|
||||
* @var string
|
||||
*/
|
||||
protected $username = '';
|
||||
|
||||
/**
|
||||
* Password
|
||||
*
|
||||
* @access protected
|
||||
* @var string
|
||||
*/
|
||||
protected $password = '';
|
||||
|
||||
/**
|
||||
* Get authentication provider name
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return 'Database';
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate the user
|
||||
*
|
||||
* @access public
|
||||
* @return boolean
|
||||
*/
|
||||
public function authenticate()
|
||||
{
|
||||
$user = $this->db
|
||||
->table(User::TABLE)
|
||||
->columns('id', 'password')
|
||||
->eq('username', $this->username)
|
||||
->eq('disable_login_form', 0)
|
||||
->eq('is_ldap_user', 0)
|
||||
->findOne();
|
||||
|
||||
if (! empty($user) && password_verify($this->password, $user['password'])) {
|
||||
$this->userInfo = $user;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the user session is valid
|
||||
*
|
||||
* @access public
|
||||
* @return boolean
|
||||
*/
|
||||
public function isValidSession()
|
||||
{
|
||||
return $this->user->exists($this->userSession->getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user object
|
||||
*
|
||||
* @access public
|
||||
* @return \Kanboard\User\DatabaseUserProvider
|
||||
*/
|
||||
public function getUser()
|
||||
{
|
||||
if (empty($this->userInfo)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new DatabaseUserProvider($this->userInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set username
|
||||
*
|
||||
* @access public
|
||||
* @param string $username
|
||||
*/
|
||||
public function setUsername($username)
|
||||
{
|
||||
$this->username = $username;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set password
|
||||
*
|
||||
* @access public
|
||||
* @param string $password
|
||||
*/
|
||||
public function setPassword($password)
|
||||
{
|
||||
$this->password = $password;
|
||||
}
|
||||
}
|
|
@ -1,123 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Auth;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
use Kanboard\Event\AuthEvent;
|
||||
|
||||
/**
|
||||
* Github backend
|
||||
*
|
||||
* @package auth
|
||||
*/
|
||||
class Github extends Base
|
||||
{
|
||||
/**
|
||||
* Backend name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const AUTH_NAME = 'Github';
|
||||
|
||||
/**
|
||||
* OAuth2 instance
|
||||
*
|
||||
* @access private
|
||||
* @var \Kanboard\Core\OAuth2
|
||||
*/
|
||||
private $service;
|
||||
|
||||
/**
|
||||
* Authenticate a Github user
|
||||
*
|
||||
* @access public
|
||||
* @param string $github_id Github user id
|
||||
* @return boolean
|
||||
*/
|
||||
public function authenticate($github_id)
|
||||
{
|
||||
$user = $this->user->getByGithubId($github_id);
|
||||
|
||||
if (! empty($user)) {
|
||||
$this->userSession->initialize($user);
|
||||
$this->container['dispatcher']->dispatch('auth.success', new AuthEvent(self::AUTH_NAME, $user['id']));
|
||||
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)
|
||||
{
|
||||
return $this->user->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
|
||||
*/
|
||||
public function updateUser($user_id, array $profile)
|
||||
{
|
||||
$user = $this->user->getById($user_id);
|
||||
|
||||
return $this->user->update(array(
|
||||
'id' => $user_id,
|
||||
'github_id' => $profile['id'],
|
||||
'email' => empty($user['email']) ? $profile['email'] : $user['email'],
|
||||
'name' => empty($user['name']) ? $profile['name'] : $user['name'],
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get OAuth2 configured service
|
||||
*
|
||||
* @access public
|
||||
* @return Kanboard\Core\OAuth2
|
||||
*/
|
||||
public function getService()
|
||||
{
|
||||
if (empty($this->service)) {
|
||||
$this->service = $this->oauth->createService(
|
||||
GITHUB_CLIENT_ID,
|
||||
GITHUB_CLIENT_SECRET,
|
||||
$this->helper->url->to('oauth', 'github', array(), '', true),
|
||||
GITHUB_OAUTH_AUTHORIZE_URL,
|
||||
GITHUB_OAUTH_TOKEN_URL,
|
||||
array()
|
||||
);
|
||||
}
|
||||
|
||||
return $this->service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Github profile
|
||||
*
|
||||
* @access public
|
||||
* @param string $code
|
||||
* @return array
|
||||
*/
|
||||
public function getProfile($code)
|
||||
{
|
||||
$this->getService()->getAccessToken($code);
|
||||
|
||||
return $this->httpClient->getJson(
|
||||
GITHUB_API_URL.'user',
|
||||
array($this->getService()->getAuthorizationHeader())
|
||||
);
|
||||
}
|
||||
}
|
143
sources/app/Auth/GithubAuth.php
Normal file
143
sources/app/Auth/GithubAuth.php
Normal file
|
@ -0,0 +1,143 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Auth;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
use Kanboard\Core\Security\OAuthAuthenticationProviderInterface;
|
||||
use Kanboard\User\GithubUserProvider;
|
||||
|
||||
/**
|
||||
* Github Authentication Provider
|
||||
*
|
||||
* @package auth
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class GithubAuth extends Base implements OAuthAuthenticationProviderInterface
|
||||
{
|
||||
/**
|
||||
* User properties
|
||||
*
|
||||
* @access protected
|
||||
* @var \Kanboard\User\GithubUserProvider
|
||||
*/
|
||||
protected $userInfo = null;
|
||||
|
||||
/**
|
||||
* OAuth2 instance
|
||||
*
|
||||
* @access protected
|
||||
* @var \Kanboard\Core\Http\OAuth2
|
||||
*/
|
||||
protected $service;
|
||||
|
||||
/**
|
||||
* OAuth2 code
|
||||
*
|
||||
* @access protected
|
||||
* @var string
|
||||
*/
|
||||
protected $code = '';
|
||||
|
||||
/**
|
||||
* Get authentication provider name
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return 'Github';
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate the user
|
||||
*
|
||||
* @access public
|
||||
* @return boolean
|
||||
*/
|
||||
public function authenticate()
|
||||
{
|
||||
$profile = $this->getProfile();
|
||||
|
||||
if (! empty($profile)) {
|
||||
$this->userInfo = new GithubUserProvider($profile);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Code
|
||||
*
|
||||
* @access public
|
||||
* @param string $code
|
||||
* @return GithubAuth
|
||||
*/
|
||||
public function setCode($code)
|
||||
{
|
||||
$this->code = $code;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user object
|
||||
*
|
||||
* @access public
|
||||
* @return GithubUserProvider
|
||||
*/
|
||||
public function getUser()
|
||||
{
|
||||
return $this->userInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get configured OAuth2 service
|
||||
*
|
||||
* @access public
|
||||
* @return \Kanboard\Core\Http\OAuth2
|
||||
*/
|
||||
public function getService()
|
||||
{
|
||||
if (empty($this->service)) {
|
||||
$this->service = $this->oauth->createService(
|
||||
GITHUB_CLIENT_ID,
|
||||
GITHUB_CLIENT_SECRET,
|
||||
$this->helper->url->to('oauth', 'github', array(), '', true),
|
||||
GITHUB_OAUTH_AUTHORIZE_URL,
|
||||
GITHUB_OAUTH_TOKEN_URL,
|
||||
array()
|
||||
);
|
||||
}
|
||||
|
||||
return $this->service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Github profile
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getProfile()
|
||||
{
|
||||
$this->getService()->getAccessToken($this->code);
|
||||
|
||||
return $this->httpClient->getJson(
|
||||
GITHUB_API_URL.'user',
|
||||
array($this->getService()->getAuthorizationHeader())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlink user
|
||||
*
|
||||
* @access public
|
||||
* @param integer $userId
|
||||
* @return bool
|
||||
*/
|
||||
public function unlink($userId)
|
||||
{
|
||||
return $this->user->update(array('id' => $userId, 'github_id' => ''));
|
||||
}
|
||||
}
|
|
@ -1,123 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Auth;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
use Kanboard\Event\AuthEvent;
|
||||
|
||||
/**
|
||||
* Gitlab backend
|
||||
*
|
||||
* @package auth
|
||||
*/
|
||||
class Gitlab extends Base
|
||||
{
|
||||
/**
|
||||
* Backend name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const AUTH_NAME = 'Gitlab';
|
||||
|
||||
/**
|
||||
* OAuth2 instance
|
||||
*
|
||||
* @access private
|
||||
* @var \Kanboard\Core\OAuth2
|
||||
*/
|
||||
private $service;
|
||||
|
||||
/**
|
||||
* Authenticate a Gitlab user
|
||||
*
|
||||
* @access public
|
||||
* @param string $gitlab_id Gitlab user id
|
||||
* @return boolean
|
||||
*/
|
||||
public function authenticate($gitlab_id)
|
||||
{
|
||||
$user = $this->user->getByGitlabId($gitlab_id);
|
||||
|
||||
if (! empty($user)) {
|
||||
$this->userSession->initialize($user);
|
||||
$this->container['dispatcher']->dispatch('auth.success', new AuthEvent(self::AUTH_NAME, $user['id']));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlink a Gitlab account for a given user
|
||||
*
|
||||
* @access public
|
||||
* @param integer $user_id User id
|
||||
* @return boolean
|
||||
*/
|
||||
public function unlink($user_id)
|
||||
{
|
||||
return $this->user->update(array(
|
||||
'id' => $user_id,
|
||||
'gitlab_id' => '',
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the user table based on the Gitlab profile information
|
||||
*
|
||||
* @access public
|
||||
* @param integer $user_id User id
|
||||
* @param array $profile Gitlab profile
|
||||
* @return boolean
|
||||
*/
|
||||
public function updateUser($user_id, array $profile)
|
||||
{
|
||||
$user = $this->user->getById($user_id);
|
||||
|
||||
return $this->user->update(array(
|
||||
'id' => $user_id,
|
||||
'gitlab_id' => $profile['id'],
|
||||
'email' => empty($user['email']) ? $profile['email'] : $user['email'],
|
||||
'name' => empty($user['name']) ? $profile['name'] : $user['name'],
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get OAuth2 configured service
|
||||
*
|
||||
* @access public
|
||||
* @return Kanboard\Core\OAuth2
|
||||
*/
|
||||
public function getService()
|
||||
{
|
||||
if (empty($this->service)) {
|
||||
$this->service = $this->oauth->createService(
|
||||
GITLAB_CLIENT_ID,
|
||||
GITLAB_CLIENT_SECRET,
|
||||
$this->helper->url->to('oauth', 'gitlab', array(), '', true),
|
||||
GITLAB_OAUTH_AUTHORIZE_URL,
|
||||
GITLAB_OAUTH_TOKEN_URL,
|
||||
array()
|
||||
);
|
||||
}
|
||||
|
||||
return $this->service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Gitlab profile
|
||||
*
|
||||
* @access public
|
||||
* @param string $code
|
||||
* @return array
|
||||
*/
|
||||
public function getProfile($code)
|
||||
{
|
||||
$this->getService()->getAccessToken($code);
|
||||
|
||||
return $this->httpClient->getJson(
|
||||
GITLAB_API_URL.'user',
|
||||
array($this->getService()->getAuthorizationHeader())
|
||||
);
|
||||
}
|
||||
}
|
143
sources/app/Auth/GitlabAuth.php
Normal file
143
sources/app/Auth/GitlabAuth.php
Normal file
|
@ -0,0 +1,143 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Auth;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
use Kanboard\Core\Security\OAuthAuthenticationProviderInterface;
|
||||
use Kanboard\User\GitlabUserProvider;
|
||||
|
||||
/**
|
||||
* Gitlab Authentication Provider
|
||||
*
|
||||
* @package auth
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class GitlabAuth extends Base implements OAuthAuthenticationProviderInterface
|
||||
{
|
||||
/**
|
||||
* User properties
|
||||
*
|
||||
* @access private
|
||||
* @var \Kanboard\User\GitlabUserProvider
|
||||
*/
|
||||
private $userInfo = null;
|
||||
|
||||
/**
|
||||
* OAuth2 instance
|
||||
*
|
||||
* @access protected
|
||||
* @var \Kanboard\Core\Http\OAuth2
|
||||
*/
|
||||
protected $service;
|
||||
|
||||
/**
|
||||
* OAuth2 code
|
||||
*
|
||||
* @access protected
|
||||
* @var string
|
||||
*/
|
||||
protected $code = '';
|
||||
|
||||
/**
|
||||
* Get authentication provider name
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return 'Gitlab';
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate the user
|
||||
*
|
||||
* @access public
|
||||
* @return boolean
|
||||
*/
|
||||
public function authenticate()
|
||||
{
|
||||
$profile = $this->getProfile();
|
||||
|
||||
if (! empty($profile)) {
|
||||
$this->userInfo = new GitlabUserProvider($profile);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Code
|
||||
*
|
||||
* @access public
|
||||
* @param string $code
|
||||
* @return GitlabAuth
|
||||
*/
|
||||
public function setCode($code)
|
||||
{
|
||||
$this->code = $code;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user object
|
||||
*
|
||||
* @access public
|
||||
* @return GitlabUserProvider
|
||||
*/
|
||||
public function getUser()
|
||||
{
|
||||
return $this->userInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get configured OAuth2 service
|
||||
*
|
||||
* @access public
|
||||
* @return \Kanboard\Core\Http\OAuth2
|
||||
*/
|
||||
public function getService()
|
||||
{
|
||||
if (empty($this->service)) {
|
||||
$this->service = $this->oauth->createService(
|
||||
GITLAB_CLIENT_ID,
|
||||
GITLAB_CLIENT_SECRET,
|
||||
$this->helper->url->to('oauth', 'gitlab', array(), '', true),
|
||||
GITLAB_OAUTH_AUTHORIZE_URL,
|
||||
GITLAB_OAUTH_TOKEN_URL,
|
||||
array()
|
||||
);
|
||||
}
|
||||
|
||||
return $this->service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Gitlab profile
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getProfile()
|
||||
{
|
||||
$this->getService()->getAccessToken($this->code);
|
||||
|
||||
return $this->httpClient->getJson(
|
||||
GITLAB_API_URL.'user',
|
||||
array($this->getService()->getAuthorizationHeader())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlink user
|
||||
*
|
||||
* @access public
|
||||
* @param integer $userId
|
||||
* @return bool
|
||||
*/
|
||||
public function unlink($userId)
|
||||
{
|
||||
return $this->user->update(array('id' => $userId, 'gitlab_id' => ''));
|
||||
}
|
||||
}
|
|
@ -1,124 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Auth;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
use Kanboard\Event\AuthEvent;
|
||||
|
||||
/**
|
||||
* Google backend
|
||||
*
|
||||
* @package auth
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Google extends Base
|
||||
{
|
||||
/**
|
||||
* Backend name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const AUTH_NAME = 'Google';
|
||||
|
||||
/**
|
||||
* OAuth2 instance
|
||||
*
|
||||
* @access private
|
||||
* @var \Kanboard\Core\OAuth2
|
||||
*/
|
||||
private $service;
|
||||
|
||||
/**
|
||||
* Authenticate a Google user
|
||||
*
|
||||
* @access public
|
||||
* @param string $google_id Google unique id
|
||||
* @return boolean
|
||||
*/
|
||||
public function authenticate($google_id)
|
||||
{
|
||||
$user = $this->user->getByGoogleId($google_id);
|
||||
|
||||
if (! empty($user)) {
|
||||
$this->userSession->initialize($user);
|
||||
$this->container['dispatcher']->dispatch('auth.success', new AuthEvent(self::AUTH_NAME, $user['id']));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlink a Google account for a given user
|
||||
*
|
||||
* @access public
|
||||
* @param integer $user_id User id
|
||||
* @return boolean
|
||||
*/
|
||||
public function unlink($user_id)
|
||||
{
|
||||
return $this->user->update(array(
|
||||
'id' => $user_id,
|
||||
'google_id' => '',
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the user table based on the Google profile information
|
||||
*
|
||||
* @access public
|
||||
* @param integer $user_id User id
|
||||
* @param array $profile Google profile
|
||||
* @return boolean
|
||||
*/
|
||||
public function updateUser($user_id, array $profile)
|
||||
{
|
||||
$user = $this->user->getById($user_id);
|
||||
|
||||
return $this->user->update(array(
|
||||
'id' => $user_id,
|
||||
'google_id' => $profile['id'],
|
||||
'email' => empty($user['email']) ? $profile['email'] : $user['email'],
|
||||
'name' => empty($user['name']) ? $profile['name'] : $user['name'],
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get OAuth2 configured service
|
||||
*
|
||||
* @access public
|
||||
* @return KanboardCore\OAuth2
|
||||
*/
|
||||
public function getService()
|
||||
{
|
||||
if (empty($this->service)) {
|
||||
$this->service = $this->oauth->createService(
|
||||
GOOGLE_CLIENT_ID,
|
||||
GOOGLE_CLIENT_SECRET,
|
||||
$this->helper->url->to('oauth', 'google', array(), '', true),
|
||||
'https://accounts.google.com/o/oauth2/auth',
|
||||
'https://accounts.google.com/o/oauth2/token',
|
||||
array('https://www.googleapis.com/auth/userinfo.email', 'https://www.googleapis.com/auth/userinfo.profile')
|
||||
);
|
||||
}
|
||||
|
||||
return $this->service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Google profile
|
||||
*
|
||||
* @access public
|
||||
* @param string $code
|
||||
* @return array
|
||||
*/
|
||||
public function getProfile($code)
|
||||
{
|
||||
$this->getService()->getAccessToken($code);
|
||||
|
||||
return $this->httpClient->getJson(
|
||||
'https://www.googleapis.com/oauth2/v1/userinfo',
|
||||
array($this->getService()->getAuthorizationHeader())
|
||||
);
|
||||
}
|
||||
}
|
143
sources/app/Auth/GoogleAuth.php
Normal file
143
sources/app/Auth/GoogleAuth.php
Normal file
|
@ -0,0 +1,143 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Auth;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
use Kanboard\Core\Security\OAuthAuthenticationProviderInterface;
|
||||
use Kanboard\User\GoogleUserProvider;
|
||||
|
||||
/**
|
||||
* Google Authentication Provider
|
||||
*
|
||||
* @package auth
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class GoogleAuth extends Base implements OAuthAuthenticationProviderInterface
|
||||
{
|
||||
/**
|
||||
* User properties
|
||||
*
|
||||
* @access protected
|
||||
* @var \Kanboard\User\GoogleUserProvider
|
||||
*/
|
||||
protected $userInfo = null;
|
||||
|
||||
/**
|
||||
* OAuth2 instance
|
||||
*
|
||||
* @access protected
|
||||
* @var \Kanboard\Core\Http\OAuth2
|
||||
*/
|
||||
protected $service;
|
||||
|
||||
/**
|
||||
* OAuth2 code
|
||||
*
|
||||
* @access protected
|
||||
* @var string
|
||||
*/
|
||||
protected $code = '';
|
||||
|
||||
/**
|
||||
* Get authentication provider name
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return 'Google';
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate the user
|
||||
*
|
||||
* @access public
|
||||
* @return boolean
|
||||
*/
|
||||
public function authenticate()
|
||||
{
|
||||
$profile = $this->getProfile();
|
||||
|
||||
if (! empty($profile)) {
|
||||
$this->userInfo = new GoogleUserProvider($profile);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Code
|
||||
*
|
||||
* @access public
|
||||
* @param string $code
|
||||
* @return GoogleAuth
|
||||
*/
|
||||
public function setCode($code)
|
||||
{
|
||||
$this->code = $code;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user object
|
||||
*
|
||||
* @access public
|
||||
* @return GoogleUserProvider
|
||||
*/
|
||||
public function getUser()
|
||||
{
|
||||
return $this->userInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get configured OAuth2 service
|
||||
*
|
||||
* @access public
|
||||
* @return \Kanboard\Core\Http\OAuth2
|
||||
*/
|
||||
public function getService()
|
||||
{
|
||||
if (empty($this->service)) {
|
||||
$this->service = $this->oauth->createService(
|
||||
GOOGLE_CLIENT_ID,
|
||||
GOOGLE_CLIENT_SECRET,
|
||||
$this->helper->url->to('oauth', 'google', array(), '', true),
|
||||
'https://accounts.google.com/o/oauth2/auth',
|
||||
'https://accounts.google.com/o/oauth2/token',
|
||||
array('https://www.googleapis.com/auth/userinfo.email', 'https://www.googleapis.com/auth/userinfo.profile')
|
||||
);
|
||||
}
|
||||
|
||||
return $this->service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Google profile
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getProfile()
|
||||
{
|
||||
$this->getService()->getAccessToken($this->code);
|
||||
|
||||
return $this->httpClient->getJson(
|
||||
'https://www.googleapis.com/oauth2/v1/userinfo',
|
||||
array($this->getService()->getAuthorizationHeader())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlink user
|
||||
*
|
||||
* @access public
|
||||
* @param integer $userId
|
||||
* @return bool
|
||||
*/
|
||||
public function unlink($userId)
|
||||
{
|
||||
return $this->user->update(array('id' => $userId, 'google_id' => ''));
|
||||
}
|
||||
}
|
|
@ -1,521 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Auth;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
use Kanboard\Event\AuthEvent;
|
||||
|
||||
/**
|
||||
* LDAP model
|
||||
*
|
||||
* @package auth
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Ldap extends Base
|
||||
{
|
||||
/**
|
||||
* Backend name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const AUTH_NAME = 'LDAP';
|
||||
|
||||
/**
|
||||
* Get LDAP server name
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getLdapServer()
|
||||
{
|
||||
return LDAP_SERVER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get LDAP bind type
|
||||
*
|
||||
* @access public
|
||||
* @return integer
|
||||
*/
|
||||
public function getLdapBindType()
|
||||
{
|
||||
return LDAP_BIND_TYPE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get LDAP server port
|
||||
*
|
||||
* @access public
|
||||
* @return integer
|
||||
*/
|
||||
public function getLdapPort()
|
||||
{
|
||||
return LDAP_PORT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get LDAP username (proxy auth)
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getLdapUsername()
|
||||
{
|
||||
return LDAP_USERNAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get LDAP password (proxy auth)
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getLdapPassword()
|
||||
{
|
||||
return LDAP_PASSWORD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get LDAP Base DN
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getLdapBaseDn()
|
||||
{
|
||||
return LDAP_ACCOUNT_BASE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get LDAP account id attribute
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getLdapAccountId()
|
||||
{
|
||||
return LDAP_ACCOUNT_ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get LDAP account email attribute
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getLdapAccountEmail()
|
||||
{
|
||||
return LDAP_ACCOUNT_EMAIL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get LDAP account name attribute
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getLdapAccountName()
|
||||
{
|
||||
return LDAP_ACCOUNT_FULLNAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get LDAP account memberof attribute
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getLdapAccountMemberOf()
|
||||
{
|
||||
return LDAP_ACCOUNT_MEMBEROF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get LDAP admin group DN
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getLdapGroupAdmin()
|
||||
{
|
||||
return LDAP_GROUP_ADMIN_DN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get LDAP project admin group DN
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getLdapGroupProjectAdmin()
|
||||
{
|
||||
return LDAP_GROUP_PROJECT_ADMIN_DN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get LDAP username pattern
|
||||
*
|
||||
* @access public
|
||||
* @param string $username
|
||||
* @return string
|
||||
*/
|
||||
public function getLdapUserPattern($username)
|
||||
{
|
||||
return sprintf(LDAP_USER_PATTERN, $username);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the LDAP username is case sensitive
|
||||
*
|
||||
* @access public
|
||||
* @return boolean
|
||||
*/
|
||||
public function isLdapAccountCaseSensitive()
|
||||
{
|
||||
return LDAP_USERNAME_CASE_SENSITIVE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the automatic account creation is enabled
|
||||
*
|
||||
* @access public
|
||||
* @return boolean
|
||||
*/
|
||||
public function isLdapAccountCreationEnabled()
|
||||
{
|
||||
return LDAP_ACCOUNT_CREATION;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ge the list of attributes to fetch when reading the LDAP user entry
|
||||
*
|
||||
* Must returns array with index that start at 0 otherwise ldap_search returns a warning "Array initialization wrong"
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getProfileAttributes()
|
||||
{
|
||||
return array_values(array_filter(array(
|
||||
$this->getLdapAccountId(),
|
||||
$this->getLdapAccountName(),
|
||||
$this->getLdapAccountEmail(),
|
||||
$this->getLdapAccountMemberOf()
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate the user
|
||||
*
|
||||
* @access public
|
||||
* @param string $username Username
|
||||
* @param string $password Password
|
||||
* @return boolean
|
||||
*/
|
||||
public function authenticate($username, $password)
|
||||
{
|
||||
$username = $this->isLdapAccountCaseSensitive() ? $username : strtolower($username);
|
||||
$result = $this->findUser($username, $password);
|
||||
|
||||
if (is_array($result)) {
|
||||
$user = $this->user->getByUsername($username);
|
||||
|
||||
if (! empty($user)) {
|
||||
|
||||
// There is already a local user with that name
|
||||
if ($user['is_ldap_user'] == 0) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
|
||||
// We create automatically a new user
|
||||
if ($this->isLdapAccountCreationEnabled() && $this->user->create($result) !== false) {
|
||||
$user = $this->user->getByUsername($username);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// We open the session
|
||||
$this->userSession->initialize($user);
|
||||
$this->container['dispatcher']->dispatch('auth.success', new AuthEvent(self::AUTH_NAME, $user['id']));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the user from the LDAP server
|
||||
*
|
||||
* @access public
|
||||
* @param string $username Username
|
||||
* @param string $password Password
|
||||
* @return boolean|array
|
||||
*/
|
||||
public function findUser($username, $password)
|
||||
{
|
||||
$ldap = $this->connect();
|
||||
|
||||
if ($ldap !== false && $this->bind($ldap, $username, $password)) {
|
||||
return $this->getProfile($ldap, $username, $password);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* LDAP connection
|
||||
*
|
||||
* @access public
|
||||
* @return resource|boolean
|
||||
*/
|
||||
public function connect()
|
||||
{
|
||||
if (! function_exists('ldap_connect')) {
|
||||
$this->logger->error('LDAP: The PHP LDAP extension is required');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip SSL certificate verification
|
||||
if (! LDAP_SSL_VERIFY) {
|
||||
putenv('LDAPTLS_REQCERT=never');
|
||||
}
|
||||
|
||||
$ldap = ldap_connect($this->getLdapServer(), $this->getLdapPort());
|
||||
|
||||
if ($ldap === false) {
|
||||
$this->logger->error('LDAP: Unable to connect to the LDAP server');
|
||||
return false;
|
||||
}
|
||||
|
||||
ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3);
|
||||
ldap_set_option($ldap, LDAP_OPT_REFERRALS, 0);
|
||||
ldap_set_option($ldap, LDAP_OPT_NETWORK_TIMEOUT, 1);
|
||||
ldap_set_option($ldap, LDAP_OPT_TIMELIMIT, 1);
|
||||
|
||||
if (LDAP_START_TLS && ! @ldap_start_tls($ldap)) {
|
||||
$this->logger->error('LDAP: Unable to use ldap_start_tls()');
|
||||
return false;
|
||||
}
|
||||
|
||||
return $ldap;
|
||||
}
|
||||
|
||||
/**
|
||||
* LDAP authentication
|
||||
*
|
||||
* @access public
|
||||
* @param resource $ldap
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @return boolean
|
||||
*/
|
||||
public function bind($ldap, $username, $password)
|
||||
{
|
||||
if ($this->getLdapBindType() === 'user') {
|
||||
$ldap_username = sprintf($this->getLdapUsername(), $username);
|
||||
$ldap_password = $password;
|
||||
} elseif ($this->getLdapBindType() === 'proxy') {
|
||||
$ldap_username = $this->getLdapUsername();
|
||||
$ldap_password = $this->getLdapPassword();
|
||||
} else {
|
||||
$ldap_username = null;
|
||||
$ldap_password = null;
|
||||
}
|
||||
|
||||
if (! @ldap_bind($ldap, $ldap_username, $ldap_password)) {
|
||||
$this->logger->error('LDAP: Unable to bind to server with: '.$ldap_username);
|
||||
$this->logger->error('LDAP: bind type='.$this->getLdapBindType());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get LDAP user profile
|
||||
*
|
||||
* @access public
|
||||
* @param resource $ldap
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @return boolean|array
|
||||
*/
|
||||
public function getProfile($ldap, $username, $password)
|
||||
{
|
||||
$user_pattern = $this->getLdapUserPattern($username);
|
||||
$entries = $this->executeQuery($ldap, $user_pattern);
|
||||
|
||||
if ($entries === false) {
|
||||
$this->logger->error('LDAP: Unable to get user profile: '.$user_pattern);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (@ldap_bind($ldap, $entries[0]['dn'], $password)) {
|
||||
return $this->prepareProfile($ldap, $entries, $username);
|
||||
}
|
||||
|
||||
if (DEBUG) {
|
||||
$this->logger->debug('LDAP: wrong password for '.$entries[0]['dn']);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build user profile from LDAP information
|
||||
*
|
||||
* @access public
|
||||
* @param resource $ldap
|
||||
* @param array $entries
|
||||
* @param string $username
|
||||
* @return boolean|array
|
||||
*/
|
||||
public function prepareProfile($ldap, array $entries, $username)
|
||||
{
|
||||
if ($this->getLdapAccountId() !== '') {
|
||||
$username = $this->getEntry($entries, $this->getLdapAccountId(), $username);
|
||||
}
|
||||
|
||||
return array(
|
||||
'username' => $username,
|
||||
'name' => $this->getEntry($entries, $this->getLdapAccountName()),
|
||||
'email' => $this->getEntry($entries, $this->getLdapAccountEmail()),
|
||||
'is_admin' => (int) $this->isMemberOf($this->getEntries($entries, $this->getLdapAccountMemberOf()), $this->getLdapGroupAdmin()),
|
||||
'is_project_admin' => (int) $this->isMemberOf($this->getEntries($entries, $this->getLdapAccountMemberOf()), $this->getLdapGroupProjectAdmin()),
|
||||
'is_ldap_user' => 1,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check group membership
|
||||
*
|
||||
* @access public
|
||||
* @param array $group_entries
|
||||
* @param string $group_dn
|
||||
* @return boolean
|
||||
*/
|
||||
public function isMemberOf(array $group_entries, $group_dn)
|
||||
{
|
||||
if (! isset($group_entries['count']) || empty($group_dn)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for ($i = 0; $i < $group_entries['count']; $i++) {
|
||||
if ($group_entries[$i] === $group_dn) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve info on LDAP user by username or email
|
||||
*
|
||||
* @access public
|
||||
* @param string $username
|
||||
* @param string $email
|
||||
* @return boolean|array
|
||||
*/
|
||||
public function lookup($username = null, $email = null)
|
||||
{
|
||||
$query = $this->getLookupQuery($username, $email);
|
||||
if ($query === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Connect and attempt anonymous or proxy binding
|
||||
$ldap = $this->connect();
|
||||
if ($ldap === false || ! $this->bind($ldap, null, null)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try to find user
|
||||
$entries = $this->executeQuery($ldap, $query);
|
||||
if ($entries === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// User id not retrieved: LDAP_ACCOUNT_ID not properly configured
|
||||
if (empty($username) && ! isset($entries[0][$this->getLdapAccountId()][0])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->prepareProfile($ldap, $entries, $username);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute LDAP query
|
||||
*
|
||||
* @access private
|
||||
* @param resource $ldap
|
||||
* @param string $query
|
||||
* @return boolean|array
|
||||
*/
|
||||
private function executeQuery($ldap, $query)
|
||||
{
|
||||
$sr = @ldap_search($ldap, $this->getLdapBaseDn(), $query, $this->getProfileAttributes());
|
||||
if ($sr === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$entries = ldap_get_entries($ldap, $sr);
|
||||
if ($entries === false || count($entries) === 0 || $entries['count'] == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the LDAP query to find a user
|
||||
*
|
||||
* @access private
|
||||
* @param string $username
|
||||
* @param string $email
|
||||
* @return string
|
||||
*/
|
||||
private function getLookupQuery($username, $email)
|
||||
{
|
||||
if (! empty($username) && ! empty($email)) {
|
||||
return '(&('.$this->getLdapUserPattern($username).')('.$this->getLdapAccountEmail().'='.$email.'))';
|
||||
} elseif (! empty($username)) {
|
||||
return $this->getLdapUserPattern($username);
|
||||
} elseif (! empty($email)) {
|
||||
return '('.$this->getLdapAccountEmail().'='.$email.')';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return one entry from a list of entries
|
||||
*
|
||||
* @access private
|
||||
* @param array $entries LDAP entries
|
||||
* @param string $key Key
|
||||
* @param string $default Default value if key not set in entry
|
||||
* @return string
|
||||
*/
|
||||
private function getEntry(array $entries, $key, $default = '')
|
||||
{
|
||||
return isset($entries[0][$key][0]) ? $entries[0][$key][0] : $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return subset of entries
|
||||
*
|
||||
* @access private
|
||||
* @param array $entries
|
||||
* @param string $key
|
||||
* @param array $default
|
||||
* @return array
|
||||
*/
|
||||
private function getEntries(array $entries, $key, $default = array())
|
||||
{
|
||||
return isset($entries[0][$key]) ? $entries[0][$key] : $default;
|
||||
}
|
||||
}
|
172
sources/app/Auth/LdapAuth.php
Normal file
172
sources/app/Auth/LdapAuth.php
Normal file
|
@ -0,0 +1,172 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Auth;
|
||||
|
||||
use LogicException;
|
||||
use Kanboard\Core\Base;
|
||||
use Kanboard\Core\Ldap\Client as LdapClient;
|
||||
use Kanboard\Core\Ldap\ClientException as LdapException;
|
||||
use Kanboard\Core\Ldap\User as LdapUser;
|
||||
use Kanboard\Core\Security\PasswordAuthenticationProviderInterface;
|
||||
|
||||
/**
|
||||
* LDAP Authentication Provider
|
||||
*
|
||||
* @package auth
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class LdapAuth extends Base implements PasswordAuthenticationProviderInterface
|
||||
{
|
||||
/**
|
||||
* User properties
|
||||
*
|
||||
* @access protected
|
||||
* @var \Kanboard\User\LdapUserProvider
|
||||
*/
|
||||
protected $userInfo = null;
|
||||
|
||||
/**
|
||||
* Username
|
||||
*
|
||||
* @access protected
|
||||
* @var string
|
||||
*/
|
||||
protected $username = '';
|
||||
|
||||
/**
|
||||
* Password
|
||||
*
|
||||
* @access protected
|
||||
* @var string
|
||||
*/
|
||||
protected $password = '';
|
||||
|
||||
/**
|
||||
* Get authentication provider name
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return 'LDAP';
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate the user
|
||||
*
|
||||
* @access public
|
||||
* @return boolean
|
||||
*/
|
||||
public function authenticate()
|
||||
{
|
||||
try {
|
||||
|
||||
$client = LdapClient::connect($this->getLdapUsername(), $this->getLdapPassword());
|
||||
$user = LdapUser::getUser($client, $this->username);
|
||||
|
||||
if ($user === null) {
|
||||
$this->logger->info('User not found in LDAP server');
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($user->getUsername() === '') {
|
||||
throw new LogicException('Username not found in LDAP profile, check the parameter LDAP_USER_ATTRIBUTE_USERNAME');
|
||||
}
|
||||
|
||||
if ($client->authenticate($user->getDn(), $this->password)) {
|
||||
$this->userInfo = $user;
|
||||
return true;
|
||||
}
|
||||
|
||||
} catch (LdapException $e) {
|
||||
$this->logger->error($e->getMessage());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user object
|
||||
*
|
||||
* @access public
|
||||
* @return \Kanboard\User\LdapUserProvider
|
||||
*/
|
||||
public function getUser()
|
||||
{
|
||||
return $this->userInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set username
|
||||
*
|
||||
* @access public
|
||||
* @param string $username
|
||||
*/
|
||||
public function setUsername($username)
|
||||
{
|
||||
$this->username = $username;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set password
|
||||
*
|
||||
* @access public
|
||||
* @param string $password
|
||||
*/
|
||||
public function setPassword($password)
|
||||
{
|
||||
$this->password = $password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get LDAP username (proxy auth)
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getLdapUsername()
|
||||
{
|
||||
switch ($this->getLdapBindType()) {
|
||||
case 'proxy':
|
||||
return LDAP_USERNAME;
|
||||
case 'user':
|
||||
return sprintf(LDAP_USERNAME, $this->username);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get LDAP password (proxy auth)
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getLdapPassword()
|
||||
{
|
||||
switch ($this->getLdapBindType()) {
|
||||
case 'proxy':
|
||||
return LDAP_PASSWORD;
|
||||
case 'user':
|
||||
return $this->password;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get LDAP bind type
|
||||
*
|
||||
* @access public
|
||||
* @return integer
|
||||
*/
|
||||
public function getLdapBindType()
|
||||
{
|
||||
if (LDAP_BIND_TYPE !== 'user' && LDAP_BIND_TYPE !== 'proxy' && LDAP_BIND_TYPE !== 'anonymous') {
|
||||
throw new LogicException('Wrong value for the parameter LDAP_BIND_TYPE');
|
||||
}
|
||||
|
||||
return LDAP_BIND_TYPE;
|
||||
}
|
||||
}
|
|
@ -1,323 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Auth;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
use Kanboard\Core\Http\Request;
|
||||
use Kanboard\Event\AuthEvent;
|
||||
use Kanboard\Core\Security\Token;
|
||||
|
||||
/**
|
||||
* RememberMe model
|
||||
*
|
||||
* @package auth
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class RememberMe extends Base
|
||||
{
|
||||
/**
|
||||
* Backend name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const AUTH_NAME = 'RememberMe';
|
||||
|
||||
/**
|
||||
* SQL table name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const TABLE = 'remember_me';
|
||||
|
||||
/**
|
||||
* Cookie name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const COOKIE_NAME = '__R';
|
||||
|
||||
/**
|
||||
* Expiration (60 days)
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
const EXPIRATION = 5184000;
|
||||
|
||||
/**
|
||||
* Get a remember me record
|
||||
*
|
||||
* @access public
|
||||
* @param $token
|
||||
* @param $sequence
|
||||
* @return mixed
|
||||
*/
|
||||
public function find($token, $sequence)
|
||||
{
|
||||
return $this->db
|
||||
->table(self::TABLE)
|
||||
->eq('token', $token)
|
||||
->eq('sequence', $sequence)
|
||||
->gt('expiration', time())
|
||||
->findOne();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all sessions for a given user
|
||||
*
|
||||
* @access public
|
||||
* @param integer $user_id User id
|
||||
* @return array
|
||||
*/
|
||||
public function getAll($user_id)
|
||||
{
|
||||
return $this->db
|
||||
->table(self::TABLE)
|
||||
->eq('user_id', $user_id)
|
||||
->desc('date_creation')
|
||||
->columns('id', 'ip', 'user_agent', 'date_creation', 'expiration')
|
||||
->findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate the user with the cookie
|
||||
*
|
||||
* @access public
|
||||
* @return bool
|
||||
*/
|
||||
public function authenticate()
|
||||
{
|
||||
$credentials = $this->readCookie();
|
||||
|
||||
if ($credentials !== false) {
|
||||
$record = $this->find($credentials['token'], $credentials['sequence']);
|
||||
|
||||
if ($record) {
|
||||
|
||||
// Update the sequence
|
||||
$this->writeCookie(
|
||||
$record['token'],
|
||||
$this->update($record['token']),
|
||||
$record['expiration']
|
||||
);
|
||||
|
||||
// Create the session
|
||||
$this->userSession->initialize($this->user->getById($record['user_id']));
|
||||
|
||||
// Do not ask 2FA for remember me session
|
||||
$this->sessionStorage->postAuth['validated'] = true;
|
||||
|
||||
$this->container['dispatcher']->dispatch(
|
||||
'auth.success',
|
||||
new AuthEvent(self::AUTH_NAME, $this->userSession->getId())
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a session record
|
||||
*
|
||||
* @access public
|
||||
* @param integer $session_id Session id
|
||||
* @return mixed
|
||||
*/
|
||||
public function remove($session_id)
|
||||
{
|
||||
return $this->db
|
||||
->table(self::TABLE)
|
||||
->eq('id', $session_id)
|
||||
->remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the current RememberMe session and the cookie
|
||||
*
|
||||
* @access public
|
||||
* @param integer $user_id User id
|
||||
*/
|
||||
public function destroy($user_id)
|
||||
{
|
||||
$credentials = $this->readCookie();
|
||||
|
||||
if ($credentials !== false) {
|
||||
$this->deleteCookie();
|
||||
|
||||
$this->db
|
||||
->table(self::TABLE)
|
||||
->eq('user_id', $user_id)
|
||||
->eq('token', $credentials['token'])
|
||||
->remove();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new RememberMe session
|
||||
*
|
||||
* @access public
|
||||
* @param integer $user_id User id
|
||||
* @param string $ip IP Address
|
||||
* @param string $user_agent User Agent
|
||||
* @return array
|
||||
*/
|
||||
public function create($user_id, $ip, $user_agent)
|
||||
{
|
||||
$token = hash('sha256', $user_id.$user_agent.$ip.Token::getToken());
|
||||
$sequence = Token::getToken();
|
||||
$expiration = time() + self::EXPIRATION;
|
||||
|
||||
$this->cleanup($user_id);
|
||||
|
||||
$this
|
||||
->db
|
||||
->table(self::TABLE)
|
||||
->insert(array(
|
||||
'user_id' => $user_id,
|
||||
'ip' => $ip,
|
||||
'user_agent' => $user_agent,
|
||||
'token' => $token,
|
||||
'sequence' => $sequence,
|
||||
'expiration' => $expiration,
|
||||
'date_creation' => time(),
|
||||
));
|
||||
|
||||
return array(
|
||||
'token' => $token,
|
||||
'sequence' => $sequence,
|
||||
'expiration' => $expiration,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove old sessions for a given user
|
||||
*
|
||||
* @access public
|
||||
* @param integer $user_id User id
|
||||
* @return bool
|
||||
*/
|
||||
public function cleanup($user_id)
|
||||
{
|
||||
return $this->db
|
||||
->table(self::TABLE)
|
||||
->eq('user_id', $user_id)
|
||||
->lt('expiration', time())
|
||||
->remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new sequence token and update the database
|
||||
*
|
||||
* @access public
|
||||
* @param string $token Session token
|
||||
* @return string
|
||||
*/
|
||||
public function update($token)
|
||||
{
|
||||
$new_sequence = Token::getToken();
|
||||
|
||||
$this->db
|
||||
->table(self::TABLE)
|
||||
->eq('token', $token)
|
||||
->update(array('sequence' => $new_sequence));
|
||||
|
||||
return $new_sequence;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the cookie
|
||||
*
|
||||
* @access public
|
||||
* @param string $token Session token
|
||||
* @param string $sequence Sequence token
|
||||
* @return string
|
||||
*/
|
||||
public function encodeCookie($token, $sequence)
|
||||
{
|
||||
return implode('|', array($token, $sequence));
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode the value of a cookie
|
||||
*
|
||||
* @access public
|
||||
* @param string $value Raw cookie data
|
||||
* @return array
|
||||
*/
|
||||
public function decodeCookie($value)
|
||||
{
|
||||
list($token, $sequence) = explode('|', $value);
|
||||
|
||||
return array(
|
||||
'token' => $token,
|
||||
'sequence' => $sequence,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the current user has a RememberMe cookie
|
||||
*
|
||||
* @access public
|
||||
* @return bool
|
||||
*/
|
||||
public function hasCookie()
|
||||
{
|
||||
return ! empty($_COOKIE[self::COOKIE_NAME]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write and encode the cookie
|
||||
*
|
||||
* @access public
|
||||
* @param string $token Session token
|
||||
* @param string $sequence Sequence token
|
||||
* @param string $expiration Cookie expiration
|
||||
*/
|
||||
public function writeCookie($token, $sequence, $expiration)
|
||||
{
|
||||
setcookie(
|
||||
self::COOKIE_NAME,
|
||||
$this->encodeCookie($token, $sequence),
|
||||
$expiration,
|
||||
$this->helper->url->dir(),
|
||||
null,
|
||||
Request::isHTTPS(),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and decode the cookie
|
||||
*
|
||||
* @access public
|
||||
* @return mixed
|
||||
*/
|
||||
public function readCookie()
|
||||
{
|
||||
if (empty($_COOKIE[self::COOKIE_NAME])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->decodeCookie($_COOKIE[self::COOKIE_NAME]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the cookie
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function deleteCookie()
|
||||
{
|
||||
setcookie(
|
||||
self::COOKIE_NAME,
|
||||
'',
|
||||
time() - 3600,
|
||||
$this->helper->url->dir(),
|
||||
null,
|
||||
Request::isHTTPS(),
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
79
sources/app/Auth/RememberMeAuth.php
Normal file
79
sources/app/Auth/RememberMeAuth.php
Normal file
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Auth;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
use Kanboard\Core\Security\PreAuthenticationProviderInterface;
|
||||
use Kanboard\User\DatabaseUserProvider;
|
||||
|
||||
/**
|
||||
* Rember Me Cookie Authentication Provider
|
||||
*
|
||||
* @package auth
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class RememberMeAuth extends Base implements PreAuthenticationProviderInterface
|
||||
{
|
||||
/**
|
||||
* User properties
|
||||
*
|
||||
* @access protected
|
||||
* @var array
|
||||
*/
|
||||
protected $userInfo = array();
|
||||
|
||||
/**
|
||||
* Get authentication provider name
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return 'RememberMe';
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate the user
|
||||
*
|
||||
* @access public
|
||||
* @return boolean
|
||||
*/
|
||||
public function authenticate()
|
||||
{
|
||||
$credentials = $this->rememberMeCookie->read();
|
||||
|
||||
if ($credentials !== false) {
|
||||
$session = $this->rememberMeSession->find($credentials['token'], $credentials['sequence']);
|
||||
|
||||
if (! empty($session)) {
|
||||
$this->rememberMeCookie->write(
|
||||
$session['token'],
|
||||
$this->rememberMeSession->updateSequence($session['token']),
|
||||
$session['expiration']
|
||||
);
|
||||
|
||||
$this->userInfo = $this->user->getById($session['user_id']);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user object
|
||||
*
|
||||
* @access public
|
||||
* @return DatabaseUserProvider
|
||||
*/
|
||||
public function getUser()
|
||||
{
|
||||
if (empty($this->userInfo)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new DatabaseUserProvider($this->userInfo);
|
||||
}
|
||||
}
|
|
@ -1,83 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Auth;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
use Kanboard\Event\AuthEvent;
|
||||
|
||||
/**
|
||||
* ReverseProxy backend
|
||||
*
|
||||
* @package auth
|
||||
* @author Sylvain Veyrié
|
||||
*/
|
||||
class ReverseProxy extends Base
|
||||
{
|
||||
/**
|
||||
* Backend name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const AUTH_NAME = 'ReverseProxy';
|
||||
|
||||
/**
|
||||
* Get username from the reverse proxy
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getUsername()
|
||||
{
|
||||
return isset($_SERVER[REVERSE_PROXY_USER_HEADER]) ? $_SERVER[REVERSE_PROXY_USER_HEADER] : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate the user with the HTTP header
|
||||
*
|
||||
* @access public
|
||||
* @return bool
|
||||
*/
|
||||
public function authenticate()
|
||||
{
|
||||
if (isset($_SERVER[REVERSE_PROXY_USER_HEADER])) {
|
||||
$login = $_SERVER[REVERSE_PROXY_USER_HEADER];
|
||||
$user = $this->user->getByUsername($login);
|
||||
|
||||
if (empty($user)) {
|
||||
$this->createUser($login);
|
||||
$user = $this->user->getByUsername($login);
|
||||
}
|
||||
|
||||
$this->userSession->initialize($user);
|
||||
$this->container['dispatcher']->dispatch('auth.success', new AuthEvent(self::AUTH_NAME, $user['id']));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create automatically a new local user after the authentication
|
||||
*
|
||||
* @access private
|
||||
* @param string $login Username
|
||||
* @return bool
|
||||
*/
|
||||
private function createUser($login)
|
||||
{
|
||||
$email = strpos($login, '@') !== false ? $login : '';
|
||||
|
||||
if (REVERSE_PROXY_DEFAULT_DOMAIN !== '' && empty($email)) {
|
||||
$email = $login.'@'.REVERSE_PROXY_DEFAULT_DOMAIN;
|
||||
}
|
||||
|
||||
return $this->user->create(array(
|
||||
'email' => $email,
|
||||
'username' => $login,
|
||||
'is_admin' => REVERSE_PROXY_DEFAULT_ADMIN === $login,
|
||||
'is_ldap_user' => 1,
|
||||
'disable_login_form' => 1,
|
||||
));
|
||||
}
|
||||
}
|
76
sources/app/Auth/ReverseProxyAuth.php
Normal file
76
sources/app/Auth/ReverseProxyAuth.php
Normal file
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Auth;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
use Kanboard\Core\Security\PreAuthenticationProviderInterface;
|
||||
use Kanboard\Core\Security\SessionCheckProviderInterface;
|
||||
use Kanboard\User\ReverseProxyUserProvider;
|
||||
|
||||
/**
|
||||
* Reverse-Proxy Authentication Provider
|
||||
*
|
||||
* @package auth
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class ReverseProxyAuth extends Base implements PreAuthenticationProviderInterface, SessionCheckProviderInterface
|
||||
{
|
||||
/**
|
||||
* User properties
|
||||
*
|
||||
* @access protected
|
||||
* @var \Kanboard\User\ReverseProxyUserProvider
|
||||
*/
|
||||
protected $userInfo = null;
|
||||
|
||||
/**
|
||||
* Get authentication provider name
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return 'ReverseProxy';
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate the user
|
||||
*
|
||||
* @access public
|
||||
* @return boolean
|
||||
*/
|
||||
public function authenticate()
|
||||
{
|
||||
$username = $this->request->getRemoteUser();
|
||||
|
||||
if (! empty($username)) {
|
||||
$this->userInfo = new ReverseProxyUserProvider($username);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the user session is valid
|
||||
*
|
||||
* @access public
|
||||
* @return boolean
|
||||
*/
|
||||
public function isValidSession()
|
||||
{
|
||||
return $this->request->getRemoteUser() === $this->userSession->getUsername();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user object
|
||||
*
|
||||
* @access public
|
||||
* @return ReverseProxyUserProvider
|
||||
*/
|
||||
public function getUser()
|
||||
{
|
||||
return $this->userInfo;
|
||||
}
|
||||
}
|
144
sources/app/Auth/TotpAuth.php
Normal file
144
sources/app/Auth/TotpAuth.php
Normal file
|
@ -0,0 +1,144 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Auth;
|
||||
|
||||
use Otp\Otp;
|
||||
use Otp\GoogleAuthenticator;
|
||||
use Base32\Base32;
|
||||
use Kanboard\Core\Base;
|
||||
use Kanboard\Core\Security\PostAuthenticationProviderInterface;
|
||||
|
||||
/**
|
||||
* TOTP Authentication Provider
|
||||
*
|
||||
* @package auth
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class TotpAuth extends Base implements PostAuthenticationProviderInterface
|
||||
{
|
||||
/**
|
||||
* User pin code
|
||||
*
|
||||
* @access protected
|
||||
* @var string
|
||||
*/
|
||||
protected $code = '';
|
||||
|
||||
/**
|
||||
* Private key
|
||||
*
|
||||
* @access protected
|
||||
* @var string
|
||||
*/
|
||||
protected $secret = '';
|
||||
|
||||
/**
|
||||
* Get authentication provider name
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return t('Time-based One-time Password Algorithm');
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate the user
|
||||
*
|
||||
* @access public
|
||||
* @return boolean
|
||||
*/
|
||||
public function authenticate()
|
||||
{
|
||||
$otp = new Otp;
|
||||
return $otp->checkTotp(Base32::decode($this->secret), $this->code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called before to prompt the user
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function beforeCode()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Set validation code
|
||||
*
|
||||
* @access public
|
||||
* @param string $code
|
||||
*/
|
||||
public function setCode($code)
|
||||
{
|
||||
$this->code = $code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate secret
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function generateSecret()
|
||||
{
|
||||
$this->secret = GoogleAuthenticator::generateRandom();
|
||||
return $this->secret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set secret token
|
||||
*
|
||||
* @access public
|
||||
* @param string $secret
|
||||
*/
|
||||
public function setSecret($secret)
|
||||
{
|
||||
$this->secret = $secret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get secret token
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getSecret()
|
||||
{
|
||||
return $this->secret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get QR code url
|
||||
*
|
||||
* @access public
|
||||
* @param string $label
|
||||
* @return string
|
||||
*/
|
||||
public function getQrCodeUrl($label)
|
||||
{
|
||||
if (empty($this->secret)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return GoogleAuthenticator::getQrCodeUrl('totp', $label, $this->secret);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get key url (empty if no url can be provided)
|
||||
*
|
||||
* @access public
|
||||
* @param string $label
|
||||
* @return string
|
||||
*/
|
||||
public function getKeyUrl($label)
|
||||
{
|
||||
if (empty($this->secret)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return GoogleAuthenticator::getKeyUri('totp', $label, $this->secret);
|
||||
}
|
||||
}
|
|
@ -18,17 +18,18 @@ class Action extends Base
|
|||
public function index()
|
||||
{
|
||||
$project = $this->getProject();
|
||||
$actions = $this->action->getAllByProject($project['id']);
|
||||
|
||||
$this->response->html($this->projectLayout('action/index', array(
|
||||
'values' => array('project_id' => $project['id']),
|
||||
'project' => $project,
|
||||
'actions' => $this->action->getAllByProject($project['id']),
|
||||
'available_actions' => $this->action->getAvailableActions(),
|
||||
'available_events' => $this->action->getAvailableEvents(),
|
||||
'available_params' => $this->action->getAllActionParameters(),
|
||||
'actions' => $actions,
|
||||
'available_actions' => $this->actionManager->getAvailableActions(),
|
||||
'available_events' => $this->eventManager->getAll(),
|
||||
'available_params' => $this->actionManager->getAvailableParameters($actions),
|
||||
'columns_list' => $this->board->getColumnsList($project['id']),
|
||||
'users_list' => $this->projectPermission->getMemberList($project['id']),
|
||||
'projects_list' => $this->project->getList(false),
|
||||
'users_list' => $this->projectUserRole->getAssignableUsersList($project['id']),
|
||||
'projects_list' => $this->projectUserRole->getProjectsByUser($this->userSession->getId()),
|
||||
'colors_list' => $this->color->getList(),
|
||||
'categories_list' => $this->category->getList($project['id']),
|
||||
'links_list' => $this->link->getList(0, false),
|
||||
|
@ -53,7 +54,7 @@ class Action extends Base
|
|||
$this->response->html($this->projectLayout('action/event', array(
|
||||
'values' => $values,
|
||||
'project' => $project,
|
||||
'events' => $this->action->getCompatibleEvents($values['action_name']),
|
||||
'events' => $this->actionManager->getCompatibleEvents($values['action_name']),
|
||||
'title' => t('Automatic actions')
|
||||
)));
|
||||
}
|
||||
|
@ -72,21 +73,21 @@ class Action extends Base
|
|||
$this->response->redirect($this->helper->url->to('action', 'index', array('project_id' => $project['id'])));
|
||||
}
|
||||
|
||||
$action = $this->action->load($values['action_name'], $values['project_id'], $values['event_name']);
|
||||
$action = $this->actionManager->getAction($values['action_name']);
|
||||
$action_params = $action->getActionRequiredParameters();
|
||||
|
||||
if (empty($action_params)) {
|
||||
$this->doCreation($project, $values + array('params' => array()));
|
||||
}
|
||||
|
||||
$projects_list = $this->project->getList(false);
|
||||
$projects_list = $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId());
|
||||
unset($projects_list[$project['id']]);
|
||||
|
||||
$this->response->html($this->projectLayout('action/params', array(
|
||||
'values' => $values,
|
||||
'action_params' => $action_params,
|
||||
'columns_list' => $this->board->getColumnsList($project['id']),
|
||||
'users_list' => $this->projectPermission->getMemberList($project['id']),
|
||||
'users_list' => $this->projectUserRole->getAssignableUsersList($project['id']),
|
||||
'projects_list' => $projects_list,
|
||||
'colors_list' => $this->color->getList(),
|
||||
'categories_list' => $this->category->getList($project['id']),
|
||||
|
@ -115,7 +116,7 @@ class Action extends Base
|
|||
*/
|
||||
private function doCreation(array $project, array $values)
|
||||
{
|
||||
list($valid, ) = $this->action->validateCreation($values);
|
||||
list($valid, ) = $this->actionValidator->validateCreation($values);
|
||||
|
||||
if ($valid) {
|
||||
if ($this->action->create($values) !== false) {
|
||||
|
@ -139,8 +140,8 @@ class Action extends Base
|
|||
|
||||
$this->response->html($this->projectLayout('action/remove', array(
|
||||
'action' => $this->action->getById($this->request->getIntegerParam('action_id')),
|
||||
'available_events' => $this->action->getAvailableEvents(),
|
||||
'available_actions' => $this->action->getAvailableActions(),
|
||||
'available_events' => $this->eventManager->getAll(),
|
||||
'available_actions' => $this->actionManager->getAvailableActions(),
|
||||
'project' => $project,
|
||||
'title' => t('Remove an action')
|
||||
)));
|
||||
|
|
|
@ -20,7 +20,7 @@ class Activity extends Base
|
|||
$project = $this->getProject();
|
||||
|
||||
$this->response->html($this->template->layout('activity/project', array(
|
||||
'board_selector' => $this->projectPermission->getAllowedProjects($this->userSession->getId()),
|
||||
'board_selector' => $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()),
|
||||
'events' => $this->projectActivity->getProject($project['id']),
|
||||
'project' => $project,
|
||||
'title' => t('%s\'s activity', $project['name'])
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Controller;
|
||||
use Kanboard\Model\Task as TaskModel;
|
||||
|
||||
/**
|
||||
* Project Analytic controller
|
||||
|
@ -20,7 +21,7 @@ class Analytic extends Base
|
|||
*/
|
||||
private function layout($template, array $params)
|
||||
{
|
||||
$params['board_selector'] = $this->projectPermission->getAllowedProjects($this->userSession->getId());
|
||||
$params['board_selector'] = $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId());
|
||||
$params['content_for_sublayout'] = $this->template->render($template, $params);
|
||||
|
||||
return $this->template->layout('analytic/layout', $params);
|
||||
|
@ -34,17 +35,7 @@ class Analytic extends Base
|
|||
public function leadAndCycleTime()
|
||||
{
|
||||
$project = $this->getProject();
|
||||
$values = $this->request->getValues();
|
||||
|
||||
$this->projectDailyStats->updateTotals($project['id'], date('Y-m-d'));
|
||||
|
||||
$from = $this->request->getStringParam('from', date('Y-m-d', strtotime('-1week')));
|
||||
$to = $this->request->getStringParam('to', date('Y-m-d'));
|
||||
|
||||
if (! empty($values)) {
|
||||
$from = $values['from'];
|
||||
$to = $values['to'];
|
||||
}
|
||||
list($from, $to) = $this->getDates();
|
||||
|
||||
$this->response->html($this->layout('analytic/lead_cycle_time', array(
|
||||
'values' => array(
|
||||
|
@ -52,7 +43,7 @@ class Analytic extends Base
|
|||
'to' => $to,
|
||||
),
|
||||
'project' => $project,
|
||||
'average' => $this->projectAnalytic->getAverageLeadAndCycleTime($project['id']),
|
||||
'average' => $this->averageLeadCycleTimeAnalytic->build($project['id']),
|
||||
'metrics' => $this->projectDailyStats->getRawMetrics($project['id'], $from, $to),
|
||||
'date_format' => $this->config->get('application_date_format'),
|
||||
'date_formats' => $this->dateParser->getAvailableFormats(),
|
||||
|
@ -60,6 +51,32 @@ class Analytic extends Base
|
|||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Show comparison between actual and estimated hours chart
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function compareHours()
|
||||
{
|
||||
$project = $this->getProject();
|
||||
$params = $this->getProjectFilters('analytic', 'compareHours');
|
||||
$query = $this->taskFilter->create()->filterByProject($params['project']['id'])->getQuery();
|
||||
|
||||
$paginator = $this->paginator
|
||||
->setUrl('analytic', 'compareHours', array('project_id' => $project['id']))
|
||||
->setMax(30)
|
||||
->setOrder(TaskModel::TABLE.'.id')
|
||||
->setQuery($query)
|
||||
->calculate();
|
||||
|
||||
$this->response->html($this->layout('analytic/compare_hours', array(
|
||||
'project' => $project,
|
||||
'paginator' => $paginator,
|
||||
'metrics' => $this->estimatedTimeComparisonAnalytic->build($project['id']),
|
||||
'title' => t('Compare hours for "%s"', $project['name']),
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Show average time spent by column
|
||||
*
|
||||
|
@ -71,7 +88,7 @@ class Analytic extends Base
|
|||
|
||||
$this->response->html($this->layout('analytic/avg_time_columns', array(
|
||||
'project' => $project,
|
||||
'metrics' => $this->projectAnalytic->getAverageTimeSpentByColumn($project['id']),
|
||||
'metrics' => $this->averageTimeSpentColumnAnalytic->build($project['id']),
|
||||
'title' => t('Average time spent into each column for "%s"', $project['name']),
|
||||
)));
|
||||
}
|
||||
|
@ -87,7 +104,7 @@ class Analytic extends Base
|
|||
|
||||
$this->response->html($this->layout('analytic/tasks', array(
|
||||
'project' => $project,
|
||||
'metrics' => $this->projectAnalytic->getTaskRepartition($project['id']),
|
||||
'metrics' => $this->taskDistributionAnalytic->build($project['id']),
|
||||
'title' => t('Task repartition for "%s"', $project['name']),
|
||||
)));
|
||||
}
|
||||
|
@ -103,7 +120,7 @@ class Analytic extends Base
|
|||
|
||||
$this->response->html($this->layout('analytic/users', array(
|
||||
'project' => $project,
|
||||
'metrics' => $this->projectAnalytic->getUserRepartition($project['id']),
|
||||
'metrics' => $this->userDistributionAnalytic->build($project['id']),
|
||||
'title' => t('User repartition for "%s"', $project['name']),
|
||||
)));
|
||||
}
|
||||
|
@ -132,21 +149,14 @@ class Analytic extends Base
|
|||
* Common method for CFD and Burdown chart
|
||||
*
|
||||
* @access private
|
||||
* @param string $template
|
||||
* @param string $column
|
||||
* @param string $title
|
||||
*/
|
||||
private function commonAggregateMetrics($template, $column, $title)
|
||||
{
|
||||
$project = $this->getProject();
|
||||
$values = $this->request->getValues();
|
||||
|
||||
$this->projectDailyColumnStats->updateTotals($project['id'], date('Y-m-d'));
|
||||
|
||||
$from = $this->request->getStringParam('from', date('Y-m-d', strtotime('-1week')));
|
||||
$to = $this->request->getStringParam('to', date('Y-m-d'));
|
||||
|
||||
if (! empty($values)) {
|
||||
$from = $values['from'];
|
||||
$to = $values['to'];
|
||||
}
|
||||
list($from, $to) = $this->getDates();
|
||||
|
||||
$display_graph = $this->projectDailyColumnStats->countDays($project['id'], $from, $to) >= 2;
|
||||
|
||||
|
@ -163,4 +173,19 @@ class Analytic extends Base
|
|||
'title' => t($title, $project['name']),
|
||||
)));
|
||||
}
|
||||
|
||||
private function getDates()
|
||||
{
|
||||
$values = $this->request->getValues();
|
||||
|
||||
$from = $this->request->getStringParam('from', date('Y-m-d', strtotime('-1week')));
|
||||
$to = $this->request->getStringParam('to', date('Y-m-d'));
|
||||
|
||||
if (! empty($values)) {
|
||||
$from = $values['from'];
|
||||
$to = $values['to'];
|
||||
}
|
||||
|
||||
return array($from, $to);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ class App extends Base
|
|||
*/
|
||||
private function layout($template, array $params)
|
||||
{
|
||||
$params['board_selector'] = $this->projectPermission->getAllowedProjects($this->userSession->getId());
|
||||
$params['board_selector'] = $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId());
|
||||
$params['content_for_sublayout'] = $this->template->render($template, $params);
|
||||
|
||||
return $this->template->layout('app/layout', $params);
|
||||
|
@ -42,7 +42,7 @@ class App extends Base
|
|||
->setUrl('app', $action, array('pagination' => 'projects', 'user_id' => $user_id))
|
||||
->setMax($max)
|
||||
->setOrder('name')
|
||||
->setQuery($this->project->getQueryColumnStats($this->projectPermission->getActiveMemberProjectIds($user_id)))
|
||||
->setQuery($this->project->getQueryColumnStats($this->projectPermission->getActiveProjectIds($user_id)))
|
||||
->calculateOnlyIf($this->request->getStringParam('pagination') === 'projects');
|
||||
}
|
||||
|
||||
|
@ -169,7 +169,7 @@ class App extends Base
|
|||
|
||||
$this->response->html($this->layout('app/activity', array(
|
||||
'title' => t('My activity stream'),
|
||||
'events' => $this->projectActivity->getProjects($this->projectPermission->getActiveMemberProjectIds($user['id']), 100),
|
||||
'events' => $this->projectActivity->getProjects($this->projectPermission->getActiveProjectIds($user['id']), 100),
|
||||
'user' => $user,
|
||||
)));
|
||||
}
|
||||
|
@ -202,49 +202,4 @@ class App extends Base
|
|||
'user' => $user,
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Render Markdown text and reply with the HTML Code
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function preview()
|
||||
{
|
||||
$payload = $this->request->getJson();
|
||||
|
||||
if (empty($payload['text'])) {
|
||||
$this->response->html('<p>'.t('Nothing to preview...').'</p>');
|
||||
}
|
||||
|
||||
$this->response->html($this->helper->text->markdown($payload['text']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Task autocompletion (Ajax)
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function autocomplete()
|
||||
{
|
||||
$search = $this->request->getStringParam('term');
|
||||
$projects = $this->projectPermission->getActiveMemberProjectIds($this->userSession->getId());
|
||||
|
||||
if (empty($projects)) {
|
||||
$this->response->json(array());
|
||||
}
|
||||
|
||||
$filter = $this->taskFilterAutoCompleteFormatter
|
||||
->create()
|
||||
->filterByProjects($projects)
|
||||
->excludeTasks(array($this->request->getIntegerParam('exclude_task_id')));
|
||||
|
||||
// Search by task id or by title
|
||||
if (ctype_digit($search)) {
|
||||
$filter->filterById($search);
|
||||
} else {
|
||||
$filter->filterByTitle($search);
|
||||
}
|
||||
|
||||
$this->response->json($filter->format());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
|
||||
namespace Kanboard\Controller;
|
||||
|
||||
use Gregwar\Captcha\CaptchaBuilder;
|
||||
|
||||
/**
|
||||
* Authentication controller
|
||||
*
|
||||
|
@ -24,7 +22,7 @@ class Auth extends Base
|
|||
}
|
||||
|
||||
$this->response->html($this->template->layout('auth/index', array(
|
||||
'captcha' => isset($values['username']) && $this->authentication->hasCaptcha($values['username']),
|
||||
'captcha' => ! empty($values['username']) && $this->userLocking->hasCaptcha($values['username']),
|
||||
'errors' => $errors,
|
||||
'values' => $values,
|
||||
'no_layout' => true,
|
||||
|
@ -40,18 +38,11 @@ class Auth extends Base
|
|||
public function check()
|
||||
{
|
||||
$values = $this->request->getValues();
|
||||
list($valid, $errors) = $this->authentication->validateForm($values);
|
||||
$this->sessionStorage->hasRememberMe = ! empty($values['remember_me']);
|
||||
list($valid, $errors) = $this->authValidator->validateForm($values);
|
||||
|
||||
if ($valid) {
|
||||
if (isset($this->sessionStorage->redirectAfterLogin)
|
||||
&& ! empty($this->sessionStorage->redirectAfterLogin)
|
||||
&& ! filter_var($this->sessionStorage->redirectAfterLogin, FILTER_VALIDATE_URL)) {
|
||||
$redirect = $this->sessionStorage->redirectAfterLogin;
|
||||
unset($this->sessionStorage->redirectAfterLogin);
|
||||
$this->response->redirect($redirect);
|
||||
}
|
||||
|
||||
$this->response->redirect($this->helper->url->to('app', 'index'));
|
||||
$this->redirectAfterLogin();
|
||||
}
|
||||
|
||||
$this->login($values, $errors);
|
||||
|
@ -64,23 +55,23 @@ class Auth extends Base
|
|||
*/
|
||||
public function logout()
|
||||
{
|
||||
$this->authentication->backend('rememberMe')->destroy($this->userSession->getId());
|
||||
$this->sessionManager->close();
|
||||
$this->response->redirect($this->helper->url->to('auth', 'login'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display captcha image
|
||||
* Redirect the user after the authentication
|
||||
*
|
||||
* @access public
|
||||
* @access private
|
||||
*/
|
||||
public function captcha()
|
||||
private function redirectAfterLogin()
|
||||
{
|
||||
$this->response->contentType('image/jpeg');
|
||||
if (isset($this->sessionStorage->redirectAfterLogin) && ! empty($this->sessionStorage->redirectAfterLogin) && ! filter_var($this->sessionStorage->redirectAfterLogin, FILTER_VALIDATE_URL)) {
|
||||
$redirect = $this->sessionStorage->redirectAfterLogin;
|
||||
unset($this->sessionStorage->redirectAfterLogin);
|
||||
$this->response->redirect($redirect);
|
||||
}
|
||||
|
||||
$builder = new CaptchaBuilder;
|
||||
$builder->build();
|
||||
$this->sessionStorage->captcha = $builder->getPhrase();
|
||||
$builder->output();
|
||||
$this->response->redirect($this->helper->url->to('app', 'index'));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,8 +2,7 @@
|
|||
|
||||
namespace Kanboard\Controller;
|
||||
|
||||
use Pimple\Container;
|
||||
use Symfony\Component\EventDispatcher\Event;
|
||||
use Kanboard\Core\Security\Role;
|
||||
|
||||
/**
|
||||
* Base controller
|
||||
|
@ -14,36 +13,22 @@ use Symfony\Component\EventDispatcher\Event;
|
|||
abstract class Base extends \Kanboard\Core\Base
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @access public
|
||||
* @param \Pimple\Container $container
|
||||
*/
|
||||
public function __construct(Container $container)
|
||||
{
|
||||
$this->container = $container;
|
||||
|
||||
if (DEBUG) {
|
||||
$this->logger->debug('START_REQUEST='.$_SERVER['REQUEST_URI']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Destructor
|
||||
* Method executed before each action
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function __destruct()
|
||||
public function beforeAction()
|
||||
{
|
||||
if (DEBUG) {
|
||||
foreach ($this->db->getLogMessages() as $message) {
|
||||
$this->logger->debug($message);
|
||||
}
|
||||
$this->sessionManager->open();
|
||||
$this->dispatcher->dispatch('app.bootstrap');
|
||||
$this->sendHeaders();
|
||||
$this->authenticationManager->checkCurrentSession();
|
||||
|
||||
$this->logger->debug('SQL_QUERIES={nb}', array('nb' => $this->container['db']->nbQueries));
|
||||
$this->logger->debug('RENDERING={time}', array('time' => microtime(true) - @$_SERVER['REQUEST_TIME_FLOAT']));
|
||||
$this->logger->debug('MEMORY='.$this->helper->text->bytes(memory_get_usage()));
|
||||
$this->logger->debug('END_REQUEST='.$_SERVER['REQUEST_URI']);
|
||||
if (! $this->applicationAuthorization->isAllowed($this->router->getController(), $this->router->getAction(), Role::APP_PUBLIC)) {
|
||||
$this->handleAuthentication();
|
||||
$this->handlePostAuthentication();
|
||||
$this->checkApplicationAuthorization();
|
||||
$this->checkProjectAuthorization();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -52,7 +37,7 @@ abstract class Base extends \Kanboard\Core\Base
|
|||
*
|
||||
* @access private
|
||||
*/
|
||||
private function sendHeaders($action)
|
||||
private function sendHeaders()
|
||||
{
|
||||
// HTTP secure headers
|
||||
$this->response->csp($this->container['cspRules']);
|
||||
|
@ -60,7 +45,7 @@ abstract class Base extends \Kanboard\Core\Base
|
|||
$this->response->xss();
|
||||
|
||||
// Allow the public board iframe inclusion
|
||||
if (ENABLE_XFRAME && $action !== 'readonly') {
|
||||
if (ENABLE_XFRAME && $this->router->getAction() !== 'readonly') {
|
||||
$this->response->xframe();
|
||||
}
|
||||
|
||||
|
@ -69,34 +54,14 @@ abstract class Base extends \Kanboard\Core\Base
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method executed before each action
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function beforeAction($controller, $action)
|
||||
{
|
||||
$this->sessionManager->open();
|
||||
$this->sendHeaders($action);
|
||||
$this->container['dispatcher']->dispatch('session.bootstrap', new Event);
|
||||
|
||||
if (! $this->acl->isPublicAction($controller, $action)) {
|
||||
$this->handleAuthentication();
|
||||
$this->handle2FA($controller, $action);
|
||||
$this->handleAuthorization($controller, $action);
|
||||
|
||||
$this->sessionStorage->hasSubtaskInProgress = $this->subtask->hasSubtaskInProgress($this->userSession->getId());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check authentication
|
||||
*
|
||||
* @access public
|
||||
* @access private
|
||||
*/
|
||||
public function handleAuthentication()
|
||||
private function handleAuthentication()
|
||||
{
|
||||
if (! $this->authentication->isAuthenticated()) {
|
||||
if (! $this->userSession->isLogged() && ! $this->authenticationManager->preAuthentication()) {
|
||||
if ($this->request->isAjax()) {
|
||||
$this->response->text('Not Authorized', 401);
|
||||
}
|
||||
|
@ -107,15 +72,17 @@ abstract class Base extends \Kanboard\Core\Base
|
|||
}
|
||||
|
||||
/**
|
||||
* Check 2FA
|
||||
* Handle Post-Authentication (2FA)
|
||||
*
|
||||
* @access public
|
||||
* @access private
|
||||
*/
|
||||
public function handle2FA($controller, $action)
|
||||
private function handlePostAuthentication()
|
||||
{
|
||||
$controller = strtolower($this->router->getController());
|
||||
$action = strtolower($this->router->getAction());
|
||||
$ignore = ($controller === 'twofactor' && in_array($action, array('code', 'check'))) || ($controller === 'auth' && $action === 'logout');
|
||||
|
||||
if ($ignore === false && $this->userSession->has2FA() && ! $this->userSession->check2FA()) {
|
||||
if ($ignore === false && $this->userSession->hasPostAuthentication() && ! $this->userSession->isPostAuthenticationValidated()) {
|
||||
if ($this->request->isAjax()) {
|
||||
$this->response->text('Not Authorized', 401);
|
||||
}
|
||||
|
@ -125,11 +92,23 @@ abstract class Base extends \Kanboard\Core\Base
|
|||
}
|
||||
|
||||
/**
|
||||
* Check page access and authorization
|
||||
* Check application authorization
|
||||
*
|
||||
* @access public
|
||||
* @access private
|
||||
*/
|
||||
public function handleAuthorization($controller, $action)
|
||||
private function checkApplicationAuthorization()
|
||||
{
|
||||
if (! $this->helper->user->hasAccess($this->router->getController(), $this->router->getAction())) {
|
||||
$this->forbidden();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check project authorization
|
||||
*
|
||||
* @access private
|
||||
*/
|
||||
private function checkProjectAuthorization()
|
||||
{
|
||||
$project_id = $this->request->getIntegerParam('project_id');
|
||||
$task_id = $this->request->getIntegerParam('task_id');
|
||||
|
@ -139,7 +118,7 @@ abstract class Base extends \Kanboard\Core\Base
|
|||
$project_id = $this->taskFinder->getProjectId($task_id);
|
||||
}
|
||||
|
||||
if (! $this->acl->isAllowed($controller, $action, $project_id)) {
|
||||
if ($project_id > 0 && ! $this->helper->user->hasProjectAccess($this->router->getController(), $this->router->getAction(), $project_id)) {
|
||||
$this->forbidden();
|
||||
}
|
||||
}
|
||||
|
@ -147,10 +126,10 @@ abstract class Base extends \Kanboard\Core\Base
|
|||
/**
|
||||
* Application not found page (404 error)
|
||||
*
|
||||
* @access public
|
||||
* @access protected
|
||||
* @param boolean $no_layout Display the layout or not
|
||||
*/
|
||||
public function notfound($no_layout = false)
|
||||
protected function notfound($no_layout = false)
|
||||
{
|
||||
$this->response->html($this->template->layout('app/notfound', array(
|
||||
'title' => t('Page not found'),
|
||||
|
@ -161,11 +140,15 @@ abstract class Base extends \Kanboard\Core\Base
|
|||
/**
|
||||
* Application forbidden page
|
||||
*
|
||||
* @access public
|
||||
* @access protected
|
||||
* @param boolean $no_layout Display the layout or not
|
||||
*/
|
||||
public function forbidden($no_layout = false)
|
||||
protected function forbidden($no_layout = false)
|
||||
{
|
||||
if ($this->request->isAjax()) {
|
||||
$this->response->text('Access Forbidden', 403);
|
||||
}
|
||||
|
||||
$this->response->html($this->template->layout('app/forbidden', array(
|
||||
'title' => t('Access Forbidden'),
|
||||
'no_layout' => $no_layout,
|
||||
|
@ -209,7 +192,7 @@ abstract class Base extends \Kanboard\Core\Base
|
|||
$content = $this->template->render($template, $params);
|
||||
$params['task_content_for_layout'] = $content;
|
||||
$params['title'] = $params['task']['project_name'].' > '.$params['task']['title'];
|
||||
$params['board_selector'] = $this->projectPermission->getAllowedProjects($this->userSession->getId());
|
||||
$params['board_selector'] = $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId());
|
||||
|
||||
return $this->template->layout('task/layout', $params);
|
||||
}
|
||||
|
@ -227,7 +210,7 @@ abstract class Base extends \Kanboard\Core\Base
|
|||
$content = $this->template->render($template, $params);
|
||||
$params['project_content_for_layout'] = $content;
|
||||
$params['title'] = $params['project']['name'] === $params['title'] ? $params['title'] : $params['project']['name'].' > '.$params['title'];
|
||||
$params['board_selector'] = $this->projectPermission->getAllowedProjects($this->userSession->getId());
|
||||
$params['board_selector'] = $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId());
|
||||
$params['sidebar_template'] = $sidebar_template;
|
||||
|
||||
return $this->template->layout('project/layout', $params);
|
||||
|
@ -300,12 +283,15 @@ abstract class Base extends \Kanboard\Core\Base
|
|||
* Common method to get project filters
|
||||
*
|
||||
* @access protected
|
||||
* @param string $controller
|
||||
* @param string $action
|
||||
* @return array
|
||||
*/
|
||||
protected function getProjectFilters($controller, $action)
|
||||
{
|
||||
$project = $this->getProject();
|
||||
$search = $this->request->getStringParam('search', $this->userSession->getFilters($project['id']));
|
||||
$board_selector = $this->projectPermission->getAllowedProjects($this->userSession->getId());
|
||||
$board_selector = $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId());
|
||||
unset($board_selector[$project['id']]);
|
||||
|
||||
$filters = array(
|
||||
|
|
|
@ -51,7 +51,7 @@ class Board extends Base
|
|||
|
||||
$this->response->html($this->template->layout('board/view_private', array(
|
||||
'categories_list' => $this->category->getList($params['project']['id'], false),
|
||||
'users_list' => $this->projectPermission->getMemberList($params['project']['id'], false),
|
||||
'users_list' => $this->projectUserRole->getAssignableUsersList($params['project']['id'], false),
|
||||
'custom_filters_list' => $this->customFilter->getAll($params['project']['id'], $this->userSession->getId()),
|
||||
'swimlanes' => $this->taskFilter->search($params['filters']['search'])->getBoard($params['project']['id']),
|
||||
'description' => $params['project']['description'],
|
||||
|
@ -73,10 +73,6 @@ class Board extends Base
|
|||
return $this->response->status(403);
|
||||
}
|
||||
|
||||
if (! $this->projectPermission->isUserAllowed($project_id, $this->userSession->getId())) {
|
||||
$this->response->text('Forbidden', 403);
|
||||
}
|
||||
|
||||
$values = $this->request->getJson();
|
||||
|
||||
$result =$this->taskPosition->movePosition(
|
||||
|
@ -101,22 +97,18 @@ class Board extends Base
|
|||
*/
|
||||
public function check()
|
||||
{
|
||||
if (! $this->request->isAjax()) {
|
||||
return $this->response->status(403);
|
||||
}
|
||||
|
||||
$project_id = $this->request->getIntegerParam('project_id');
|
||||
$timestamp = $this->request->getIntegerParam('timestamp');
|
||||
|
||||
if (! $this->projectPermission->isUserAllowed($project_id, $this->userSession->getId())) {
|
||||
$this->response->text('Forbidden', 403);
|
||||
if (! $project_id || ! $this->request->isAjax()) {
|
||||
return $this->response->status(403);
|
||||
}
|
||||
|
||||
if (! $this->project->isModifiedSince($project_id, $timestamp)) {
|
||||
return $this->response->status(304);
|
||||
}
|
||||
|
||||
$this->response->html($this->renderBoard($project_id));
|
||||
return $this->response->html($this->renderBoard($project_id));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -126,14 +118,10 @@ class Board extends Base
|
|||
*/
|
||||
public function reload()
|
||||
{
|
||||
if (! $this->request->isAjax()) {
|
||||
return $this->response->status(403);
|
||||
}
|
||||
|
||||
$project_id = $this->request->getIntegerParam('project_id');
|
||||
|
||||
if (! $this->projectPermission->isUserAllowed($project_id, $this->userSession->getId())) {
|
||||
$this->response->text('Forbidden', 403);
|
||||
if (! $project_id || ! $this->request->isAjax()) {
|
||||
return $this->response->status(403);
|
||||
}
|
||||
|
||||
$values = $this->request->getJson();
|
||||
|
@ -142,195 +130,6 @@ class Board extends Base
|
|||
$this->response->html($this->renderBoard($project_id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get links on mouseover
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function tasklinks()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
$this->response->html($this->template->render('board/tooltip_tasklinks', array(
|
||||
'links' => $this->taskLink->getAll($task['id']),
|
||||
'task' => $task,
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get subtasks on mouseover
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function subtasks()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
$this->response->html($this->template->render('board/tooltip_subtasks', array(
|
||||
'subtasks' => $this->subtask->getAll($task['id']),
|
||||
'task' => $task,
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display all attachments during the task mouseover
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function attachments()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
|
||||
$this->response->html($this->template->render('board/tooltip_files', array(
|
||||
'files' => $this->file->getAll($task['id']),
|
||||
'task' => $task,
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display comments during a task mouseover
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function comments()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
|
||||
$this->response->html($this->template->render('board/tooltip_comments', array(
|
||||
'comments' => $this->comment->getAll($task['id'], $this->userSession->getCommentSorting())
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display task description
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function description()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
|
||||
$this->response->html($this->template->render('board/tooltip_description', array(
|
||||
'task' => $task
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Change a task assignee directly from the board
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function changeAssignee()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
$project = $this->project->getById($task['project_id']);
|
||||
|
||||
$this->response->html($this->template->render('board/popover_assignee', array(
|
||||
'values' => $task,
|
||||
'users_list' => $this->projectPermission->getMemberList($project['id']),
|
||||
'project' => $project,
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate an assignee modification
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function updateAssignee()
|
||||
{
|
||||
$values = $this->request->getValues();
|
||||
|
||||
list($valid, ) = $this->taskValidator->validateAssigneeModification($values);
|
||||
|
||||
if ($valid && $this->taskModification->update($values)) {
|
||||
$this->flash->success(t('Task updated successfully.'));
|
||||
} else {
|
||||
$this->flash->failure(t('Unable to update your task.'));
|
||||
}
|
||||
|
||||
$this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $values['project_id'])));
|
||||
}
|
||||
|
||||
/**
|
||||
* Change a task category directly from the board
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function changeCategory()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
$project = $this->project->getById($task['project_id']);
|
||||
|
||||
$this->response->html($this->template->render('board/popover_category', array(
|
||||
'values' => $task,
|
||||
'categories_list' => $this->category->getList($project['id']),
|
||||
'project' => $project,
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a category modification
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function updateCategory()
|
||||
{
|
||||
$values = $this->request->getValues();
|
||||
|
||||
list($valid, ) = $this->taskValidator->validateCategoryModification($values);
|
||||
|
||||
if ($valid && $this->taskModification->update($values)) {
|
||||
$this->flash->success(t('Task updated successfully.'));
|
||||
} else {
|
||||
$this->flash->failure(t('Unable to update your task.'));
|
||||
}
|
||||
|
||||
$this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $values['project_id'])));
|
||||
}
|
||||
|
||||
/**
|
||||
* Screenshot popover
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function screenshot()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
|
||||
$this->response->html($this->template->render('file/screenshot', array(
|
||||
'task' => $task,
|
||||
'redirect' => 'board',
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get recurrence information on mouseover
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function recurrence()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
|
||||
$this->response->html($this->template->render('task/recurring_info', array(
|
||||
'task' => $task,
|
||||
'recurrence_trigger_list' => $this->task->getRecurrenceTriggerList(),
|
||||
'recurrence_timeframe_list' => $this->task->getRecurrenceTimeframeList(),
|
||||
'recurrence_basedate_list' => $this->task->getRecurrenceBasedateList(),
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display swimlane description in tooltip
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function swimlane()
|
||||
{
|
||||
$this->getProject();
|
||||
$swimlane = $this->swimlane->getById($this->request->getIntegerParam('swimlane_id'));
|
||||
$this->response->html($this->template->render('board/tooltip_description', array('task' => $swimlane)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable collapsed mode
|
||||
*
|
||||
|
@ -355,6 +154,7 @@ class Board extends Base
|
|||
* Change display mode
|
||||
*
|
||||
* @access private
|
||||
* @param boolean $mode
|
||||
*/
|
||||
private function changeDisplayMode($mode)
|
||||
{
|
||||
|
@ -372,6 +172,7 @@ class Board extends Base
|
|||
* Render board
|
||||
*
|
||||
* @access private
|
||||
* @param integer $project_id
|
||||
*/
|
||||
private function renderBoard($project_id)
|
||||
{
|
||||
|
|
136
sources/app/Controller/BoardPopover.php
Normal file
136
sources/app/Controller/BoardPopover.php
Normal file
|
@ -0,0 +1,136 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Controller;
|
||||
|
||||
/**
|
||||
* Board Popover
|
||||
*
|
||||
* @package controller
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class BoardPopover extends Base
|
||||
{
|
||||
/**
|
||||
* Change a task assignee directly from the board
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function changeAssignee()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
$project = $this->project->getById($task['project_id']);
|
||||
|
||||
$this->response->html($this->template->render('board/popover_assignee', array(
|
||||
'values' => $task,
|
||||
'users_list' => $this->projectUserRole->getAssignableUsersList($project['id']),
|
||||
'project' => $project,
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate an assignee modification
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function updateAssignee()
|
||||
{
|
||||
$values = $this->request->getValues();
|
||||
|
||||
list($valid, ) = $this->taskValidator->validateAssigneeModification($values);
|
||||
|
||||
if ($valid && $this->taskModification->update($values)) {
|
||||
$this->flash->success(t('Task updated successfully.'));
|
||||
} else {
|
||||
$this->flash->failure(t('Unable to update your task.'));
|
||||
}
|
||||
|
||||
$this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $values['project_id'])));
|
||||
}
|
||||
|
||||
/**
|
||||
* Change a task category directly from the board
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function changeCategory()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
$project = $this->project->getById($task['project_id']);
|
||||
|
||||
$this->response->html($this->template->render('board/popover_category', array(
|
||||
'values' => $task,
|
||||
'categories_list' => $this->category->getList($project['id']),
|
||||
'project' => $project,
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a category modification
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function updateCategory()
|
||||
{
|
||||
$values = $this->request->getValues();
|
||||
|
||||
list($valid, ) = $this->taskValidator->validateCategoryModification($values);
|
||||
|
||||
if ($valid && $this->taskModification->update($values)) {
|
||||
$this->flash->success(t('Task updated successfully.'));
|
||||
} else {
|
||||
$this->flash->failure(t('Unable to update your task.'));
|
||||
}
|
||||
|
||||
$this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $values['project_id'])));
|
||||
}
|
||||
|
||||
/**
|
||||
* Screenshot popover
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function screenshot()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
|
||||
$this->response->html($this->template->render('file/screenshot', array(
|
||||
'task' => $task,
|
||||
'redirect' => 'board',
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirmation before to close all column tasks
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function confirmCloseColumnTasks()
|
||||
{
|
||||
$project = $this->getProject();
|
||||
$column_id = $this->request->getIntegerParam('column_id');
|
||||
$swimlane_id = $this->request->getIntegerParam('swimlane_id');
|
||||
|
||||
$this->response->html($this->template->render('board/popover_close_all_tasks_column', array(
|
||||
'project' => $project,
|
||||
'nb_tasks' => $this->taskFinder->countByColumnAndSwimlaneId($project['id'], $column_id, $swimlane_id),
|
||||
'column' => $this->board->getColumnTitleById($column_id),
|
||||
'swimlane' => $this->swimlane->getNameById($swimlane_id) ?: t($project['default_swimlane']),
|
||||
'values' => array('column_id' => $column_id, 'swimlane_id' => $swimlane_id),
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Close all column tasks
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function closeColumnTasks()
|
||||
{
|
||||
$project = $this->getProject();
|
||||
$values = $this->request->getValues();
|
||||
|
||||
$this->taskStatus->closeTasksBySwimlaneAndColumn($values['swimlane_id'], $values['column_id']);
|
||||
$this->flash->success(t('All tasks of the column "%s" and the swimlane "%s" have been closed successfully.', $this->board->getColumnTitleById($values['column_id']), $this->swimlane->getNameById($values['swimlane_id']) ?: t($project['default_swimlane'])));
|
||||
$this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $project['id'])));
|
||||
}
|
||||
}
|
112
sources/app/Controller/BoardTooltip.php
Normal file
112
sources/app/Controller/BoardTooltip.php
Normal file
|
@ -0,0 +1,112 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Controller;
|
||||
|
||||
/**
|
||||
* Board Tooltip
|
||||
*
|
||||
* @package controller
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class BoardTooltip extends Base
|
||||
{
|
||||
/**
|
||||
* Get links on mouseover
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function tasklinks()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
$this->response->html($this->template->render('board/tooltip_tasklinks', array(
|
||||
'links' => $this->taskLink->getAll($task['id']),
|
||||
'task' => $task,
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get subtasks on mouseover
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function subtasks()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
$this->response->html($this->template->render('board/tooltip_subtasks', array(
|
||||
'subtasks' => $this->subtask->getAll($task['id']),
|
||||
'task' => $task,
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display all attachments during the task mouseover
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function attachments()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
|
||||
$this->response->html($this->template->render('board/tooltip_files', array(
|
||||
'files' => $this->file->getAll($task['id']),
|
||||
'task' => $task,
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display comments during a task mouseover
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function comments()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
|
||||
$this->response->html($this->template->render('board/tooltip_comments', array(
|
||||
'comments' => $this->comment->getAll($task['id'], $this->userSession->getCommentSorting())
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display task description
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function description()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
|
||||
$this->response->html($this->template->render('board/tooltip_description', array(
|
||||
'task' => $task
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get recurrence information on mouseover
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function recurrence()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
|
||||
$this->response->html($this->template->render('task/recurring_info', array(
|
||||
'task' => $task,
|
||||
'recurrence_trigger_list' => $this->task->getRecurrenceTriggerList(),
|
||||
'recurrence_timeframe_list' => $this->task->getRecurrenceTimeframeList(),
|
||||
'recurrence_basedate_list' => $this->task->getRecurrenceBasedateList(),
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display swimlane description in tooltip
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function swimlane()
|
||||
{
|
||||
$this->getProject();
|
||||
$swimlane = $this->swimlane->getById($this->request->getIntegerParam('swimlane_id'));
|
||||
$this->response->html($this->template->render('board/tooltip_description', array('task' => $swimlane)));
|
||||
}
|
||||
}
|
29
sources/app/Controller/Captcha.php
Normal file
29
sources/app/Controller/Captcha.php
Normal file
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Controller;
|
||||
|
||||
use Gregwar\Captcha\CaptchaBuilder;
|
||||
|
||||
/**
|
||||
* Captcha Controller
|
||||
*
|
||||
* @package controller
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Captcha extends Base
|
||||
{
|
||||
/**
|
||||
* Display captcha image
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function image()
|
||||
{
|
||||
$this->response->contentType('image/jpeg');
|
||||
|
||||
$builder = new CaptchaBuilder;
|
||||
$builder->build();
|
||||
$this->sessionStorage->captcha = $builder->getPhrase();
|
||||
$builder->output();
|
||||
}
|
||||
}
|
|
@ -57,7 +57,7 @@ class Category extends Base
|
|||
$project = $this->getProject();
|
||||
|
||||
$values = $this->request->getValues();
|
||||
list($valid, $errors) = $this->category->validateCreation($values);
|
||||
list($valid, $errors) = $this->categoryValidator->validateCreation($values);
|
||||
|
||||
if ($valid) {
|
||||
if ($this->category->create($values)) {
|
||||
|
@ -99,7 +99,7 @@ class Category extends Base
|
|||
$project = $this->getProject();
|
||||
|
||||
$values = $this->request->getValues();
|
||||
list($valid, $errors) = $this->category->validateModification($values);
|
||||
list($valid, $errors) = $this->categoryValidator->validateModification($values);
|
||||
|
||||
if ($valid) {
|
||||
if ($this->category->update($values)) {
|
||||
|
|
|
@ -51,7 +51,7 @@ class Column extends Base
|
|||
$values['title['.$column_id.']'] = $column_title;
|
||||
}
|
||||
|
||||
list($valid, $errors) = $this->board->validateCreation($data);
|
||||
list($valid, $errors) = $this->columnValidator->validateCreation($data);
|
||||
|
||||
if ($valid) {
|
||||
if ($this->board->addColumn($project['id'], $data['title'], $data['task_limit'], $data['description'])) {
|
||||
|
@ -94,7 +94,7 @@ class Column extends Base
|
|||
$project = $this->getProject();
|
||||
$values = $this->request->getValues();
|
||||
|
||||
list($valid, $errors) = $this->board->validateModification($values);
|
||||
list($valid, $errors) = $this->columnValidator->validateModification($values);
|
||||
|
||||
if ($valid) {
|
||||
if ($this->board->updateColumn($values['id'], $values['title'], $values['task_limit'], $values['description'])) {
|
||||
|
|
|
@ -78,7 +78,7 @@ class Comment extends Base
|
|||
$values = $this->request->getValues();
|
||||
$ajax = $this->request->isAjax() || $this->request->getIntegerParam('ajax');
|
||||
|
||||
list($valid, $errors) = $this->comment->validateCreation($values);
|
||||
list($valid, $errors) = $this->commentValidator->validateCreation($values);
|
||||
|
||||
if ($valid) {
|
||||
if ($this->comment->create($values)) {
|
||||
|
@ -127,7 +127,7 @@ class Comment extends Base
|
|||
$comment = $this->getComment();
|
||||
|
||||
$values = $this->request->getValues();
|
||||
list($valid, $errors) = $this->comment->validateModification($values);
|
||||
list($valid, $errors) = $this->commentValidator->validateModification($values);
|
||||
|
||||
if ($valid) {
|
||||
if ($this->comment->update($values)) {
|
||||
|
|
|
@ -20,7 +20,7 @@ class Config extends Base
|
|||
*/
|
||||
private function layout($template, array $params)
|
||||
{
|
||||
$params['board_selector'] = $this->projectPermission->getAllowedProjects($this->userSession->getId());
|
||||
$params['board_selector'] = $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId());
|
||||
$params['values'] = $this->config->getAll();
|
||||
$params['errors'] = array();
|
||||
$params['config_content_for_layout'] = $this->template->render($template, $params);
|
||||
|
@ -40,6 +40,9 @@ class Config extends Base
|
|||
$values = $this->request->getValues();
|
||||
|
||||
switch ($redirect) {
|
||||
case 'application':
|
||||
$values += array('password_reset' => 0);
|
||||
break;
|
||||
case 'project':
|
||||
$values += array('subtask_restriction' => 0, 'subtask_time_tracking' => 0, 'cfd_include_closed_tasks' => 0);
|
||||
break;
|
||||
|
|
|
@ -20,7 +20,7 @@ class Currency extends Base
|
|||
*/
|
||||
private function layout($template, array $params)
|
||||
{
|
||||
$params['board_selector'] = $this->projectPermission->getAllowedProjects($this->userSession->getId());
|
||||
$params['board_selector'] = $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId());
|
||||
$params['config_content_for_layout'] = $this->template->render($template, $params);
|
||||
|
||||
return $this->template->layout('config/layout', $params);
|
||||
|
@ -38,7 +38,7 @@ class Currency extends Base
|
|||
'values' => $values,
|
||||
'errors' => $errors,
|
||||
'rates' => $this->currency->getAll(),
|
||||
'currencies' => $this->config->getCurrencies(),
|
||||
'currencies' => $this->currency->getCurrencies(),
|
||||
'title' => t('Settings').' > '.t('Currency rates'),
|
||||
)));
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ class Currency extends Base
|
|||
public function create()
|
||||
{
|
||||
$values = $this->request->getValues();
|
||||
list($valid, $errors) = $this->currency->validate($values);
|
||||
list($valid, $errors) = $this->currencyValidator->validateCreation($values);
|
||||
|
||||
if ($valid) {
|
||||
if ($this->currency->create($values['currency'], $values['rate'])) {
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
namespace Kanboard\Controller;
|
||||
|
||||
use Kanboard\Core\Security\Role;
|
||||
|
||||
/**
|
||||
* Custom Filter management
|
||||
*
|
||||
|
@ -40,7 +42,7 @@ class Customfilter extends Base
|
|||
$values = $this->request->getValues();
|
||||
$values['user_id'] = $this->userSession->getId();
|
||||
|
||||
list($valid, $errors) = $this->customFilter->validateCreation($values);
|
||||
list($valid, $errors) = $this->customFilterValidator->validateCreation($values);
|
||||
|
||||
if ($valid) {
|
||||
if ($this->customFilter->create($values)) {
|
||||
|
@ -119,7 +121,7 @@ class Customfilter extends Base
|
|||
$values += array('append' => 0);
|
||||
}
|
||||
|
||||
list($valid, $errors) = $this->customFilter->validateModification($values);
|
||||
list($valid, $errors) = $this->customFilterValidator->validateModification($values);
|
||||
|
||||
if ($valid) {
|
||||
if ($this->customFilter->update($values)) {
|
||||
|
@ -137,7 +139,7 @@ class Customfilter extends Base
|
|||
{
|
||||
$user_id = $this->userSession->getId();
|
||||
|
||||
if ($filter['user_id'] != $user_id && (! $this->projectPermission->isManager($project['id'], $user_id) || ! $this->userSession->isAdmin())) {
|
||||
if ($filter['user_id'] != $user_id && ($this->projectUserRole->getUserRole($project['id'], $user_id) === Role::PROJECT_MANAGER || ! $this->userSession->isAdmin())) {
|
||||
$this->forbidden();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,7 +53,7 @@ class Doc extends Base
|
|||
}
|
||||
|
||||
$this->response->html($this->template->layout('doc/show', $this->readFile($filename) + array(
|
||||
'board_selector' => $this->projectPermission->getAllowedProjects($this->userSession->getId()),
|
||||
'board_selector' => $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()),
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,10 +25,8 @@ class Feed extends Base
|
|||
$this->forbidden(true);
|
||||
}
|
||||
|
||||
$projects = $this->projectPermission->getActiveMemberProjects($user['id']);
|
||||
|
||||
$this->response->xml($this->template->render('feed/user', array(
|
||||
'events' => $this->projectActivity->getProjects(array_keys($projects)),
|
||||
'events' => $this->projectActivity->getProjects($this->projectPermission->getActiveProjectIds($user['id'])),
|
||||
'user' => $user,
|
||||
)));
|
||||
}
|
||||
|
|
|
@ -20,13 +20,13 @@ class Gantt extends Base
|
|||
if ($this->userSession->isAdmin()) {
|
||||
$project_ids = $this->project->getAllIds();
|
||||
} else {
|
||||
$project_ids = $this->projectPermission->getMemberProjectIds($this->userSession->getId());
|
||||
$project_ids = $this->projectPermission->getActiveProjectIds($this->userSession->getId());
|
||||
}
|
||||
|
||||
$this->response->html($this->template->layout('gantt/projects', array(
|
||||
'projects' => $this->projectGanttFormatter->filter($project_ids)->format(),
|
||||
'title' => t('Gantt chart for all projects'),
|
||||
'board_selector' => $this->projectPermission->getAllowedProjects($this->userSession->getId()),
|
||||
'board_selector' => $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()),
|
||||
)));
|
||||
}
|
||||
|
||||
|
@ -66,7 +66,7 @@ class Gantt extends Base
|
|||
}
|
||||
|
||||
$this->response->html($this->template->layout('gantt/project', $params + array(
|
||||
'users_list' => $this->projectPermission->getMemberList($params['project']['id'], false),
|
||||
'users_list' => $this->projectUserRole->getAssignableUsersList($params['project']['id'], false),
|
||||
'sorting' => $sorting,
|
||||
'tasks' => $filter->format(),
|
||||
)));
|
||||
|
@ -109,7 +109,7 @@ class Gantt extends Base
|
|||
'column_id' => $this->board->getFirstColumn($project['id']),
|
||||
'position' => 1
|
||||
),
|
||||
'users_list' => $this->projectPermission->getMemberList($project['id'], true, false, true),
|
||||
'users_list' => $this->projectUserRole->getAssignableUsersList($project['id'], true, false, true),
|
||||
'colors_list' => $this->color->getList(),
|
||||
'categories_list' => $this->category->getList($project['id']),
|
||||
'swimlanes_list' => $this->swimlane->getList($project['id'], false, true),
|
||||
|
|
255
sources/app/Controller/Group.php
Normal file
255
sources/app/Controller/Group.php
Normal file
|
@ -0,0 +1,255 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Controller;
|
||||
|
||||
/**
|
||||
* Group Controller
|
||||
*
|
||||
* @package controller
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Group extends Base
|
||||
{
|
||||
/**
|
||||
* List all groups
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$paginator = $this->paginator
|
||||
->setUrl('group', 'index')
|
||||
->setMax(30)
|
||||
->setOrder('name')
|
||||
->setQuery($this->group->getQuery())
|
||||
->calculate();
|
||||
|
||||
$this->response->html($this->template->layout('group/index', array(
|
||||
'board_selector' => $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()),
|
||||
'title' => t('Groups').' ('.$paginator->getTotal().')',
|
||||
'paginator' => $paginator,
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* List all users
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function users()
|
||||
{
|
||||
$group_id = $this->request->getIntegerParam('group_id');
|
||||
$group = $this->group->getById($group_id);
|
||||
|
||||
$paginator = $this->paginator
|
||||
->setUrl('group', 'users', array('group_id' => $group_id))
|
||||
->setMax(30)
|
||||
->setOrder('username')
|
||||
->setQuery($this->groupMember->getQuery($group_id))
|
||||
->calculate();
|
||||
|
||||
$this->response->html($this->template->layout('group/users', array(
|
||||
'board_selector' => $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()),
|
||||
'title' => t('Members of %s', $group['name']).' ('.$paginator->getTotal().')',
|
||||
'paginator' => $paginator,
|
||||
'group' => $group,
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a form to create a new group
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function create(array $values = array(), array $errors = array())
|
||||
{
|
||||
$this->response->html($this->template->layout('group/create', array(
|
||||
'board_selector' => $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()),
|
||||
'errors' => $errors,
|
||||
'values' => $values,
|
||||
'title' => t('New group')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate and save a new group
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
$values = $this->request->getValues();
|
||||
list($valid, $errors) = $this->groupValidator->validateCreation($values);
|
||||
|
||||
if ($valid) {
|
||||
if ($this->group->create($values['name']) !== false) {
|
||||
$this->flash->success(t('Group created successfully.'));
|
||||
$this->response->redirect($this->helper->url->to('group', 'index'));
|
||||
} else {
|
||||
$this->flash->failure(t('Unable to create your group.'));
|
||||
}
|
||||
}
|
||||
|
||||
$this->create($values, $errors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a form to update a group
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function edit(array $values = array(), array $errors = array())
|
||||
{
|
||||
if (empty($values)) {
|
||||
$values = $this->group->getById($this->request->getIntegerParam('group_id'));
|
||||
}
|
||||
|
||||
$this->response->html($this->template->layout('group/edit', array(
|
||||
'board_selector' => $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()),
|
||||
'errors' => $errors,
|
||||
'values' => $values,
|
||||
'title' => t('Edit group')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate and save a group
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function update()
|
||||
{
|
||||
$values = $this->request->getValues();
|
||||
list($valid, $errors) = $this->groupValidator->validateModification($values);
|
||||
|
||||
if ($valid) {
|
||||
if ($this->group->update($values) !== false) {
|
||||
$this->flash->success(t('Group updated successfully.'));
|
||||
$this->response->redirect($this->helper->url->to('group', 'index'));
|
||||
} else {
|
||||
$this->flash->failure(t('Unable to update your group.'));
|
||||
}
|
||||
}
|
||||
|
||||
$this->edit($values, $errors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Form to associate a user to a group
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function associate(array $values = array(), array $errors = array())
|
||||
{
|
||||
$group_id = $this->request->getIntegerParam('group_id');
|
||||
$group = $this->group->getbyId($group_id);
|
||||
|
||||
if (empty($values)) {
|
||||
$values['group_id'] = $group_id;
|
||||
}
|
||||
|
||||
$this->response->html($this->template->layout('group/associate', array(
|
||||
'board_selector' => $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()),
|
||||
'users' => $this->user->prepareList($this->groupMember->getNotMembers($group_id)),
|
||||
'group' => $group,
|
||||
'errors' => $errors,
|
||||
'values' => $values,
|
||||
'title' => t('Add group member to "%s"', $group['name']),
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add user to a group
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function addUser()
|
||||
{
|
||||
$values = $this->request->getValues();
|
||||
|
||||
if (isset($values['group_id']) && isset($values['user_id'])) {
|
||||
if ($this->groupMember->addUser($values['group_id'], $values['user_id'])) {
|
||||
$this->flash->success(t('Group member added successfully.'));
|
||||
$this->response->redirect($this->helper->url->to('group', 'users', array('group_id' => $values['group_id'])));
|
||||
} else {
|
||||
$this->flash->failure(t('Unable to add group member.'));
|
||||
}
|
||||
}
|
||||
|
||||
$this->associate($values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirmation dialog to remove a user from a group
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function dissociate()
|
||||
{
|
||||
$group_id = $this->request->getIntegerParam('group_id');
|
||||
$user_id = $this->request->getIntegerParam('user_id');
|
||||
$group = $this->group->getById($group_id);
|
||||
$user = $this->user->getById($user_id);
|
||||
|
||||
$this->response->html($this->template->layout('group/dissociate', array(
|
||||
'group' => $group,
|
||||
'user' => $user,
|
||||
'title' => t('Remove user from group "%s"', $group['name']),
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a user from a group
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function removeUser()
|
||||
{
|
||||
$this->checkCSRFParam();
|
||||
$group_id = $this->request->getIntegerParam('group_id');
|
||||
$user_id = $this->request->getIntegerParam('user_id');
|
||||
|
||||
if ($this->groupMember->removeUser($group_id, $user_id)) {
|
||||
$this->flash->success(t('User removed successfully from this group.'));
|
||||
} else {
|
||||
$this->flash->failure(t('Unable to remove this user from the group.'));
|
||||
}
|
||||
|
||||
$this->response->redirect($this->helper->url->to('group', 'users', array('group_id' => $group_id)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirmation dialog to remove a group
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function confirm()
|
||||
{
|
||||
$group_id = $this->request->getIntegerParam('group_id');
|
||||
$group = $this->group->getById($group_id);
|
||||
|
||||
$this->response->html($this->template->layout('group/remove', array(
|
||||
'group' => $group,
|
||||
'title' => t('Remove group'),
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a group
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function remove()
|
||||
{
|
||||
$this->checkCSRFParam();
|
||||
$group_id = $this->request->getIntegerParam('group_id');
|
||||
|
||||
if ($this->group->remove($group_id)) {
|
||||
$this->flash->success(t('Group removed successfully.'));
|
||||
} else {
|
||||
$this->flash->failure(t('Unable to remove this group.'));
|
||||
}
|
||||
|
||||
$this->response->redirect($this->helper->url->to('group', 'index'));
|
||||
}
|
||||
}
|
24
sources/app/Controller/GroupHelper.php
Normal file
24
sources/app/Controller/GroupHelper.php
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Controller;
|
||||
|
||||
/**
|
||||
* Group Helper
|
||||
*
|
||||
* @package controller
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class GroupHelper extends Base
|
||||
{
|
||||
/**
|
||||
* Group autocompletion (Ajax)
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function autocomplete()
|
||||
{
|
||||
$search = $this->request->getStringParam('term');
|
||||
$groups = $this->groupManager->find($search);
|
||||
$this->response->json($this->groupAutoCompleteFormatter->setGroups($groups)->format());
|
||||
}
|
||||
}
|
|
@ -21,7 +21,7 @@ class Link extends Base
|
|||
*/
|
||||
private function layout($template, array $params)
|
||||
{
|
||||
$params['board_selector'] = $this->projectPermission->getAllowedProjects($this->userSession->getId());
|
||||
$params['board_selector'] = $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId());
|
||||
$params['config_content_for_layout'] = $this->template->render($template, $params);
|
||||
|
||||
return $this->template->layout('config/layout', $params);
|
||||
|
@ -67,7 +67,7 @@ class Link extends Base
|
|||
public function save()
|
||||
{
|
||||
$values = $this->request->getValues();
|
||||
list($valid, $errors) = $this->link->validateCreation($values);
|
||||
list($valid, $errors) = $this->linkValidator->validateCreation($values);
|
||||
|
||||
if ($valid) {
|
||||
if ($this->link->create($values['label'], $values['opposite_label']) !== false) {
|
||||
|
@ -108,7 +108,7 @@ class Link extends Base
|
|||
public function update()
|
||||
{
|
||||
$values = $this->request->getValues();
|
||||
list($valid, $errors) = $this->link->validateModification($values);
|
||||
list($valid, $errors) = $this->linkValidator->validateModification($values);
|
||||
|
||||
if ($valid) {
|
||||
if ($this->link->update($values)) {
|
||||
|
|
|
@ -17,7 +17,7 @@ class Oauth extends Base
|
|||
*/
|
||||
public function google()
|
||||
{
|
||||
$this->step1('google');
|
||||
$this->step1('Google');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -27,7 +27,7 @@ class Oauth extends Base
|
|||
*/
|
||||
public function github()
|
||||
{
|
||||
$this->step1('github');
|
||||
$this->step1('Github');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -37,7 +37,7 @@ class Oauth extends Base
|
|||
*/
|
||||
public function gitlab()
|
||||
{
|
||||
$this->step1('gitlab');
|
||||
$this->step1('Gitlab');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -45,12 +45,12 @@ class Oauth extends Base
|
|||
*
|
||||
* @access public
|
||||
*/
|
||||
public function unlink($backend = '')
|
||||
public function unlink()
|
||||
{
|
||||
$backend = $this->request->getStringParam('backend', $backend);
|
||||
$backend = $this->request->getStringParam('backend');
|
||||
$this->checkCSRFParam();
|
||||
|
||||
if ($this->authentication->backend($backend)->unlink($this->userSession->getId())) {
|
||||
if ($this->authenticationManager->getProvider($backend)->unlink($this->userSession->getId())) {
|
||||
$this->flash->success(t('Your external account is not linked anymore to your profile.'));
|
||||
} else {
|
||||
$this->flash->failure(t('Unable to unlink your external account.'));
|
||||
|
@ -63,15 +63,16 @@ class Oauth extends Base
|
|||
* Redirect to the provider if no code received
|
||||
*
|
||||
* @access private
|
||||
* @param string $provider
|
||||
*/
|
||||
private function step1($backend)
|
||||
private function step1($provider)
|
||||
{
|
||||
$code = $this->request->getStringParam('code');
|
||||
|
||||
if (! empty($code)) {
|
||||
$this->step2($backend, $code);
|
||||
$this->step2($provider, $code);
|
||||
} else {
|
||||
$this->response->redirect($this->authentication->backend($backend)->getService()->getAuthorizationUrl());
|
||||
$this->response->redirect($this->authenticationManager->getProvider($provider)->getService()->getAuthorizationUrl());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -79,30 +80,35 @@ class Oauth extends Base
|
|||
* Link or authenticate the user
|
||||
*
|
||||
* @access private
|
||||
* @param string $provider
|
||||
* @param string $code
|
||||
*/
|
||||
private function step2($backend, $code)
|
||||
private function step2($provider, $code)
|
||||
{
|
||||
$profile = $this->authentication->backend($backend)->getProfile($code);
|
||||
$this->authenticationManager->getProvider($provider)->setCode($code);
|
||||
|
||||
if ($this->userSession->isLogged()) {
|
||||
$this->link($backend, $profile);
|
||||
$this->link($provider);
|
||||
}
|
||||
|
||||
$this->authenticate($backend, $profile);
|
||||
$this->authenticate($provider);
|
||||
}
|
||||
|
||||
/**
|
||||
* Link the account
|
||||
*
|
||||
* @access private
|
||||
* @param string $provider
|
||||
*/
|
||||
private function link($backend, $profile)
|
||||
private function link($provider)
|
||||
{
|
||||
if (empty($profile)) {
|
||||
$authProvider = $this->authenticationManager->getProvider($provider);
|
||||
|
||||
if (! $authProvider->authenticate()) {
|
||||
$this->flash->failure(t('External authentication failed'));
|
||||
} else {
|
||||
$this->userProfile->assign($this->userSession->getId(), $authProvider->getUser());
|
||||
$this->flash->success(t('Your external account is linked to your profile successfully.'));
|
||||
$this->authentication->backend($backend)->updateUser($this->userSession->getId(), $profile);
|
||||
}
|
||||
|
||||
$this->response->redirect($this->helper->url->to('user', 'external', array('user_id' => $this->userSession->getId())));
|
||||
|
@ -112,10 +118,11 @@ class Oauth extends Base
|
|||
* Authenticate the account
|
||||
*
|
||||
* @access private
|
||||
* @param string $provider
|
||||
*/
|
||||
private function authenticate($backend, $profile)
|
||||
private function authenticate($provider)
|
||||
{
|
||||
if (! empty($profile) && $this->authentication->backend($backend)->authenticate($profile['id'])) {
|
||||
if ($this->authenticationManager->oauthAuthentication($provider)) {
|
||||
$this->response->redirect($this->helper->url->to('app', 'index'));
|
||||
} else {
|
||||
$this->response->html($this->template->layout('auth/index', array(
|
||||
|
|
120
sources/app/Controller/PasswordReset.php
Normal file
120
sources/app/Controller/PasswordReset.php
Normal file
|
@ -0,0 +1,120 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Controller;
|
||||
|
||||
/**
|
||||
* Password Reset Controller
|
||||
*
|
||||
* @package controller
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class PasswordReset extends Base
|
||||
{
|
||||
/**
|
||||
* Show the form to reset the password
|
||||
*/
|
||||
public function create(array $values = array(), array $errors = array())
|
||||
{
|
||||
$this->checkActivation();
|
||||
|
||||
$this->response->html($this->template->layout('password_reset/create', array(
|
||||
'errors' => $errors,
|
||||
'values' => $values,
|
||||
'no_layout' => true,
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate and send the email
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
$this->checkActivation();
|
||||
|
||||
$values = $this->request->getValues();
|
||||
list($valid, $errors) = $this->passwordResetValidator->validateCreation($values);
|
||||
|
||||
if ($valid) {
|
||||
$this->sendEmail($values['username']);
|
||||
$this->response->redirect($this->helper->url->to('auth', 'login'));
|
||||
}
|
||||
|
||||
$this->create($values, $errors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form to set a new password
|
||||
*/
|
||||
public function change(array $values = array(), array $errors = array())
|
||||
{
|
||||
$this->checkActivation();
|
||||
|
||||
$token = $this->request->getStringParam('token');
|
||||
$user_id = $this->passwordReset->getUserIdByToken($token);
|
||||
|
||||
if ($user_id !== false) {
|
||||
$this->response->html($this->template->layout('password_reset/change', array(
|
||||
'token' => $token,
|
||||
'errors' => $errors,
|
||||
'values' => $values,
|
||||
'no_layout' => true,
|
||||
)));
|
||||
}
|
||||
|
||||
$this->response->redirect($this->helper->url->to('auth', 'login'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the new password
|
||||
*/
|
||||
public function update()
|
||||
{
|
||||
$this->checkActivation();
|
||||
|
||||
$token = $this->request->getStringParam('token');
|
||||
$values = $this->request->getValues();
|
||||
list($valid, $errors) = $this->passwordResetValidator->validateModification($values);
|
||||
|
||||
if ($valid) {
|
||||
$user_id = $this->passwordReset->getUserIdByToken($token);
|
||||
|
||||
if ($user_id !== false) {
|
||||
$this->user->update(array('id' => $user_id, 'password' => $values['password']));
|
||||
$this->passwordReset->disable($user_id);
|
||||
}
|
||||
|
||||
$this->response->redirect($this->helper->url->to('auth', 'login'));
|
||||
}
|
||||
|
||||
$this->change($values, $errors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the email
|
||||
*/
|
||||
private function sendEmail($username)
|
||||
{
|
||||
$token = $this->passwordReset->create($username);
|
||||
|
||||
if ($token !== false) {
|
||||
$user = $this->user->getByUsername($username);
|
||||
|
||||
$this->emailClient->send(
|
||||
$user['email'],
|
||||
$user['name'] ?: $user['username'],
|
||||
t('Password Reset for Kanboard'),
|
||||
$this->template->render('password_reset/email', array('token' => $token))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check feature availability
|
||||
*/
|
||||
private function checkActivation()
|
||||
{
|
||||
if ($this->config->get('password_reset', 0) == 0) {
|
||||
$this->response->redirect($this->helper->url->to('auth', 'login'));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -20,7 +20,7 @@ class Project extends Base
|
|||
if ($this->userSession->isAdmin()) {
|
||||
$project_ids = $this->project->getAllIds();
|
||||
} else {
|
||||
$project_ids = $this->projectPermission->getMemberProjectIds($this->userSession->getId());
|
||||
$project_ids = $this->projectPermission->getActiveProjectIds($this->userSession->getId());
|
||||
}
|
||||
|
||||
$nb_projects = count($project_ids);
|
||||
|
@ -33,7 +33,7 @@ class Project extends Base
|
|||
->calculate();
|
||||
|
||||
$this->response->html($this->template->layout('project/index', array(
|
||||
'board_selector' => $this->projectPermission->getAllowedProjects($this->userSession->getId()),
|
||||
'board_selector' => $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()),
|
||||
'paginator' => $paginator,
|
||||
'nb_projects' => $nb_projects,
|
||||
'title' => t('Projects').' ('.$nb_projects.')'
|
||||
|
@ -160,16 +160,16 @@ class Project extends Base
|
|||
$values = $this->request->getValues();
|
||||
|
||||
if (isset($values['is_private'])) {
|
||||
if (! $this->helper->user->isProjectAdministrationAllowed($project['id'])) {
|
||||
if (! $this->helper->user->hasProjectAccess('project', 'create', $project['id'])) {
|
||||
unset($values['is_private']);
|
||||
}
|
||||
} elseif ($project['is_private'] == 1 && ! isset($values['is_private'])) {
|
||||
if ($this->helper->user->isProjectAdministrationAllowed($project['id'])) {
|
||||
if ($this->helper->user->hasProjectAccess('project', 'create', $project['id'])) {
|
||||
$values += array('is_private' => 0);
|
||||
}
|
||||
}
|
||||
|
||||
list($valid, $errors) = $this->project->validateModification($values);
|
||||
list($valid, $errors) = $this->projectValidator->validateModification($values);
|
||||
|
||||
if ($valid) {
|
||||
if ($this->project->update($values)) {
|
||||
|
@ -183,120 +183,6 @@ class Project extends Base
|
|||
$this->edit($values, $errors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Users list for the selected project
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function users()
|
||||
{
|
||||
$project = $this->getProject();
|
||||
|
||||
$this->response->html($this->projectLayout('project/users', array(
|
||||
'project' => $project,
|
||||
'users' => $this->projectPermission->getAllUsers($project['id']),
|
||||
'title' => t('Edit project access list')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow everybody
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function allowEverybody()
|
||||
{
|
||||
$project = $this->getProject();
|
||||
$values = $this->request->getValues() + array('is_everybody_allowed' => 0);
|
||||
list($valid, ) = $this->projectPermission->validateProjectModification($values);
|
||||
|
||||
if ($valid) {
|
||||
if ($this->project->update($values)) {
|
||||
$this->flash->success(t('Project updated successfully.'));
|
||||
} else {
|
||||
$this->flash->failure(t('Unable to update this project.'));
|
||||
}
|
||||
}
|
||||
|
||||
$this->response->redirect($this->helper->url->to('project', 'users', array('project_id' => $project['id'])));
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow a specific user (admin only)
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function allow()
|
||||
{
|
||||
$values = $this->request->getValues();
|
||||
list($valid, ) = $this->projectPermission->validateUserModification($values);
|
||||
|
||||
if ($valid) {
|
||||
if ($this->projectPermission->addMember($values['project_id'], $values['user_id'])) {
|
||||
$this->flash->success(t('Project updated successfully.'));
|
||||
} else {
|
||||
$this->flash->failure(t('Unable to update this project.'));
|
||||
}
|
||||
}
|
||||
|
||||
$this->response->redirect($this->helper->url->to('project', 'users', array('project_id' => $values['project_id'])));
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the role of a project member
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function role()
|
||||
{
|
||||
$this->checkCSRFParam();
|
||||
|
||||
$values = array(
|
||||
'project_id' => $this->request->getIntegerParam('project_id'),
|
||||
'user_id' => $this->request->getIntegerParam('user_id'),
|
||||
'is_owner' => $this->request->getIntegerParam('is_owner'),
|
||||
);
|
||||
|
||||
list($valid, ) = $this->projectPermission->validateUserModification($values);
|
||||
|
||||
if ($valid) {
|
||||
if ($this->projectPermission->changeRole($values['project_id'], $values['user_id'], $values['is_owner'])) {
|
||||
$this->flash->success(t('Project updated successfully.'));
|
||||
} else {
|
||||
$this->flash->failure(t('Unable to update this project.'));
|
||||
}
|
||||
}
|
||||
|
||||
$this->response->redirect($this->helper->url->to('project', 'users', array('project_id' => $values['project_id'])));
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke user access (admin only)
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function revoke()
|
||||
{
|
||||
$this->checkCSRFParam();
|
||||
|
||||
$values = array(
|
||||
'project_id' => $this->request->getIntegerParam('project_id'),
|
||||
'user_id' => $this->request->getIntegerParam('user_id'),
|
||||
);
|
||||
|
||||
list($valid, ) = $this->projectPermission->validateUserModification($values);
|
||||
|
||||
if ($valid) {
|
||||
if ($this->projectPermission->revokeMember($values['project_id'], $values['user_id'])) {
|
||||
$this->flash->success(t('Project updated successfully.'));
|
||||
} else {
|
||||
$this->flash->failure(t('Unable to update this project.'));
|
||||
}
|
||||
}
|
||||
|
||||
$this->response->redirect($this->helper->url->to('project', 'users', array('project_id' => $values['project_id'])));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a project
|
||||
*
|
||||
|
@ -413,17 +299,28 @@ class Project extends Base
|
|||
*/
|
||||
public function create(array $values = array(), array $errors = array())
|
||||
{
|
||||
$is_private = $this->request->getIntegerParam('private', $this->userSession->isAdmin() || $this->userSession->isProjectAdmin() ? 0 : 1);
|
||||
$is_private = isset($values['is_private']) && $values['is_private'] == 1;
|
||||
|
||||
$this->response->html($this->template->layout('project/new', array(
|
||||
'board_selector' => $this->projectPermission->getAllowedProjects($this->userSession->getId()),
|
||||
'values' => empty($values) ? array('is_private' => $is_private) : $values,
|
||||
'board_selector' => $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()),
|
||||
'values' => $values,
|
||||
'errors' => $errors,
|
||||
'is_private' => $is_private,
|
||||
'title' => $is_private ? t('New private project') : t('New project'),
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a form to create a private project
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function createPrivate(array $values = array(), array $errors = array())
|
||||
{
|
||||
$values['is_private'] = 1;
|
||||
$this->create($values, $errors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate and save a new project
|
||||
*
|
||||
|
@ -432,7 +329,7 @@ class Project extends Base
|
|||
public function save()
|
||||
{
|
||||
$values = $this->request->getValues();
|
||||
list($valid, $errors) = $this->project->validateCreation($values);
|
||||
list($valid, $errors) = $this->projectValidator->validateCreation($values);
|
||||
|
||||
if ($valid) {
|
||||
$project_id = $this->project->create($values, $this->userSession->getId(), true);
|
||||
|
|
177
sources/app/Controller/ProjectPermission.php
Normal file
177
sources/app/Controller/ProjectPermission.php
Normal file
|
@ -0,0 +1,177 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Controller;
|
||||
|
||||
use Kanboard\Core\Security\Role;
|
||||
|
||||
/**
|
||||
* Project Permission
|
||||
*
|
||||
* @package controller
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class ProjectPermission extends Base
|
||||
{
|
||||
/**
|
||||
* Show all permissions
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function index(array $values = array(), array $errors = array())
|
||||
{
|
||||
$project = $this->getProject();
|
||||
|
||||
if (empty($values)) {
|
||||
$values['role'] = Role::PROJECT_MEMBER;
|
||||
}
|
||||
|
||||
$this->response->html($this->projectLayout('project_permission/index', array(
|
||||
'project' => $project,
|
||||
'users' => $this->projectUserRole->getUsers($project['id']),
|
||||
'groups' => $this->projectGroupRole->getGroups($project['id']),
|
||||
'roles' => $this->role->getProjectRoles(),
|
||||
'values' => $values,
|
||||
'errors' => $errors,
|
||||
'title' => t('Project Permissions'),
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow everybody
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function allowEverybody()
|
||||
{
|
||||
$project = $this->getProject();
|
||||
$values = $this->request->getValues() + array('is_everybody_allowed' => 0);
|
||||
|
||||
if ($this->project->update($values)) {
|
||||
$this->flash->success(t('Project updated successfully.'));
|
||||
} else {
|
||||
$this->flash->failure(t('Unable to update this project.'));
|
||||
}
|
||||
|
||||
$this->response->redirect($this->helper->url->to('ProjectPermission', 'index', array('project_id' => $project['id'])));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add user to the project
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function addUser()
|
||||
{
|
||||
$values = $this->request->getValues();
|
||||
|
||||
if ($this->projectUserRole->addUser($values['project_id'], $values['user_id'], $values['role'])) {
|
||||
$this->flash->success(t('Project updated successfully.'));
|
||||
} else {
|
||||
$this->flash->failure(t('Unable to update this project.'));
|
||||
}
|
||||
|
||||
$this->response->redirect($this->helper->url->to('ProjectPermission', 'index', array('project_id' => $values['project_id'])));
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke user access
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function removeUser()
|
||||
{
|
||||
$this->checkCSRFParam();
|
||||
|
||||
$values = array(
|
||||
'project_id' => $this->request->getIntegerParam('project_id'),
|
||||
'user_id' => $this->request->getIntegerParam('user_id'),
|
||||
);
|
||||
|
||||
if ($this->projectUserRole->removeUser($values['project_id'], $values['user_id'])) {
|
||||
$this->flash->success(t('Project updated successfully.'));
|
||||
} else {
|
||||
$this->flash->failure(t('Unable to update this project.'));
|
||||
}
|
||||
|
||||
$this->response->redirect($this->helper->url->to('ProjectPermission', 'index', array('project_id' => $values['project_id'])));
|
||||
}
|
||||
|
||||
/**
|
||||
* Change user role
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function changeUserRole()
|
||||
{
|
||||
$project_id = $this->request->getIntegerParam('project_id');
|
||||
$values = $this->request->getJson();
|
||||
|
||||
if (! empty($project_id) && ! empty($values) && $this->projectUserRole->changeUserRole($project_id, $values['id'], $values['role'])) {
|
||||
$this->response->json(array('status' => 'ok'));
|
||||
} else {
|
||||
$this->response->json(array('status' => 'error'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add group to the project
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function addGroup()
|
||||
{
|
||||
$values = $this->request->getValues();
|
||||
|
||||
if (empty($values['group_id']) && ! empty($values['external_id'])) {
|
||||
$values['group_id'] = $this->group->create($values['name'], $values['external_id']);
|
||||
}
|
||||
|
||||
if ($this->projectGroupRole->addGroup($values['project_id'], $values['group_id'], $values['role'])) {
|
||||
$this->flash->success(t('Project updated successfully.'));
|
||||
} else {
|
||||
$this->flash->failure(t('Unable to update this project.'));
|
||||
}
|
||||
|
||||
$this->response->redirect($this->helper->url->to('ProjectPermission', 'index', array('project_id' => $values['project_id'])));
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke group access
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function removeGroup()
|
||||
{
|
||||
$this->checkCSRFParam();
|
||||
|
||||
$values = array(
|
||||
'project_id' => $this->request->getIntegerParam('project_id'),
|
||||
'group_id' => $this->request->getIntegerParam('group_id'),
|
||||
);
|
||||
|
||||
if ($this->projectGroupRole->removeGroup($values['project_id'], $values['group_id'])) {
|
||||
$this->flash->success(t('Project updated successfully.'));
|
||||
} else {
|
||||
$this->flash->failure(t('Unable to update this project.'));
|
||||
}
|
||||
|
||||
$this->response->redirect($this->helper->url->to('ProjectPermission', 'index', array('project_id' => $values['project_id'])));
|
||||
}
|
||||
|
||||
/**
|
||||
* Change group role
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function changeGroupRole()
|
||||
{
|
||||
$project_id = $this->request->getIntegerParam('project_id');
|
||||
$values = $this->request->getJson();
|
||||
|
||||
if (! empty($project_id) && ! empty($values) && $this->projectGroupRole->changeGroupRole($project_id, $values['id'], $values['role'])) {
|
||||
$this->response->json(array('status' => 'ok'));
|
||||
} else {
|
||||
$this->response->json(array('status' => 'error'));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ namespace Kanboard\Controller;
|
|||
|
||||
use Kanboard\Model\User as UserModel;
|
||||
use Kanboard\Model\Task as TaskModel;
|
||||
use Kanboard\Core\Security\Role;
|
||||
|
||||
/**
|
||||
* Project User overview
|
||||
|
@ -23,7 +24,7 @@ class Projectuser extends Base
|
|||
*/
|
||||
private function layout($template, array $params)
|
||||
{
|
||||
$params['board_selector'] = $this->projectPermission->getAllowedProjects($this->userSession->getId());
|
||||
$params['board_selector'] = $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId());
|
||||
$params['content_for_sublayout'] = $this->template->render($template, $params);
|
||||
$params['filter'] = array('user_id' => $params['user_id']);
|
||||
|
||||
|
@ -37,17 +38,17 @@ class Projectuser extends Base
|
|||
if ($this->userSession->isAdmin()) {
|
||||
$project_ids = $this->project->getAllIds();
|
||||
} else {
|
||||
$project_ids = $this->projectPermission->getMemberProjectIds($this->userSession->getId());
|
||||
$project_ids = $this->projectPermission->getActiveProjectIds($this->userSession->getId());
|
||||
}
|
||||
|
||||
return array($user_id, $project_ids, $this->user->getList(true));
|
||||
}
|
||||
|
||||
private function role($is_owner, $action, $title, $title_user)
|
||||
private function role($role, $action, $title, $title_user)
|
||||
{
|
||||
list($user_id, $project_ids, $users) = $this->common();
|
||||
|
||||
$query = $this->projectPermission->getQueryByRole($project_ids, $is_owner)->callback(array($this->project, 'applyColumnStats'));
|
||||
$query = $this->projectPermission->getQueryByRole($project_ids, $role)->callback(array($this->project, 'applyColumnStats'));
|
||||
|
||||
if ($user_id !== UserModel::EVERYBODY_ID) {
|
||||
$query->eq(UserModel::TABLE.'.id', $user_id);
|
||||
|
@ -101,7 +102,7 @@ class Projectuser extends Base
|
|||
*/
|
||||
public function managers()
|
||||
{
|
||||
$this->role(1, 'managers', t('People who are project managers'), 'Projects where "%s" is manager');
|
||||
$this->role(Role::PROJECT_MANAGER, 'managers', t('People who are project managers'), 'Projects where "%s" is manager');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -110,7 +111,7 @@ class Projectuser extends Base
|
|||
*/
|
||||
public function members()
|
||||
{
|
||||
$this->role(0, 'members', t('People who are project members'), 'Projects where "%s" is member');
|
||||
$this->role(ROLE::PROJECT_MEMBER, 'members', t('People who are project members'), 'Projects where "%s" is member');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -12,7 +12,7 @@ class Search extends Base
|
|||
{
|
||||
public function index()
|
||||
{
|
||||
$projects = $this->projectPermission->getAllowedProjects($this->userSession->getId());
|
||||
$projects = $this->projectUserRole->getProjectsByUser($this->userSession->getId());
|
||||
$search = urldecode($this->request->getStringParam('search'));
|
||||
$nb_tasks = 0;
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ class Subtask extends Base
|
|||
$this->response->html($this->taskLayout('subtask/create', array(
|
||||
'values' => $values,
|
||||
'errors' => $errors,
|
||||
'users_list' => $this->projectPermission->getMemberList($task['project_id']),
|
||||
'users_list' => $this->projectUserRole->getAssignableUsersList($task['project_id']),
|
||||
'task' => $task,
|
||||
)));
|
||||
}
|
||||
|
@ -63,7 +63,7 @@ class Subtask extends Base
|
|||
$task = $this->getTask();
|
||||
$values = $this->request->getValues();
|
||||
|
||||
list($valid, $errors) = $this->subtask->validateCreation($values);
|
||||
list($valid, $errors) = $this->subtaskValidator->validateCreation($values);
|
||||
|
||||
if ($valid) {
|
||||
if ($this->subtask->create($values)) {
|
||||
|
@ -95,7 +95,7 @@ class Subtask extends Base
|
|||
$this->response->html($this->taskLayout('subtask/edit', array(
|
||||
'values' => empty($values) ? $subtask : $values,
|
||||
'errors' => $errors,
|
||||
'users_list' => $this->projectPermission->getMemberList($task['project_id']),
|
||||
'users_list' => $this->projectUserRole->getAssignableUsersList($task['project_id']),
|
||||
'status_list' => $this->subtask->getStatusList(),
|
||||
'subtask' => $subtask,
|
||||
'task' => $task,
|
||||
|
@ -113,7 +113,7 @@ class Subtask extends Base
|
|||
$this->getSubtask();
|
||||
|
||||
$values = $this->request->getValues();
|
||||
list($valid, $errors) = $this->subtask->validateModification($values);
|
||||
list($valid, $errors) = $this->subtaskValidator->validateModification($values);
|
||||
|
||||
if ($valid) {
|
||||
if ($this->subtask->update($values)) {
|
||||
|
|
|
@ -60,7 +60,7 @@ class Swimlane extends Base
|
|||
{
|
||||
$project = $this->getProject();
|
||||
$values = $this->request->getValues();
|
||||
list($valid, $errors) = $this->swimlane->validateCreation($values);
|
||||
list($valid, $errors) = $this->swimlaneValidator->validateCreation($values);
|
||||
|
||||
if ($valid) {
|
||||
if ($this->swimlane->create($values)) {
|
||||
|
@ -84,7 +84,7 @@ class Swimlane extends Base
|
|||
$project = $this->getProject();
|
||||
|
||||
$values = $this->request->getValues() + array('show_default_swimlane' => 0);
|
||||
list($valid, ) = $this->swimlane->validateDefaultModification($values);
|
||||
list($valid, ) = $this->swimlaneValidator->validateDefaultModification($values);
|
||||
|
||||
if ($valid) {
|
||||
if ($this->swimlane->updateDefault($values)) {
|
||||
|
@ -126,7 +126,7 @@ class Swimlane extends Base
|
|||
$project = $this->getProject();
|
||||
|
||||
$values = $this->request->getValues();
|
||||
list($valid, $errors) = $this->swimlane->validateModification($values);
|
||||
list($valid, $errors) = $this->swimlaneValidator->validateModification($values);
|
||||
|
||||
if ($valid) {
|
||||
if ($this->swimlane->update($values)) {
|
||||
|
|
|
@ -76,7 +76,7 @@ class Task extends Base
|
|||
'link_label_list' => $this->link->getList(0, false),
|
||||
'columns_list' => $this->board->getColumnsList($task['project_id']),
|
||||
'colors_list' => $this->color->getList(),
|
||||
'users_list' => $this->projectPermission->getMemberList($task['project_id'], true, false, false),
|
||||
'users_list' => $this->projectUserRole->getAssignableUsersList($task['project_id'], true, false, false),
|
||||
'date_format' => $this->config->get('application_date_format'),
|
||||
'date_formats' => $this->dateParser->getAvailableFormats(),
|
||||
'title' => $task['project_name'].' > '.$task['title'],
|
||||
|
|
57
sources/app/Controller/TaskHelper.php
Normal file
57
sources/app/Controller/TaskHelper.php
Normal file
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Controller;
|
||||
|
||||
/**
|
||||
* Task Ajax Helper
|
||||
*
|
||||
* @package controller
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class TaskHelper extends Base
|
||||
{
|
||||
/**
|
||||
* Render Markdown text and reply with the HTML Code
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function preview()
|
||||
{
|
||||
$payload = $this->request->getJson();
|
||||
|
||||
if (empty($payload['text'])) {
|
||||
$this->response->html('<p>'.t('Nothing to preview...').'</p>');
|
||||
}
|
||||
|
||||
$this->response->html($this->helper->text->markdown($payload['text']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Task autocompletion (Ajax)
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function autocomplete()
|
||||
{
|
||||
$search = $this->request->getStringParam('term');
|
||||
$projects = $this->projectPermission->getActiveProjectIds($this->userSession->getId());
|
||||
|
||||
if (empty($projects)) {
|
||||
$this->response->json(array());
|
||||
}
|
||||
|
||||
$filter = $this->taskFilterAutoCompleteFormatter
|
||||
->create()
|
||||
->filterByProjects($projects)
|
||||
->excludeTasks(array($this->request->getIntegerParam('exclude_task_id')));
|
||||
|
||||
// Search by task id or by title
|
||||
if (ctype_digit($search)) {
|
||||
$filter->filterById($search);
|
||||
} else {
|
||||
$filter->filterByTitle($search);
|
||||
}
|
||||
|
||||
$this->response->json($filter->format());
|
||||
}
|
||||
}
|
|
@ -36,7 +36,7 @@ class Taskcreation extends Base
|
|||
'errors' => $errors,
|
||||
'values' => $values + array('project_id' => $project['id']),
|
||||
'columns_list' => $this->board->getColumnsList($project['id']),
|
||||
'users_list' => $this->projectPermission->getMemberList($project['id'], true, false, true),
|
||||
'users_list' => $this->projectUserRole->getAssignableUsersList($project['id'], true, false, true),
|
||||
'colors_list' => $this->color->getList(),
|
||||
'categories_list' => $this->category->getList($project['id']),
|
||||
'swimlanes_list' => $swimlanes_list,
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
namespace Kanboard\Controller;
|
||||
|
||||
use Kanboard\Model\Project as ProjectModel;
|
||||
|
||||
/**
|
||||
* Task Duplication controller
|
||||
*
|
||||
|
@ -107,7 +109,7 @@ class Taskduplication extends Base
|
|||
private function chooseDestination(array $task, $template)
|
||||
{
|
||||
$values = array();
|
||||
$projects_list = $this->projectPermission->getActiveMemberProjects($this->userSession->getId());
|
||||
$projects_list = $this->projectUserRole->getProjectsByUser($this->userSession->getId(), array(ProjectModel::ACTIVE));
|
||||
|
||||
unset($projects_list[$task['project_id']]);
|
||||
|
||||
|
@ -117,7 +119,7 @@ class Taskduplication extends Base
|
|||
$swimlanes_list = $this->swimlane->getList($dst_project_id, false, true);
|
||||
$columns_list = $this->board->getColumnsList($dst_project_id);
|
||||
$categories_list = $this->category->getList($dst_project_id);
|
||||
$users_list = $this->projectPermission->getMemberList($dst_project_id);
|
||||
$users_list = $this->projectUserRole->getAssignableUsersList($dst_project_id);
|
||||
|
||||
$values = $this->taskDuplication->checkDestinationProjectValues($task);
|
||||
$values['project_id'] = $dst_project_id;
|
||||
|
|
|
@ -69,7 +69,7 @@ class Tasklink extends Base
|
|||
$values = $this->request->getValues();
|
||||
$ajax = $this->request->isAjax() || $this->request->getIntegerParam('ajax');
|
||||
|
||||
list($valid, $errors) = $this->taskLink->validateCreation($values);
|
||||
list($valid, $errors) = $this->taskLinkValidator->validateCreation($values);
|
||||
|
||||
if ($valid) {
|
||||
if ($this->taskLink->create($values['task_id'], $values['opposite_task_id'], $values['link_id'])) {
|
||||
|
@ -125,7 +125,7 @@ class Tasklink extends Base
|
|||
$task = $this->getTask();
|
||||
$values = $this->request->getValues();
|
||||
|
||||
list($valid, $errors) = $this->taskLink->validateModification($values);
|
||||
list($valid, $errors) = $this->taskLinkValidator->validateModification($values);
|
||||
|
||||
if ($valid) {
|
||||
if ($this->taskLink->update($values['id'], $values['task_id'], $values['opposite_task_id'], $values['link_id'])) {
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue