mirror of
https://github.com/YunoHost-Apps/kanboard_ynh.git
synced 2024-09-03 19:36:17 +02:00
Update to kanboard v1.0.24
This commit is contained in:
parent
039a8de71a
commit
8690250086
481 changed files with 14744 additions and 14617 deletions
|
@ -1,3 +1,80 @@
|
|||
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
|
||||
--------------
|
||||
|
||||
|
@ -33,7 +110,7 @@ Version 1.0.21
|
|||
|
||||
Breaking changes:
|
||||
|
||||
* Projects with duplicate name are now allowed:
|
||||
* 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
|
||||
|
||||
|
@ -44,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
|
||||
|
@ -92,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
|
||||
--------------
|
||||
|
@ -124,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
|
||||
|
@ -144,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)
|
||||
|
@ -169,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
|
||||
|
@ -177,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
|
||||
|
||||
|
@ -194,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
|
||||
--------------
|
||||
|
@ -217,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
|
||||
|
@ -235,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:
|
||||
|
@ -244,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
|
||||
|
@ -272,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,10 +2,6 @@
|
|||
|
||||
namespace Kanboard\Action;
|
||||
|
||||
use Kanboard\Integration\GithubWebhook;
|
||||
use Kanboard\Integration\GitlabWebhook;
|
||||
use Kanboard\Integration\BitbucketWebhook;
|
||||
|
||||
/**
|
||||
* Open automatically a task
|
||||
*
|
||||
|
@ -14,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
|
||||
*
|
||||
|
@ -22,11 +29,7 @@ class TaskOpen extends Base
|
|||
*/
|
||||
public function getCompatibleEvents()
|
||||
{
|
||||
return array(
|
||||
GithubWebhook::EVENT_ISSUE_REOPENED,
|
||||
GitlabWebhook::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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ class Auth extends Base
|
|||
*/
|
||||
public function checkCredentials($username, $password, $class, $method)
|
||||
{
|
||||
$this->container['dispatcher']->dispatch('app.bootstrap');
|
||||
$this->dispatcher->dispatch('app.bootstrap');
|
||||
|
||||
if ($this->isUserAuthenticated($username, $password)) {
|
||||
$this->checkProcedurePermission(true, $method);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ 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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,25 +5,68 @@ 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 getMembers($project_id)
|
||||
public function getProjectUsers($project_id)
|
||||
{
|
||||
return $this->projectUserRole->getAllUsers($project_id);
|
||||
}
|
||||
|
||||
public function revokeUser($project_id, $user_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->getProjectUsers($project_id);
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
public function revokeUser($project_id, $user_id)
|
||||
{
|
||||
return $this->removeProjectUser($project_id, $user_id);
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
public function allowUser($project_id, $user_id)
|
||||
{
|
||||
return $this->projectUserRole->addUser($project_id, $user_id, Role::PROJECT_MEMBER);
|
||||
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,7 +81,7 @@ class Task extends Base
|
|||
{
|
||||
$this->checkProjectPermission($project_id);
|
||||
|
||||
if ($owner_id !== 0 && ! $this->projectPermission->isMember($project_id, $owner_id)) {
|
||||
if ($owner_id !== 0 && ! $this->projectPermission->isAssignable($project_id, $owner_id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -117,7 +127,7 @@ class Task extends Base
|
|||
return false;
|
||||
}
|
||||
|
||||
if ($owner_id !== null && ! $this->projectPermission->isMember($project_id, $owner_id)) {
|
||||
if ($owner_id !== null && $owner_id != 0 && ! $this->projectPermission->isAssignable($project_id, $owner_id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ class User extends \Kanboard\Core\Base
|
|||
'role' => $role,
|
||||
);
|
||||
|
||||
list($valid, ) = $this->user->validateCreation($values);
|
||||
list($valid, ) = $this->userValidator->validateCreation($values);
|
||||
return $valid ? $this->user->create($values) : false;
|
||||
}
|
||||
|
||||
|
@ -94,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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,26 +19,26 @@ class DatabaseAuth extends Base implements PasswordAuthenticationProviderInterfa
|
|||
/**
|
||||
* User properties
|
||||
*
|
||||
* @access private
|
||||
* @access protected
|
||||
* @var array
|
||||
*/
|
||||
private $userInfo = array();
|
||||
protected $userInfo = array();
|
||||
|
||||
/**
|
||||
* Username
|
||||
*
|
||||
* @access private
|
||||
* @access protected
|
||||
* @var string
|
||||
*/
|
||||
private $username = '';
|
||||
protected $username = '';
|
||||
|
||||
/**
|
||||
* Password
|
||||
*
|
||||
* @access private
|
||||
* @access protected
|
||||
* @var string
|
||||
*/
|
||||
private $password = '';
|
||||
protected $password = '';
|
||||
|
||||
/**
|
||||
* Get authentication provider name
|
||||
|
|
|
@ -17,26 +17,26 @@ class GithubAuth extends Base implements OAuthAuthenticationProviderInterface
|
|||
/**
|
||||
* User properties
|
||||
*
|
||||
* @access private
|
||||
* @access protected
|
||||
* @var \Kanboard\User\GithubUserProvider
|
||||
*/
|
||||
private $userInfo = null;
|
||||
protected $userInfo = null;
|
||||
|
||||
/**
|
||||
* OAuth2 instance
|
||||
*
|
||||
* @access private
|
||||
* @access protected
|
||||
* @var \Kanboard\Core\Http\OAuth2
|
||||
*/
|
||||
private $service;
|
||||
protected $service;
|
||||
|
||||
/**
|
||||
* OAuth2 code
|
||||
*
|
||||
* @access private
|
||||
* @access protected
|
||||
* @var string
|
||||
*/
|
||||
private $code = '';
|
||||
protected $code = '';
|
||||
|
||||
/**
|
||||
* Get authentication provider name
|
||||
|
|
|
@ -25,18 +25,18 @@ class GitlabAuth extends Base implements OAuthAuthenticationProviderInterface
|
|||
/**
|
||||
* OAuth2 instance
|
||||
*
|
||||
* @access private
|
||||
* @access protected
|
||||
* @var \Kanboard\Core\Http\OAuth2
|
||||
*/
|
||||
private $service;
|
||||
protected $service;
|
||||
|
||||
/**
|
||||
* OAuth2 code
|
||||
*
|
||||
* @access private
|
||||
* @access protected
|
||||
* @var string
|
||||
*/
|
||||
private $code = '';
|
||||
protected $code = '';
|
||||
|
||||
/**
|
||||
* Get authentication provider name
|
||||
|
|
|
@ -17,26 +17,26 @@ class GoogleAuth extends Base implements OAuthAuthenticationProviderInterface
|
|||
/**
|
||||
* User properties
|
||||
*
|
||||
* @access private
|
||||
* @access protected
|
||||
* @var \Kanboard\User\GoogleUserProvider
|
||||
*/
|
||||
private $userInfo = null;
|
||||
protected $userInfo = null;
|
||||
|
||||
/**
|
||||
* OAuth2 instance
|
||||
*
|
||||
* @access private
|
||||
* @access protected
|
||||
* @var \Kanboard\Core\Http\OAuth2
|
||||
*/
|
||||
private $service;
|
||||
protected $service;
|
||||
|
||||
/**
|
||||
* OAuth2 code
|
||||
*
|
||||
* @access private
|
||||
* @access protected
|
||||
* @var string
|
||||
*/
|
||||
private $code = '';
|
||||
protected $code = '';
|
||||
|
||||
/**
|
||||
* Get authentication provider name
|
||||
|
|
|
@ -20,26 +20,26 @@ class LdapAuth extends Base implements PasswordAuthenticationProviderInterface
|
|||
/**
|
||||
* User properties
|
||||
*
|
||||
* @access private
|
||||
* @access protected
|
||||
* @var \Kanboard\User\LdapUserProvider
|
||||
*/
|
||||
private $userInfo = null;
|
||||
protected $userInfo = null;
|
||||
|
||||
/**
|
||||
* Username
|
||||
*
|
||||
* @access private
|
||||
* @access protected
|
||||
* @var string
|
||||
*/
|
||||
private $username = '';
|
||||
protected $username = '';
|
||||
|
||||
/**
|
||||
* Password
|
||||
*
|
||||
* @access private
|
||||
* @access protected
|
||||
* @var string
|
||||
*/
|
||||
private $password = '';
|
||||
protected $password = '';
|
||||
|
||||
/**
|
||||
* Get authentication provider name
|
||||
|
|
|
@ -17,10 +17,10 @@ class RememberMeAuth extends Base implements PreAuthenticationProviderInterface
|
|||
/**
|
||||
* User properties
|
||||
*
|
||||
* @access private
|
||||
* @access protected
|
||||
* @var array
|
||||
*/
|
||||
private $userInfo = array();
|
||||
protected $userInfo = array();
|
||||
|
||||
/**
|
||||
* Get authentication provider name
|
||||
|
|
|
@ -19,18 +19,18 @@ class TotpAuth extends Base implements PostAuthenticationProviderInterface
|
|||
/**
|
||||
* User pin code
|
||||
*
|
||||
* @access private
|
||||
* @access protected
|
||||
* @var string
|
||||
*/
|
||||
private $code = '';
|
||||
protected $code = '';
|
||||
|
||||
/**
|
||||
* Private key
|
||||
*
|
||||
* @access private
|
||||
* @access protected
|
||||
* @var string
|
||||
*/
|
||||
private $secret = '';
|
||||
protected $secret = '';
|
||||
|
||||
/**
|
||||
* Get authentication provider name
|
||||
|
@ -40,7 +40,7 @@ class TotpAuth extends Base implements PostAuthenticationProviderInterface
|
|||
*/
|
||||
public function getName()
|
||||
{
|
||||
return 'Time-based One-time Password Algorithm';
|
||||
return t('Time-based One-time Password Algorithm');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -55,6 +55,16 @@ class TotpAuth extends Base implements PostAuthenticationProviderInterface
|
|||
return $otp->checkTotp(Base32::decode($this->secret), $this->code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called before to prompt the user
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function beforeCode()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Set validation code
|
||||
*
|
||||
|
@ -66,6 +76,18 @@ class TotpAuth extends Base implements PostAuthenticationProviderInterface
|
|||
$this->code = $code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate secret
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function generateSecret()
|
||||
{
|
||||
$this->secret = GoogleAuthenticator::generateRandom();
|
||||
return $this->secret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set secret token
|
||||
*
|
||||
|
@ -85,10 +107,6 @@ class TotpAuth extends Base implements PostAuthenticationProviderInterface
|
|||
*/
|
||||
public function getSecret()
|
||||
{
|
||||
if (empty($this->secret)) {
|
||||
$this->secret = GoogleAuthenticator::generateRandom();
|
||||
}
|
||||
|
||||
return $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->projectUserRole->getAssignableUsersList($project['id']),
|
||||
'projects_list' => $this->project->getList(false),
|
||||
'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,14 +73,14 @@ 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(
|
||||
|
@ -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->projectUserRole->getProjectsByUser($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->projectUserRole->getProjectsByUser($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']),
|
||||
)));
|
||||
}
|
||||
|
@ -139,17 +156,7 @@ class Analytic extends Base
|
|||
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;
|
||||
|
||||
|
@ -166,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->projectUserRole->getProjectsByUser($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);
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
|
||||
namespace Kanboard\Controller;
|
||||
|
||||
use Gregwar\Captcha\CaptchaBuilder;
|
||||
|
||||
/**
|
||||
* Authentication controller
|
||||
*
|
||||
|
@ -41,7 +39,7 @@ class Auth extends Base
|
|||
{
|
||||
$values = $this->request->getValues();
|
||||
$this->sessionStorage->hasRememberMe = ! empty($values['remember_me']);
|
||||
list($valid, $errors) = $this->authentication->validateForm($values);
|
||||
list($valid, $errors) = $this->authValidator->validateForm($values);
|
||||
|
||||
if ($valid) {
|
||||
$this->redirectAfterLogin();
|
||||
|
@ -61,21 +59,6 @@ class Auth extends Base
|
|||
$this->response->redirect($this->helper->url->to('auth', 'login'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display captcha image
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function captcha()
|
||||
{
|
||||
$this->response->contentType('image/jpeg');
|
||||
|
||||
$builder = new CaptchaBuilder;
|
||||
$builder->build();
|
||||
$this->sessionStorage->captcha = $builder->getPhrase();
|
||||
$builder->output();
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect the user after the authentication
|
||||
*
|
||||
|
|
|
@ -17,18 +17,18 @@ abstract class Base extends \Kanboard\Core\Base
|
|||
*
|
||||
* @access public
|
||||
*/
|
||||
public function beforeAction($controller, $action)
|
||||
public function beforeAction()
|
||||
{
|
||||
$this->sessionManager->open();
|
||||
$this->dispatcher->dispatch('app.bootstrap');
|
||||
$this->sendHeaders($action);
|
||||
$this->sendHeaders();
|
||||
$this->authenticationManager->checkCurrentSession();
|
||||
|
||||
if (! $this->applicationAuthorization->isAllowed($controller, $action, Role::APP_PUBLIC)) {
|
||||
if (! $this->applicationAuthorization->isAllowed($this->router->getController(), $this->router->getAction(), Role::APP_PUBLIC)) {
|
||||
$this->handleAuthentication();
|
||||
$this->handlePostAuthentication($controller, $action);
|
||||
$this->checkApplicationAuthorization($controller, $action);
|
||||
$this->checkProjectAuthorization($controller, $action);
|
||||
$this->handlePostAuthentication();
|
||||
$this->checkApplicationAuthorization();
|
||||
$this->checkProjectAuthorization();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -37,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']);
|
||||
|
@ -45,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();
|
||||
}
|
||||
|
||||
|
@ -76,8 +76,10 @@ abstract class Base extends \Kanboard\Core\Base
|
|||
*
|
||||
* @access private
|
||||
*/
|
||||
private function handlePostAuthentication($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->hasPostAuthentication() && ! $this->userSession->isPostAuthenticationValidated()) {
|
||||
|
@ -94,9 +96,9 @@ abstract class Base extends \Kanboard\Core\Base
|
|||
*
|
||||
* @access private
|
||||
*/
|
||||
private function checkApplicationAuthorization($controller, $action)
|
||||
private function checkApplicationAuthorization()
|
||||
{
|
||||
if (! $this->helper->user->hasAccess($controller, $action)) {
|
||||
if (! $this->helper->user->hasAccess($this->router->getController(), $this->router->getAction())) {
|
||||
$this->forbidden();
|
||||
}
|
||||
}
|
||||
|
@ -106,7 +108,7 @@ abstract class Base extends \Kanboard\Core\Base
|
|||
*
|
||||
* @access private
|
||||
*/
|
||||
private function checkProjectAuthorization($controller, $action)
|
||||
private function checkProjectAuthorization()
|
||||
{
|
||||
$project_id = $this->request->getIntegerParam('project_id');
|
||||
$task_id = $this->request->getIntegerParam('task_id');
|
||||
|
@ -116,7 +118,7 @@ abstract class Base extends \Kanboard\Core\Base
|
|||
$project_id = $this->taskFinder->getProjectId($task_id);
|
||||
}
|
||||
|
||||
if ($project_id > 0 && ! $this->helper->user->hasProjectAccess($controller, $action, $project_id)) {
|
||||
if ($project_id > 0 && ! $this->helper->user->hasProjectAccess($this->router->getController(), $this->router->getAction(), $project_id)) {
|
||||
$this->forbidden();
|
||||
}
|
||||
}
|
||||
|
@ -144,7 +146,7 @@ abstract class Base extends \Kanboard\Core\Base
|
|||
protected function forbidden($no_layout = false)
|
||||
{
|
||||
if ($this->request->isAjax()) {
|
||||
$this->response->text('Not Authorized', 401);
|
||||
$this->response->text('Access Forbidden', 403);
|
||||
}
|
||||
|
||||
$this->response->html($this->template->layout('app/forbidden', array(
|
||||
|
@ -190,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->projectUserRole->getProjectsByUser($this->userSession->getId());
|
||||
$params['board_selector'] = $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId());
|
||||
|
||||
return $this->template->layout('task/layout', $params);
|
||||
}
|
||||
|
@ -208,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->projectUserRole->getProjectsByUser($this->userSession->getId());
|
||||
$params['board_selector'] = $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId());
|
||||
$params['sidebar_template'] = $sidebar_template;
|
||||
|
||||
return $this->template->layout('project/layout', $params);
|
||||
|
@ -289,7 +291,7 @@ abstract class Base extends \Kanboard\Core\Base
|
|||
{
|
||||
$project = $this->getProject();
|
||||
$search = $this->request->getStringParam('search', $this->userSession->getFilters($project['id']));
|
||||
$board_selector = $this->projectUserRole->getProjectsByUser($this->userSession->getId());
|
||||
$board_selector = $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId());
|
||||
unset($board_selector[$project['id']]);
|
||||
|
||||
$filters = array(
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -98,4 +98,39 @@ class BoardPopover extends Base
|
|||
'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'])));
|
||||
}
|
||||
}
|
||||
|
|
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->projectUserRole->getProjectsByUser($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->projectUserRole->getProjectsByUser($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'])) {
|
||||
|
|
|
@ -42,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)) {
|
||||
|
@ -121,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)) {
|
||||
|
|
|
@ -53,7 +53,7 @@ class Doc extends Base
|
|||
}
|
||||
|
||||
$this->response->html($this->template->layout('doc/show', $this->readFile($filename) + array(
|
||||
'board_selector' => $this->projectUserRole->getProjectsByUser($this->userSession->getId()),
|
||||
'board_selector' => $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()),
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ class Gantt extends Base
|
|||
$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->projectUserRole->getProjectsByUser($this->userSession->getId()),
|
||||
'board_selector' => $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()),
|
||||
)));
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ class Group extends Base
|
|||
->calculate();
|
||||
|
||||
$this->response->html($this->template->layout('group/index', array(
|
||||
'board_selector' => $this->projectUserRole->getProjectsByUser($this->userSession->getId()),
|
||||
'board_selector' => $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()),
|
||||
'title' => t('Groups').' ('.$paginator->getTotal().')',
|
||||
'paginator' => $paginator,
|
||||
)));
|
||||
|
@ -42,14 +42,14 @@ class Group extends Base
|
|||
$group = $this->group->getById($group_id);
|
||||
|
||||
$paginator = $this->paginator
|
||||
->setUrl('group', 'users')
|
||||
->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->getProjectsByUser($this->userSession->getId()),
|
||||
'board_selector' => $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()),
|
||||
'title' => t('Members of %s', $group['name']).' ('.$paginator->getTotal().')',
|
||||
'paginator' => $paginator,
|
||||
'group' => $group,
|
||||
|
@ -64,7 +64,7 @@ class Group extends Base
|
|||
public function create(array $values = array(), array $errors = array())
|
||||
{
|
||||
$this->response->html($this->template->layout('group/create', array(
|
||||
'board_selector' => $this->projectUserRole->getProjectsByUser($this->userSession->getId()),
|
||||
'board_selector' => $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()),
|
||||
'errors' => $errors,
|
||||
'values' => $values,
|
||||
'title' => t('New group')
|
||||
|
@ -79,7 +79,7 @@ class Group extends Base
|
|||
public function save()
|
||||
{
|
||||
$values = $this->request->getValues();
|
||||
list($valid, $errors) = $this->group->validateCreation($values);
|
||||
list($valid, $errors) = $this->groupValidator->validateCreation($values);
|
||||
|
||||
if ($valid) {
|
||||
if ($this->group->create($values['name']) !== false) {
|
||||
|
@ -105,7 +105,7 @@ class Group extends Base
|
|||
}
|
||||
|
||||
$this->response->html($this->template->layout('group/edit', array(
|
||||
'board_selector' => $this->projectUserRole->getProjectsByUser($this->userSession->getId()),
|
||||
'board_selector' => $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()),
|
||||
'errors' => $errors,
|
||||
'values' => $values,
|
||||
'title' => t('Edit group')
|
||||
|
@ -120,7 +120,7 @@ class Group extends Base
|
|||
public function update()
|
||||
{
|
||||
$values = $this->request->getValues();
|
||||
list($valid, $errors) = $this->group->validateModification($values);
|
||||
list($valid, $errors) = $this->groupValidator->validateModification($values);
|
||||
|
||||
if ($valid) {
|
||||
if ($this->group->update($values) !== false) {
|
||||
|
@ -149,7 +149,7 @@ class Group extends Base
|
|||
}
|
||||
|
||||
$this->response->html($this->template->layout('group/associate', array(
|
||||
'board_selector' => $this->projectUserRole->getProjectsByUser($this->userSession->getId()),
|
||||
'board_selector' => $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()),
|
||||
'users' => $this->user->prepareList($this->groupMember->getNotMembers($group_id)),
|
||||
'group' => $group,
|
||||
'errors' => $errors,
|
||||
|
|
|
@ -21,7 +21,7 @@ class Link extends Base
|
|||
*/
|
||||
private function layout($template, array $params)
|
||||
{
|
||||
$params['board_selector'] = $this->projectUserRole->getProjectsByUser($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)) {
|
||||
|
|
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'));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -33,7 +33,7 @@ class Project extends Base
|
|||
->calculate();
|
||||
|
||||
$this->response->html($this->template->layout('project/index', array(
|
||||
'board_selector' => $this->projectUserRole->getProjectsByUser($this->userSession->getId()),
|
||||
'board_selector' => $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()),
|
||||
'paginator' => $paginator,
|
||||
'nb_projects' => $nb_projects,
|
||||
'title' => t('Projects').' ('.$nb_projects.')'
|
||||
|
@ -169,7 +169,7 @@ class Project extends Base
|
|||
}
|
||||
}
|
||||
|
||||
list($valid, $errors) = $this->project->validateModification($values);
|
||||
list($valid, $errors) = $this->projectValidator->validateModification($values);
|
||||
|
||||
if ($valid) {
|
||||
if ($this->project->update($values)) {
|
||||
|
@ -302,7 +302,7 @@ class Project extends Base
|
|||
$is_private = isset($values['is_private']) && $values['is_private'] == 1;
|
||||
|
||||
$this->response->html($this->template->layout('project/new', array(
|
||||
'board_selector' => $this->projectUserRole->getProjectsByUser($this->userSession->getId()),
|
||||
'board_selector' => $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()),
|
||||
'values' => $values,
|
||||
'errors' => $errors,
|
||||
'is_private' => $is_private,
|
||||
|
@ -329,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);
|
||||
|
|
|
@ -24,7 +24,7 @@ class Projectuser extends Base
|
|||
*/
|
||||
private function layout($template, array $params)
|
||||
{
|
||||
$params['board_selector'] = $this->projectUserRole->getProjectsByUser($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']);
|
||||
|
||||
|
|
|
@ -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)) {
|
||||
|
@ -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)) {
|
||||
|
|
|
@ -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'])) {
|
||||
|
|
|
@ -23,7 +23,7 @@ class Twofactor extends User
|
|||
}
|
||||
|
||||
/**
|
||||
* Index
|
||||
* Show form to disable/enable 2FA
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
|
@ -31,54 +31,45 @@ class Twofactor extends User
|
|||
{
|
||||
$user = $this->getUser();
|
||||
$this->checkCurrentUser($user);
|
||||
|
||||
$provider = $this->authenticationManager->getPostAuthenticationProvider();
|
||||
$label = $user['email'] ?: $user['username'];
|
||||
|
||||
$provider->setSecret($user['twofactor_secret']);
|
||||
unset($this->sessionStorage->twoFactorSecret);
|
||||
|
||||
$this->response->html($this->layout('twofactor/index', array(
|
||||
'user' => $user,
|
||||
'qrcode_url' => $user['twofactor_activated'] == 1 ? $provider->getQrCodeUrl($label) : '',
|
||||
'key_url' => $user['twofactor_activated'] == 1 ? $provider->getKeyUrl($label) : '',
|
||||
'provider' => $this->authenticationManager->getPostAuthenticationProvider()->getName(),
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable/disable 2FA
|
||||
* Show page with secret and test form
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function save()
|
||||
public function show()
|
||||
{
|
||||
$user = $this->getUser();
|
||||
$this->checkCurrentUser($user);
|
||||
|
||||
$values = $this->request->getValues();
|
||||
$label = $user['email'] ?: $user['username'];
|
||||
$provider = $this->authenticationManager->getPostAuthenticationProvider();
|
||||
|
||||
if (isset($values['twofactor_activated']) && $values['twofactor_activated'] == 1) {
|
||||
$this->user->update(array(
|
||||
'id' => $user['id'],
|
||||
'twofactor_activated' => 1,
|
||||
'twofactor_secret' => $this->authenticationManager->getPostAuthenticationProvider()->getSecret(),
|
||||
));
|
||||
if (! isset($this->sessionStorage->twoFactorSecret)) {
|
||||
$provider->generateSecret();
|
||||
$provider->beforeCode();
|
||||
$this->sessionStorage->twoFactorSecret = $provider->getSecret();
|
||||
} else {
|
||||
$this->user->update(array(
|
||||
'id' => $user['id'],
|
||||
'twofactor_activated' => 0,
|
||||
'twofactor_secret' => '',
|
||||
));
|
||||
$provider->setSecret($this->sessionStorage->twoFactorSecret);
|
||||
}
|
||||
|
||||
// Allow the user to test or disable the feature
|
||||
$this->userSession->disablePostAuthentication();
|
||||
|
||||
$this->flash->success(t('User updated successfully.'));
|
||||
$this->response->redirect($this->helper->url->to('twofactor', 'index', array('user_id' => $user['id'])));
|
||||
$this->response->html($this->layout('twofactor/show', array(
|
||||
'user' => $user,
|
||||
'secret' => $this->sessionStorage->twoFactorSecret,
|
||||
'qrcode_url' => $provider->getQrCodeUrl($label),
|
||||
'key_url' => $provider->getKeyUrl($label),
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test code
|
||||
* Test code and save secret
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
|
@ -91,14 +82,47 @@ class Twofactor extends User
|
|||
|
||||
$provider = $this->authenticationManager->getPostAuthenticationProvider();
|
||||
$provider->setCode(empty($values['code']) ? '' : $values['code']);
|
||||
$provider->setSecret($user['twofactor_secret']);
|
||||
$provider->setSecret($this->sessionStorage->twoFactorSecret);
|
||||
|
||||
if ($provider->authenticate()) {
|
||||
$this->flash->success(t('The two factor authentication code is valid.'));
|
||||
|
||||
$this->user->update(array(
|
||||
'id' => $user['id'],
|
||||
'twofactor_activated' => 1,
|
||||
'twofactor_secret' => $this->authenticationManager->getPostAuthenticationProvider()->getSecret(),
|
||||
));
|
||||
|
||||
unset($this->sessionStorage->twoFactorSecret);
|
||||
$this->userSession->disablePostAuthentication();
|
||||
|
||||
$this->response->redirect($this->helper->url->to('twofactor', 'index', array('user_id' => $user['id'])));
|
||||
} else {
|
||||
$this->flash->failure(t('The two factor authentication code is not valid.'));
|
||||
$this->response->redirect($this->helper->url->to('twofactor', 'show', array('user_id' => $user['id'])));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable 2FA for the current user
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function deactivate()
|
||||
{
|
||||
$user = $this->getUser();
|
||||
$this->checkCurrentUser($user);
|
||||
|
||||
$this->user->update(array(
|
||||
'id' => $user['id'],
|
||||
'twofactor_activated' => 0,
|
||||
'twofactor_secret' => '',
|
||||
));
|
||||
|
||||
// Allow the user to test or disable the feature
|
||||
$this->userSession->disablePostAuthentication();
|
||||
|
||||
$this->flash->success(t('User updated successfully.'));
|
||||
$this->response->redirect($this->helper->url->to('twofactor', 'index', array('user_id' => $user['id'])));
|
||||
}
|
||||
|
||||
|
@ -135,6 +159,12 @@ class Twofactor extends User
|
|||
*/
|
||||
public function code()
|
||||
{
|
||||
if (! isset($this->sessionStorage->twoFactorBeforeCodeCalled)) {
|
||||
$provider = $this->authenticationManager->getPostAuthenticationProvider();
|
||||
$provider->beforeCode();
|
||||
$this->sessionStorage->twoFactorBeforeCodeCalled = true;
|
||||
}
|
||||
|
||||
$this->response->html($this->template->layout('twofactor/check', array(
|
||||
'title' => t('Check two factor authentication code'),
|
||||
)));
|
||||
|
|
|
@ -26,7 +26,7 @@ class User extends Base
|
|||
{
|
||||
$content = $this->template->render($template, $params);
|
||||
$params['user_content_for_layout'] = $content;
|
||||
$params['board_selector'] = $this->projectUserRole->getProjectsByUser($this->userSession->getId());
|
||||
$params['board_selector'] = $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId());
|
||||
|
||||
if (isset($params['user'])) {
|
||||
$params['title'] = ($params['user']['name'] ?: $params['user']['username']).' (#'.$params['user']['id'].')';
|
||||
|
@ -51,12 +51,34 @@ class User extends Base
|
|||
|
||||
$this->response->html(
|
||||
$this->template->layout('user/index', array(
|
||||
'board_selector' => $this->projectUserRole->getProjectsByUser($this->userSession->getId()),
|
||||
'board_selector' => $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()),
|
||||
'title' => t('Users').' ('.$paginator->getTotal().')',
|
||||
'paginator' => $paginator,
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Public user profile
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function profile()
|
||||
{
|
||||
$user = $this->user->getById($this->request->getIntegerParam('user_id'));
|
||||
|
||||
if (empty($user)) {
|
||||
$this->notfound();
|
||||
}
|
||||
|
||||
$this->response->html(
|
||||
$this->template->layout('user/profile', array(
|
||||
'board_selector' => $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()),
|
||||
'title' => $user['name'] ?: $user['username'],
|
||||
'user' => $user,
|
||||
)
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a form to create a new user
|
||||
*
|
||||
|
@ -70,7 +92,7 @@ class User extends Base
|
|||
'timezones' => $this->config->getTimezones(true),
|
||||
'languages' => $this->config->getLanguages(true),
|
||||
'roles' => $this->role->getApplicationRoles(),
|
||||
'board_selector' => $this->projectUserRole->getProjectsByUser($this->userSession->getId()),
|
||||
'board_selector' => $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()),
|
||||
'projects' => $this->project->getList(),
|
||||
'errors' => $errors,
|
||||
'values' => $values + array('role' => Role::APP_USER),
|
||||
|
@ -86,7 +108,7 @@ class User extends Base
|
|||
public function save()
|
||||
{
|
||||
$values = $this->request->getValues();
|
||||
list($valid, $errors) = $this->user->validateCreation($values);
|
||||
list($valid, $errors) = $this->userValidator->validateCreation($values);
|
||||
|
||||
if ($valid) {
|
||||
$project_id = empty($values['project_id']) ? 0 : $values['project_id'];
|
||||
|
@ -150,6 +172,20 @@ class User extends Base
|
|||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display last password reset
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function passwordReset()
|
||||
{
|
||||
$user = $this->getUser();
|
||||
$this->response->html($this->layout('user/password_reset', array(
|
||||
'tokens' => $this->passwordReset->getAll($user['id']),
|
||||
'user' => $user,
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display last connections
|
||||
*
|
||||
|
@ -293,7 +329,7 @@ class User extends Base
|
|||
|
||||
if ($this->request->isPost()) {
|
||||
$values = $this->request->getValues();
|
||||
list($valid, $errors) = $this->user->validatePasswordModification($values);
|
||||
list($valid, $errors) = $this->userValidator->validatePasswordModification($values);
|
||||
|
||||
if ($valid) {
|
||||
if ($this->user->update($values)) {
|
||||
|
@ -335,7 +371,7 @@ class User extends Base
|
|||
}
|
||||
}
|
||||
|
||||
list($valid, $errors) = $this->user->validateModification($values);
|
||||
list($valid, $errors) = $this->userValidator->validateModification($values);
|
||||
|
||||
if ($valid) {
|
||||
if ($this->user->update($values)) {
|
||||
|
@ -373,7 +409,7 @@ class User extends Base
|
|||
|
||||
if ($this->request->isPost()) {
|
||||
$values = $this->request->getValues() + array('disable_login_form' => 0, 'is_ldap_user' => 0);
|
||||
list($valid, $errors) = $this->user->validateModification($values);
|
||||
list($valid, $errors) = $this->userValidator->validateModification($values);
|
||||
|
||||
if ($valid) {
|
||||
if ($this->user->update($values)) {
|
||||
|
|
|
@ -21,4 +21,17 @@ class UserHelper extends Base
|
|||
$users = $this->userFilterAutoCompleteFormatter->create($search)->filterByUsernameOrByName()->format();
|
||||
$this->response->json($users);
|
||||
}
|
||||
|
||||
/**
|
||||
* User mention autocompletion (Ajax)
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function mention()
|
||||
{
|
||||
$project_id = $this->request->getStringParam('project_id');
|
||||
$query = $this->request->getStringParam('q');
|
||||
$users = $this->projectPermission->findUsernames($project_id, $query);
|
||||
$this->response->json($users);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,57 +39,4 @@ class Webhook extends Base
|
|||
|
||||
$this->response->text('FAILED');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle Github webhooks
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function github()
|
||||
{
|
||||
$this->checkWebhookToken();
|
||||
|
||||
$this->githubWebhook->setProjectId($this->request->getIntegerParam('project_id'));
|
||||
|
||||
$result = $this->githubWebhook->parsePayload(
|
||||
$this->request->getHeader('X-Github-Event'),
|
||||
$this->request->getJson()
|
||||
);
|
||||
|
||||
echo $result ? 'PARSED' : 'IGNORED';
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle Gitlab webhooks
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function gitlab()
|
||||
{
|
||||
$this->checkWebhookToken();
|
||||
|
||||
$this->gitlabWebhook->setProjectId($this->request->getIntegerParam('project_id'));
|
||||
$result = $this->gitlabWebhook->parsePayload($this->request->getJson());
|
||||
|
||||
echo $result ? 'PARSED' : 'IGNORED';
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle Bitbucket webhooks
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function bitbucket()
|
||||
{
|
||||
$this->checkWebhookToken();
|
||||
|
||||
$this->bitbucketWebhook->setProjectId($this->request->getIntegerParam('project_id'));
|
||||
|
||||
$result = $this->bitbucketWebhook->parsePayload(
|
||||
$this->request->getHeader('X-Event-Key'),
|
||||
$this->request->getJson()
|
||||
);
|
||||
|
||||
echo $result ? 'PARSED' : 'IGNORED';
|
||||
}
|
||||
}
|
||||
|
|
142
sources/app/Core/Action/ActionManager.php
Normal file
142
sources/app/Core/Action/ActionManager.php
Normal file
|
@ -0,0 +1,142 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Core\Action;
|
||||
|
||||
use RuntimeException;
|
||||
use Kanboard\Core\Base;
|
||||
use Kanboard\Action\Base as ActionBase;
|
||||
|
||||
/**
|
||||
* Action Manager
|
||||
*
|
||||
* @package action
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class ActionManager extends Base
|
||||
{
|
||||
/**
|
||||
* List of automatic actions
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private $actions = array();
|
||||
|
||||
/**
|
||||
* Register a new automatic action
|
||||
*
|
||||
* @access public
|
||||
* @param ActionBase $action
|
||||
* @return ActionManager
|
||||
*/
|
||||
public function register(ActionBase $action)
|
||||
{
|
||||
$this->actions[$action->getName()] = $action;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get automatic action instance
|
||||
*
|
||||
* @access public
|
||||
* @param string $name Absolute class name with namespace
|
||||
* @return ActionBase
|
||||
*/
|
||||
public function getAction($name)
|
||||
{
|
||||
if (isset($this->actions[$name])) {
|
||||
return $this->actions[$name];
|
||||
}
|
||||
|
||||
throw new RuntimeException('Automatic Action Not Found: '.$name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available automatic actions
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getAvailableActions()
|
||||
{
|
||||
$actions = array();
|
||||
|
||||
foreach ($this->actions as $action) {
|
||||
if (count($action->getEvents()) > 0) {
|
||||
$actions[$action->getName()] = $action->getDescription();
|
||||
}
|
||||
}
|
||||
|
||||
asort($actions);
|
||||
|
||||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all available action parameters
|
||||
*
|
||||
* @access public
|
||||
* @param array $actions
|
||||
* @return array
|
||||
*/
|
||||
public function getAvailableParameters(array $actions)
|
||||
{
|
||||
$params = array();
|
||||
|
||||
foreach ($actions as $action) {
|
||||
$currentAction = $this->getAction($action['action_name']);
|
||||
$params[$currentAction->getName()] = $currentAction->getActionRequiredParameters();
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of compatible events for a given action
|
||||
*
|
||||
* @access public
|
||||
* @param string $name
|
||||
* @return array
|
||||
*/
|
||||
public function getCompatibleEvents($name)
|
||||
{
|
||||
$events = array();
|
||||
$actionEvents = $this->getAction($name)->getEvents();
|
||||
|
||||
foreach ($this->eventManager->getAll() as $event => $description) {
|
||||
if (in_array($event, $actionEvents)) {
|
||||
$events[$event] = $description;
|
||||
}
|
||||
}
|
||||
|
||||
return $events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind automatic actions to events
|
||||
*
|
||||
* @access public
|
||||
* @return ActionManager
|
||||
*/
|
||||
public function attachEvents()
|
||||
{
|
||||
if ($this->userSession->isLogged()) {
|
||||
$actions = $this->action->getAllByUser($this->userSession->getId());
|
||||
} else {
|
||||
$actions = $this->action->getAll();
|
||||
}
|
||||
|
||||
foreach ($actions as $action) {
|
||||
$listener = clone $this->getAction($action['action_name']);
|
||||
$listener->setProjectId($action['project_id']);
|
||||
|
||||
foreach ($action['params'] as $param_name => $param_value) {
|
||||
$listener->setParam($param_name, $param_value);
|
||||
}
|
||||
|
||||
$this->dispatcher->addListener($action['event_name'], array($listener, 'execute'));
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
|
@ -10,7 +10,14 @@ use Pimple\Container;
|
|||
* @package core
|
||||
* @author Frederic Guillot
|
||||
*
|
||||
* @property \Kanboard\Analytic\TaskDistributionAnalytic $taskDistributionAnalytic
|
||||
* @property \Kanboard\Analytic\UserDistributionAnalytic $userDistributionAnalytic
|
||||
* @property \Kanboard\Analytic\EstimatedTimeComparisonAnalytic $estimatedTimeComparisonAnalytic
|
||||
* @property \Kanboard\Analytic\AverageLeadCycleTimeAnalytic $averageLeadCycleTimeAnalytic
|
||||
* @property \Kanboard\Analytic\AverageTimeSpentColumnAnalytic $averageTimeSpentColumnAnalytic
|
||||
* @property \Kanboard\Core\Action\ActionManager $actionManager
|
||||
* @property \Kanboard\Core\Cache\MemoryCache $memoryCache
|
||||
* @property \Kanboard\Core\Event\EventManager $eventManager
|
||||
* @property \Kanboard\Core\Group\GroupManager $groupManager
|
||||
* @property \Kanboard\Core\Http\Client $httpClient
|
||||
* @property \Kanboard\Core\Http\OAuth2 $oauth
|
||||
|
@ -18,6 +25,7 @@ use Pimple\Container;
|
|||
* @property \Kanboard\Core\Http\Request $request
|
||||
* @property \Kanboard\Core\Http\Response $response
|
||||
* @property \Kanboard\Core\Http\Router $router
|
||||
* @property \Kanboard\Core\Http\Route $route
|
||||
* @property \Kanboard\Core\Mail\Client $emailClient
|
||||
* @property \Kanboard\Core\ObjectStorage\ObjectStorageInterface $objectStorage
|
||||
* @property \Kanboard\Core\Plugin\Hook $hook
|
||||
|
@ -42,9 +50,6 @@ use Pimple\Container;
|
|||
* @property \Kanboard\Core\Lexer $lexer
|
||||
* @property \Kanboard\Core\Paginator $paginator
|
||||
* @property \Kanboard\Core\Template $template
|
||||
* @property \Kanboard\Integration\BitbucketWebhook $bitbucketWebhook
|
||||
* @property \Kanboard\Integration\GithubWebhook $githubWebhook
|
||||
* @property \Kanboard\Integration\GitlabWebhook $gitlabWebhook
|
||||
* @property \Kanboard\Formatter\ProjectGanttFormatter $projectGanttFormatter
|
||||
* @property \Kanboard\Formatter\TaskFilterGanttFormatter $taskFilterGanttFormatter
|
||||
* @property \Kanboard\Formatter\TaskFilterAutoCompleteFormatter $taskFilterAutoCompleteFormatter
|
||||
|
@ -53,7 +58,7 @@ use Pimple\Container;
|
|||
* @property \Kanboard\Formatter\UserFilterAutoCompleteFormatter $userFilterAutoCompleteFormatter
|
||||
* @property \Kanboard\Formatter\GroupAutoCompleteFormatter $groupAutoCompleteFormatter
|
||||
* @property \Kanboard\Model\Action $action
|
||||
* @property \Kanboard\Model\Authentication $authentication
|
||||
* @property \Kanboard\Model\ActionParameter $actionParameter
|
||||
* @property \Kanboard\Model\Board $board
|
||||
* @property \Kanboard\Model\Category $category
|
||||
* @property \Kanboard\Model\Color $color
|
||||
|
@ -68,6 +73,7 @@ use Pimple\Container;
|
|||
* @property \Kanboard\Model\Link $link
|
||||
* @property \Kanboard\Model\Notification $notification
|
||||
* @property \Kanboard\Model\OverdueNotification $overdueNotification
|
||||
* @property \Kanboard\Model\PasswordReset $passwordReset
|
||||
* @property \Kanboard\Model\Project $project
|
||||
* @property \Kanboard\Model\ProjectActivity $projectActivity
|
||||
* @property \Kanboard\Model\ProjectAnalytic $projectAnalytic
|
||||
|
@ -77,6 +83,7 @@ use Pimple\Container;
|
|||
* @property \Kanboard\Model\ProjectMetadata $projectMetadata
|
||||
* @property \Kanboard\Model\ProjectPermission $projectPermission
|
||||
* @property \Kanboard\Model\ProjectUserRole $projectUserRole
|
||||
* @property \Kanboard\Model\projectUserRoleFilter $projectUserRoleFilter
|
||||
* @property \Kanboard\Model\ProjectGroupRole $projectGroupRole
|
||||
* @property \Kanboard\Model\ProjectNotification $projectNotification
|
||||
* @property \Kanboard\Model\ProjectNotificationType $projectNotificationType
|
||||
|
@ -98,20 +105,36 @@ use Pimple\Container;
|
|||
* @property \Kanboard\Model\TaskPermission $taskPermission
|
||||
* @property \Kanboard\Model\TaskPosition $taskPosition
|
||||
* @property \Kanboard\Model\TaskStatus $taskStatus
|
||||
* @property \Kanboard\Model\TaskValidator $taskValidator
|
||||
* @property \Kanboard\Model\TaskMetadata $taskMetadata
|
||||
* @property \Kanboard\Model\Transition $transition
|
||||
* @property \Kanboard\Model\User $user
|
||||
* @property \Kanboard\Model\UserImport $userImport
|
||||
* @property \Kanboard\Model\UserLocking $userLocking
|
||||
* @property \Kanboard\Model\UserMention $userMention
|
||||
* @property \Kanboard\Model\UserNotification $userNotification
|
||||
* @property \Kanboard\Model\UserNotificationType $userNotificationType
|
||||
* @property \Kanboard\Model\UserNotificationFilter $userNotificationFilter
|
||||
* @property \Kanboard\Model\UserUnreadNotification $userUnreadNotification
|
||||
* @property \Kanboard\Model\UserMetadata $userMetadata
|
||||
* @property \Kanboard\Model\Webhook $webhook
|
||||
* @property \Kanboard\Validator\ActionValidator $actionValidator
|
||||
* @property \Kanboard\Validator\AuthValidator $authValidator
|
||||
* @property \Kanboard\Validator\ColumnValidator $columnValidator
|
||||
* @property \Kanboard\Validator\CategoryValidator $categoryValidator
|
||||
* @property \Kanboard\Validator\ColumnValidator $columnValidator
|
||||
* @property \Kanboard\Validator\CommentValidator $commentValidator
|
||||
* @property \Kanboard\Validator\CurrencyValidator $currencyValidator
|
||||
* @property \Kanboard\Validator\CustomFilterValidator $customFilterValidator
|
||||
* @property \Kanboard\Validator\GroupValidator $groupValidator
|
||||
* @property \Kanboard\Validator\LinkValidator $linkValidator
|
||||
* @property \Kanboard\Validator\PasswordResetValidator $passwordResetValidator
|
||||
* @property \Kanboard\Validator\ProjectValidator $projectValidator
|
||||
* @property \Kanboard\Validator\SubtaskValidator $subtaskValidator
|
||||
* @property \Kanboard\Validator\SwimlaneValidator $swimlaneValidator
|
||||
* @property \Kanboard\Validator\TaskLinkValidator $taskLinkValidator
|
||||
* @property \Kanboard\Validator\TaskValidator $taskValidator
|
||||
* @property \Kanboard\Validator\UserValidator $userValidator
|
||||
* @property \Psr\Log\LoggerInterface $logger
|
||||
* @property \League\HTMLToMarkdown\HtmlConverter $htmlConverter
|
||||
* @property \PicoDb\Database $db
|
||||
* @property \Symfony\Component\EventDispatcher\EventDispatcher $dispatcher
|
||||
*/
|
||||
|
|
62
sources/app/Core/Event/EventManager.php
Normal file
62
sources/app/Core/Event/EventManager.php
Normal file
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Core\Event;
|
||||
|
||||
use Kanboard\Model\Task;
|
||||
use Kanboard\Model\TaskLink;
|
||||
|
||||
/**
|
||||
* Event Manager
|
||||
*
|
||||
* @package event
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class EventManager
|
||||
{
|
||||
/**
|
||||
* Extended events
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private $events = array();
|
||||
|
||||
/**
|
||||
* Add new event
|
||||
*
|
||||
* @access public
|
||||
* @param string $event
|
||||
* @param string $description
|
||||
* @return EventManager
|
||||
*/
|
||||
public function register($event, $description)
|
||||
{
|
||||
$this->events[$event] = $description;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of events and description that can be used from the user interface
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getAll()
|
||||
{
|
||||
$events = array(
|
||||
TaskLink::EVENT_CREATE_UPDATE => t('Task link creation or modification'),
|
||||
Task::EVENT_MOVE_COLUMN => t('Move a task to another column'),
|
||||
Task::EVENT_UPDATE => t('Task modification'),
|
||||
Task::EVENT_CREATE => t('Task creation'),
|
||||
Task::EVENT_OPEN => t('Reopen a task'),
|
||||
Task::EVENT_CLOSE => t('Closing a task'),
|
||||
Task::EVENT_CREATE_UPDATE => t('Task creation or modification'),
|
||||
Task::EVENT_ASSIGNEE_CHANGE => t('Task assignee change'),
|
||||
);
|
||||
|
||||
$events = array_merge($events, $this->events);
|
||||
asort($events);
|
||||
|
||||
return $events;
|
||||
}
|
||||
}
|
|
@ -41,6 +41,16 @@ class Request extends Base
|
|||
$this->cookies = empty($cookies) ? $_COOKIE : $cookies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set GET parameters
|
||||
*
|
||||
* @param array $params
|
||||
*/
|
||||
public function setParams(array $params)
|
||||
{
|
||||
$this->get = array_merge($this->get, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get query string string parameter
|
||||
*
|
||||
|
@ -146,6 +156,29 @@ class Request extends Base
|
|||
return isset($this->files[$name]['tmp_name']) ? $this->files[$name]['tmp_name'] : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get info of an uploaded file
|
||||
*
|
||||
* @access public
|
||||
* @param string $name Form file name
|
||||
* @return array
|
||||
*/
|
||||
public function getFileInfo($name)
|
||||
{
|
||||
return isset($this->files[$name]) ? $this->files[$name] : array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return HTTP method
|
||||
*
|
||||
* @access public
|
||||
* @return bool
|
||||
*/
|
||||
public function getMethod()
|
||||
{
|
||||
return $this->getServerVariable('REQUEST_METHOD');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the HTTP request is sent with the POST method
|
||||
*
|
||||
|
@ -154,7 +187,7 @@ class Request extends Base
|
|||
*/
|
||||
public function isPost()
|
||||
{
|
||||
return isset($this->server['REQUEST_METHOD']) && $this->server['REQUEST_METHOD'] === 'POST';
|
||||
return $this->getServerVariable('REQUEST_METHOD') === 'POST';
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -203,7 +236,7 @@ class Request extends Base
|
|||
public function getHeader($name)
|
||||
{
|
||||
$name = 'HTTP_'.str_replace('-', '_', strtoupper($name));
|
||||
return isset($this->server[$name]) ? $this->server[$name] : '';
|
||||
return $this->getServerVariable($name);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -214,18 +247,18 @@ class Request extends Base
|
|||
*/
|
||||
public function getRemoteUser()
|
||||
{
|
||||
return isset($this->server[REVERSE_PROXY_USER_HEADER]) ? $this->server[REVERSE_PROXY_USER_HEADER] : '';
|
||||
return $this->getServerVariable(REVERSE_PROXY_USER_HEADER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns current request's query string, useful for redirecting
|
||||
* Returns query string
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getQueryString()
|
||||
{
|
||||
return isset($this->server['QUERY_STRING']) ? $this->server['QUERY_STRING'] : '';
|
||||
return $this->getServerVariable('QUERY_STRING');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -236,7 +269,7 @@ class Request extends Base
|
|||
*/
|
||||
public function getUri()
|
||||
{
|
||||
return isset($this->server['REQUEST_URI']) ? $this->server['REQUEST_URI'] : '';
|
||||
return $this->getServerVariable('REQUEST_URI');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -269,7 +302,7 @@ class Request extends Base
|
|||
);
|
||||
|
||||
foreach ($keys as $key) {
|
||||
if (! empty($this->server[$key])) {
|
||||
if ($this->getServerVariable($key) !== '') {
|
||||
foreach (explode(',', $this->server[$key]) as $ipAddress) {
|
||||
return trim($ipAddress);
|
||||
}
|
||||
|
@ -287,6 +320,18 @@ class Request extends Base
|
|||
*/
|
||||
public function getStartTime()
|
||||
{
|
||||
return isset($this->server['REQUEST_TIME_FLOAT']) ? $this->server['REQUEST_TIME_FLOAT'] : 0;
|
||||
return $this->getServerVariable('REQUEST_TIME_FLOAT') ?: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get server variable
|
||||
*
|
||||
* @access public
|
||||
* @param string $variable
|
||||
* @return string
|
||||
*/
|
||||
public function getServerVariable($variable)
|
||||
{
|
||||
return isset($this->server[$variable]) ? $this->server[$variable] : '';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,7 +60,7 @@ class Response extends Base
|
|||
public function status($status_code)
|
||||
{
|
||||
header('Status: '.$status_code);
|
||||
header($_SERVER['SERVER_PROTOCOL'].' '.$status_code);
|
||||
header($this->request->getServerVariable('SERVER_PROTOCOL').' '.$status_code);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -71,7 +71,7 @@ class Response extends Base
|
|||
*/
|
||||
public function redirect($url)
|
||||
{
|
||||
if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest') {
|
||||
if ($this->request->getServerVariable('HTTP_X_REQUESTED_WITH') === 'XMLHttpRequest') {
|
||||
header('X-Ajax-Redirect: '.$url);
|
||||
} else {
|
||||
header('Location: '.$url);
|
||||
|
@ -220,7 +220,6 @@ class Response extends Base
|
|||
*/
|
||||
public function csp(array $policies = array())
|
||||
{
|
||||
$policies['default-src'] = "'self'";
|
||||
$values = '';
|
||||
|
||||
foreach ($policies as $policy => $acl) {
|
||||
|
|
187
sources/app/Core/Http/Route.php
Normal file
187
sources/app/Core/Http/Route.php
Normal file
|
@ -0,0 +1,187 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Core\Http;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
|
||||
/**
|
||||
* Route Handler
|
||||
*
|
||||
* @package http
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Route extends Base
|
||||
{
|
||||
/**
|
||||
* Flag that enable the routing table
|
||||
*
|
||||
* @access private
|
||||
* @var boolean
|
||||
*/
|
||||
private $activated = false;
|
||||
|
||||
/**
|
||||
* Store routes for path lookup
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private $paths = array();
|
||||
|
||||
/**
|
||||
* Store routes for url lookup
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private $urls = array();
|
||||
|
||||
/**
|
||||
* Enable routing table
|
||||
*
|
||||
* @access public
|
||||
* @return Route
|
||||
*/
|
||||
public function enable()
|
||||
{
|
||||
$this->activated = true;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add route
|
||||
*
|
||||
* @access public
|
||||
* @param string $path
|
||||
* @param string $controller
|
||||
* @param string $action
|
||||
* @param string $plugin
|
||||
* @return Route
|
||||
*/
|
||||
public function addRoute($path, $controller, $action, $plugin = '')
|
||||
{
|
||||
if ($this->activated) {
|
||||
$path = ltrim($path, '/');
|
||||
$items = explode('/', $path);
|
||||
$params = $this->findParams($items);
|
||||
|
||||
$this->paths[] = array(
|
||||
'items' => $items,
|
||||
'count' => count($items),
|
||||
'controller' => $controller,
|
||||
'action' => $action,
|
||||
'plugin' => $plugin,
|
||||
);
|
||||
|
||||
$this->urls[$plugin][$controller][$action][] = array(
|
||||
'path' => $path,
|
||||
'params' => $params,
|
||||
'count' => count($params),
|
||||
);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a route according to the given path
|
||||
*
|
||||
* @access public
|
||||
* @param string $path
|
||||
* @return array
|
||||
*/
|
||||
public function findRoute($path)
|
||||
{
|
||||
$items = explode('/', ltrim($path, '/'));
|
||||
$count = count($items);
|
||||
|
||||
foreach ($this->paths as $route) {
|
||||
if ($count === $route['count']) {
|
||||
$params = array();
|
||||
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
if ($route['items'][$i]{0} === ':') {
|
||||
$params[substr($route['items'][$i], 1)] = $items[$i];
|
||||
} elseif ($route['items'][$i] !== $items[$i]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($i === $count) {
|
||||
$this->request->setParams($params);
|
||||
return array(
|
||||
'controller' => $route['controller'],
|
||||
'action' => $route['action'],
|
||||
'plugin' => $route['plugin'],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array(
|
||||
'controller' => 'app',
|
||||
'action' => 'index',
|
||||
'plugin' => '',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find route url
|
||||
*
|
||||
* @access public
|
||||
* @param string $controller
|
||||
* @param string $action
|
||||
* @param array $params
|
||||
* @param string $plugin
|
||||
* @return string
|
||||
*/
|
||||
public function findUrl($controller, $action, array $params = array(), $plugin = '')
|
||||
{
|
||||
if ($plugin === '' && isset($params['plugin'])) {
|
||||
$plugin = $params['plugin'];
|
||||
unset($params['plugin']);
|
||||
}
|
||||
|
||||
if (! isset($this->urls[$plugin][$controller][$action])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
foreach ($this->urls[$plugin][$controller][$action] as $route) {
|
||||
if (array_diff_key($params, $route['params']) === array()) {
|
||||
$url = $route['path'];
|
||||
$i = 0;
|
||||
|
||||
foreach ($params as $variable => $value) {
|
||||
$url = str_replace(':'.$variable, $value, $url);
|
||||
$i++;
|
||||
}
|
||||
|
||||
if ($i === $route['count']) {
|
||||
return $url;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Find url params
|
||||
*
|
||||
* @access public
|
||||
* @param array $items
|
||||
* @return array
|
||||
*/
|
||||
public function findParams(array $items)
|
||||
{
|
||||
$params = array();
|
||||
|
||||
foreach ($items as $item) {
|
||||
if ($item !== '' && $item{0} === ':') {
|
||||
$params[substr($item, 1)] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
}
|
|
@ -6,13 +6,21 @@ use RuntimeException;
|
|||
use Kanboard\Core\Base;
|
||||
|
||||
/**
|
||||
* Router class
|
||||
* Route Dispatcher
|
||||
*
|
||||
* @package http
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Router extends Base
|
||||
{
|
||||
/**
|
||||
* Plugin name
|
||||
*
|
||||
* @access private
|
||||
* @var string
|
||||
*/
|
||||
private $plugin = '';
|
||||
|
||||
/**
|
||||
* Controller
|
||||
*
|
||||
|
@ -30,30 +38,14 @@ class Router extends Base
|
|||
private $action = '';
|
||||
|
||||
/**
|
||||
* Store routes for path lookup
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private $paths = array();
|
||||
|
||||
/**
|
||||
* Store routes for url lookup
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private $urls = array();
|
||||
|
||||
/**
|
||||
* Get action
|
||||
* Get plugin name
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getAction()
|
||||
public function getPlugin()
|
||||
{
|
||||
return $this->action;
|
||||
return $this->plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -67,23 +59,32 @@ class Router extends Base
|
|||
return $this->controller;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get action
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getAction()
|
||||
{
|
||||
return $this->action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path to compare patterns
|
||||
*
|
||||
* @access public
|
||||
* @param string $uri
|
||||
* @param string $query_string
|
||||
* @return string
|
||||
*/
|
||||
public function getPath($uri, $query_string = '')
|
||||
public function getPath()
|
||||
{
|
||||
$path = substr($uri, strlen($this->helper->url->dir()));
|
||||
$path = substr($this->request->getUri(), strlen($this->helper->url->dir()));
|
||||
|
||||
if (! empty($query_string)) {
|
||||
$path = substr($path, 0, - strlen($query_string) - 1);
|
||||
if ($this->request->getQueryString() !== '') {
|
||||
$path = substr($path, 0, - strlen($this->request->getQueryString()) - 1);
|
||||
}
|
||||
|
||||
if (! empty($path) && $path{0} === '/') {
|
||||
if ($path !== '' && $path{0} === '/') {
|
||||
$path = substr($path, 1);
|
||||
}
|
||||
|
||||
|
@ -91,140 +92,78 @@ class Router extends Base
|
|||
}
|
||||
|
||||
/**
|
||||
* Add route
|
||||
* Find controller/action from the route table or from get arguments
|
||||
*
|
||||
* @access public
|
||||
* @param string $path
|
||||
* @param string $controller
|
||||
* @param string $action
|
||||
* @param array $params
|
||||
*/
|
||||
public function addRoute($path, $controller, $action, array $params = array())
|
||||
public function dispatch()
|
||||
{
|
||||
$pattern = explode('/', $path);
|
||||
$controller = $this->request->getStringParam('controller');
|
||||
$action = $this->request->getStringParam('action');
|
||||
$plugin = $this->request->getStringParam('plugin');
|
||||
|
||||
$this->paths[] = array(
|
||||
'pattern' => $pattern,
|
||||
'count' => count($pattern),
|
||||
'controller' => $controller,
|
||||
'action' => $action,
|
||||
);
|
||||
|
||||
$this->urls[$controller][$action][] = array(
|
||||
'path' => $path,
|
||||
'params' => array_flip($params),
|
||||
'count' => count($params),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a route according to the given path
|
||||
*
|
||||
* @access public
|
||||
* @param string $path
|
||||
* @return array
|
||||
*/
|
||||
public function findRoute($path)
|
||||
{
|
||||
$parts = explode('/', $path);
|
||||
$count = count($parts);
|
||||
|
||||
foreach ($this->paths as $route) {
|
||||
if ($count === $route['count']) {
|
||||
$params = array();
|
||||
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
if ($route['pattern'][$i]{0} === ':') {
|
||||
$params[substr($route['pattern'][$i], 1)] = $parts[$i];
|
||||
} elseif ($route['pattern'][$i] !== $parts[$i]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($i === $count) {
|
||||
$_GET = array_merge($_GET, $params);
|
||||
return array($route['controller'], $route['action']);
|
||||
}
|
||||
}
|
||||
if ($controller === '') {
|
||||
$route = $this->route->findRoute($this->getPath());
|
||||
$controller = $route['controller'];
|
||||
$action = $route['action'];
|
||||
$plugin = $route['plugin'];
|
||||
}
|
||||
|
||||
return array('app', 'index');
|
||||
}
|
||||
$this->controller = ucfirst($this->sanitize($controller, 'app'));
|
||||
$this->action = $this->sanitize($action, 'index');
|
||||
$this->plugin = ucfirst($this->sanitize($plugin));
|
||||
|
||||
/**
|
||||
* Find route url
|
||||
*
|
||||
* @access public
|
||||
* @param string $controller
|
||||
* @param string $action
|
||||
* @param array $params
|
||||
* @return string
|
||||
*/
|
||||
public function findUrl($controller, $action, array $params = array())
|
||||
{
|
||||
if (! isset($this->urls[$controller][$action])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
foreach ($this->urls[$controller][$action] as $pattern) {
|
||||
if (array_diff_key($params, $pattern['params']) === array()) {
|
||||
$url = $pattern['path'];
|
||||
$i = 0;
|
||||
|
||||
foreach ($params as $variable => $value) {
|
||||
$url = str_replace(':'.$variable, $value, $url);
|
||||
$i++;
|
||||
}
|
||||
|
||||
if ($i === $pattern['count']) {
|
||||
return $url;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
return $this->executeAction();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check controller and action parameter
|
||||
*
|
||||
* @access public
|
||||
* @param string $value Controller or action name
|
||||
* @param string $default_value Default value if validation fail
|
||||
* @param string $value
|
||||
* @param string $default
|
||||
* @return string
|
||||
*/
|
||||
public function sanitize($value, $default_value)
|
||||
public function sanitize($value, $default = '')
|
||||
{
|
||||
return ! preg_match('/^[a-zA-Z_0-9]+$/', $value) ? $default_value : $value;
|
||||
return preg_match('/^[a-zA-Z_0-9]+$/', $value) ? $value : $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find controller/action from the route table or from get arguments
|
||||
* Execute controller action
|
||||
*
|
||||
* @access public
|
||||
* @param string $uri
|
||||
* @param string $query_string
|
||||
* @access private
|
||||
*/
|
||||
public function dispatch($uri, $query_string = '')
|
||||
private function executeAction()
|
||||
{
|
||||
if (! empty($_GET['controller']) && ! empty($_GET['action'])) {
|
||||
$this->controller = $this->sanitize($_GET['controller'], 'app');
|
||||
$this->action = $this->sanitize($_GET['action'], 'index');
|
||||
$plugin = ! empty($_GET['plugin']) ? $this->sanitize($_GET['plugin'], '') : '';
|
||||
} else {
|
||||
list($this->controller, $this->action) = $this->findRoute($this->getPath($uri, $query_string)); // TODO: add plugin for routes
|
||||
$plugin = '';
|
||||
$class = $this->getControllerClassName();
|
||||
|
||||
if (! class_exists($class)) {
|
||||
throw new RuntimeException('Controller not found');
|
||||
}
|
||||
|
||||
$class = '\Kanboard\\';
|
||||
$class .= empty($plugin) ? 'Controller\\'.ucfirst($this->controller) : 'Plugin\\'.ucfirst($plugin).'\Controller\\'.ucfirst($this->controller);
|
||||
|
||||
if (! class_exists($class) || ! method_exists($class, $this->action)) {
|
||||
throw new RuntimeException('Controller or method not found for the given url!');
|
||||
if (! method_exists($class, $this->action)) {
|
||||
throw new RuntimeException('Action not implemented');
|
||||
}
|
||||
|
||||
$instance = new $class($this->container);
|
||||
$instance->beforeAction($this->controller, $this->action);
|
||||
$instance->beforeAction();
|
||||
$instance->{$this->action}();
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get controller class name
|
||||
*
|
||||
* @access private
|
||||
* @return string
|
||||
*/
|
||||
private function getControllerClassName()
|
||||
{
|
||||
if ($this->plugin !== '') {
|
||||
return '\Kanboard\Plugin\\'.$this->plugin.'\Controller\\'.$this->controller;
|
||||
}
|
||||
|
||||
return '\Kanboard\Controller\\'.$this->controller;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,10 +15,10 @@ class Client
|
|||
/**
|
||||
* LDAP resource
|
||||
*
|
||||
* @access private
|
||||
* @access protected
|
||||
* @var resource
|
||||
*/
|
||||
private $ldap;
|
||||
protected $ldap;
|
||||
|
||||
/**
|
||||
* Establish LDAP connection
|
||||
|
|
|
@ -13,10 +13,10 @@ class Entries
|
|||
/**
|
||||
* LDAP entries
|
||||
*
|
||||
* @access private
|
||||
* @access protected
|
||||
* @var array
|
||||
*/
|
||||
private $entries = array();
|
||||
protected $entries = array();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
|
|
|
@ -13,10 +13,10 @@ class Entry
|
|||
/**
|
||||
* LDAP entry
|
||||
*
|
||||
* @access private
|
||||
* @access protected
|
||||
* @var array
|
||||
*/
|
||||
private $entry = array();
|
||||
protected $entry = array();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
|
|
|
@ -16,10 +16,10 @@ class Group
|
|||
/**
|
||||
* Query
|
||||
*
|
||||
* @access private
|
||||
* @access protected
|
||||
* @var Query
|
||||
*/
|
||||
private $query;
|
||||
protected $query;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
|
@ -43,7 +43,8 @@ class Group
|
|||
*/
|
||||
public static function getGroups(Client $client, $query)
|
||||
{
|
||||
$self = new self(new Query($client));
|
||||
$className = get_called_class();
|
||||
$self = new $className(new Query($client));
|
||||
return $self->find($query);
|
||||
}
|
||||
|
||||
|
|
|
@ -13,18 +13,18 @@ class Query
|
|||
/**
|
||||
* LDAP client
|
||||
*
|
||||
* @access private
|
||||
* @access protected
|
||||
* @var Client
|
||||
*/
|
||||
private $client = null;
|
||||
protected $client = null;
|
||||
|
||||
/**
|
||||
* Query result
|
||||
*
|
||||
* @access private
|
||||
* @access protected
|
||||
* @var array
|
||||
*/
|
||||
private $entries = array();
|
||||
protected $entries = array();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
|
|
|
@ -17,10 +17,10 @@ class User
|
|||
/**
|
||||
* Query
|
||||
*
|
||||
* @access private
|
||||
* @access protected
|
||||
* @var Query
|
||||
*/
|
||||
private $query;
|
||||
protected $query;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
|
@ -40,11 +40,12 @@ class User
|
|||
* @access public
|
||||
* @param Client $client
|
||||
* @param string $username
|
||||
* @return array
|
||||
* @return LdapUserProvider
|
||||
*/
|
||||
public static function getUser(Client $client, $username)
|
||||
{
|
||||
$self = new self(new Query($client));
|
||||
$className = get_called_class();
|
||||
$self = new $className(new Query($client));
|
||||
return $self->find($self->getLdapUserPattern($username));
|
||||
}
|
||||
|
||||
|
|
|
@ -45,19 +45,21 @@ class Client extends Base
|
|||
*/
|
||||
public function send($email, $name, $subject, $html)
|
||||
{
|
||||
$this->container['logger']->debug('Sending email to '.$email.' ('.MAIL_TRANSPORT.')');
|
||||
if (! empty($email)) {
|
||||
$this->logger->debug('Sending email to '.$email.' ('.MAIL_TRANSPORT.')');
|
||||
|
||||
$start_time = microtime(true);
|
||||
$author = 'Kanboard';
|
||||
$start_time = microtime(true);
|
||||
$author = 'Kanboard';
|
||||
|
||||
if ($this->userSession->isLogged()) {
|
||||
$author = e('%s via Kanboard', $this->helper->user->getFullname());
|
||||
}
|
||||
if ($this->userSession->isLogged()) {
|
||||
$author = e('%s via Kanboard', $this->helper->user->getFullname());
|
||||
}
|
||||
|
||||
$this->getTransport(MAIL_TRANSPORT)->sendEmail($email, $name, $subject, $html, $author);
|
||||
$this->getTransport(MAIL_TRANSPORT)->sendEmail($email, $name, $subject, $html, $author);
|
||||
|
||||
if (DEBUG) {
|
||||
$this->logger->debug('Email sent in '.round(microtime(true) - $start_time, 6).' seconds');
|
||||
if (DEBUG) {
|
||||
$this->logger->debug('Email sent in '.round(microtime(true) - $start_time, 6).' seconds');
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
namespace Kanboard\Core;
|
||||
|
||||
use Parsedown;
|
||||
use Kanboard\Helper\Url;
|
||||
use Pimple\Container;
|
||||
|
||||
/**
|
||||
* Specific Markdown rules for Kanboard
|
||||
|
@ -14,22 +14,51 @@ use Kanboard\Helper\Url;
|
|||
*/
|
||||
class Markdown extends Parsedown
|
||||
{
|
||||
private $link;
|
||||
private $helper;
|
||||
/**
|
||||
* Link params for tasks
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private $link = array();
|
||||
|
||||
public function __construct($link, Url $helper)
|
||||
/**
|
||||
* Container
|
||||
*
|
||||
* @access private
|
||||
* @var Container
|
||||
*/
|
||||
private $container;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @access public
|
||||
* @param Container $container
|
||||
* @param array $link
|
||||
*/
|
||||
public function __construct(Container $container, array $link)
|
||||
{
|
||||
$this->link = $link;
|
||||
$this->helper = $helper;
|
||||
$this->container = $container;
|
||||
$this->InlineTypes['#'][] = 'TaskLink';
|
||||
$this->inlineMarkerList .= '#';
|
||||
$this->InlineTypes['@'][] = 'UserLink';
|
||||
$this->inlineMarkerList .= '#@';
|
||||
}
|
||||
|
||||
protected function inlineTaskLink($Excerpt)
|
||||
/**
|
||||
* Handle Task Links
|
||||
*
|
||||
* Replace "#123" by a link to the task
|
||||
*
|
||||
* @access public
|
||||
* @param array $Excerpt
|
||||
* @return array
|
||||
*/
|
||||
protected function inlineTaskLink(array $Excerpt)
|
||||
{
|
||||
// Replace task #123 by a link to the task
|
||||
if (! empty($this->link) && preg_match('!#(\d+)!i', $Excerpt['text'], $matches)) {
|
||||
$url = $this->helper->href(
|
||||
$url = $this->container['helper']->url->href(
|
||||
$this->link['controller'],
|
||||
$this->link['action'],
|
||||
$this->link['params'] + array('task_id' => $matches[1])
|
||||
|
@ -40,7 +69,38 @@ class Markdown extends Parsedown
|
|||
'element' => array(
|
||||
'name' => 'a',
|
||||
'text' => $matches[0],
|
||||
'attributes' => array('href' => $url)));
|
||||
'attributes' => array('href' => $url)
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle User Mentions
|
||||
*
|
||||
* Replace "@username" by a link to the user
|
||||
*
|
||||
* @access public
|
||||
* @param array $Excerpt
|
||||
* @return array
|
||||
*/
|
||||
protected function inlineUserLink(array $Excerpt)
|
||||
{
|
||||
if (preg_match('/^@([^\s]+)/', $Excerpt['text'], $matches)) {
|
||||
$user_id = $this->container['user']->getIdByUsername($matches[1]);
|
||||
|
||||
if (! empty($user_id)) {
|
||||
$url = $this->container['helper']->url->href('user', 'profile', array('user_id' => $user_id));
|
||||
|
||||
return array(
|
||||
'extent' => strlen($matches[0]),
|
||||
'element' => array(
|
||||
'name' => 'a',
|
||||
'text' => $matches[0],
|
||||
'attributes' => array('href' => $url, 'class' => 'user-mention-link'),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -86,6 +86,26 @@ class AccessMap
|
|||
return $roles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the highest role from a list
|
||||
*
|
||||
* @access public
|
||||
* @param array $roles
|
||||
* @return string
|
||||
*/
|
||||
public function getHighestRole(array $roles)
|
||||
{
|
||||
$rank = array();
|
||||
|
||||
foreach ($roles as $role) {
|
||||
$rank[$role] = count($this->getRoleHierarchy($role));
|
||||
}
|
||||
|
||||
asort($rank);
|
||||
|
||||
return key($rank);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add new access rules
|
||||
*
|
||||
|
|
|
@ -10,6 +10,13 @@ namespace Kanboard\Core\Security;
|
|||
*/
|
||||
interface PostAuthenticationProviderInterface extends AuthenticationProviderInterface
|
||||
{
|
||||
/**
|
||||
* Called only one time before to prompt the user for pin code
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function beforeCode();
|
||||
|
||||
/**
|
||||
* Set user pin-code
|
||||
*
|
||||
|
@ -18,6 +25,14 @@ interface PostAuthenticationProviderInterface extends AuthenticationProviderInte
|
|||
*/
|
||||
public function setCode($code);
|
||||
|
||||
/**
|
||||
* Generate secret if necessary
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function generateSecret();
|
||||
|
||||
/**
|
||||
* Set secret token (fetched from user profile)
|
||||
*
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue