1
0
Fork 0
mirror of https://github.com/YunoHost-Apps/kanboard_ynh.git synced 2024-09-03 19:36:17 +02:00

Download sources (except dependencies) during installation and upgrade to 1.0.35

This commit is contained in:
Jimmy Monin 2016-12-27 18:50:51 +01:00
parent 26e016a46b
commit 5eadb538b0
1821 changed files with 43224 additions and 120048 deletions

View file

@ -33,7 +33,7 @@ From command line:
Infos Infos
----- -----
Kanboard v1.0.31 Kanboard v1.0.35
Yunohost forum thread: <https://forum.yunohost.org/t/kanboard-package/78> Yunohost forum thread: <https://forum.yunohost.org/t/kanboard-package/78>

View file

@ -1,5 +1,8 @@
<?php <?php
// Data folder (must be writeable by the web server user)
define('DATA_DIR', 'data');
// Enable/Disable debug // Enable/Disable debug
define('DEBUG', false); define('DEBUG', false);
@ -7,13 +10,25 @@ define('DEBUG', false);
define('LOG_DRIVER', ''); define('LOG_DRIVER', '');
// Log filename if the log driver is "file" // Log filename if the log driver is "file"
define('LOG_FILE', __DIR__.DIRECTORY_SEPARATOR.'data'.DIRECTORY_SEPARATOR.'debug.log'); define('LOG_FILE', DATA_DIR.DIRECTORY_SEPARATOR.'debug.log');
// Plugins directory // Plugins directory
define('PLUGINS_DIR', 'plugins'); define('PLUGINS_DIR', 'plugins');
// Folder for uploaded files // Plugins directory URL
define('FILES_DIR', 'data'.DIRECTORY_SEPARATOR.'files'); define('PLUGIN_API_URL', 'https://kanboard.net/plugins.json');
// Enable/Disable plugin installer
define('PLUGIN_INSTALLER', true);
// Available cache drivers are "file" and "memory"
define('CACHE_DRIVER', 'memory');
// Cache folder to use if cache driver is "file" (must be writeable by the web server user)
define('CACHE_DIR', DATA_DIR.DIRECTORY_SEPARATOR.'cache');
// Folder for uploaded files (must be writeable by the web server user)
define('FILES_DIR', DATA_DIR.DIRECTORY_SEPARATOR.'files');
// E-mail address for the "From" header (notifications) // E-mail address for the "From" header (notifications)
define('MAIL_FROM', 'yuno_email'); define('MAIL_FROM', 'yuno_email');
@ -31,6 +46,11 @@ define('MAIL_SMTP_ENCRYPTION', null); // Valid values are "null", "ssl" or "tls"
// Sendmail command to use when the transport is "sendmail" // Sendmail command to use when the transport is "sendmail"
define('MAIL_SENDMAIL_COMMAND', '/usr/sbin/sendmail -bs'); define('MAIL_SENDMAIL_COMMAND', '/usr/sbin/sendmail -bs');
// Run automatically database migrations
// If set to false, you will have to run manually the SQL migrations from the CLI during the next Kanboard upgrade
// Do not run the migrations from multiple processes at the same time (example: web page + background worker)
define('DB_RUN_MIGRATIONS', true);
// Database driver: sqlite, mysql or postgres (sqlite by default) // Database driver: sqlite, mysql or postgres (sqlite by default)
define('DB_DRIVER', 'mysql'); define('DB_DRIVER', 'mysql');
@ -183,7 +203,7 @@ define('ENABLE_URL_REWRITE', false);
// Hide login form, useful if all your users use Google/Github/ReverseProxy authentication // Hide login form, useful if all your users use Google/Github/ReverseProxy authentication
define('HIDE_LOGIN_FORM', true); define('HIDE_LOGIN_FORM', true);
// Disabling logout (for external SSO authentication) // Disabling logout (useful for external SSO authentication)
define('DISABLE_LOGOUT', true); define('DISABLE_LOGOUT', true);
// Enable captcha after 3 authentication failure // Enable captcha after 3 authentication failure
@ -204,3 +224,6 @@ define('HTTP_PROXY_HOSTNAME', '');
define('HTTP_PROXY_PORT', '3128'); define('HTTP_PROXY_PORT', '3128');
define('HTTP_PROXY_USERNAME', ''); define('HTTP_PROXY_USERNAME', '');
define('HTTP_PROXY_PASSWORD', ''); define('HTTP_PROXY_PASSWORD', '');
// TOTP (2FA) issuer name
define('TOTP_ISSUER', 'Kanboard');

View file

@ -42,9 +42,17 @@ ynh_app_setting_set $app mysqlpwd $dbpass
ynh_app_setting_set $app adminusername $admin ynh_app_setting_set $app adminusername $admin
ynh_app_setting_set $app is_public $is_public ynh_app_setting_set $app is_public $is_public
# Download sources
sudo wget -q https://github.com/kanboard/kanboard/archive/v1.0.35.zip -O kanboard.zip
# Uncompress
sudo unzip -qq kanboard.zip -d ..
# Copy sources # Copy sources
sudo mkdir -p $DESTDIR sudo mkdir -p $DESTDIR
sudo cp -a ../sources/. $DESTDIR sudo cp -a ../kanboard*/. $DESTDIR
# Copy dependencies
sudo cp -a ../sources/. ${DESTDIR}
# Copy and edit config.php # Copy and edit config.php
sudo cp ../conf/config.php ${DESTDIR} sudo cp ../conf/config.php ${DESTDIR}

View file

@ -37,6 +37,16 @@ sudo rm -rf /var/lib/php5/session/*
sudo mv ${DESTDIR} ${DESTDIR}.old sudo mv ${DESTDIR} ${DESTDIR}.old
sudo mkdir -p ${DESTDIR} sudo mkdir -p ${DESTDIR}
# Download sources
sudo wget -q https://github.com/kanboard/kanboard/archive/v1.0.35.zip -O kanboard.zip
# Uncompress
sudo unzip -qq kanboard.zip -d ..
# Copy sources
sudo cp -a ../kanboard*/. ${DESTDIR}
# Copy dependencies
sudo cp -a ../sources/. ${DESTDIR} sudo cp -a ../sources/. ${DESTDIR}
# restore data # restore data
sudo cp -a ${DESTDIR}.old/data ${DESTDIR} sudo cp -a ${DESTDIR}.old/data ${DESTDIR}

View file

@ -1,26 +0,0 @@
<IfModule mod_rewrite.c>
Options -MultiViews
SetEnv HTTP_MOD_REWRITE On
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [QSA,L]
</IfModule>
<FilesMatch "(kanboard|config.php|config.default.php)">
<IfModule mod_version.c>
<IfVersion >= 2.3>
Require all denied
</IfVersion>
<IfVersion < 2.3>
Order allow,deny
Deny from all
</IfVersion>
</IfModule>
<IfModule !mod_version.c>
Order allow,deny
Deny from all
</IfModule>
</FilesMatch>

View file

@ -1,637 +0,0 @@
Version 1.0.31
--------------
New features:
* Added tags: global and specific by project
* Added application and project roles validation for API procedure calls
* Added new API call: "getProjectByIdentifier"
* Added new API calls for external task links, project attachments and subtask time tracking
Improvements:
* Use PHP 7 for the Docker image
* Preserve role for existing users when using ReverseProxy authentication
* Handle priority for task and project duplication
* Expose task reference field to the user interface
* Improve ICal export
* Added argument owner_id and identifier to project API calls
* Rewrite integration tests to run with Docker containers
* Use the same task form layout everywhere
* Removed some tasks dropdown menus that are now available with task edit form
* Make embedded documentation readable in multiple languages (if a translation is available)
* Added acceptance tests (browser tests)
Bug fixes:
* Fixed broken CSV exports
* Fixed identical background color for LetterAvatar on 32bits platforms (Hash greater than PHP_MAX_INT)
* Fixed lexer issue with non word characters
* Flush memory cache in worker to get latest config values
* Fixed empty title for web notification with only one overdue task
* Take default swimlane into consideration for SwimlaneModel::getFirstActiveSwimlane()
* Fixed "due today" highlighting
Version 1.0.30
--------------
Improvements:
* Show tasks that are due today in a different color
Bug fixes:
* Fixed wrong controller for search in dashboard
* Fixed plural form in alert message
* Fixed CSS cosmetic issue with popover and tooltips
Version 1.0.29
--------------
New features:
* Manage plugin from the user interface and from the command line
* Added support for background workers
* Added the possibility to convert a subtask to a task
* Added menu entry to add tasks from all project views
* Add tasks in bulk from the board
* Add dropdown for projects
* Added config parameter to allow self-signed certificates for the HTTP client
Improvements:
* Display local date format in date picker
* Configure email settings with the user interface in addition to config file
* Upgrade Docker image to Alpine Linux 3.4
* Move task import to a separate section
* Mark web notification as read when clicking on it
* Support strtotime strings for date search
* Reset failed login counter and unlock user when changing password
* Task do not open anymore in a new window on the Gantt chart
* Do not display task progress for tasks with no start/end date
* Use Gulp and Bower to manage assets
* Controller and Middleware refactoring
* Replace jQuery mobile detection by the library isMobile
Bug fixes:
* Fixed user date format parsing for dates that can be valid in multiple formats
* Do not sync user role if LDAP groups are not configured
* Fixed issue with unicode handling for letter based avatars and user initials
* Do not send notifications to disabled users
* Fixed wrong redirect when removing a task from the task view page
Breaking changes:
* Webhook to create tasks have been removed, use the API instead
* All controllers have been renamed, people who are not using URL rewriting will see different URLs
* All models have been renamed, plugin maintainers will have to update their plugins
Version 1.0.28
--------------
New features:
* Added automated action to change task color based on the priority
* Added support for LDAP Posix Groups (OpenLDAP with memberUid or groupOfNames)
* Added support for LDAP user photo attribute (Avatar image)
* Added support for language LDAP attribute
* Added support for Mysql SSL connection
* Search in activity stream
* Search in comments
* Search by task creator
* Added command line utility to reset user password and to disable 2FA
Improvements:
* Improve Avatar upload form
* User roles are now synced with LDAP at each login
* Improve web page title on the task view
* Unify task drop-down menu between different views
* Improve LDAP user group membership synchronization
* Category and user filters do not append anymore in search field
* Added more template hooks
* Added tasks search with the API
* Added priority field to API procedures
* Added API procedure "getMemberGroups"
* Added parameters for overdue tasks notifications: group by projects and send only to managers
* Allow people to install Kanboard outside of the DocumentRoot
* Allow plugins to be loaded from another folder
* Filter/Lexer/QueryBuilder refactoring
Bug fixes:
* Allow a project owner to manage his own public project
* Fixed PHP warning when removing a user with no Avatar image
* Fixed improper Markdown escaping for some tooltips
* Closing all tasks by column, also update closed tasks
* Fixed wrong task link generation within Markdown text
* Fixed wrong URL on comment toggle link for sorting
* Fixed form submission with Meta+Enter keyboard shortcut
* Removed PHP notices in comment suppression view
Version 1.0.27
--------------
New features:
* Added Markdown editor
* Added user avatars with pluggable system
- Default is a letter based avatar
- Gravatar
- Avatar Image upload
* Added Korean translation
Improvements:
* Added more logging for LDAP client
* Improve schema migration process
* Improve notification configuration form
* Handle state in OAuth2 client
* Allow to use the original template in overridden templates
* Unification of the project header
* Refactoring of Javascript code
* Improve comments design
* Improve task summary sections
* Put back the action sidebar in task view
* Added support for multiple placeholders for LDAP_USER_FILTER
* Added local file link provider
* Show configuration in settings page
* Added "?" to display list of keyboard shortcuts
* Added new keyboard shortcuts for task view
* Always display project name and task title in task views
* Improve automatic action creation
* Move notifications to the bottom of the screen
* Added the possibility to import automatic actions from another project
* Added Ajax loading icon for submit buttons
* Added support for HTTP header "X-Forwarded-Proto: https"
Bug fixes:
* Fix bad unique constraints in Mysql table user_has_notifications
* Force integer type for aggregated metrics (Burndown chart concat values instead of summing)
* Fixes cycle time calculation when the start date is defined in the future
* Access allowed to any tasks from the shared public board by changing the URL parameters
* Fix invalid user filter for API procedure createLdapUser()
* Ambiguous column name with very old version of Sqlite
Version 1.0.26
--------------
Breaking changes:
* API procedures:
- "moveColumnUp" and "moveColumnDown" are replaced by "changeColumnPosition"
- "moveSwimlaneUp" and "moveSwimlaneDown" are replaced by "changeSwimlanePosition"
New features:
* Add drag and drop to change subtasks, swimlanes and columns positions
* Add file drag and drop and asynchronous upload
* Enable/Disable users
* Add setting option to disable private projects
* Add new config option to disable logout
Improvements:
* Use inline popup to create new columns
* Improve filter box design
* Improve image thumbnails and files table
* Add confirmation inline popup to remove custom filter
* Increase client_max_body_size value for Nginx
* Split Board model into multiple classes
* Improve logging for the Docker image
Bug fixes:
* Fix PHP notices during creation of first project and in subtasks table
* Fix filter dropdown not accessible when there are too many items
* Fix regression: unable to change project in "task move/duplicate to another project"
Version 1.0.25
--------------
Breaking changes:
* Core functionalities moved to external plugins:
- Google Auth: https://github.com/kanboard/plugin-google-auth
- Github Auth: https://github.com/kanboard/plugin-github-auth
- Gitlab Auth: https://github.com/kanboard/plugin-gitlab-auth
New features:
* When creating a new project, have the possibility to select another project to duplicate
* Add a "Me" button to assignee form element
* Add external links for tasks with plugin api
* Add project owner (Directly Responsible Individual)
* Add configurable task priority
* Add Greek translation
* Add automatic actions to close tasks with no activity
* Add automatic actions to send an email when there is no activity on a task
* Regroup all daily background tasks in one command: "cronjob"
* Add task dropdown menu on listing pages
Improvements:
* New Dockerfile based on Alpine Linux and Nginx/PHP-FPM
* The date time format can be chosen in application settings
* Export only open tasks in iCal feed
* Remove time form on task summary page and move that to task edit form
* Replace box shadow by a larger border width when a task is recently modified
* Do not refresh the whole page when changing subtask status
* Add dropdown menu with inline popup for all task actions
* Change sidebar style
* Change task summary layout
* Use inline popup for subtasks, categories, swimlanes, actions and columns
* Move homepage menus to the user dropdown
* Have a new task assigned to the creator by default instead of "no assignee"
* Show progress for task links in board tooltips
* Simplify code to handle ajax popover and redirects
* Simplify layout and templates generation
* Move task form elements to Task helper
Bug fixes:
* Category label is broken on the board if there's a url in the description
* Fix pagination on task time tracking page
Version 1.0.24
--------------
New features:
* Forgot Password
* Add drop-down menu on each board column title to close all tasks
* Add Malay language
* Add new API procedures for groups, roles, project permissions and to move/duplicate tasks to another project
Improvements:
* Avoid to send XHR request when a task has not moved after a drag and drop
* Set maximum dropzone height when the individual column scrolling is disabled
* Always show the search box in board selector
* Replace logout link by a drop-down menu
* Handle notification for group members attached to a project
* Return the highest role for a project when a user is member of multiple groups
* Show in user interface the saving state of the task
* Add drop-down menu for subtasks, categories, swimlanes, columns, custom filters, task links and groups
* Add new template hooks
* Application settings are not cached anymore in the session
* Do not check board status during task move
* Move validators to a separate namespace
* Improve and write unit tests for reports
* Reduce the number of SQL queries for project daily column stats
* Remove event subscriber to update date_moved field
* Make sure that some event subscribers are not executed multiple times
* Show rendering time of individual templates when debug mode is enabled
* Make sure that no events are fired if nothing has been modified in the task
* Make dashboard section title clickable
* Add unit tests for LastLogin
Bug fixes:
* Automatic action listeners were using the same instance
* Fix wrong link for category in task footer
* Unable to set currency rate with Postgres database
* Avoid automatic actions that change the color to fire subsequent events
* Unable to unassign a task from the API
* Revert back previous optimizations of TaskPosition (incompatibility with some environment)
Version 1.0.23
--------------
Breaking changes:
* Plugin API changes for Automatic Actions
* Automatic Action to close a task doesn't have the column parameter anymore (use the action "Close a task in a specific column")
* Action name stored in the database is now the absolute class name
* Core functionalities moved to external plugins:
- Github Webhook: https://github.com/kanboard/plugin-github-webhook
- Gitlab Webhook: https://github.com/kanboard/plugin-gitlab-webhook
- Bitbucket Webhook: https://github.com/kanboard/plugin-bitbucket-webhook
New features:
* Added support of user mentions (@username)
* Added report to compare working hours between open and closed tasks
* Added the possibility to define custom routes from plugins
* Added new method to remove metadata
Improvements:
* Improve Two-Factor activation and plugin API
* Improving performance during task position change (SQL queries are 3 times faster than before)
* Do not show window scrollbars when individual column scrolling is enabled
* Automatic Actions code improvements and unit tests
* Increase action name column length in actions table
Bug fixes:
* Fix compatibility issue with FreeBSD for session.hash_function parameter
* Fix wrong constant name that causes a PHP error in project management section
* Fix pagination in group members listing
* Avoid PHP error when enabling LDAP group provider with PHP < 5.5
Version 1.0.22
--------------
Breaking changes:
* LDAP configuration parameters changes (See documentation)
* SQL table changes:
- "users" table: added new column "role" and removed columns "is_admin" and "is_project_admin"
- "project_has_users" table: replaced column "is_owner" with column "role"
- Sqlite does not support alter table, old columns still there but unused
* API procedure changes:
- createUser
- createLdapUser
- updateUser
- updateTask
* Event removed: "session.bootstrap", use "app.boostrap" instead
New features:
* Add pluggable authentication and authorization system (complete rewrite)
* Add groups (teams/organization)
* Add LDAP groups synchronization
* Add project group permissions
* Add new project role Viewer
* Add generic LDAP client library
* Add search query attribute for task link
* Add the possibility to define API token in config file
* Add capability to reopen Gitlab issues
* Try to load config.php from /data if not available
Version 1.0.21
--------------
Breaking changes:
* Projects with duplicate names are now allowed:
- For Postgres and Mysql the unique constraint is removed by database migration
- However Sqlite does not support alter table, only new databases will have the unique constraint removed
New features:
* New automatic action: Assign a category based on a link
* Added Bosnian translation
Improvements:
* 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
* Add new hooks for plugins
* Remove workaround for "INSERT ON DUPLICATE KEY UPDATE..."
Internal code refactoring:
* Rewrite of session management
* Move some classes to a new namespace Kanboard\Core\Http
Bug fixes:
* Loading cs_CZ locale display the wrong language in datetime picker
* Datepicker is closed unexpectedly on blur event
* Fix bug in daily project summary CSV export
* Fix PHP error when adding a new user with email notification enabled
* Add missing template for activity stream to show event "file.create"
* Fix wrong value for PLUGINS_DIR in config.default.php
* Make CSV export compatible with PHP 5.3
* Avoid Safari to append .html at the end of downloaded files
Version 1.0.20
--------------
Breaking changes:
* Add namespace Kanboard (update your plugins)
* Move Mailgun, Sendgrid, Postmark, Slack, Hipchat and Jabber to plugins
* ReverseProxy authentication check for each request that the username match the user session
New features:
* Add CSV import for users and tasks
* Add Task, User and Project metadata for plugin creators
Improvements:
* Allow to change comments sorting
* Add the possibility to append or not custom filters
* Make mail transports pluggable
* Do not show scroll-bars when a column is collapsed on Windows systems
* Regenerate thumbnails if missing
Bug fixes:
* People should not see any tasks during a search when they are not associated to a project
* Avoid disabling the default swimlane during renaming when there is no other activated swimlane
Version 1.0.19
--------------
New features:
* Added web notifications
* Added LDAP group sync
* Added swimlane description
* New plugin system (alpha)
* Added Bahasa Indonesia translation
* Added API procedures: getMyOverdueTasks, getOverdueTasksByProject and GetMyProjects
* Added user API access for procedure getProjectActivity()
* Added config parameter to enable/disable Syslog
* Added custom filters
* Added http client proxy support
Core functionalities moved to plugins:
* Budget planning: https://github.com/kanboard/plugin-budget
* SubtaskForecast: https://github.com/kanboard/plugin-subtask-forecast
* Timetable: https://github.com/kanboard/plugin-timetable
Improvements:
* When duplicating a task redirect to the new task
* Include more shortcut links into the view "My projects"
* Duplicate a project with tasks will copy the new tasks in the same columns
* 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 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 drop-down menu for closed tasks
* Show assignee on card only when someone is assigned (hide nobody text)
* Highlight selected item in drop-down menus
* Gantt chart: change bar color according to task progress
* 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
* Show localized documentation if available
* Add event subtask.delete
* Add abstract storage layer
* Add abstract cache layer
* Add Docker tag for stable version
Others:
* 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 prevents Gitlab OAuth link to be displayed
* Fix Markdown preview links focus
* 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)
* Fix Postgres issue "Cardinality violation" when there is multiple "is_milestone_of" links
* Fix issue with due date greater than year 2038
Version 1.0.18
--------------
New features:
* Include documentation in the application
* Add Gitlab authentication
* Add users and categories filters on the board
* Add hide/show columns
* Add Gantt chart for projects and tasks
* Add new role "Project Administrator"
* 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
* Add config parameter to disable/enable RememberMe authentication
* 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 for task title when using an integer only input
* Add Portuguese (European) translation
* Add Norwegian translation
Improvements:
* Add handle to move tasks on touch devices
* Improve file attachments tooltip on the board
* Adjust automatically the height of the placeholder during drag and drop
* Show all tasks when using no search criteria
* Add column vertical scrolling
* Set dynamically column height based on viewport size
* Enable support for Github Enterprise when using Github Authentication
* Update iCalendar library to display organizer name
* 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
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 reset when using subtask timer
* Fix screenshot feature with Firefox 40
* Fix bug when uploading files with Cyrilic characters
Version 1.0.17
--------------
New features:
* Added url rewrite and new routes
* Added new search engine with advanced syntax
* Added global search section
* Added search form on the dashboard
* Added new dashboard layout
* Added new layout for board/calendar/list views
* Added filters helper for search forms
* 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 to debug log
* Added form to create remote user
* Added edit form for user authentication
* Added config option to hide login form
* Display OAuth2 urls on integration page
* Added keyboard shortcuts to switch between board/calendar/list view
* Added keyboard shortcut to focus on the search box
* Added Slack channel override
* 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 the start date automatically
* Added datetime picker for start date
Improvements:
* Updated documentation
* 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 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
* Do not rewrite remember me cookie for each request
* Set the assignee as organizer for ical events
* Increase date range for ics export
* Reduce spacing on cards
* Move board collapse/expand mode to server side to avoid board flickering
* Use ajax requests for board collapse/expand
* Do not set anchor for the default swimlane on the link back to board
* Replace timeserie axis to category axis for charts
* Hide task age in compact mode
* Improve quick-add subtasks form
* Reduce the size of the filter box for smaller screen
* Added icon to hide/show sidebar
* Update GitLab logo
* Improve Dockerfile
Translations:
* Added Czech translation
* Updated Spanish translation
* Updated German Translation
Bug fixes:
* 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
Code refactoring:
* Split task controller into smaller classes
* Remove method Category::getBoardCategories()
* Rewrite movePosition() to improve performances
* Refactoring of Github and Google authentication
Breaking changes:
* New OAuth url for Google and Github authentication
API:
* Add urls in api response for tasks and projects
Other:
* Added automated Docker build
* Remove edit recurrence from the task menu on the board
* Switch to MIT License instead of AGPLv3
Version 1.0.0 to 1.0.16
-----------------------
* See commit history and website news

View file

@ -1,7 +0,0 @@
<IfVersion >= 2.3>
Require all denied
</IfVersion>
<IfVersion < 2.3>
Order allow,deny
Deny from all
</IfVersion>

View file

@ -1,297 +0,0 @@
<?php
namespace Kanboard\Action;
use Kanboard\Event\GenericEvent;
/**
* Base class for automatic actions
*
* @package action
* @author Frederic Guillot
*/
abstract class Base extends \Kanboard\Core\Base
{
/**
* Extended events
*
* @access private
* @var array
*/
private $compatibleEvents = array();
/**
* Flag for called listener
*
* @access private
* @var boolean
*/
private $called = false;
/**
* Project id
*
* @access private
* @var integer
*/
private $projectId = 0;
/**
* User parameters
*
* @access private
* @var array
*/
private $params = array();
/**
* Get automatic action name
*
* @final
* @access public
* @return string
*/
final public function getName()
{
return '\\'.get_called_class();
}
/**
* Get automatic action description
*
* @abstract
* @access public
* @return string
*/
abstract public function getDescription();
/**
* Execute the action
*
* @abstract
* @access public
* @param array $data Event data dictionary
* @return bool True if the action was executed or false when not executed
*/
abstract public function doAction(array $data);
/**
* Get the required parameter for the action (defined by the user)
*
* @abstract
* @access public
* @return array
*/
abstract public function getActionRequiredParameters();
/**
* Get the required parameter for the event (check if for the event data)
*
* @abstract
* @access public
* @return array
*/
abstract public function getEventRequiredParameters();
/**
* Get the compatible events
*
* @abstract
* @access public
* @return array
*/
abstract public function getCompatibleEvents();
/**
* Check if the event data meet the action condition
*
* @access public
* @param array $data Event data dictionary
* @return bool
*/
abstract public function hasRequiredCondition(array $data);
/**
* Return class information
*
* @access public
* @return string
*/
public function __toString()
{
$params = array();
foreach ($this->params as $key => $value) {
$params[] = $key.'='.var_export($value, true);
}
return $this->getName().'('.implode('|', $params).')';
}
/**
* Set project id
*
* @access public
* @param integer $project_id
* @return Base
*/
public function setProjectId($project_id)
{
$this->projectId = $project_id;
return $this;
}
/**
* Get project id
*
* @access public
* @return integer
*/
public function getProjectId()
{
return $this->projectId;
}
/**
* Set an user defined parameter
*
* @access public
* @param string $name Parameter name
* @param mixed $value Value
* @return Base
*/
public function setParam($name, $value)
{
$this->params[$name] = $value;
return $this;
}
/**
* Get an user defined parameter
*
* @access public
* @param string $name Parameter name
* @param mixed $default Default value
* @return mixed
*/
public function getParam($name, $default = null)
{
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
* @param string $eventName
* @return bool
*/
public function isExecutable(array $data, $eventName)
{
return $this->hasCompatibleEvent($eventName) &&
$this->hasRequiredProject($data) &&
$this->hasRequiredParameters($data) &&
$this->hasRequiredCondition($data);
}
/**
* Check if the event is compatible with the action
*
* @access public
* @param string $eventName
* @return bool
*/
public function hasCompatibleEvent($eventName)
{
return in_array($eventName, $this->getEvents());
}
/**
* Check if the event data has the required project
*
* @access public
* @param array $data Event data dictionary
* @return bool
*/
public function hasRequiredProject(array $data)
{
return isset($data['project_id']) && $data['project_id'] == $this->getProjectId();
}
/**
* Check if the event data has required parameters to execute the action
*
* @access public
* @param array $data Event data dictionary
* @return bool True if all keys are there
*/
public function hasRequiredParameters(array $data)
{
foreach ($this->getEventRequiredParameters() as $parameter) {
if (! isset($data[$parameter])) {
return false;
}
}
return true;
}
/**
* Execute the action
*
* @access public
* @param \Kanboard\Event\GenericEvent $event
* @param string $eventName
* @return bool
*/
public function execute(GenericEvent $event, $eventName)
{
// Avoid infinite loop, a listener instance can be called only one time
if ($this->called) {
return false;
}
$data = $event->getAll();
$executable = $this->isExecutable($data, $eventName);
$executed = false;
if ($executable) {
$this->called = true;
$executed = $this->doAction($data);
}
$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
* @return Base
*/
public function addEvent($event, $description = '')
{
if ($description !== '') {
$this->eventManager->register($event, $description);
}
$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));
}
}

View file

@ -1,87 +0,0 @@
<?php
namespace Kanboard\Action;
/**
* Create automatically a comment from a webhook
*
* @package action
* @author Frederic Guillot
*/
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
*
* @access public
* @return string[]
*/
public function getCompatibleEvents()
{
return array();
}
/**
* Get the required parameter for the action (defined by the user)
*
* @access public
* @return string[]
*/
public function getActionRequiredParameters()
{
return array();
}
/**
* Get the required parameter for the event
*
* @access public
* @return array
*/
public function getEventRequiredParameters()
{
return array(
'task_id',
);
}
/**
* Execute the action (create a new comment)
*
* @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 (bool) $this->commentModel->create(array(
'reference' => isset($data['reference']) ? $data['reference'] : '',
'comment' => $data['comment'],
'task_id' => $data['task_id'],
'user_id' => isset($data['user_id']) && $this->projectPermissionModel->isAssignable($this->getProjectId(), $data['user_id']) ? $data['user_id'] : 0,
));
}
/**
* 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 ! empty($data['comment']);
}
}

View file

@ -1,94 +0,0 @@
<?php
namespace Kanboard\Action;
use Kanboard\Model\TaskModel;
/**
* Add a comment of the triggering event to the task description.
*
* @package action
* @author Oren Ben-Kiki
*/
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
*
* @access public
* @return array
*/
public function getCompatibleEvents()
{
return array(
TaskModel::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 (append to the task description).
*
* @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;
}
$column = $this->columnModel->getById($data['column_id']);
return (bool) $this->commentModel->create(array(
'comment' => t('Moved to column %s', $column['title']),
'task_id' => $data['task_id'],
'user_id' => $this->userSession->getId(),
));
}
/**
* 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');
}
}

View file

@ -1,95 +0,0 @@
<?php
namespace Kanboard\Action;
use Kanboard\Model\TaskModel;
/**
* Set a category automatically according to the color
*
* @package action
* @author Frederic Guillot
*/
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
*
* @access public
* @return array
*/
public function getCompatibleEvents()
{
return array(
TaskModel::EVENT_CREATE_UPDATE,
);
}
/**
* Get the required parameter for the action (defined by the user)
*
* @access public
* @return array
*/
public function getActionRequiredParameters()
{
return array(
'color_id' => t('Color'),
'category_id' => t('Category'),
);
}
/**
* Get the required parameter for the event
*
* @access public
* @return string[]
*/
public function getEventRequiredParameters()
{
return array(
'task_id',
'color_id',
);
}
/**
* Execute the action (change the category)
*
* @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)
{
$values = array(
'id' => $data['task_id'],
'category_id' => $this->getParam('category_id'),
);
return $this->taskModificationModel->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['color_id'] == $this->getParam('color_id');
}
}

View file

@ -1,91 +0,0 @@
<?php
namespace Kanboard\Action;
/**
* Set a category automatically according to a label
*
* @package action
* @author Frederic Guillot
*/
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
*
* @access public
* @return array
*/
public function getCompatibleEvents()
{
return array();
}
/**
* Get the required parameter for the action (defined by the user)
*
* @access public
* @return array
*/
public function getActionRequiredParameters()
{
return array(
'label' => t('Label'),
'category_id' => t('Category'),
);
}
/**
* Get the required parameter for the event
*
* @access public
* @return string[]
*/
public function getEventRequiredParameters()
{
return array(
'task_id',
'label',
);
}
/**
* Execute the action (change the category)
*
* @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)
{
$values = array(
'id' => $data['task_id'],
'category_id' => $this->getParam('category_id'),
);
return $this->taskModificationModel->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['label'] == $this->getParam('label') && empty($data['category_id']);
}
}

View file

@ -1,101 +0,0 @@
<?php
namespace Kanboard\Action;
use Kanboard\Model\TaskLinkModel;
/**
* Set a category automatically according to a task link
*
* @package action
* @author Olivier Maridat
* @author Frederic Guillot
*/
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
*
* @access public
* @return array
*/
public function getCompatibleEvents()
{
return array(
TaskLinkModel::EVENT_CREATE_UPDATE,
);
}
/**
* Get the required parameter for the action (defined by the user)
*
* @access public
* @return array
*/
public function getActionRequiredParameters()
{
return array(
'category_id' => t('Category'),
'link_id' => t('Link type'),
);
}
/**
* Get the required parameter for the event
*
* @access public
* @return string[]
*/
public function getEventRequiredParameters()
{
return array(
'task_id',
'link_id',
);
}
/**
* Execute the action (change the category)
*
* @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)
{
$values = array(
'id' => $data['task_id'],
'category_id' => $this->getParam('category_id'),
);
return $this->taskModificationModel->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)
{
if ($data['link_id'] == $this->getParam('link_id')) {
$task = $this->taskFinderModel->getById($data['task_id']);
return empty($task['category_id']);
}
return false;
}
}

View file

@ -1,95 +0,0 @@
<?php
namespace Kanboard\Action;
use Kanboard\Model\TaskModel;
/**
* Assign a color to a specific category
*
* @package action
* @author Frederic Guillot
*/
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
*
* @access public
* @return array
*/
public function getCompatibleEvents()
{
return array(
TaskModel::EVENT_CREATE_UPDATE,
);
}
/**
* Get the required parameter for the action (defined by the user)
*
* @access public
* @return array
*/
public function getActionRequiredParameters()
{
return array(
'color_id' => t('Color'),
'category_id' => t('Category'),
);
}
/**
* Get the required parameter for the event
*
* @access public
* @return string[]
*/
public function getEventRequiredParameters()
{
return array(
'task_id',
'category_id',
);
}
/**
* Execute the action (change the task color)
*
* @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)
{
$values = array(
'id' => $data['task_id'],
'color_id' => $this->getParam('color_id'),
);
return $this->taskModificationModel->update($values, false);
}
/**
* 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['category_id'] == $this->getParam('category_id');
}
}

View file

@ -1,96 +0,0 @@
<?php
namespace Kanboard\Action;
use Kanboard\Model\TaskModel;
/**
* Assign a color to a task
*
* @package action
* @author Frederic Guillot
*/
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
*
* @access public
* @return array
*/
public function getCompatibleEvents()
{
return array(
TaskModel::EVENT_CREATE,
TaskModel::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'),
'color_id' => t('Color'),
);
}
/**
* Get the required parameter for the event
*
* @access public
* @return string[]
*/
public function getEventRequiredParameters()
{
return array(
'task_id',
'column_id',
);
}
/**
* Execute the action (set the task color)
*
* @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)
{
$values = array(
'id' => $data['task_id'],
'color_id' => $this->getParam('color_id'),
);
return $this->taskModificationModel->update($values, false);
}
/**
* 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');
}
}

View file

@ -1,95 +0,0 @@
<?php
namespace Kanboard\Action;
use Kanboard\Model\TaskLinkModel;
/**
* Assign a color to a specific task link
*
* @package action
* @author Frederic Guillot
*/
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
*
* @access public
* @return array
*/
public function getCompatibleEvents()
{
return array(
TaskLinkModel::EVENT_CREATE_UPDATE,
);
}
/**
* Get the required parameter for the action (defined by the user)
*
* @access public
* @return array
*/
public function getActionRequiredParameters()
{
return array(
'color_id' => t('Color'),
'link_id' => t('Link type'),
);
}
/**
* Get the required parameter for the event
*
* @access public
* @return string[]
*/
public function getEventRequiredParameters()
{
return array(
'task_id',
'link_id',
);
}
/**
* Execute the action (change the task color)
*
* @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)
{
$values = array(
'id' => $data['task_id'],
'color_id' => $this->getParam('color_id'),
);
return $this->taskModificationModel->update($values, false);
}
/**
* 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['link_id'] == $this->getParam('link_id');
}
}

View file

@ -1,95 +0,0 @@
<?php
namespace Kanboard\Action;
use Kanboard\Model\TaskModel;
/**
* Assign a color to a priority
*
* @package action
* @author Frederic Guillot
*/
class TaskAssignColorPriority extends Base
{
/**
* Get automatic action description
*
* @access public
* @return string
*/
public function getDescription()
{
return t('Assign automatically a color based on a priority');
}
/**
* Get the list of compatible events
*
* @access public
* @return array
*/
public function getCompatibleEvents()
{
return array(
TaskModel::EVENT_CREATE_UPDATE,
);
}
/**
* Get the required parameter for the action (defined by the user)
*
* @access public
* @return array
*/
public function getActionRequiredParameters()
{
return array(
'color_id' => t('Color'),
'priority' => t('Priority'),
);
}
/**
* Get the required parameter for the event
*
* @access public
* @return string[]
*/
public function getEventRequiredParameters()
{
return array(
'task_id',
'priority',
);
}
/**
* Execute the action (change the task color)
*
* @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)
{
$values = array(
'id' => $data['task_id'],
'color_id' => $this->getParam('color_id'),
);
return $this->taskModificationModel->update($values, false);
}
/**
* 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['priority'] == $this->getParam('priority');
}
}

View file

@ -1,96 +0,0 @@
<?php
namespace Kanboard\Action;
use Kanboard\Model\TaskModel;
/**
* Assign a color to a specific user
*
* @package action
* @author Frederic Guillot
*/
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
*
* @access public
* @return array
*/
public function getCompatibleEvents()
{
return array(
TaskModel::EVENT_CREATE,
TaskModel::EVENT_ASSIGNEE_CHANGE,
);
}
/**
* Get the required parameter for the action (defined by the user)
*
* @access public
* @return array
*/
public function getActionRequiredParameters()
{
return array(
'color_id' => t('Color'),
'user_id' => t('Assignee'),
);
}
/**
* Get the required parameter for the event
*
* @access public
* @return string[]
*/
public function getEventRequiredParameters()
{
return array(
'task_id',
'owner_id',
);
}
/**
* Execute the action (change the task color)
*
* @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)
{
$values = array(
'id' => $data['task_id'],
'color_id' => $this->getParam('color_id'),
);
return $this->taskModificationModel->update($values, false);
}
/**
* 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['owner_id'] == $this->getParam('user_id');
}
}

View file

@ -1,95 +0,0 @@
<?php
namespace Kanboard\Action;
use Kanboard\Model\TaskModel;
/**
* Assign a task to the logged user
*
* @package action
* @author Frederic Guillot
*/
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
*
* @access public
* @return array
*/
public function getCompatibleEvents()
{
return array(
TaskModel::EVENT_CREATE,
);
}
/**
* Get the required parameter for the action (defined by the user)
*
* @access public
* @return array
*/
public function getActionRequiredParameters()
{
return array();
}
/**
* Get the required parameter for the event
*
* @access public
* @return string[]
*/
public function getEventRequiredParameters()
{
return array(
'task_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->taskModificationModel->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 true;
}
}

View file

@ -1,98 +0,0 @@
<?php
namespace Kanboard\Action;
use Kanboard\Model\TaskModel;
/**
* 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(
TaskModel::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->taskModificationModel->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');
}
}

View file

@ -1,96 +0,0 @@
<?php
namespace Kanboard\Action;
use Kanboard\Model\TaskModel;
/**
* Assign a task to a specific user
*
* @package action
* @author Frederic Guillot
*/
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
*
* @access public
* @return array
*/
public function getCompatibleEvents()
{
return array(
TaskModel::EVENT_CREATE_UPDATE,
TaskModel::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'),
'user_id' => t('Assignee'),
);
}
/**
* Get the required parameter for the event
*
* @access public
* @return string[]
*/
public function getEventRequiredParameters()
{
return array(
'task_id',
'column_id',
);
}
/**
* Execute the action (assign the given user)
*
* @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)
{
$values = array(
'id' => $data['task_id'],
'owner_id' => $this->getParam('user_id'),
);
return $this->taskModificationModel->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');
}
}

View file

@ -1,88 +0,0 @@
<?php
namespace Kanboard\Action;
/**
* Assign a task to someone
*
* @package action
* @author Frederic Guillot
*/
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
*
* @access public
* @return array
*/
public function getCompatibleEvents()
{
return array();
}
/**
* Get the required parameter for the action (defined by the user)
*
* @access public
* @return array
*/
public function getActionRequiredParameters()
{
return array();
}
/**
* Get the required parameter for the event
*
* @access public
* @return string[]
*/
public function getEventRequiredParameters()
{
return array(
'task_id',
'owner_id',
);
}
/**
* Execute the action (assign the given user)
*
* @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)
{
$values = array(
'id' => $data['task_id'],
'owner_id' => $data['owner_id'],
);
return $this->taskModificationModel->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 $this->projectPermissionModel->isAssignable($this->getProjectId(), $data['owner_id']);
}
}

View file

@ -1,80 +0,0 @@
<?php
namespace Kanboard\Action;
/**
* Close automatically a task
*
* @package action
* @author Frederic Guillot
*/
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
*
* @access public
* @return array
*/
public function getCompatibleEvents()
{
return array();
}
/**
* Get the required parameter for the action (defined by the user)
*
* @access public
* @return array
*/
public function getActionRequiredParameters()
{
return array();
}
/**
* Get the required parameter for the event
*
* @access public
* @return string[]
*/
public function getEventRequiredParameters()
{
return array('task_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->taskStatusModel->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 true;
}
}

View file

@ -1,84 +0,0 @@
<?php
namespace Kanboard\Action;
use Kanboard\Model\TaskModel;
/**
* 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(
TaskModel::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->taskStatusModel->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');
}
}

View file

@ -1,95 +0,0 @@
<?php
namespace Kanboard\Action;
use Kanboard\Model\TaskModel;
/**
* Close automatically a task after when inactive
*
* @package action
* @author Frederic Guillot
*/
class TaskCloseNoActivity extends Base
{
/**
* Get automatic action description
*
* @access public
* @return string
*/
public function getDescription()
{
return t('Close a task when there is no activity');
}
/**
* Get the list of compatible events
*
* @access public
* @return array
*/
public function getCompatibleEvents()
{
return array(TaskModel::EVENT_DAILY_CRONJOB);
}
/**
* Get the required parameter for the action (defined by the user)
*
* @access public
* @return array
*/
public function getActionRequiredParameters()
{
return array(
'duration' => t('Duration in days')
);
}
/**
* Get the required parameter for the event
*
* @access public
* @return string[]
*/
public function getEventRequiredParameters()
{
return array('tasks');
}
/**
* 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)
{
$results = array();
$max = $this->getParam('duration') * 86400;
foreach ($data['tasks'] as $task) {
$duration = time() - $task['date_modification'];
if ($duration > $max) {
$results[] = $this->taskStatusModel->close($task['id']);
}
}
return in_array(true, $results, true);
}
/**
* 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 count($data['tasks']) > 0;
}
}

View file

@ -1,88 +0,0 @@
<?php
namespace Kanboard\Action;
/**
* Create automatically a task from a webhook
*
* @package action
* @author Frederic Guillot
*/
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
*
* @access public
* @return array
*/
public function getCompatibleEvents()
{
return array();
}
/**
* Get the required parameter for the action (defined by the user)
*
* @access public
* @return array
*/
public function getActionRequiredParameters()
{
return array();
}
/**
* Get the required parameter for the event
*
* @access public
* @return string[]
*/
public function getEventRequiredParameters()
{
return array(
'reference',
'title',
);
}
/**
* Execute the action (create a new 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 (bool) $this->taskCreationModel->create(array(
'project_id' => $data['project_id'],
'title' => $data['title'],
'reference' => $data['reference'],
'description' => isset($data['description']) ? $data['description'] : '',
));
}
/**
* 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 true;
}
}

View file

@ -1,93 +0,0 @@
<?php
namespace Kanboard\Action;
use Kanboard\Model\TaskModel;
/**
* Duplicate a task to another project
*
* @package action
* @author Frederic Guillot
*/
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
*
* @access public
* @return array
*/
public function getCompatibleEvents()
{
return array(
TaskModel::EVENT_MOVE_COLUMN,
TaskModel::EVENT_CLOSE,
TaskModel::EVENT_CREATE,
);
}
/**
* Get the required parameter for the action (defined by the user)
*
* @access public
* @return array
*/
public function getActionRequiredParameters()
{
return array(
'column_id' => t('Column'),
'project_id' => t('Project'),
);
}
/**
* Get the required parameter for the event
*
* @access public
* @return string[]
*/
public function getEventRequiredParameters()
{
return array(
'task_id',
'column_id',
);
}
/**
* Execute the action (duplicate the task to another project)
*
* @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)
{
$destination_column_id = $this->columnModel->getFirstColumnId($this->getParam('project_id'));
return (bool) $this->taskProjectDuplicationModel->duplicateToProject($data['task_id'], $this->getParam('project_id'), null, $destination_column_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') && $data['project_id'] != $this->getParam('project_id');
}
}

View file

@ -1,107 +0,0 @@
<?php
namespace Kanboard\Action;
use Kanboard\Model\TaskModel;
/**
* Email a task to someone
*
* @package action
* @author Frederic Guillot
*/
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
*
* @access public
* @return array
*/
public function getCompatibleEvents()
{
return array(
TaskModel::EVENT_MOVE_COLUMN,
TaskModel::EVENT_CLOSE,
);
}
/**
* Get the required parameter for the action (defined by the user)
*
* @access public
* @return array
*/
public function getActionRequiredParameters()
{
return array(
'column_id' => t('Column'),
'user_id' => t('User that will receive the email'),
'subject' => t('Email subject'),
);
}
/**
* Get the required parameter for the event
*
* @access public
* @return string[]
*/
public function getEventRequiredParameters()
{
return array(
'task_id',
'column_id',
);
}
/**
* Execute the action (move the task to another column)
*
* @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)
{
$user = $this->userModel->getById($this->getParam('user_id'));
if (! empty($user['email'])) {
$task = $this->taskFinderModel->getDetails($data['task_id']);
$this->emailClient->send(
$user['email'],
$user['name'] ?: $user['username'],
$this->getParam('subject'),
$this->template->render('notification/task_create', array('task' => $task, 'application_url' => $this->configModel->get('application_url')))
);
return true;
}
return false;
}
/**
* 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');
}
}

View file

@ -1,124 +0,0 @@
<?php
namespace Kanboard\Action;
use Kanboard\Model\TaskModel;
/**
* Email a task with no activity
*
* @package action
* @author Frederic Guillot
*/
class TaskEmailNoActivity extends Base
{
/**
* Get automatic action description
*
* @access public
* @return string
*/
public function getDescription()
{
return t('Send email when there is no activity on a task');
}
/**
* Get the list of compatible events
*
* @access public
* @return array
*/
public function getCompatibleEvents()
{
return array(
TaskModel::EVENT_DAILY_CRONJOB,
);
}
/**
* Get the required parameter for the action (defined by the user)
*
* @access public
* @return array
*/
public function getActionRequiredParameters()
{
return array(
'user_id' => t('User that will receive the email'),
'subject' => t('Email subject'),
'duration' => t('Duration in days'),
);
}
/**
* Get the required parameter for the event
*
* @access public
* @return string[]
*/
public function getEventRequiredParameters()
{
return array('tasks');
}
/**
* 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 count($data['tasks']) > 0;
}
/**
* Execute the action (move the task to another column)
*
* @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)
{
$results = array();
$max = $this->getParam('duration') * 86400;
$user = $this->userModel->getById($this->getParam('user_id'));
if (! empty($user['email'])) {
foreach ($data['tasks'] as $task) {
$duration = time() - $task['date_modification'];
if ($duration > $max) {
$results[] = $this->sendEmail($task['id'], $user);
}
}
}
return in_array(true, $results, true);
}
/**
* Send email
*
* @access private
* @param integer $task_id
* @param array $user
* @return boolean
*/
private function sendEmail($task_id, array $user)
{
$task = $this->taskFinderModel->getDetails($task_id);
$this->emailClient->send(
$user['email'],
$user['name'] ?: $user['username'],
$this->getParam('subject'),
$this->template->render('notification/task_create', array('task' => $task, 'application_url' => $this->configModel->get('application_url')))
);
return true;
}
}

View file

@ -1,92 +0,0 @@
<?php
namespace Kanboard\Action;
use Kanboard\Model\TaskModel;
/**
* Move a task to another project
*
* @package action
* @author Frederic Guillot
*/
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
*
* @access public
* @return array
*/
public function getCompatibleEvents()
{
return array(
TaskModel::EVENT_MOVE_COLUMN,
TaskModel::EVENT_CLOSE,
);
}
/**
* Get the required parameter for the action (defined by the user)
*
* @access public
* @return array
*/
public function getActionRequiredParameters()
{
return array(
'column_id' => t('Column'),
'project_id' => t('Project'),
);
}
/**
* Get the required parameter for the event
*
* @access public
* @return string[]
*/
public function getEventRequiredParameters()
{
return array(
'task_id',
'column_id',
'project_id',
);
}
/**
* Execute the action (move the task to another project)
*
* @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->taskProjectMoveModel->moveToProject($data['task_id'], $this->getParam('project_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') && $data['project_id'] != $this->getParam('project_id');
}
}

View file

@ -1,101 +0,0 @@
<?php
namespace Kanboard\Action;
use Kanboard\Model\TaskModel;
/**
* Move a task to another column when an assignee is set
*
* @package action
* @author Francois Ferrand
*/
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
*
* @access public
* @return array
*/
public function getCompatibleEvents()
{
return array(
TaskModel::EVENT_ASSIGNEE_CHANGE,
TaskModel::EVENT_UPDATE,
);
}
/**
* Get the required parameter for the action (defined by the user)
*
* @access public
* @return array
*/
public function getActionRequiredParameters()
{
return array(
'src_column_id' => t('Source column'),
'dest_column_id' => t('Destination column')
);
}
/**
* Get the required parameter for the event
*
* @access public
* @return string[]
*/
public function getEventRequiredParameters()
{
return array(
'task_id',
'column_id',
'owner_id'
);
}
/**
* Execute the action (move the task to another column)
*
* @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)
{
$original_task = $this->taskFinderModel->getById($data['task_id']);
return $this->taskPositionModel->movePosition(
$data['project_id'],
$data['task_id'],
$this->getParam('dest_column_id'),
$original_task['position'],
$original_task['swimlane_id'],
false
);
}
/**
* 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('src_column_id') && $data['owner_id'] > 0;
}
}

View file

@ -1,100 +0,0 @@
<?php
namespace Kanboard\Action;
use Kanboard\Model\TaskModel;
/**
* Move a task to another column when the category is changed
*
* @package action
* @author Francois Ferrand
*/
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
*
* @access public
* @return array
*/
public function getCompatibleEvents()
{
return array(
TaskModel::EVENT_UPDATE,
);
}
/**
* Get the required parameter for the action (defined by the user)
*
* @access public
* @return array
*/
public function getActionRequiredParameters()
{
return array(
'dest_column_id' => t('Destination column'),
'category_id' => t('Category'),
);
}
/**
* Get the required parameter for the event
*
* @access public
* @return string[]
*/
public function getEventRequiredParameters()
{
return array(
'task_id',
'column_id',
'category_id',
);
}
/**
* Execute the action (move the task to another column)
*
* @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)
{
$original_task = $this->taskFinderModel->getById($data['task_id']);
return $this->taskPositionModel->movePosition(
$data['project_id'],
$data['task_id'],
$this->getParam('dest_column_id'),
$original_task['position'],
$original_task['swimlane_id'],
false
);
}
/**
* 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('dest_column_id') && $data['category_id'] == $this->getParam('category_id');
}
}

View file

@ -1,101 +0,0 @@
<?php
namespace Kanboard\Action;
use Kanboard\Model\TaskModel;
/**
* Move a task to another column when an assignee is cleared
*
* @package action
* @author Francois Ferrand
*/
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
*
* @access public
* @return array
*/
public function getCompatibleEvents()
{
return array(
TaskModel::EVENT_ASSIGNEE_CHANGE,
TaskModel::EVENT_UPDATE,
);
}
/**
* Get the required parameter for the action (defined by the user)
*
* @access public
* @return array
*/
public function getActionRequiredParameters()
{
return array(
'src_column_id' => t('Source column'),
'dest_column_id' => t('Destination column')
);
}
/**
* Get the required parameter for the event
*
* @access public
* @return string[]
*/
public function getEventRequiredParameters()
{
return array(
'task_id',
'column_id',
'owner_id'
);
}
/**
* Execute the action (move the task to another column)
*
* @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)
{
$original_task = $this->taskFinderModel->getById($data['task_id']);
return $this->taskPositionModel->movePosition(
$data['project_id'],
$data['task_id'],
$this->getParam('dest_column_id'),
$original_task['position'],
$original_task['swimlane_id'],
false
);
}
/**
* 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('src_column_id') && $data['owner_id'] == 0;
}
}

View file

@ -1,80 +0,0 @@
<?php
namespace Kanboard\Action;
/**
* Open automatically a task
*
* @package action
* @author Frederic Guillot
*/
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
*
* @access public
* @return array
*/
public function getCompatibleEvents()
{
return array();
}
/**
* Get the required parameter for the action (defined by the user)
*
* @access public
* @return array
*/
public function getActionRequiredParameters()
{
return array();
}
/**
* Get the required parameter for the event
*
* @access public
* @return string[]
*/
public function getEventRequiredParameters()
{
return array('task_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->taskStatusModel->open($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 true;
}
}

View file

@ -1,94 +0,0 @@
<?php
namespace Kanboard\Action;
use Kanboard\Model\TaskModel;
/**
* Set the start date of task
*
* @package action
* @author Frederic Guillot
*/
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
*
* @access public
* @return array
*/
public function getCompatibleEvents()
{
return array(
TaskModel::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 (set the task color)
*
* @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)
{
$values = array(
'id' => $data['task_id'],
'date_started' => time(),
);
return $this->taskModificationModel->update($values, false);
}
/**
* 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');
}
}

View file

@ -1,116 +0,0 @@
<?php
namespace Kanboard\Analytic;
use Kanboard\Core\Base;
use Kanboard\Model\TaskModel;
/**
* 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)
{
$end = (int) $task['date_completed'] ?: time();
$start = (int) $task['date_started'];
// Start date can be in the future when defined with the Gantt chart
if ($start > 0 && $end > $start) {
return $end - $start;
}
return 0;
}
/**
* Get the 1000 last created tasks
*
* @access private
* @param integer $project_id
* @return array
*/
private function getTasks($project_id)
{
return $this->db
->table(TaskModel::TABLE)
->columns('date_completed', 'date_creation', 'date_started')
->eq('project_id', $project_id)
->desc('id')
->limit(1000)
->findAll();
}
}

View file

@ -1,154 +0,0 @@
<?php
namespace Kanboard\Analytic;
use Kanboard\Core\Base;
use Kanboard\Model\TaskModel;
/**
* 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->columnModel->getList($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->transitionModel->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
* @return integer
*/
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(TaskModel::TABLE)
->columns('id', 'date_completed', 'date_moved', 'column_id')
->eq('project_id', $project_id)
->desc('id')
->limit(1000)
->findAll();
}
}

View file

@ -1,50 +0,0 @@
<?php
namespace Kanboard\Analytic;
use Kanboard\Core\Base;
use Kanboard\Model\TaskModel;
/**
* 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(TaskModel::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'] == TaskModel::STATUS_OPEN ? 'open' : 'closed';
$metrics[$key]['time_spent'] = $row['time_spent'];
$metrics[$key]['time_estimated'] = $row['time_estimated'];
}
return $metrics;
}
}

View file

@ -1,48 +0,0 @@
<?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->columnModel->getAll($project_id);
foreach ($columns as $column) {
$nb_tasks = $this->taskFinderModel->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;
}
}

View file

@ -1,56 +0,0 @@
<?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->taskFinderModel->getAll($project_id);
$users = $this->projectUserRoleModel->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);
}
}

View file

@ -1,19 +0,0 @@
<?php
namespace Kanboard\Api\Authorization;
/**
* Class ActionAuthorization
*
* @package Kanboard\Api\Authorization
* @author Frederic Guillot
*/
class ActionAuthorization extends ProjectAuthorization
{
public function check($class, $method, $action_id)
{
if ($this->userSession->isLogged()) {
$this->checkProjectPermission($class, $method, $this->actionModel->getProjectId($action_id));
}
}
}

View file

@ -1,19 +0,0 @@
<?php
namespace Kanboard\Api\Authorization;
/**
* Class CategoryAuthorization
*
* @package Kanboard\Api\Authorization
* @author Frederic Guillot
*/
class CategoryAuthorization extends ProjectAuthorization
{
public function check($class, $method, $category_id)
{
if ($this->userSession->isLogged()) {
$this->checkProjectPermission($class, $method, $this->categoryModel->getProjectId($category_id));
}
}
}

View file

@ -1,19 +0,0 @@
<?php
namespace Kanboard\Api\Authorization;
/**
* Class ColumnAuthorization
*
* @package Kanboard\Api\Authorization
* @author Frederic Guillot
*/
class ColumnAuthorization extends ProjectAuthorization
{
public function check($class, $method, $column_id)
{
if ($this->userSession->isLogged()) {
$this->checkProjectPermission($class, $method, $this->columnModel->getProjectId($column_id));
}
}
}

View file

@ -1,19 +0,0 @@
<?php
namespace Kanboard\Api\Authorization;
/**
* Class CommentAuthorization
*
* @package Kanboard\Api\Authorization
* @author Frederic Guillot
*/
class CommentAuthorization extends ProjectAuthorization
{
public function check($class, $method, $comment_id)
{
if ($this->userSession->isLogged()) {
$this->checkProjectPermission($class, $method, $this->commentModel->getProjectId($comment_id));
}
}
}

View file

@ -1,32 +0,0 @@
<?php
namespace Kanboard\Api\Authorization;
use JsonRPC\Exception\AccessDeniedException;
use Kanboard\Core\Base;
/**
* Class ProcedureAuthorization
*
* @package Kanboard\Api\Authorization
* @author Frederic Guillot
*/
class ProcedureAuthorization extends Base
{
private $userSpecificProcedures = array(
'getMe',
'getMyDashboard',
'getMyActivityStream',
'createMyPrivateProject',
'getMyProjectsList',
'getMyProjects',
'getMyOverdueTasks',
);
public function check($procedure)
{
if (! $this->userSession->isLogged() && in_array($procedure, $this->userSpecificProcedures)) {
throw new AccessDeniedException('This procedure is not available with the API credentials');
}
}
}

View file

@ -1,35 +0,0 @@
<?php
namespace Kanboard\Api\Authorization;
use JsonRPC\Exception\AccessDeniedException;
use Kanboard\Core\Base;
/**
* Class ProjectAuthorization
*
* @package Kanboard\Api\Authorization
* @author Frederic Guillot
*/
class ProjectAuthorization extends Base
{
public function check($class, $method, $project_id)
{
if ($this->userSession->isLogged()) {
$this->checkProjectPermission($class, $method, $project_id);
}
}
protected function checkProjectPermission($class, $method, $project_id)
{
if (empty($project_id)) {
throw new AccessDeniedException('Project not found');
}
$role = $this->projectUserRoleModel->getUserRole($project_id, $this->userSession->getId());
if (! $this->apiProjectAuthorization->isAllowed($class, $method, $role)) {
throw new AccessDeniedException('Project access denied');
}
}
}

View file

@ -1,19 +0,0 @@
<?php
namespace Kanboard\Api\Authorization;
/**
* Class SubtaskAuthorization
*
* @package Kanboard\Api\Authorization
* @author Frederic Guillot
*/
class SubtaskAuthorization extends ProjectAuthorization
{
public function check($class, $method, $subtask_id)
{
if ($this->userSession->isLogged()) {
$this->checkProjectPermission($class, $method, $this->subtaskModel->getProjectId($subtask_id));
}
}
}

View file

@ -1,19 +0,0 @@
<?php
namespace Kanboard\Api\Authorization;
/**
* Class TaskAuthorization
*
* @package Kanboard\Api\Authorization
* @author Frederic Guillot
*/
class TaskAuthorization extends ProjectAuthorization
{
public function check($class, $method, $category_id)
{
if ($this->userSession->isLogged()) {
$this->checkProjectPermission($class, $method, $this->taskFinderModel->getProjectId($category_id));
}
}
}

View file

@ -1,19 +0,0 @@
<?php
namespace Kanboard\Api\Authorization;
/**
* Class TaskFileAuthorization
*
* @package Kanboard\Api\Authorization
* @author Frederic Guillot
*/
class TaskFileAuthorization extends ProjectAuthorization
{
public function check($class, $method, $file_id)
{
if ($this->userSession->isLogged()) {
$this->checkProjectPermission($class, $method, $this->taskFileModel->getProjectId($file_id));
}
}
}

View file

@ -1,19 +0,0 @@
<?php
namespace Kanboard\Api\Authorization;
/**
* Class TaskLinkAuthorization
*
* @package Kanboard\Api\Authorization
* @author Frederic Guillot
*/
class TaskLinkAuthorization extends ProjectAuthorization
{
public function check($class, $method, $task_link_id)
{
if ($this->userSession->isLogged()) {
$this->checkProjectPermission($class, $method, $this->taskLinkModel->getProjectId($task_link_id));
}
}
}

View file

@ -1,22 +0,0 @@
<?php
namespace Kanboard\Api\Authorization;
use JsonRPC\Exception\AccessDeniedException;
use Kanboard\Core\Base;
/**
* Class UserAuthorization
*
* @package Kanboard\Api\Authorization
* @author Frederic Guillot
*/
class UserAuthorization extends Base
{
public function check($class, $method)
{
if ($this->userSession->isLogged() && ! $this->apiAuthorization->isAllowed($class, $method, $this->userSession->getRole())) {
throw new AccessDeniedException('You are not allowed to access to this resource');
}
}
}

View file

@ -1,82 +0,0 @@
<?php
namespace Kanboard\Api\Middleware;
use JsonRPC\Exception\AccessDeniedException;
use JsonRPC\Exception\AuthenticationFailureException;
use JsonRPC\MiddlewareInterface;
use Kanboard\Core\Base;
/**
* Class AuthenticationApiMiddleware
*
* @package Kanboard\Api\Middleware
* @author Frederic Guillot
*/
class AuthenticationMiddleware extends Base implements MiddlewareInterface
{
/**
* Execute Middleware
*
* @access public
* @param string $username
* @param string $password
* @param string $procedureName
* @throws AccessDeniedException
* @throws AuthenticationFailureException
*/
public function execute($username, $password, $procedureName)
{
$this->dispatcher->dispatch('app.bootstrap');
if ($this->isUserAuthenticated($username, $password)) {
$this->userSession->initialize($this->userModel->getByUsername($username));
} elseif (! $this->isAppAuthenticated($username, $password)) {
$this->logger->error('API authentication failure for '.$username);
throw new AuthenticationFailureException('Wrong credentials');
}
}
/**
* Check user credentials
*
* @access public
* @param string $username
* @param string $password
* @return boolean
*/
private function isUserAuthenticated($username, $password)
{
return $username !== 'jsonrpc' &&
! $this->userLockingModel->isLocked($username) &&
$this->authenticationManager->passwordAuthentication($username, $password);
}
/**
* Check administrative credentials
*
* @access public
* @param string $username
* @param string $password
* @return boolean
*/
private function isAppAuthenticated($username, $password)
{
return $username === 'jsonrpc' && $password === $this->getApiToken();
}
/**
* Get API Token
*
* @access private
* @return string
*/
private function getApiToken()
{
if (defined('API_AUTHENTICATION_TOKEN')) {
return API_AUTHENTICATION_TOKEN;
}
return $this->configModel->get('api_token');
}
}

View file

@ -1,91 +0,0 @@
<?php
namespace Kanboard\Api\Procedure;
use Kanboard\Api\Authorization\ActionAuthorization;
use Kanboard\Api\Authorization\ProjectAuthorization;
/**
* Action API controller
*
* @package Kanboard\Api\Procedure
* @author Frederic Guillot
*/
class ActionProcedure extends BaseProcedure
{
public function getAvailableActions()
{
return $this->actionManager->getAvailableActions();
}
public function getAvailableActionEvents()
{
return $this->eventManager->getAll();
}
public function getCompatibleActionEvents($action_name)
{
return $this->actionManager->getCompatibleEvents($action_name);
}
public function removeAction($action_id)
{
ActionAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeAction', $action_id);
return $this->actionModel->remove($action_id);
}
public function getActions($project_id)
{
ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getActions', $project_id);
return $this->actionModel->getAllByProject($project_id);
}
public function createAction($project_id, $event_name, $action_name, array $params)
{
ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'createAction', $project_id);
$values = array(
'project_id' => $project_id,
'event_name' => $event_name,
'action_name' => $action_name,
'params' => $params,
);
list($valid, ) = $this->actionValidator->validateCreation($values);
if (! $valid) {
return false;
}
// Check if the action exists
$actions = $this->actionManager->getAvailableActions();
if (! isset($actions[$action_name])) {
return false;
}
// Check the event
$action = $this->actionManager->getAction($action_name);
if (! in_array($event_name, $action->getEvents())) {
return false;
}
$required_params = $action->getActionRequiredParameters();
// Check missing parameters
foreach ($required_params as $param => $value) {
if (! isset($params[$param])) {
return false;
}
}
// Check extra parameters
foreach ($params as $param => $value) {
if (! isset($required_params[$param])) {
return false;
}
}
return $this->actionModel->create($values);
}
}

View file

@ -1,47 +0,0 @@
<?php
namespace Kanboard\Api\Procedure;
/**
* App API controller
*
* @package Kanboard\Api\Procedure
* @author Frederic Guillot
*/
class AppProcedure extends BaseProcedure
{
public function getTimezone()
{
return $this->timezoneModel->getCurrentTimezone();
}
public function getVersion()
{
return APP_VERSION;
}
public function getDefaultTaskColor()
{
return $this->colorModel->getDefaultColor();
}
public function getDefaultTaskColors()
{
return $this->colorModel->getDefaultColors();
}
public function getColorList()
{
return $this->colorModel->getList();
}
public function getApplicationRoles()
{
return $this->role->getApplicationRoles();
}
public function getProjectRoles()
{
return $this->role->getProjectRoles();
}
}

View file

@ -1,85 +0,0 @@
<?php
namespace Kanboard\Api\Procedure;
use Kanboard\Api\Authorization\ProcedureAuthorization;
use Kanboard\Api\Authorization\UserAuthorization;
use Kanboard\Core\Base;
use ReflectionClass;
/**
* Base class
*
* @package Kanboard\Api\Procedure
* @author Frederic Guillot
*/
abstract class BaseProcedure extends Base
{
public function beforeProcedure($procedure)
{
ProcedureAuthorization::getInstance($this->container)->check($procedure);
UserAuthorization::getInstance($this->container)->check($this->getClassName(), $procedure);
}
protected function formatTask($task)
{
if (! empty($task)) {
$task['url'] = $this->helper->url->to('TaskViewController', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']), '', true);
$task['color'] = $this->colorModel->getColorProperties($task['color_id']);
}
return $task;
}
protected function formatTasks($tasks)
{
if (! empty($tasks)) {
foreach ($tasks as &$task) {
$task = $this->formatTask($task);
}
}
return $tasks;
}
protected function formatProject($project)
{
if (! empty($project)) {
$project['url'] = array(
'board' => $this->helper->url->to('BoardViewController', 'show', array('project_id' => $project['id']), '', true),
'calendar' => $this->helper->url->to('CalendarController', 'show', array('project_id' => $project['id']), '', true),
'list' => $this->helper->url->to('TaskListController', 'show', array('project_id' => $project['id']), '', true),
);
}
return $project;
}
protected function formatProjects($projects)
{
if (! empty($projects)) {
foreach ($projects as &$project) {
$project = $this->formatProject($project);
}
}
return $projects;
}
protected function filterValues(array $values)
{
foreach ($values as $key => $value) {
if (is_null($value)) {
unset($values[$key]);
}
}
return $values;
}
protected function getClassName()
{
$reflection = new ReflectionClass(get_called_class());
return $reflection->getShortName();
}
}

View file

@ -1,25 +0,0 @@
<?php
namespace Kanboard\Api\Procedure;
use Kanboard\Api\Authorization\ProjectAuthorization;
use Kanboard\Formatter\BoardFormatter;
/**
* Board API controller
*
* @package Kanboard\Api\Procedure
* @author Frederic Guillot
*/
class BoardProcedure extends BaseProcedure
{
public function getBoard($project_id)
{
ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getBoard', $project_id);
return BoardFormatter::getInstance($this->container)
->withProjectId($project_id)
->withQuery($this->taskFinderModel->getExtendedQuery())
->format();
}
}

View file

@ -1,59 +0,0 @@
<?php
namespace Kanboard\Api\Procedure;
use Kanboard\Api\Authorization\CategoryAuthorization;
use Kanboard\Api\Authorization\ProjectAuthorization;
/**
* Category API controller
*
* @package Kanboard\Api\Procedure
* @author Frederic Guillot
*/
class CategoryProcedure extends BaseProcedure
{
public function getCategory($category_id)
{
CategoryAuthorization::getInstance($this->container)->check($this->getClassName(), 'getCategory', $category_id);
return $this->categoryModel->getById($category_id);
}
public function getAllCategories($project_id)
{
ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getAllCategories', $project_id);
return $this->categoryModel->getAll($project_id);
}
public function removeCategory($category_id)
{
CategoryAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeCategory', $category_id);
return $this->categoryModel->remove($category_id);
}
public function createCategory($project_id, $name)
{
ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'createCategory', $project_id);
$values = array(
'project_id' => $project_id,
'name' => $name,
);
list($valid, ) = $this->categoryValidator->validateCreation($values);
return $valid ? $this->categoryModel->create($values) : false;
}
public function updateCategory($id, $name)
{
CategoryAuthorization::getInstance($this->container)->check($this->getClassName(), 'updateCategory', $id);
$values = array(
'id' => $id,
'name' => $name,
);
list($valid, ) = $this->categoryValidator->validateModification($values);
return $valid && $this->categoryModel->update($values);
}
}

View file

@ -1,51 +0,0 @@
<?php
namespace Kanboard\Api\Procedure;
use Kanboard\Api\Authorization\ColumnAuthorization;
use Kanboard\Api\Authorization\ProjectAuthorization;
/**
* Column API controller
*
* @package Kanboard\Api\Procedure
* @author Frederic Guillot
*/
class ColumnProcedure extends BaseProcedure
{
public function getColumns($project_id)
{
ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getColumns', $project_id);
return $this->columnModel->getAll($project_id);
}
public function getColumn($column_id)
{
ColumnAuthorization::getInstance($this->container)->check($this->getClassName(), 'getColumn', $column_id);
return $this->columnModel->getById($column_id);
}
public function updateColumn($column_id, $title, $task_limit = 0, $description = '')
{
ColumnAuthorization::getInstance($this->container)->check($this->getClassName(), 'updateColumn', $column_id);
return $this->columnModel->update($column_id, $title, $task_limit, $description);
}
public function addColumn($project_id, $title, $task_limit = 0, $description = '')
{
ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'addColumn', $project_id);
return $this->columnModel->create($project_id, $title, $task_limit, $description);
}
public function removeColumn($column_id)
{
ColumnAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeColumn', $column_id);
return $this->columnModel->remove($column_id);
}
public function changeColumnPosition($project_id, $column_id, $position)
{
ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'changeColumnPosition', $project_id);
return $this->columnModel->changePosition($project_id, $column_id, $position);
}
}

View file

@ -1,62 +0,0 @@
<?php
namespace Kanboard\Api\Procedure;
use Kanboard\Api\Authorization\CommentAuthorization;
use Kanboard\Api\Authorization\TaskAuthorization;
/**
* Comment API controller
*
* @package Kanboard\Api\Procedure
* @author Frederic Guillot
*/
class CommentProcedure extends BaseProcedure
{
public function getComment($comment_id)
{
CommentAuthorization::getInstance($this->container)->check($this->getClassName(), 'getComment', $comment_id);
return $this->commentModel->getById($comment_id);
}
public function getAllComments($task_id)
{
TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'getAllComments', $task_id);
return $this->commentModel->getAll($task_id);
}
public function removeComment($comment_id)
{
CommentAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeComment', $comment_id);
return $this->commentModel->remove($comment_id);
}
public function createComment($task_id, $user_id, $content, $reference = '')
{
TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'createComment', $task_id);
$values = array(
'task_id' => $task_id,
'user_id' => $user_id,
'comment' => $content,
'reference' => $reference,
);
list($valid, ) = $this->commentValidator->validateCreation($values);
return $valid ? $this->commentModel->create($values) : false;
}
public function updateComment($id, $content)
{
CommentAuthorization::getInstance($this->container)->check($this->getClassName(), 'updateComment', $id);
$values = array(
'id' => $id,
'comment' => $content,
);
list($valid, ) = $this->commentValidator->validateModification($values);
return $valid && $this->commentModel->update($values);
}
}

View file

@ -1,37 +0,0 @@
<?php
namespace Kanboard\Api\Procedure;
/**
* Group Member API controller
*
* @package Kanboard\Api\Procedure
* @author Frederic Guillot
*/
class GroupMemberProcedure extends BaseProcedure
{
public function getMemberGroups($user_id)
{
return $this->groupMemberModel->getGroups($user_id);
}
public function getGroupMembers($group_id)
{
return $this->groupMemberModel->getMembers($group_id);
}
public function addGroupMember($group_id, $user_id)
{
return $this->groupMemberModel->addUser($group_id, $user_id);
}
public function removeGroupMember($group_id, $user_id)
{
return $this->groupMemberModel->removeUser($group_id, $user_id);
}
public function isGroupMember($group_id, $user_id)
{
return $this->groupMemberModel->isMember($group_id, $user_id);
}
}

View file

@ -1,49 +0,0 @@
<?php
namespace Kanboard\Api\Procedure;
/**
* Group API controller
*
* @package Kanboard\Api\Procedure
* @author Frederic Guillot
*/
class GroupProcedure extends BaseProcedure
{
public function createGroup($name, $external_id = '')
{
return $this->groupModel->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->groupModel->update($values);
}
public function removeGroup($group_id)
{
return $this->groupModel->remove($group_id);
}
public function getGroup($group_id)
{
return $this->groupModel->getById($group_id);
}
public function getAllGroups()
{
return $this->groupModel->getAll();
}
}

View file

@ -1,111 +0,0 @@
<?php
namespace Kanboard\Api\Procedure;
/**
* Link API controller
*
* @package Kanboard\Api\Procedure
* @author Frederic Guillot
*/
class LinkProcedure extends BaseProcedure
{
/**
* Get a link by id
*
* @access public
* @param integer $link_id Link id
* @return array
*/
public function getLinkById($link_id)
{
return $this->linkModel->getById($link_id);
}
/**
* Get a link by name
*
* @access public
* @param string $label
* @return array
*/
public function getLinkByLabel($label)
{
return $this->linkModel->getByLabel($label);
}
/**
* Get the opposite link id
*
* @access public
* @param integer $link_id Link id
* @return integer
*/
public function getOppositeLinkId($link_id)
{
return $this->linkModel->getOppositeLinkId($link_id);
}
/**
* Get all links
*
* @access public
* @return array
*/
public function getAllLinks()
{
return $this->linkModel->getAll();
}
/**
* Create a new link label
*
* @access public
* @param string $label
* @param string $opposite_label
* @return boolean|integer
*/
public function createLink($label, $opposite_label = '')
{
$values = array(
'label' => $label,
'opposite_label' => $opposite_label,
);
list($valid, ) = $this->linkValidator->validateCreation($values);
return $valid ? $this->linkModel->create($label, $opposite_label) : false;
}
/**
* Update a link
*
* @access public
* @param integer $link_id
* @param integer $opposite_link_id
* @param string $label
* @return boolean
*/
public function updateLink($link_id, $opposite_link_id, $label)
{
$values = array(
'id' => $link_id,
'opposite_id' => $opposite_link_id,
'label' => $label,
);
list($valid, ) = $this->linkValidator->validateModification($values);
return $valid && $this->linkModel->update($values);
}
/**
* Remove a link a the relation to its opposite
*
* @access public
* @param integer $link_id
* @return boolean
*/
public function removeLink($link_id)
{
return $this->linkModel->remove($link_id);
}
}

View file

@ -1,72 +0,0 @@
<?php
namespace Kanboard\Api\Procedure;
use Kanboard\Model\SubtaskModel;
/**
* Me API controller
*
* @package Kanboard\Api\Procedure
* @author Frederic Guillot
*/
class MeProcedure extends BaseProcedure
{
public function getMe()
{
return $this->sessionStorage->user;
}
public function getMyDashboard()
{
$user_id = $this->userSession->getId();
$projects = $this->projectModel->getQueryColumnStats($this->projectPermissionModel->getActiveProjectIds($user_id))->findAll();
$tasks = $this->taskFinderModel->getUserQuery($user_id)->findAll();
return array(
'projects' => $this->formatProjects($projects),
'tasks' => $this->formatTasks($tasks),
'subtasks' => $this->subtaskModel->getUserQuery($user_id, array(SubtaskModel::STATUS_TODO, SubtaskModel::STATUS_INPROGRESS))->findAll(),
);
}
public function getMyActivityStream()
{
$project_ids = $this->projectPermissionModel->getActiveProjectIds($this->userSession->getId());
return $this->helper->projectActivity->getProjectsEvents($project_ids, 100);
}
public function createMyPrivateProject($name, $description = null)
{
if ($this->configModel->get('disable_private_project', 0) == 1) {
return false;
}
$values = array(
'name' => $name,
'description' => $description,
'is_private' => 1,
);
list($valid, ) = $this->projectValidator->validateCreation($values);
return $valid ? $this->projectModel->create($values, $this->userSession->getId(), true) : false;
}
public function getMyProjectsList()
{
return $this->projectUserRoleModel->getProjectsByUser($this->userSession->getId());
}
public function getMyOverdueTasks()
{
return $this->taskFinderModel->getOverdueTasksByUser($this->userSession->getId());
}
public function getMyProjects()
{
$project_ids = $this->projectPermissionModel->getActiveProjectIds($this->userSession->getId());
$projects = $this->projectModel->getAllByIds($project_ids);
return $this->formatProjects($projects);
}
}

View file

@ -1,68 +0,0 @@
<?php
namespace Kanboard\Api\Procedure;
use Kanboard\Api\Authorization\ProjectAuthorization;
use Kanboard\Core\ObjectStorage\ObjectStorageException;
/**
* Project File API controller
*
* @package Kanboard\Api\Procedure
* @author Frederic Guillot
*/
class ProjectFileProcedure extends BaseProcedure
{
public function getProjectFile($project_id, $file_id)
{
ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getProjectFile', $project_id);
return $this->projectFileModel->getById($file_id);
}
public function getAllProjectFiles($project_id)
{
ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getAllProjectFiles', $project_id);
return $this->projectFileModel->getAll($project_id);
}
public function downloadProjectFile($project_id, $file_id)
{
ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'downloadProjectFile', $project_id);
try {
$file = $this->projectFileModel->getById($file_id);
if (! empty($file)) {
return base64_encode($this->objectStorage->get($file['path']));
}
} catch (ObjectStorageException $e) {
$this->logger->error($e->getMessage());
}
return '';
}
public function createProjectFile($project_id, $filename, $blob)
{
ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'createProjectFile', $project_id);
try {
return $this->projectFileModel->uploadContent($project_id, $filename, $blob);
} catch (ObjectStorageException $e) {
$this->logger->error(__METHOD__.': '.$e->getMessage());
return false;
}
}
public function removeProjectFile($project_id, $file_id)
{
ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeProjectFile', $project_id);
return $this->projectFileModel->remove($file_id);
}
public function removeAllProjectFiles($project_id)
{
ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeAllProjectFiles', $project_id);
return $this->projectFileModel->removeAll($project_id);
}
}

View file

@ -1,69 +0,0 @@
<?php
namespace Kanboard\Api\Procedure;
use Kanboard\Api\Authorization\ProjectAuthorization;
use Kanboard\Core\Security\Role;
/**
* Project Permission API controller
*
* @package Kanboard\Api\Procedure
* @author Frederic Guillot
*/
class ProjectPermissionProcedure extends BaseProcedure
{
public function getProjectUsers($project_id)
{
ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getProjectUsers', $project_id);
return $this->projectUserRoleModel->getAllUsers($project_id);
}
public function getAssignableUsers($project_id, $prepend_unassigned = false)
{
ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getAssignableUsers', $project_id);
return $this->projectUserRoleModel->getAssignableUsersList($project_id, $prepend_unassigned);
}
public function addProjectUser($project_id, $user_id, $role = Role::PROJECT_MEMBER)
{
ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'addProjectUser', $project_id);
return $this->projectUserRoleModel->addUser($project_id, $user_id, $role);
}
public function addProjectGroup($project_id, $group_id, $role = Role::PROJECT_MEMBER)
{
ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'addProjectGroup', $project_id);
return $this->projectGroupRoleModel->addGroup($project_id, $group_id, $role);
}
public function removeProjectUser($project_id, $user_id)
{
ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeProjectUser', $project_id);
return $this->projectUserRoleModel->removeUser($project_id, $user_id);
}
public function removeProjectGroup($project_id, $group_id)
{
ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeProjectGroup', $project_id);
return $this->projectGroupRoleModel->removeGroup($project_id, $group_id);
}
public function changeProjectUserRole($project_id, $user_id, $role)
{
ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'changeProjectUserRole', $project_id);
return $this->projectUserRoleModel->changeUserRole($project_id, $user_id, $role);
}
public function changeProjectGroupRole($project_id, $group_id, $role)
{
ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'changeProjectGroupRole', $project_id);
return $this->projectGroupRoleModel->changeGroupRole($project_id, $group_id, $role);
}
public function getProjectUserRole($project_id, $user_id)
{
ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getProjectUserRole', $project_id);
return $this->projectUserRoleModel->getUserRole($project_id, $user_id);
}
}

View file

@ -1,113 +0,0 @@
<?php
namespace Kanboard\Api\Procedure;
use Kanboard\Api\Authorization\ProjectAuthorization;
/**
* Project API controller
*
* @package Kanboard\Api\Procedure
* @author Frederic Guillot
*/
class ProjectProcedure extends BaseProcedure
{
public function getProjectById($project_id)
{
ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getProjectById', $project_id);
return $this->formatProject($this->projectModel->getById($project_id));
}
public function getProjectByName($name)
{
$project = $this->projectModel->getByName($name);
ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getProjectByName', $project['id']);
return $this->formatProject($project);
}
public function getProjectByIdentifier($identifier)
{
$project = $this->formatProject($this->projectModel->getByIdentifier($identifier));
ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getProjectByIdentifier', $project['id']);
return $this->formatProject($project);
}
public function getAllProjects()
{
return $this->formatProjects($this->projectModel->getAll());
}
public function removeProject($project_id)
{
ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeProject', $project_id);
return $this->projectModel->remove($project_id);
}
public function enableProject($project_id)
{
ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'enableProject', $project_id);
return $this->projectModel->enable($project_id);
}
public function disableProject($project_id)
{
ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'disableProject', $project_id);
return $this->projectModel->disable($project_id);
}
public function enableProjectPublicAccess($project_id)
{
ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'enableProjectPublicAccess', $project_id);
return $this->projectModel->enablePublicAccess($project_id);
}
public function disableProjectPublicAccess($project_id)
{
ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'disableProjectPublicAccess', $project_id);
return $this->projectModel->disablePublicAccess($project_id);
}
public function getProjectActivities(array $project_ids)
{
foreach ($project_ids as $project_id) {
ProjectAuthorization::getInstance($this->container)
->check($this->getClassName(), 'getProjectActivities', $project_id);
}
return $this->helper->projectActivity->getProjectsEvents($project_ids);
}
public function getProjectActivity($project_id)
{
ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getProjectActivity', $project_id);
return $this->helper->projectActivity->getProjectEvents($project_id);
}
public function createProject($name, $description = null, $owner_id = 0, $identifier = null)
{
$values = $this->filterValues(array(
'name' => $name,
'description' => $description,
'identifier' => $identifier,
));
list($valid, ) = $this->projectValidator->validateCreation($values);
return $valid ? $this->projectModel->create($values, $owner_id, $this->userSession->isLogged()) : false;
}
public function updateProject($project_id, $name = null, $description = null, $owner_id = null, $identifier = null)
{
ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'updateProject', $project_id);
$values = $this->filterValues(array(
'id' => $project_id,
'name' => $name,
'description' => $description,
'owner_id' => $owner_id,
'identifier' => $identifier,
));
list($valid, ) = $this->projectValidator->validateModification($values);
return $valid && $this->projectModel->update($values);
}
}

View file

@ -1,74 +0,0 @@
<?php
namespace Kanboard\Api\Procedure;
use Kanboard\Api\Authorization\SubtaskAuthorization;
use Kanboard\Api\Authorization\TaskAuthorization;
/**
* Subtask API controller
*
* @package Kanboard\Api\Procedure
* @author Frederic Guillot
*/
class SubtaskProcedure extends BaseProcedure
{
public function getSubtask($subtask_id)
{
SubtaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'getSubtask', $subtask_id);
return $this->subtaskModel->getById($subtask_id);
}
public function getAllSubtasks($task_id)
{
TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'getAllSubtasks', $task_id);
return $this->subtaskModel->getAll($task_id);
}
public function removeSubtask($subtask_id)
{
SubtaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeSubtask', $subtask_id);
return $this->subtaskModel->remove($subtask_id);
}
public function createSubtask($task_id, $title, $user_id = 0, $time_estimated = 0, $time_spent = 0, $status = 0)
{
TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'createSubtask', $task_id);
$values = array(
'title' => $title,
'task_id' => $task_id,
'user_id' => $user_id,
'time_estimated' => $time_estimated,
'time_spent' => $time_spent,
'status' => $status,
);
list($valid, ) = $this->subtaskValidator->validateCreation($values);
return $valid ? $this->subtaskModel->create($values) : false;
}
public function updateSubtask($id, $task_id, $title = null, $user_id = null, $time_estimated = null, $time_spent = null, $status = null)
{
TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'updateSubtask', $task_id);
$values = array(
'id' => $id,
'task_id' => $task_id,
'title' => $title,
'user_id' => $user_id,
'time_estimated' => $time_estimated,
'time_spent' => $time_spent,
'status' => $status,
);
foreach ($values as $key => $value) {
if (is_null($value)) {
unset($values[$key]);
}
}
list($valid, ) = $this->subtaskValidator->validateApiModification($values);
return $valid && $this->subtaskModel->update($values);
}
}

View file

@ -1,39 +0,0 @@
<?php
namespace Kanboard\Api\Procedure;
use Kanboard\Api\Authorization\SubtaskAuthorization;
/**
* Subtask Time Tracking API controller
*
* @package Kanboard\Api\Procedure
* @author Frederic Guillot
* @author Nikolaos Georgakis
*/
class SubtaskTimeTrackingProcedure extends BaseProcedure
{
public function hasSubtaskTimer($subtask_id, $user_id)
{
SubtaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'hasSubtaskTimer', $subtask_id);
return $this->subtaskTimeTrackingModel->hasTimer($subtask_id, $user_id);
}
public function setSubtaskStartTime($subtask_id, $user_id)
{
SubtaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'setSubtaskStartTime', $subtask_id);
return $this->subtaskTimeTrackingModel->logStartTime($subtask_id, $user_id);
}
public function setSubtaskEndTime($subtask_id, $user_id)
{
SubtaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'setSubtaskEndTime', $subtask_id);
return $this->subtaskTimeTrackingModel->logEndTime($subtask_id, $user_id);
}
public function getSubtaskTimeSpent($subtask_id, $user_id)
{
SubtaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'getSubtaskTimeSpent', $subtask_id);
return $this->subtaskTimeTrackingModel->getTimeSpent($subtask_id, $user_id);
}
}

View file

@ -1,91 +0,0 @@
<?php
namespace Kanboard\Api\Procedure;
use Kanboard\Api\Authorization\ProjectAuthorization;
/**
* Swimlane API controller
*
* @package Kanboard\Api\Procedure
* @author Frederic Guillot
*/
class SwimlaneProcedure extends BaseProcedure
{
public function getActiveSwimlanes($project_id)
{
ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getActiveSwimlanes', $project_id);
return $this->swimlaneModel->getSwimlanes($project_id);
}
public function getAllSwimlanes($project_id)
{
ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getAllSwimlanes', $project_id);
return $this->swimlaneModel->getAll($project_id);
}
public function getSwimlaneById($swimlane_id)
{
$swimlane = $this->swimlaneModel->getById($swimlane_id);
ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getSwimlaneById', $swimlane['project_id']);
return $swimlane;
}
public function getSwimlaneByName($project_id, $name)
{
ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getSwimlaneByName', $project_id);
return $this->swimlaneModel->getByName($project_id, $name);
}
public function getSwimlane($swimlane_id)
{
return $this->swimlaneModel->getById($swimlane_id);
}
public function getDefaultSwimlane($project_id)
{
ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getDefaultSwimlane', $project_id);
return $this->swimlaneModel->getDefault($project_id);
}
public function addSwimlane($project_id, $name, $description = '')
{
ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'addSwimlane', $project_id);
return $this->swimlaneModel->create(array('project_id' => $project_id, 'name' => $name, 'description' => $description));
}
public function updateSwimlane($swimlane_id, $name, $description = null)
{
$values = array('id' => $swimlane_id, 'name' => $name);
if (!is_null($description)) {
$values['description'] = $description;
}
return $this->swimlaneModel->update($values);
}
public function removeSwimlane($project_id, $swimlane_id)
{
ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeSwimlane', $project_id);
return $this->swimlaneModel->remove($project_id, $swimlane_id);
}
public function disableSwimlane($project_id, $swimlane_id)
{
ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'disableSwimlane', $project_id);
return $this->swimlaneModel->disable($project_id, $swimlane_id);
}
public function enableSwimlane($project_id, $swimlane_id)
{
ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'enableSwimlane', $project_id);
return $this->swimlaneModel->enable($project_id, $swimlane_id);
}
public function changeSwimlanePosition($project_id, $swimlane_id, $position)
{
ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'changeSwimlanePosition', $project_id);
return $this->swimlaneModel->changePosition($project_id, $swimlane_id, $position);
}
}

View file

@ -1,106 +0,0 @@
<?php
namespace Kanboard\Api\Procedure;
use Kanboard\Api\Authorization\TaskAuthorization;
use Kanboard\Core\ExternalLink\ExternalLinkManager;
use Kanboard\Core\ExternalLink\ExternalLinkProviderNotFound;
/**
* Task External Link API controller
*
* @package Kanboard\Api\Procedure
* @author Frederic Guillot
*/
class TaskExternalLinkProcedure extends BaseProcedure
{
public function getExternalTaskLinkTypes()
{
return $this->externalLinkManager->getTypes();
}
public function getExternalTaskLinkProviderDependencies($providerName)
{
try {
return $this->externalLinkManager->getProvider($providerName)->getDependencies();
} catch (ExternalLinkProviderNotFound $e) {
$this->logger->error(__METHOD__.': '.$e->getMessage());
return false;
}
}
public function getExternalTaskLinkById($task_id, $link_id)
{
TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'getExternalTaskLink', $task_id);
return $this->taskExternalLinkModel->getById($link_id);
}
public function getAllExternalTaskLinks($task_id)
{
TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'getExternalTaskLinks', $task_id);
return $this->taskExternalLinkModel->getAll($task_id);
}
public function createExternalTaskLink($task_id, $url, $dependency, $type = ExternalLinkManager::TYPE_AUTO, $title = '')
{
TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'createExternalTaskLink', $task_id);
try {
$provider = $this->externalLinkManager
->setUserInputText($url)
->setUserInputType($type)
->find();
$link = $provider->getLink();
$values = array(
'task_id' => $task_id,
'title' => $title ?: $link->getTitle(),
'url' => $link->getUrl(),
'link_type' => $provider->getType(),
'dependency' => $dependency,
);
list($valid, $errors) = $this->externalLinkValidator->validateCreation($values);
if (! $valid) {
$this->logger->error(__METHOD__.': '.var_export($errors));
return false;
}
return $this->taskExternalLinkModel->create($values);
} catch (ExternalLinkProviderNotFound $e) {
$this->logger->error(__METHOD__.': '.$e->getMessage());
}
return false;
}
public function updateExternalTaskLink($task_id, $link_id, $title = null, $url = null, $dependency = null)
{
TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'updateExternalTaskLink', $task_id);
$link = $this->taskExternalLinkModel->getById($link_id);
$values = $this->filterValues(array(
'title' => $title,
'url' => $url,
'dependency' => $dependency,
));
$values = array_merge($link, $values);
list($valid, $errors) = $this->externalLinkValidator->validateModification($values);
if (! $valid) {
$this->logger->error(__METHOD__.': '.var_export($errors));
return false;
}
return $this->taskExternalLinkModel->update($values);
}
public function removeExternalTaskLink($task_id, $link_id)
{
TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeExternalTaskLink', $task_id);
return $this->taskExternalLinkModel->remove($link_id);
}
}

View file

@ -1,70 +0,0 @@
<?php
namespace Kanboard\Api\Procedure;
use Kanboard\Api\Authorization\ProjectAuthorization;
use Kanboard\Api\Authorization\TaskAuthorization;
use Kanboard\Api\Authorization\TaskFileAuthorization;
use Kanboard\Core\ObjectStorage\ObjectStorageException;
/**
* Task File API controller
*
* @package Kanboard\Api\Procedure
* @author Frederic Guillot
*/
class TaskFileProcedure extends BaseProcedure
{
public function getTaskFile($file_id)
{
TaskFileAuthorization::getInstance($this->container)->check($this->getClassName(), 'getTaskFile', $file_id);
return $this->taskFileModel->getById($file_id);
}
public function getAllTaskFiles($task_id)
{
TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'getAllTaskFiles', $task_id);
return $this->taskFileModel->getAll($task_id);
}
public function downloadTaskFile($file_id)
{
TaskFileAuthorization::getInstance($this->container)->check($this->getClassName(), 'downloadTaskFile', $file_id);
try {
$file = $this->taskFileModel->getById($file_id);
if (! empty($file)) {
return base64_encode($this->objectStorage->get($file['path']));
}
} catch (ObjectStorageException $e) {
$this->logger->error($e->getMessage());
}
return '';
}
public function createTaskFile($project_id, $task_id, $filename, $blob)
{
ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'createTaskFile', $project_id);
try {
return $this->taskFileModel->uploadContent($task_id, $filename, $blob);
} catch (ObjectStorageException $e) {
$this->logger->error(__METHOD__.': '.$e->getMessage());
return false;
}
}
public function removeTaskFile($file_id)
{
TaskFileAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeTaskFile', $file_id);
return $this->taskFileModel->remove($file_id);
}
public function removeAllTaskFiles($task_id)
{
TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeAllTaskFiles', $task_id);
return $this->taskFileModel->removeAll($task_id);
}
}

View file

@ -1,85 +0,0 @@
<?php
namespace Kanboard\Api\Procedure;
use Kanboard\Api\Authorization\TaskAuthorization;
use Kanboard\Api\Authorization\TaskLinkAuthorization;
/**
* TaskLink API controller
*
* @package Kanboard\Api\Procedure
* @author Frederic Guillot
*/
class TaskLinkProcedure extends BaseProcedure
{
/**
* Get a task link
*
* @access public
* @param integer $task_link_id Task link id
* @return array
*/
public function getTaskLinkById($task_link_id)
{
TaskLinkAuthorization::getInstance($this->container)->check($this->getClassName(), 'getTaskLinkById', $task_link_id);
return $this->taskLinkModel->getById($task_link_id);
}
/**
* Get all links attached to a task
*
* @access public
* @param integer $task_id Task id
* @return array
*/
public function getAllTaskLinks($task_id)
{
TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'getAllTaskLinks', $task_id);
return $this->taskLinkModel->getAll($task_id);
}
/**
* Create a new link
*
* @access public
* @param integer $task_id Task id
* @param integer $opposite_task_id Opposite task id
* @param integer $link_id Link id
* @return integer Task link id
*/
public function createTaskLink($task_id, $opposite_task_id, $link_id)
{
TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'createTaskLink', $task_id);
return $this->taskLinkModel->create($task_id, $opposite_task_id, $link_id);
}
/**
* Update a task link
*
* @access public
* @param integer $task_link_id Task link id
* @param integer $task_id Task id
* @param integer $opposite_task_id Opposite task id
* @param integer $link_id Link id
* @return boolean
*/
public function updateTaskLink($task_link_id, $task_id, $opposite_task_id, $link_id)
{
TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'updateTaskLink', $task_id);
return $this->taskLinkModel->update($task_link_id, $task_id, $opposite_task_id, $link_id);
}
/**
* Remove a link between two tasks
*
* @access public
* @param integer $task_link_id
* @return boolean
*/
public function removeTaskLink($task_link_id)
{
TaskLinkAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeTaskLink', $task_link_id);
return $this->taskLinkModel->remove($task_link_id);
}
}

View file

@ -1,167 +0,0 @@
<?php
namespace Kanboard\Api\Procedure;
use Kanboard\Api\Authorization\ProjectAuthorization;
use Kanboard\Api\Authorization\TaskAuthorization;
use Kanboard\Filter\TaskProjectFilter;
use Kanboard\Model\TaskModel;
/**
* Task API controller
*
* @package Kanboard\Api\Procedure
* @author Frederic Guillot
*/
class TaskProcedure extends BaseProcedure
{
public function searchTasks($project_id, $query)
{
ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'searchTasks', $project_id);
return $this->taskLexer->build($query)->withFilter(new TaskProjectFilter($project_id))->toArray();
}
public function getTask($task_id)
{
TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'getTask', $task_id);
return $this->formatTask($this->taskFinderModel->getById($task_id));
}
public function getTaskByReference($project_id, $reference)
{
ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getTaskByReference', $project_id);
return $this->formatTask($this->taskFinderModel->getByReference($project_id, $reference));
}
public function getAllTasks($project_id, $status_id = TaskModel::STATUS_OPEN)
{
ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getAllTasks', $project_id);
return $this->formatTasks($this->taskFinderModel->getAll($project_id, $status_id));
}
public function getOverdueTasks()
{
return $this->taskFinderModel->getOverdueTasks();
}
public function getOverdueTasksByProject($project_id)
{
ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getOverdueTasksByProject', $project_id);
return $this->taskFinderModel->getOverdueTasksByProject($project_id);
}
public function openTask($task_id)
{
TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'openTask', $task_id);
return $this->taskStatusModel->open($task_id);
}
public function closeTask($task_id)
{
TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'closeTask', $task_id);
return $this->taskStatusModel->close($task_id);
}
public function removeTask($task_id)
{
TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeTask', $task_id);
return $this->taskModel->remove($task_id);
}
public function moveTaskPosition($project_id, $task_id, $column_id, $position, $swimlane_id = 0)
{
ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'moveTaskPosition', $project_id);
return $this->taskPositionModel->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)
{
ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'moveTaskToProject', $project_id);
return $this->taskProjectMoveModel->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)
{
ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'duplicateTaskToProject', $project_id);
return $this->taskProjectDuplicationModel->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, $priority = 0,
$recurrence_status = 0, $recurrence_trigger = 0, $recurrence_factor = 0, $recurrence_timeframe = 0,
$recurrence_basedate = 0, $reference = '')
{
ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'createTask', $project_id);
if ($owner_id !== 0 && ! $this->projectPermissionModel->isAssignable($project_id, $owner_id)) {
return false;
}
if ($this->userSession->isLogged()) {
$creator_id = $this->userSession->getId();
}
$values = array(
'title' => $title,
'project_id' => $project_id,
'color_id' => $color_id,
'column_id' => $column_id,
'owner_id' => $owner_id,
'creator_id' => $creator_id,
'date_due' => $date_due,
'description' => $description,
'category_id' => $category_id,
'score' => $score,
'swimlane_id' => $swimlane_id,
'recurrence_status' => $recurrence_status,
'recurrence_trigger' => $recurrence_trigger,
'recurrence_factor' => $recurrence_factor,
'recurrence_timeframe' => $recurrence_timeframe,
'recurrence_basedate' => $recurrence_basedate,
'reference' => $reference,
'priority' => $priority,
);
list($valid, ) = $this->taskValidator->validateCreation($values);
return $valid ? $this->taskCreationModel->create($values) : false;
}
public function updateTask($id, $title = null, $color_id = null, $owner_id = null,
$date_due = null, $description = null, $category_id = null, $score = null, $priority = null,
$recurrence_status = null, $recurrence_trigger = null, $recurrence_factor = null,
$recurrence_timeframe = null, $recurrence_basedate = null, $reference = null)
{
TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'updateTask', $id);
$project_id = $this->taskFinderModel->getProjectId($id);
if ($project_id === 0) {
return false;
}
if ($owner_id !== null && $owner_id != 0 && ! $this->projectPermissionModel->isAssignable($project_id, $owner_id)) {
return false;
}
$values = $this->filterValues(array(
'id' => $id,
'title' => $title,
'color_id' => $color_id,
'owner_id' => $owner_id,
'date_due' => $date_due,
'description' => $description,
'category_id' => $category_id,
'score' => $score,
'recurrence_status' => $recurrence_status,
'recurrence_trigger' => $recurrence_trigger,
'recurrence_factor' => $recurrence_factor,
'recurrence_timeframe' => $recurrence_timeframe,
'recurrence_basedate' => $recurrence_basedate,
'reference' => $reference,
'priority' => $priority,
));
list($valid) = $this->taskValidator->validateApiModification($values);
return $valid && $this->taskModificationModel->update($values);
}
}

View file

@ -1,131 +0,0 @@
<?php
namespace Kanboard\Api\Procedure;
use LogicException;
use Kanboard\Core\Security\Role;
use Kanboard\Core\Ldap\Client as LdapClient;
use Kanboard\Core\Ldap\ClientException as LdapException;
use Kanboard\Core\Ldap\User as LdapUser;
/**
* User API controller
*
* @package Kanboard\Api\Procedure
* @author Frederic Guillot
*/
class UserProcedure extends BaseProcedure
{
public function getUser($user_id)
{
return $this->userModel->getById($user_id);
}
public function getUserByName($username)
{
return $this->userModel->getByUsername($username);
}
public function getAllUsers()
{
return $this->userModel->getAll();
}
public function removeUser($user_id)
{
return $this->userModel->remove($user_id);
}
public function disableUser($user_id)
{
return $this->userModel->disable($user_id);
}
public function enableUser($user_id)
{
return $this->userModel->enable($user_id);
}
public function isActiveUser($user_id)
{
return $this->userModel->isActive($user_id);
}
public function createUser($username, $password, $name = '', $email = '', $role = Role::APP_USER)
{
$values = array(
'username' => $username,
'password' => $password,
'confirmation' => $password,
'name' => $name,
'email' => $email,
'role' => $role,
);
list($valid, ) = $this->userValidator->validateCreation($values);
return $valid ? $this->userModel->create($values) : false;
}
/**
* Create LDAP user in the database
*
* Only "anonymous" and "proxy" LDAP authentication are supported by this method
*
* User information will be fetched from the LDAP server
*
* @access public
* @param string $username
* @return bool|int
*/
public function createLdapUser($username)
{
if (LDAP_BIND_TYPE === 'user') {
$this->logger->error('LDAP authentication "user" is not supported by this API call');
return false;
}
try {
$ldap = LdapClient::connect();
$ldap->setLogger($this->logger);
$user = LdapUser::getUser($ldap, $username);
if ($user === null) {
$this->logger->info('User not found in LDAP server');
return false;
}
if ($user->getUsername() === '') {
throw new LogicException('Username not found in LDAP profile, check the parameter LDAP_USER_ATTRIBUTE_USERNAME');
}
$values = array(
'username' => $user->getUsername(),
'name' => $user->getName(),
'email' => $user->getEmail(),
'role' => $user->getRole(),
'is_ldap_user' => 1,
);
return $this->userModel->create($values);
} catch (LdapException $e) {
$this->logger->error($e->getMessage());
return false;
}
}
public function updateUser($id, $username = null, $name = null, $email = null, $role = null)
{
$values = $this->filterValues(array(
'id' => $id,
'username' => $username,
'name' => $name,
'email' => $email,
'role' => $role,
));
list($valid, ) = $this->userValidator->validateApiModification($values);
return $valid && $this->userModel->update($values);
}
}

View file

@ -1,126 +0,0 @@
<?php
namespace Kanboard\Auth;
use Kanboard\Core\Base;
use Kanboard\Core\Security\PasswordAuthenticationProviderInterface;
use Kanboard\Core\Security\SessionCheckProviderInterface;
use Kanboard\Model\UserModel;
use Kanboard\User\DatabaseUserProvider;
/**
* Database Authentication Provider
*
* @package auth
* @author Frederic Guillot
*/
class DatabaseAuth extends Base implements PasswordAuthenticationProviderInterface, SessionCheckProviderInterface
{
/**
* User properties
*
* @access protected
* @var array
*/
protected $userInfo = array();
/**
* Username
*
* @access protected
* @var string
*/
protected $username = '';
/**
* Password
*
* @access protected
* @var string
*/
protected $password = '';
/**
* Get authentication provider name
*
* @access public
* @return string
*/
public function getName()
{
return 'Database';
}
/**
* Authenticate the user
*
* @access public
* @return boolean
*/
public function authenticate()
{
$user = $this->db
->table(UserModel::TABLE)
->columns('id', 'password')
->eq('username', $this->username)
->eq('disable_login_form', 0)
->eq('is_ldap_user', 0)
->eq('is_active', 1)
->findOne();
if (! empty($user) && password_verify($this->password, $user['password'])) {
$this->userInfo = $user;
return true;
}
return false;
}
/**
* Check if the user session is valid
*
* @access public
* @return boolean
*/
public function isValidSession()
{
return $this->userModel->isActive($this->userSession->getId());
}
/**
* Get user object
*
* @access public
* @return \Kanboard\User\DatabaseUserProvider
*/
public function getUser()
{
if (empty($this->userInfo)) {
return null;
}
return new DatabaseUserProvider($this->userInfo);
}
/**
* Set username
*
* @access public
* @param string $username
*/
public function setUsername($username)
{
$this->username = $username;
}
/**
* Set password
*
* @access public
* @param string $password
*/
public function setPassword($password)
{
$this->password = $password;
}
}

View file

@ -1,176 +0,0 @@
<?php
namespace Kanboard\Auth;
use LogicException;
use Kanboard\Core\Base;
use Kanboard\Core\Ldap\Client as LdapClient;
use Kanboard\Core\Ldap\ClientException as LdapException;
use Kanboard\Core\Ldap\User as LdapUser;
use Kanboard\Core\Security\PasswordAuthenticationProviderInterface;
/**
* LDAP Authentication Provider
*
* @package auth
* @author Frederic Guillot
*/
class LdapAuth extends Base implements PasswordAuthenticationProviderInterface
{
/**
* User properties
*
* @access protected
* @var \Kanboard\User\LdapUserProvider
*/
protected $userInfo = null;
/**
* Username
*
* @access protected
* @var string
*/
protected $username = '';
/**
* Password
*
* @access protected
* @var string
*/
protected $password = '';
/**
* Get authentication provider name
*
* @access public
* @return string
*/
public function getName()
{
return 'LDAP';
}
/**
* Authenticate the user
*
* @access public
* @return boolean
*/
public function authenticate()
{
try {
$client = LdapClient::connect($this->getLdapUsername(), $this->getLdapPassword());
$client->setLogger($this->logger);
$user = LdapUser::getUser($client, $this->username);
if ($user === null) {
$this->logger->info('User ('.$this->username.') not found in LDAP server');
return false;
}
if ($user->getUsername() === '') {
throw new LogicException('Username not found in LDAP profile, check the parameter LDAP_USER_ATTRIBUTE_USERNAME');
}
$this->logger->info('Authenticate this user: '.$user->getDn());
if ($client->authenticate($user->getDn(), $this->password)) {
$this->userInfo = $user;
return true;
}
} catch (LdapException $e) {
$this->logger->error($e->getMessage());
}
return false;
}
/**
* Get user object
*
* @access public
* @return \Kanboard\User\LdapUserProvider
*/
public function getUser()
{
return $this->userInfo;
}
/**
* Set username
*
* @access public
* @param string $username
*/
public function setUsername($username)
{
$this->username = $username;
}
/**
* Set password
*
* @access public
* @param string $password
*/
public function setPassword($password)
{
$this->password = $password;
}
/**
* Get LDAP username (proxy auth)
*
* @access public
* @return string
*/
public function getLdapUsername()
{
switch ($this->getLdapBindType()) {
case 'proxy':
return LDAP_USERNAME;
case 'user':
return sprintf(LDAP_USERNAME, $this->username);
default:
return null;
}
}
/**
* Get LDAP password (proxy auth)
*
* @access public
* @return string
*/
public function getLdapPassword()
{
switch ($this->getLdapBindType()) {
case 'proxy':
return LDAP_PASSWORD;
case 'user':
return $this->password;
default:
return null;
}
}
/**
* Get LDAP bind type
*
* @access public
* @return integer
*/
public function getLdapBindType()
{
if (LDAP_BIND_TYPE !== 'user' && LDAP_BIND_TYPE !== 'proxy' && LDAP_BIND_TYPE !== 'anonymous') {
throw new LogicException('Wrong value for the parameter LDAP_BIND_TYPE');
}
return LDAP_BIND_TYPE;
}
}

View file

@ -1,79 +0,0 @@
<?php
namespace Kanboard\Auth;
use Kanboard\Core\Base;
use Kanboard\Core\Security\PreAuthenticationProviderInterface;
use Kanboard\User\DatabaseUserProvider;
/**
* Rember Me Cookie Authentication Provider
*
* @package auth
* @author Frederic Guillot
*/
class RememberMeAuth extends Base implements PreAuthenticationProviderInterface
{
/**
* User properties
*
* @access protected
* @var array
*/
protected $userInfo = array();
/**
* Get authentication provider name
*
* @access public
* @return string
*/
public function getName()
{
return 'RememberMe';
}
/**
* Authenticate the user
*
* @access public
* @return boolean
*/
public function authenticate()
{
$credentials = $this->rememberMeCookie->read();
if ($credentials !== false) {
$session = $this->rememberMeSessionModel->find($credentials['token'], $credentials['sequence']);
if (! empty($session)) {
$this->rememberMeCookie->write(
$session['token'],
$this->rememberMeSessionModel->updateSequence($session['token']),
$session['expiration']
);
$this->userInfo = $this->userModel->getById($session['user_id']);
return true;
}
}
return false;
}
/**
* Get user object
*
* @access public
* @return DatabaseUserProvider
*/
public function getUser()
{
if (empty($this->userInfo)) {
return null;
}
return new DatabaseUserProvider($this->userInfo);
}
}

View file

@ -1,77 +0,0 @@
<?php
namespace Kanboard\Auth;
use Kanboard\Core\Base;
use Kanboard\Core\Security\PreAuthenticationProviderInterface;
use Kanboard\Core\Security\SessionCheckProviderInterface;
use Kanboard\User\ReverseProxyUserProvider;
/**
* Reverse-Proxy Authentication Provider
*
* @package auth
* @author Frederic Guillot
*/
class ReverseProxyAuth extends Base implements PreAuthenticationProviderInterface, SessionCheckProviderInterface
{
/**
* User properties
*
* @access protected
* @var \Kanboard\User\ReverseProxyUserProvider
*/
protected $userInfo = null;
/**
* Get authentication provider name
*
* @access public
* @return string
*/
public function getName()
{
return 'ReverseProxy';
}
/**
* Authenticate the user
*
* @access public
* @return boolean
*/
public function authenticate()
{
$username = $this->request->getRemoteUser();
if (! empty($username)) {
$userProfile = $this->userModel->getByUsername($username);
$this->userInfo = new ReverseProxyUserProvider($username, $userProfile ?: array());
return true;
}
return false;
}
/**
* Check if the user session is valid
*
* @access public
* @return boolean
*/
public function isValidSession()
{
return $this->request->getRemoteUser() === $this->userSession->getUsername();
}
/**
* Get user object
*
* @access public
* @return ReverseProxyUserProvider
*/
public function getUser()
{
return $this->userInfo;
}
}

View file

@ -1,144 +0,0 @@
<?php
namespace Kanboard\Auth;
use Otp\Otp;
use Otp\GoogleAuthenticator;
use Base32\Base32;
use Kanboard\Core\Base;
use Kanboard\Core\Security\PostAuthenticationProviderInterface;
/**
* TOTP Authentication Provider
*
* @package auth
* @author Frederic Guillot
*/
class TotpAuth extends Base implements PostAuthenticationProviderInterface
{
/**
* User pin code
*
* @access protected
* @var string
*/
protected $code = '';
/**
* Private key
*
* @access protected
* @var string
*/
protected $secret = '';
/**
* Get authentication provider name
*
* @access public
* @return string
*/
public function getName()
{
return t('Time-based One-time Password Algorithm');
}
/**
* Authenticate the user
*
* @access public
* @return boolean
*/
public function authenticate()
{
$otp = new Otp;
return $otp->checkTotp(Base32::decode($this->secret), $this->code);
}
/**
* Called before to prompt the user
*
* @access public
*/
public function beforeCode()
{
}
/**
* Set validation code
*
* @access public
* @param string $code
*/
public function setCode($code)
{
$this->code = $code;
}
/**
* Generate secret
*
* @access public
* @return string
*/
public function generateSecret()
{
$this->secret = GoogleAuthenticator::generateRandom();
return $this->secret;
}
/**
* Set secret token
*
* @access public
* @param string $secret
*/
public function setSecret($secret)
{
$this->secret = $secret;
}
/**
* Get secret token
*
* @access public
* @return string
*/
public function getSecret()
{
return $this->secret;
}
/**
* Get QR code url
*
* @access public
* @param string $label
* @return string
*/
public function getQrCodeUrl($label)
{
if (empty($this->secret)) {
return '';
}
return GoogleAuthenticator::getQrCodeUrl('totp', $label, $this->secret);
}
/**
* Get key url (empty if no url can be provided)
*
* @access public
* @param string $label
* @return string
*/
public function getKeyUrl($label)
{
if (empty($this->secret)) {
return '';
}
return GoogleAuthenticator::getKeyUri('totp', $label, $this->secret);
}
}

View file

@ -1,67 +0,0 @@
<?php
namespace Kanboard\Console;
use Pimple\Container;
use Symfony\Component\Console\Command\Command;
/**
* Base command class
*
* @package console
* @author Frederic Guillot
*
* @property \Kanboard\Validator\PasswordResetValidator $passwordResetValidator
* @property \Kanboard\Export\SubtaskExport $subtaskExport
* @property \Kanboard\Export\TaskExport $taskExport
* @property \Kanboard\Export\TransitionExport $transitionExport
* @property \Kanboard\Model\NotificationModel $notificationModel
* @property \Kanboard\Model\ProjectModel $projectModel
* @property \Kanboard\Model\ProjectPermissionModel $projectPermissionModel
* @property \Kanboard\Model\ProjectDailyColumnStatsModel $projectDailyColumnStatsModel
* @property \Kanboard\Model\ProjectDailyStatsModel $projectDailyStatsModel
* @property \Kanboard\Model\TaskModel $taskModel
* @property \Kanboard\Model\TaskFinderModel $taskFinderModel
* @property \Kanboard\Model\UserModel $userModel
* @property \Kanboard\Model\UserNotificationModel $userNotificationModel
* @property \Kanboard\Model\UserNotificationFilterModel $userNotificationFilterModel
* @property \Kanboard\Model\ProjectUserRoleModel $projectUserRoleModel
* @property \Kanboard\Core\Plugin\Loader $pluginLoader
* @property \Kanboard\Core\Http\Client $httpClient
* @property \Kanboard\Core\Queue\QueueManager $queueManager
* @property \Symfony\Component\EventDispatcher\EventDispatcher $dispatcher
*/
abstract class BaseCommand extends Command
{
/**
* Container instance
*
* @access protected
* @var \Pimple\Container
*/
protected $container;
/**
* Constructor
*
* @access public
* @param \Pimple\Container $container
*/
public function __construct(Container $container)
{
parent::__construct();
$this->container = $container;
}
/**
* Load automatically models
*
* @access public
* @param string $name Model name
* @return mixed
*/
public function __get($name)
{
return $this->container[$name];
}
}

View file

@ -1,32 +0,0 @@
<?php
namespace Kanboard\Console;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Output\NullOutput;
class CronjobCommand extends BaseCommand
{
private $commands = array(
'projects:daily-stats',
'notification:overdue-tasks',
'trigger:tasks',
);
protected function configure()
{
$this
->setName('cronjob')
->setDescription('Execute daily cronjob');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
foreach ($this->commands as $command) {
$job = $this->getApplication()->find($command);
$job->run(new ArrayInput(array('command' => $command)), new NullOutput());
}
}
}

View file

@ -1,81 +0,0 @@
<?php
namespace Kanboard\Console;
use RecursiveIteratorIterator;
use RecursiveDirectoryIterator;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class LocaleComparatorCommand extends BaseCommand
{
const REF_LOCALE = 'fr_FR';
protected function configure()
{
$this
->setName('locale:compare')
->setDescription('Compare application translations with the '.self::REF_LOCALE.' locale');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$strings = array();
$it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator('app'));
$it->rewind();
while ($it->valid()) {
if (! $it->isDot() && substr($it->key(), -4) === '.php') {
$strings = array_merge($strings, $this->search($it->key()));
}
$it->next();
}
$this->compare(array_unique($strings));
}
public function show(array $strings)
{
foreach ($strings as $string) {
echo " '".str_replace("'", "\'", $string)."' => '',".PHP_EOL;
}
}
public function compare(array $strings)
{
$reference_file = 'app/Locale/'.self::REF_LOCALE.'/translations.php';
$reference = include $reference_file;
echo str_repeat('#', 70).PHP_EOL;
echo 'MISSING STRINGS'.PHP_EOL;
echo str_repeat('#', 70).PHP_EOL;
$this->show(array_diff($strings, array_keys($reference)));
echo str_repeat('#', 70).PHP_EOL;
echo 'USELESS STRINGS'.PHP_EOL;
echo str_repeat('#', 70).PHP_EOL;
$this->show(array_diff(array_keys($reference), $strings));
}
public function search($filename)
{
$content = file_get_contents($filename);
$strings = array();
if (preg_match_all('/\b[et]\((\'\K.*?\') *[\)\,]/', $content, $matches) && isset($matches[1])) {
$strings = $matches[1];
}
if (preg_match_all('/\bdt\((\'\K.*?\') *[\)\,]/', $content, $matches) && isset($matches[1])) {
$strings = array_merge($strings, $matches[1]);
}
array_walk($strings, function (&$value) {
$value = trim($value, "'");
$value = str_replace("\'", "'", $value);
});
return $strings;
}
}

View file

@ -1,53 +0,0 @@
<?php
namespace Kanboard\Console;
use DirectoryIterator;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class LocaleSyncCommand extends BaseCommand
{
const REF_LOCALE = 'fr_FR';
protected function configure()
{
$this
->setName('locale:sync')
->setDescription('Synchronize all translations based on the '.self::REF_LOCALE.' locale');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$reference_file = 'app/Locale/'.self::REF_LOCALE.'/translations.php';
$reference = include $reference_file;
foreach (new DirectoryIterator('app/Locale') as $fileInfo) {
if (! $fileInfo->isDot() && $fileInfo->isDir() && $fileInfo->getFilename() !== self::REF_LOCALE) {
$filename = 'app/Locale/'.$fileInfo->getFilename().'/translations.php';
echo $fileInfo->getFilename().' ('.$filename.')'.PHP_EOL;
file_put_contents($filename, $this->updateFile($reference, $filename));
}
}
}
public function updateFile(array $reference, $outdated_file)
{
$outdated = include $outdated_file;
$output = '<?php'.PHP_EOL.PHP_EOL;
$output .= 'return array('.PHP_EOL;
foreach ($reference as $key => $value) {
if (! empty($outdated[$key])) {
$output .= " '".str_replace("'", "\'", $key)."' => '".str_replace("'", "\'", $outdated[$key])."',\n";
} else {
$output .= " // '".str_replace("'", "\'", $key)."' => '',\n";
}
}
$output .= ");\n";
return $output;
}
}

View file

@ -1,36 +0,0 @@
<?php
namespace Kanboard\Console;
use Kanboard\Core\Plugin\Installer;
use Kanboard\Core\Plugin\PluginInstallerException;
use LogicException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class PluginInstallCommand extends BaseCommand
{
protected function configure()
{
$this
->setName('plugin:install')
->setDescription('Install a plugin from a remote Zip archive')
->addArgument('url', InputArgument::REQUIRED, 'Archive URL');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
if (!Installer::isConfigured()) {
throw new LogicException('Kanboard is not configured to install plugins itself');
}
try {
$installer = new Installer($this->container);
$installer->install($input->getArgument('url'));
$output->writeln('<info>Plugin installed successfully</info>');
} catch (PluginInstallerException $e) {
$output->writeln('<error>'.$e->getMessage().'</error>');
}
}
}

View file

@ -1,36 +0,0 @@
<?php
namespace Kanboard\Console;
use Kanboard\Core\Plugin\Installer;
use Kanboard\Core\Plugin\PluginInstallerException;
use LogicException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class PluginUninstallCommand extends BaseCommand
{
protected function configure()
{
$this
->setName('plugin:uninstall')
->setDescription('Remove a plugin')
->addArgument('pluginId', InputArgument::REQUIRED, 'Plugin directory name');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
if (!Installer::isConfigured()) {
throw new LogicException('Kanboard is not configured to install plugins itself');
}
try {
$installer = new Installer($this->container);
$installer->uninstall($input->getArgument('pluginId'));
$output->writeln('<info>Plugin removed successfully</info>');
} catch (PluginInstallerException $e) {
$output->writeln('<error>'.$e->getMessage().'</error>');
}
}
}

View file

@ -1,55 +0,0 @@
<?php
namespace Kanboard\Console;
use Kanboard\Core\Plugin\Base as BasePlugin;
use Kanboard\Core\Plugin\Directory;
use Kanboard\Core\Plugin\Installer;
use LogicException;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class PluginUpgradeCommand extends BaseCommand
{
protected function configure()
{
$this
->setName('plugin:upgrade')
->setDescription('Update all installed plugins')
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
if (!Installer::isConfigured()) {
throw new LogicException('Kanboard is not configured to install plugins itself');
}
$installer = new Installer($this->container);
$availablePlugins = Directory::getInstance($this->container)->getAvailablePlugins();
foreach ($this->pluginLoader->getPlugins() as $installedPlugin) {
$pluginDetails = $this->getPluginDetails($availablePlugins, $installedPlugin);
if ($pluginDetails === null) {
$output->writeln('<error>* Plugin not available in the directory: '.$installedPlugin->getPluginName().'</error>');
} elseif ($pluginDetails['version'] > $installedPlugin->getPluginVersion()) {
$output->writeln('<comment>* Updating plugin: '.$installedPlugin->getPluginName().'</comment>');
$installer->update($pluginDetails['download']);
} else {
$output->writeln('<info>* Plugin up to date: '.$installedPlugin->getPluginName().'</info>');
}
}
}
protected function getPluginDetails(array $availablePlugins, BasePlugin $installedPlugin)
{
foreach ($availablePlugins as $availablePlugin) {
if ($availablePlugin['title'] === $installedPlugin->getPluginName()) {
return $availablePlugin;
}
}
return null;
}
}

View file

@ -1,34 +0,0 @@
<?php
namespace Kanboard\Console;
use Kanboard\Core\Csv;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class ProjectDailyColumnStatsExportCommand extends BaseCommand
{
protected function configure()
{
$this
->setName('export:daily-project-column-stats')
->setDescription('Daily project column stats CSV export (number of tasks per column and per day)')
->addArgument('project_id', InputArgument::REQUIRED, 'Project id')
->addArgument('start_date', InputArgument::REQUIRED, 'Start date (YYYY-MM-DD)')
->addArgument('end_date', InputArgument::REQUIRED, 'End date (YYYY-MM-DD)');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$data = $this->projectDailyColumnStatsModel->getAggregatedMetrics(
$input->getArgument('project_id'),
$input->getArgument('start_date'),
$input->getArgument('end_date')
);
if (is_array($data)) {
Csv::output($data);
}
}
}

View file

@ -1,28 +0,0 @@
<?php
namespace Kanboard\Console;
use Kanboard\Model\ProjectModel;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class ProjectDailyStatsCalculationCommand extends BaseCommand
{
protected function configure()
{
$this
->setName('projects:daily-stats')
->setDescription('Calculate daily statistics for all projects');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$projects = $this->projectModel->getAllByStatus(ProjectModel::ACTIVE);
foreach ($projects as $project) {
$output->writeln('Run calculation for '.$project['name']);
$this->projectDailyColumnStatsModel->updateTotals($project['id'], date('Y-m-d'));
$this->projectDailyStatsModel->updateTotals($project['id'], date('Y-m-d'));
}
}
}

View file

@ -1,79 +0,0 @@
<?php
namespace Kanboard\Console;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\Question;
class ResetPasswordCommand extends BaseCommand
{
protected function configure()
{
$this
->setName('user:reset-password')
->setDescription('Change user password')
->addArgument('username', InputArgument::REQUIRED, 'Username')
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$helper = $this->getHelper('question');
$username = $input->getArgument('username');
$passwordQuestion = new Question('What is the new password for '.$username.'? (characters are not printed)'.PHP_EOL);
$passwordQuestion->setHidden(true);
$passwordQuestion->setHiddenFallback(false);
$password = $helper->ask($input, $output, $passwordQuestion);
$confirmationQuestion = new Question('Confirmation:'.PHP_EOL);
$confirmationQuestion->setHidden(true);
$confirmationQuestion->setHiddenFallback(false);
$confirmation = $helper->ask($input, $output, $confirmationQuestion);
if ($this->validatePassword($output, $password, $confirmation)) {
$this->resetPassword($output, $username, $password);
}
}
private function validatePassword(OutputInterface $output, $password, $confirmation)
{
list($valid, $errors) = $this->passwordResetValidator->validateModification(array(
'password' => $password,
'confirmation' => $confirmation,
));
if (!$valid) {
foreach ($errors as $error_list) {
foreach ($error_list as $error) {
$output->writeln('<error>'.$error.'</error>');
}
}
}
return $valid;
}
private function resetPassword(OutputInterface $output, $username, $password)
{
$userId = $this->userModel->getIdByUsername($username);
if (empty($userId)) {
$output->writeln('<error>User not found</error>');
return false;
}
if (!$this->userModel->update(array('id' => $userId, 'password' => $password))) {
$output->writeln('<error>Unable to update password</error>');
return false;
}
$output->writeln('<info>Password updated successfully</info>');
return true;
}
}

View file

@ -1,38 +0,0 @@
<?php
namespace Kanboard\Console;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class ResetTwoFactorCommand extends BaseCommand
{
protected function configure()
{
$this
->setName('user:reset-2fa')
->setDescription('Remove two-factor authentication for a user')
->addArgument('username', InputArgument::REQUIRED, 'Username');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$username = $input->getArgument('username');
$userId = $this->userModel->getIdByUsername($username);
if (empty($userId)) {
$output->writeln('<error>User not found</error>');
return false;
}
if (!$this->userModel->update(array('id' => $userId, 'twofactor_activated' => 0, 'twofactor_secret' => ''))) {
$output->writeln('<error>Unable to update user profile</error>');
return false;
}
$output->writeln('<info>Two-factor authentication disabled</info>');
return true;
}
}

View file

@ -1,34 +0,0 @@
<?php
namespace Kanboard\Console;
use Kanboard\Core\Csv;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class SubtaskExportCommand extends BaseCommand
{
protected function configure()
{
$this
->setName('export:subtasks')
->setDescription('Subtasks CSV export')
->addArgument('project_id', InputArgument::REQUIRED, 'Project id')
->addArgument('start_date', InputArgument::REQUIRED, 'Start date (YYYY-MM-DD)')
->addArgument('end_date', InputArgument::REQUIRED, 'End date (YYYY-MM-DD)');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$data = $this->subtaskExport->export(
$input->getArgument('project_id'),
$input->getArgument('start_date'),
$input->getArgument('end_date')
);
if (is_array($data)) {
Csv::output($data);
}
}
}

View file

@ -1,34 +0,0 @@
<?php
namespace Kanboard\Console;
use Kanboard\Core\Csv;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class TaskExportCommand extends BaseCommand
{
protected function configure()
{
$this
->setName('export:tasks')
->setDescription('Tasks CSV export')
->addArgument('project_id', InputArgument::REQUIRED, 'Project id')
->addArgument('start_date', InputArgument::REQUIRED, 'Start date (YYYY-MM-DD)')
->addArgument('end_date', InputArgument::REQUIRED, 'End date (YYYY-MM-DD)');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$data = $this->taskExport->export(
$input->getArgument('project_id'),
$input->getArgument('start_date'),
$input->getArgument('end_date')
);
if (is_array($data)) {
Csv::output($data);
}
}
}

View file

@ -1,191 +0,0 @@
<?php
namespace Kanboard\Console;
use Kanboard\Model\TaskModel;
use Kanboard\Core\Security\Role;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class TaskOverdueNotificationCommand extends BaseCommand
{
protected function configure()
{
$this
->setName('notification:overdue-tasks')
->setDescription('Send notifications for overdue tasks')
->addOption('show', null, InputOption::VALUE_NONE, 'Show sent overdue tasks')
->addOption('group', null, InputOption::VALUE_NONE, 'Group all overdue tasks for one user (from all projects) in one email')
->addOption('manager', null, InputOption::VALUE_NONE, 'Send all overdue tasks to project manager(s) in one email');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
if ($input->getOption('group')) {
$tasks = $this->sendGroupOverdueTaskNotifications();
} elseif ($input->getOption('manager')) {
$tasks = $this->sendOverdueTaskNotificationsToManagers();
} else {
$tasks = $this->sendOverdueTaskNotifications();
}
if ($input->getOption('show')) {
$this->showTable($output, $tasks);
}
}
public function showTable(OutputInterface $output, array $tasks)
{
$rows = array();
foreach ($tasks as $task) {
$rows[] = array(
$task['id'],
$task['title'],
date('Y-m-d', $task['date_due']),
$task['project_id'],
$task['project_name'],
$task['assignee_name'] ?: $task['assignee_username'],
);
}
$table = new Table($output);
$table
->setHeaders(array('Id', 'Title', 'Due date', 'Project Id', 'Project name', 'Assignee'))
->setRows($rows)
->render();
}
/**
* Send all overdue tasks for one user in one email
*
* @access public
*/
public function sendGroupOverdueTaskNotifications()
{
$tasks = $this->taskFinderModel->getOverdueTasks();
foreach ($this->groupByColumn($tasks, 'owner_id') as $user_tasks) {
$users = $this->userNotificationModel->getUsersWithNotificationEnabled($user_tasks[0]['project_id']);
foreach ($users as $user) {
$this->sendUserOverdueTaskNotifications($user, $user_tasks);
}
}
return $tasks;
}
/**
* Send all overdue tasks in one email to project manager(s)
*
* @access public
*/
public function sendOverdueTaskNotificationsToManagers()
{
$tasks = $this->taskFinderModel->getOverdueTasks();
foreach ($this->groupByColumn($tasks, 'project_id') as $project_id => $project_tasks) {
$users = $this->userNotificationModel->getUsersWithNotificationEnabled($project_id);
$managers = array();
foreach ($users as $user) {
$role = $this->projectUserRoleModel->getUserRole($project_id, $user['id']);
if($role == Role::PROJECT_MANAGER) {
$managers[] = $user;
}
}
foreach ($managers as $manager) {
$this->sendUserOverdueTaskNotificationsToManagers($manager, $project_tasks);
}
}
return $tasks;
}
/**
* Send overdue tasks
*
* @access public
*/
public function sendOverdueTaskNotifications()
{
$tasks = $this->taskFinderModel->getOverdueTasks();
foreach ($this->groupByColumn($tasks, 'project_id') as $project_id => $project_tasks) {
$users = $this->userNotificationModel->getUsersWithNotificationEnabled($project_id);
foreach ($users as $user) {
$this->sendUserOverdueTaskNotifications($user, $project_tasks);
}
}
return $tasks;
}
/**
* Send overdue tasks for a given user
*
* @access public
* @param array $user
* @param array $tasks
*/
public function sendUserOverdueTaskNotifications(array $user, array $tasks)
{
$user_tasks = array();
$project_names = array();
foreach ($tasks as $task) {
if ($this->userNotificationFilterModel->shouldReceiveNotification($user, array('task' => $task))) {
$user_tasks[] = $task;
$project_names[$task['project_id']] = $task['project_name'];
}
}
if (! empty($user_tasks)) {
$this->userNotificationModel->sendUserNotification(
$user,
TaskModel::EVENT_OVERDUE,
array('tasks' => $user_tasks, 'project_name' => implode(', ', $project_names))
);
}
}
/**
* Send overdue tasks for a project manager(s)
*
* @access public
* @param array $manager
* @param array $tasks
*/
public function sendUserOverdueTaskNotificationsToManagers(array $manager, array $tasks)
{
$this->userNotificationModel->sendUserNotification(
$manager,
TaskModel::EVENT_OVERDUE,
array('tasks' => $tasks, 'project_name' => $tasks[0]['project_name'])
);
}
/**
* Group a collection of records by a column
*
* @access public
* @param array $collection
* @param string $column
* @return array
*/
public function groupByColumn(array $collection, $column)
{
$result = array();
foreach ($collection as $item) {
$result[$item[$column]][] = $item;
}
return $result;
}
}

View file

@ -1,51 +0,0 @@
<?php
namespace Kanboard\Console;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Kanboard\Model\TaskModel;
use Kanboard\Event\TaskListEvent;
class TaskTriggerCommand extends BaseCommand
{
protected function configure()
{
$this
->setName('trigger:tasks')
->setDescription('Trigger scheduler event for all tasks');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
foreach ($this->getProjectIds() as $project_id) {
$tasks = $this->taskFinderModel->getAll($project_id);
$nb_tasks = count($tasks);
if ($nb_tasks > 0) {
$output->writeln('Trigger task event: project_id='.$project_id.', nb_tasks='.$nb_tasks);
$this->sendEvent($tasks, $project_id);
}
}
}
private function getProjectIds()
{
$listeners = $this->dispatcher->getListeners(TaskModel::EVENT_DAILY_CRONJOB);
$project_ids = array();
foreach ($listeners as $listener) {
$project_ids[] = $listener[0]->getProjectId();
}
return array_unique($project_ids);
}
private function sendEvent(array &$tasks, $project_id)
{
$event = new TaskListEvent(array('project_id' => $project_id));
$event->setTasks($tasks);
$this->dispatcher->dispatch(TaskModel::EVENT_DAILY_CRONJOB, $event);
}
}

View file

@ -1,34 +0,0 @@
<?php
namespace Kanboard\Console;
use Kanboard\Core\Csv;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class TransitionExportCommand extends BaseCommand
{
protected function configure()
{
$this
->setName('export:transitions')
->setDescription('Task transitions CSV export')
->addArgument('project_id', InputArgument::REQUIRED, 'Project id')
->addArgument('start_date', InputArgument::REQUIRED, 'Start date (YYYY-MM-DD)')
->addArgument('end_date', InputArgument::REQUIRED, 'End date (YYYY-MM-DD)');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$data = $this->transitionExport->export(
$input->getArgument('project_id'),
$input->getArgument('start_date'),
$input->getArgument('end_date')
);
if (is_array($data)) {
Csv::output($data);
}
}
}

View file

@ -1,28 +0,0 @@
<?php
namespace Kanboard\Console;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Class WorkerCommand
*
* @package Kanboard\Console
* @author Frederic Guillot
*/
class WorkerCommand extends BaseCommand
{
protected function configure()
{
$this
->setName('worker')
->setDescription('Execute queue worker')
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->queueManager->listen();
}
}

View file

@ -1,77 +0,0 @@
<?php
namespace Kanboard\Controller;
/**
* Automatic Actions Controller
*
* @package Kanboard\Controller
* @author Frederic Guillot
*/
class ActionController extends BaseController
{
/**
* List of automatic actions for a given project
*
* @access public
*/
public function index()
{
$project = $this->getProject();
$actions = $this->actionModel->getAllByProject($project['id']);
$this->response->html($this->helper->layout->project('action/index', array(
'values' => array('project_id' => $project['id']),
'project' => $project,
'actions' => $actions,
'available_actions' => $this->actionManager->getAvailableActions(),
'available_events' => $this->eventManager->getAll(),
'available_params' => $this->actionManager->getAvailableParameters($actions),
'columns_list' => $this->columnModel->getList($project['id']),
'users_list' => $this->projectUserRoleModel->getAssignableUsersList($project['id']),
'projects_list' => $this->projectUserRoleModel->getProjectsByUser($this->userSession->getId()),
'colors_list' => $this->colorModel->getList(),
'categories_list' => $this->categoryModel->getList($project['id']),
'links_list' => $this->linkModel->getList(0, false),
'title' => t('Automatic actions')
)));
}
/**
* Confirmation dialog before removing an action
*
* @access public
*/
public function confirm()
{
$project = $this->getProject();
$this->response->html($this->helper->layout->project('action/remove', array(
'action' => $this->actionModel->getById($this->request->getIntegerParam('action_id')),
'available_events' => $this->eventManager->getAll(),
'available_actions' => $this->actionManager->getAvailableActions(),
'project' => $project,
'title' => t('Remove an action')
)));
}
/**
* Remove an action
*
* @access public
*/
public function remove()
{
$this->checkCSRFParam();
$project = $this->getProject();
$action = $this->actionModel->getById($this->request->getIntegerParam('action_id'));
if (! empty($action) && $this->actionModel->remove($action['id'])) {
$this->flash->success(t('Action removed successfully.'));
} else {
$this->flash->failure(t('Unable to remove this action.'));
}
$this->response->redirect($this->helper->url->to('ActionController', 'index', array('project_id' => $project['id'])));
}
}

View file

@ -1,122 +0,0 @@
<?php
namespace Kanboard\Controller;
/**
* Action Creation Controller
*
* @package Kanboard\Controller
* @author Frederic Guillot
*/
class ActionCreationController extends BaseController
{
/**
* Show the form (step 1)
*
* @access public
*/
public function create()
{
$project = $this->getProject();
$this->response->html($this->template->render('action_creation/create', array(
'project' => $project,
'values' => array('project_id' => $project['id']),
'available_actions' => $this->actionManager->getAvailableActions(),
)));
}
/**
* Choose the event according to the action (step 2)
*
* @access public
*/
public function event()
{
$project = $this->getProject();
$values = $this->request->getValues();
if (empty($values['action_name']) || empty($values['project_id'])) {
return $this->create();
}
return $this->response->html($this->template->render('action_creation/event', array(
'values' => $values,
'project' => $project,
'available_actions' => $this->actionManager->getAvailableActions(),
'events' => $this->actionManager->getCompatibleEvents($values['action_name']),
)));
}
/**
* Define action parameters (step 3)
*
* @access public
*/
public function params()
{
$project = $this->getProject();
$values = $this->request->getValues();
if (empty($values['action_name']) || empty($values['project_id']) || empty($values['event_name'])) {
return $this->create();
}
$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->projectUserRoleModel->getActiveProjectsByUser($this->userSession->getId());
unset($projects_list[$project['id']]);
return $this->response->html($this->template->render('action_creation/params', array(
'values' => $values,
'action_params' => $action_params,
'columns_list' => $this->columnModel->getList($project['id']),
'users_list' => $this->projectUserRoleModel->getAssignableUsersList($project['id']),
'projects_list' => $projects_list,
'colors_list' => $this->colorModel->getList(),
'categories_list' => $this->categoryModel->getList($project['id']),
'links_list' => $this->linkModel->getList(0, false),
'priorities_list' => $this->projectTaskPriorityModel->getPriorities($project),
'project' => $project,
'available_actions' => $this->actionManager->getAvailableActions(),
'events' => $this->actionManager->getCompatibleEvents($values['action_name']),
)));
}
/**
* Save the action (last step)
*
* @access public
*/
public function save()
{
$this->doCreation($this->getProject(), $this->request->getValues());
}
/**
* Common method to save the action
*
* @access private
* @param array $project Project properties
* @param array $values Form values
*/
private function doCreation(array $project, array $values)
{
list($valid, ) = $this->actionValidator->validateCreation($values);
if ($valid) {
if ($this->actionModel->create($values) !== false) {
$this->flash->success(t('Your automatic action have been created successfully.'));
} else {
$this->flash->failure(t('Unable to create your automatic action.'));
}
}
$this->response->redirect($this->helper->url->to('ActionController', 'index', array('project_id' => $project['id'])));
}
}

View file

@ -1,45 +0,0 @@
<?php
namespace Kanboard\Controller;
/**
* Activity Controller
*
* @package Kanboard\Controller
* @author Frederic Guillot
*/
class ActivityController extends BaseController
{
/**
* Activity page for a project
*
* @access public
*/
public function project()
{
$project = $this->getProject();
$this->response->html($this->helper->layout->app('activity/project', array(
'events' => $this->helper->projectActivity->getProjectEvents($project['id']),
'project' => $project,
'title' => t('%s\'s activity', $project['name'])
)));
}
/**
* Display task activities
*
* @access public
*/
public function task()
{
$task = $this->getTask();
$this->response->html($this->helper->layout->task('activity/task', array(
'title' => $task['title'],
'task' => $task,
'project' => $this->projectModel->getById($task['project_id']),
'events' => $this->helper->projectActivity->getTaskEvents($task['id']),
)));
}
}

View file

@ -1,178 +0,0 @@
<?php
namespace Kanboard\Controller;
use Kanboard\Filter\TaskProjectFilter;
use Kanboard\Model\TaskModel;
/**
* Project Analytic Controller
*
* @package Kanboard\Controller
* @author Frederic Guillot
*/
class AnalyticController extends BaseController
{
/**
* Show average Lead and Cycle time
*
* @access public
*/
public function leadAndCycleTime()
{
$project = $this->getProject();
list($from, $to) = $this->getDates();
$this->response->html($this->helper->layout->analytic('analytic/lead_cycle_time', array(
'values' => array(
'from' => $from,
'to' => $to,
),
'project' => $project,
'average' => $this->averageLeadCycleTimeAnalytic->build($project['id']),
'metrics' => $this->projectDailyStatsModel->getRawMetrics($project['id'], $from, $to),
'date_format' => $this->configModel->get('application_date_format'),
'date_formats' => $this->dateParser->getAvailableFormats($this->dateParser->getDateFormats()),
'title' => t('Lead and Cycle time for "%s"', $project['name']),
)));
}
/**
* Show comparison between actual and estimated hours chart
*
* @access public
*/
public function compareHours()
{
$project = $this->getProject();
$paginator = $this->paginator
->setUrl('AnalyticController', 'compareHours', array('project_id' => $project['id']))
->setMax(30)
->setOrder(TaskModel::TABLE.'.id')
->setQuery($this->taskQuery
->withFilter(new TaskProjectFilter($project['id']))
->getQuery()
)
->calculate();
$this->response->html($this->helper->layout->analytic('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
*
* @access public
*/
public function averageTimeByColumn()
{
$project = $this->getProject();
$this->response->html($this->helper->layout->analytic('analytic/avg_time_columns', array(
'project' => $project,
'metrics' => $this->averageTimeSpentColumnAnalytic->build($project['id']),
'title' => t('Average time spent into each column for "%s"', $project['name']),
)));
}
/**
* Show tasks distribution graph
*
* @access public
*/
public function tasks()
{
$project = $this->getProject();
$this->response->html($this->helper->layout->analytic('analytic/tasks', array(
'project' => $project,
'metrics' => $this->taskDistributionAnalytic->build($project['id']),
'title' => t('Task repartition for "%s"', $project['name']),
)));
}
/**
* Show users repartition
*
* @access public
*/
public function users()
{
$project = $this->getProject();
$this->response->html($this->helper->layout->analytic('analytic/users', array(
'project' => $project,
'metrics' => $this->userDistributionAnalytic->build($project['id']),
'title' => t('User repartition for "%s"', $project['name']),
)));
}
/**
* Show cumulative flow diagram
*
* @access public
*/
public function cfd()
{
$this->commonAggregateMetrics('analytic/cfd', 'total', 'Cumulative flow diagram for "%s"');
}
/**
* Show burndown chart
*
* @access public
*/
public function burndown()
{
$this->commonAggregateMetrics('analytic/burndown', 'score', 'Burndown chart for "%s"');
}
/**
* Common method for CFD and Burdown chart
*
* @access private
* @param string $template
* @param string $column
* @param string $title
*/
private function commonAggregateMetrics($template, $column, $title)
{
$project = $this->getProject();
list($from, $to) = $this->getDates();
$display_graph = $this->projectDailyColumnStatsModel->countDays($project['id'], $from, $to) >= 2;
$this->response->html($this->helper->layout->analytic($template, array(
'values' => array(
'from' => $from,
'to' => $to,
),
'display_graph' => $display_graph,
'metrics' => $display_graph ? $this->projectDailyColumnStatsModel->getAggregatedMetrics($project['id'], $from, $to, $column) : array(),
'project' => $project,
'date_format' => $this->configModel->get('application_date_format'),
'date_formats' => $this->dateParser->getAvailableFormats($this->dateParser->getDateFormats()),
'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);
}
}

Some files were not shown because too many files have changed in this diff Show more