summaryrefslogtreecommitdiff
path: root/app/Model
diff options
context:
space:
mode:
Diffstat (limited to 'app/Model')
-rw-r--r--app/Model/Action.php2
-rw-r--r--app/Model/ActionParameter.php4
-rw-r--r--app/Model/Base.php99
-rw-r--r--app/Model/Board.php272
-rw-r--r--app/Model/Category.php26
-rw-r--r--app/Model/Color.php3
-rw-r--r--app/Model/Column.php209
-rw-r--r--app/Model/Config.php5
-rw-r--r--app/Model/File.php302
-rw-r--r--app/Model/LastLogin.php22
-rw-r--r--app/Model/Link.php2
-rw-r--r--app/Model/Metadata.php1
-rw-r--r--app/Model/Notification.php4
-rw-r--r--app/Model/NotificationType.php2
-rw-r--r--app/Model/OverdueNotification.php58
-rw-r--r--app/Model/PasswordReset.php2
-rw-r--r--app/Model/Project.php8
-rw-r--r--app/Model/ProjectDailyColumnStats.php2
-rw-r--r--app/Model/ProjectDuplication.php140
-rw-r--r--app/Model/ProjectFile.php40
-rw-r--r--app/Model/ProjectGroupRole.php3
-rw-r--r--app/Model/ProjectPermission.php3
-rw-r--r--app/Model/ProjectUserRole.php5
-rw-r--r--app/Model/Setting.php1
-rw-r--r--app/Model/Subtask.php106
-rw-r--r--app/Model/SubtaskExport.php119
-rw-r--r--app/Model/SubtaskTimeTracking.php10
-rw-r--r--app/Model/Swimlane.php237
-rw-r--r--app/Model/Task.php24
-rw-r--r--app/Model/TaskAnalytic.php2
-rw-r--r--app/Model/TaskCreation.php11
-rw-r--r--app/Model/TaskDuplication.php9
-rw-r--r--app/Model/TaskExport.php145
-rw-r--r--app/Model/TaskExternalLink.php99
-rw-r--r--app/Model/TaskFile.php54
-rw-r--r--app/Model/TaskFilter.php2
-rw-r--r--app/Model/TaskFinder.php38
-rw-r--r--app/Model/TaskImport.php156
-rw-r--r--app/Model/TaskLink.php8
-rw-r--r--app/Model/TaskModification.php11
-rw-r--r--app/Model/TaskPermission.php3
-rw-r--r--app/Model/Transition.php94
-rw-r--r--app/Model/User.php67
-rw-r--r--app/Model/UserImport.php118
44 files changed, 984 insertions, 1544 deletions
diff --git a/app/Model/Action.php b/app/Model/Action.php
index 4da2fb8f..f055d9d0 100644
--- a/app/Model/Action.php
+++ b/app/Model/Action.php
@@ -151,7 +151,7 @@ class Action extends Base
*
* @author Antonio Rabelo
* @param integer $src_project_id Source project id
- * @return integer $dst_project_id Destination project id
+ * @param integer $dst_project_id Destination project id
* @return boolean
*/
public function duplicate($src_project_id, $dst_project_id)
diff --git a/app/Model/ActionParameter.php b/app/Model/ActionParameter.php
index 62b03142..53edcbc8 100644
--- a/app/Model/ActionParameter.php
+++ b/app/Model/ActionParameter.php
@@ -150,8 +150,8 @@ class ActionParameter extends Base
case 'dest_column_id':
case 'dst_column_id':
case 'column_id':
- $column = $this->board->getColumn($value);
- return empty($column) ? false : $this->board->getColumnIdByTitle($project_id, $column['title']) ?: false;
+ $column = $this->column->getById($value);
+ return empty($column) ? false : $this->column->getColumnIdByTitle($project_id, $column['title']) ?: false;
case 'user_id':
case 'owner_id':
return $this->projectPermission->isAssignable($project_id, $value) ? $value : false;
diff --git a/app/Model/Base.php b/app/Model/Base.php
index 6fe3d74a..714b4308 100644
--- a/app/Model/Base.php
+++ b/app/Model/Base.php
@@ -33,86 +33,6 @@ abstract class Base extends \Kanboard\Core\Base
}
/**
- * Remove keys from an array
- *
- * @access public
- * @param array $values Input array
- * @param string[] $keys List of keys to remove
- */
- public function removeFields(array &$values, array $keys)
- {
- foreach ($keys as $key) {
- if (array_key_exists($key, $values)) {
- unset($values[$key]);
- }
- }
- }
-
- /**
- * Remove keys from an array if empty
- *
- * @access public
- * @param array $values Input array
- * @param string[] $keys List of keys to remove
- */
- public function removeEmptyFields(array &$values, array $keys)
- {
- foreach ($keys as $key) {
- if (array_key_exists($key, $values) && empty($values[$key])) {
- unset($values[$key]);
- }
- }
- }
-
- /**
- * Force fields to be at 0 if empty
- *
- * @access public
- * @param array $values Input array
- * @param string[] $keys List of keys
- */
- public function resetFields(array &$values, array $keys)
- {
- foreach ($keys as $key) {
- if (isset($values[$key]) && empty($values[$key])) {
- $values[$key] = 0;
- }
- }
- }
-
- /**
- * Force some fields to be integer
- *
- * @access public
- * @param array $values Input array
- * @param string[] $keys List of keys
- */
- public function convertIntegerFields(array &$values, array $keys)
- {
- foreach ($keys as $key) {
- if (isset($values[$key])) {
- $values[$key] = (int) $values[$key];
- }
- }
- }
-
- /**
- * Force some fields to be null if empty
- *
- * @access public
- * @param array $values Input array
- * @param string[] $keys List of keys
- */
- public function convertNullFields(array &$values, array $keys)
- {
- foreach ($keys as $key) {
- if (array_key_exists($key, $values) && empty($values[$key])) {
- $values[$key] = null;
- }
- }
- }
-
- /**
* Build SQL condition for a given time range
*
* @access protected
@@ -135,23 +55,4 @@ abstract class Base extends \Kanboard\Core\Base
return $start_column.' IS NOT NULL AND '.$start_column.' > 0 AND ('.implode(' OR ', $conditions).')';
}
-
- /**
- * 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;
- }
}
diff --git a/app/Model/Board.php b/app/Model/Board.php
index 0f980f68..d41ecafe 100644
--- a/app/Model/Board.php
+++ b/app/Model/Board.php
@@ -2,8 +2,6 @@
namespace Kanboard\Model;
-use PicoDb\Database;
-
/**
* Board model
*
@@ -13,13 +11,6 @@ use PicoDb\Database;
class Board extends Base
{
/**
- * SQL table name
- *
- * @var string
- */
- const TABLE = 'columns';
-
- /**
* Get Kanboard default columns
*
* @access public
@@ -73,7 +64,7 @@ class Board extends Base
'description' => $column['description'],
);
- if (! $this->db->table(self::TABLE)->save($values)) {
+ if (! $this->db->table(Column::TABLE)->save($values)) {
return false;
}
}
@@ -86,12 +77,12 @@ class Board extends Base
*
* @author Antonio Rabelo
* @param integer $project_from Project Template
- * @return integer $project_to Project that receives the copy
+ * @param integer $project_to Project that receives the copy
* @return boolean
*/
public function duplicate($project_from, $project_to)
{
- $columns = $this->db->table(Board::TABLE)
+ $columns = $this->db->table(Column::TABLE)
->columns('title', 'task_limit', 'description')
->eq('project_id', $project_from)
->asc('position')
@@ -101,134 +92,6 @@ class Board extends Base
}
/**
- * Add a new column to the board
- *
- * @access public
- * @param integer $project_id Project id
- * @param string $title Column title
- * @param integer $task_limit Task limit
- * @param string $description Column description
- * @return boolean|integer
- */
- public function addColumn($project_id, $title, $task_limit = 0, $description = '')
- {
- $values = array(
- 'project_id' => $project_id,
- 'title' => $title,
- 'task_limit' => intval($task_limit),
- 'position' => $this->getLastColumnPosition($project_id) + 1,
- 'description' => $description,
- );
-
- return $this->persist(self::TABLE, $values);
- }
-
- /**
- * Update a column
- *
- * @access public
- * @param integer $column_id Column id
- * @param string $title Column title
- * @param integer $task_limit Task limit
- * @param string $description Optional description
- * @return boolean
- */
- public function updateColumn($column_id, $title, $task_limit = 0, $description = '')
- {
- return $this->db->table(self::TABLE)->eq('id', $column_id)->update(array(
- 'title' => $title,
- 'task_limit' => intval($task_limit),
- 'description' => $description,
- ));
- }
-
- /**
- * Get columns with consecutive positions
- *
- * If you remove a column, the positions are not anymore consecutives
- *
- * @access public
- * @param integer $project_id
- * @return array
- */
- public function getNormalizedColumnPositions($project_id)
- {
- $columns = $this->db->hashtable(self::TABLE)->eq('project_id', $project_id)->asc('position')->getAll('id', 'position');
- $position = 1;
-
- foreach ($columns as $column_id => $column_position) {
- $columns[$column_id] = $position++;
- }
-
- return $columns;
- }
-
- /**
- * Save the new positions for a set of columns
- *
- * @access public
- * @param array $columns Hashmap of column_id/column_position
- * @return boolean
- */
- public function saveColumnPositions(array $columns)
- {
- return $this->db->transaction(function (Database $db) use ($columns) {
-
- foreach ($columns as $column_id => $position) {
- if (! $db->table(Board::TABLE)->eq('id', $column_id)->update(array('position' => $position))) {
- return false;
- }
- }
- });
- }
-
- /**
- * Move a column down, increment the column position value
- *
- * @access public
- * @param integer $project_id Project id
- * @param integer $column_id Column id
- * @return boolean
- */
- public function moveDown($project_id, $column_id)
- {
- $columns = $this->getNormalizedColumnPositions($project_id);
- $positions = array_flip($columns);
-
- if (isset($columns[$column_id]) && $columns[$column_id] < count($columns)) {
- $position = ++$columns[$column_id];
- $columns[$positions[$position]]--;
-
- return $this->saveColumnPositions($columns);
- }
-
- return false;
- }
-
- /**
- * Move a column up, decrement the column position value
- *
- * @access public
- * @param integer $project_id Project id
- * @param integer $column_id Column id
- * @return boolean
- */
- public function moveUp($project_id, $column_id)
- {
- $columns = $this->getNormalizedColumnPositions($project_id);
- $positions = array_flip($columns);
-
- if (isset($columns[$column_id]) && $columns[$column_id] > 1) {
- $position = --$columns[$column_id];
- $columns[$positions[$position]]++;
-
- return $this->saveColumnPositions($columns);
- }
-
- return false;
- }
-
- /**
* Get all tasks sorted by columns and swimlanes
*
* @access public
@@ -239,7 +102,7 @@ class Board extends Base
public function getBoard($project_id, $callback = null)
{
$swimlanes = $this->swimlane->getSwimlanes($project_id);
- $columns = $this->getColumns($project_id);
+ $columns = $this->column->getAll($project_id);
$nb_columns = count($columns);
for ($i = 0, $ilen = count($swimlanes); $i < $ilen; $i++) {
@@ -307,131 +170,4 @@ class Board extends Base
return $prepend ? array(-1 => t('All columns')) + $listing : $listing;
}
-
- /**
- * Get the first column id for a given project
- *
- * @access public
- * @param integer $project_id Project id
- * @return integer
- */
- public function getFirstColumn($project_id)
- {
- return $this->db->table(self::TABLE)->eq('project_id', $project_id)->asc('position')->findOneColumn('id');
- }
-
- /**
- * Get the last column id for a given project
- *
- * @access public
- * @param integer $project_id Project id
- * @return integer
- */
- public function getLastColumn($project_id)
- {
- return $this->db->table(self::TABLE)->eq('project_id', $project_id)->desc('position')->findOneColumn('id');
- }
-
- /**
- * Get the list of columns sorted by position [ column_id => title ]
- *
- * @access public
- * @param integer $project_id Project id
- * @param boolean $prepend Prepend a default value
- * @return array
- */
- public function getColumnsList($project_id, $prepend = false)
- {
- $listing = $this->db->hashtable(self::TABLE)->eq('project_id', $project_id)->asc('position')->getAll('id', 'title');
- return $prepend ? array(-1 => t('All columns')) + $listing : $listing;
- }
-
- /**
- * Get all columns sorted by position for a given project
- *
- * @access public
- * @param integer $project_id Project id
- * @return array
- */
- public function getColumns($project_id)
- {
- return $this->db->table(self::TABLE)->eq('project_id', $project_id)->asc('position')->findAll();
- }
-
- /**
- * Get the number of columns for a given project
- *
- * @access public
- * @param integer $project_id Project id
- * @return integer
- */
- public function countColumns($project_id)
- {
- return $this->db->table(self::TABLE)->eq('project_id', $project_id)->count();
- }
-
- /**
- * Get a column by the id
- *
- * @access public
- * @param integer $column_id Column id
- * @return array
- */
- public function getColumn($column_id)
- {
- return $this->db->table(self::TABLE)->eq('id', $column_id)->findOne();
- }
-
- /**
- * Get a column id by the name
- *
- * @access public
- * @param integer $project_id
- * @param string $title
- * @return integer
- */
- public function getColumnIdByTitle($project_id, $title)
- {
- return (int) $this->db->table(self::TABLE)->eq('project_id', $project_id)->eq('title', $title)->findOneColumn('id');
- }
-
- /**
- * Get a column title by the id
- *
- * @access public
- * @param integer $column_id
- * @return integer
- */
- public function getColumnTitleById($column_id)
- {
- return $this->db->table(self::TABLE)->eq('id', $column_id)->findOneColumn('title');
- }
-
- /**
- * Get the position of the last column for a given project
- *
- * @access public
- * @param integer $project_id Project id
- * @return integer
- */
- public function getLastColumnPosition($project_id)
- {
- return (int) $this->db
- ->table(self::TABLE)
- ->eq('project_id', $project_id)
- ->desc('position')
- ->findOneColumn('position');
- }
-
- /**
- * Remove a column and all tasks associated to this column
- *
- * @access public
- * @param integer $column_id Column id
- * @return boolean
- */
- public function removeColumn($column_id)
- {
- return $this->db->table(self::TABLE)->eq('id', $column_id)->remove();
- }
}
diff --git a/app/Model/Category.php b/app/Model/Category.php
index 58cee738..1d5f6546 100644
--- a/app/Model/Category.php
+++ b/app/Model/Category.php
@@ -22,12 +22,11 @@ class Category extends Base
*
* @access public
* @param integer $category_id Category id
- * @param integer $project_id Project id
* @return boolean
*/
- public function exists($category_id, $project_id)
+ public function exists($category_id)
{
- return $this->db->table(self::TABLE)->eq('id', $category_id)->eq('project_id', $project_id)->exists();
+ return $this->db->table(self::TABLE)->eq('id', $category_id)->exists();
}
/**
@@ -115,25 +114,29 @@ class Category extends Base
}
/**
- * Create default cetegories during project creation (transaction already started in Project::create())
+ * Create default categories during project creation (transaction already started in Project::create())
*
* @access public
* @param integer $project_id
+ * @return boolean
*/
public function createDefaultCategories($project_id)
{
+ $results = array();
$categories = explode(',', $this->config->get('project_categories'));
foreach ($categories as $category) {
$category = trim($category);
if (! empty($category)) {
- $this->db->table(self::TABLE)->insert(array(
+ $results[] = $this->db->table(self::TABLE)->insert(array(
'project_id' => $project_id,
'name' => $category,
));
}
}
+
+ return in_array(false, $results, true);
}
/**
@@ -188,16 +191,17 @@ class Category extends Base
*
* @author Antonio Rabelo
* @param integer $src_project_id Source project id
- * @return integer $dst_project_id Destination project id
+ * @param integer $dst_project_id Destination project id
* @return boolean
*/
public function duplicate($src_project_id, $dst_project_id)
{
- $categories = $this->db->table(self::TABLE)
- ->columns('name')
- ->eq('project_id', $src_project_id)
- ->asc('name')
- ->findAll();
+ $categories = $this->db
+ ->table(self::TABLE)
+ ->columns('name', 'description')
+ ->eq('project_id', $src_project_id)
+ ->asc('name')
+ ->findAll();
foreach ($categories as $category) {
$category['project_id'] = $dst_project_id;
diff --git a/app/Model/Color.php b/app/Model/Color.php
index d341dd3c..dee28643 100644
--- a/app/Model/Color.php
+++ b/app/Model/Color.php
@@ -141,6 +141,7 @@ class Color extends Base
* Get available colors
*
* @access public
+ * @param bool $prepend
* @return array
*/
public function getList($prepend = false)
@@ -177,7 +178,7 @@ class Color extends Base
}
/**
- * Get Bordercolor from string
+ * Get border color from string
*
* @access public
* @param string $color_id Color id
diff --git a/app/Model/Column.php b/app/Model/Column.php
new file mode 100644
index 00000000..ccdcb049
--- /dev/null
+++ b/app/Model/Column.php
@@ -0,0 +1,209 @@
+<?php
+
+namespace Kanboard\Model;
+
+/**
+ * Column Model
+ *
+ * @package model
+ * @author Frederic Guillot
+ */
+class Column extends Base
+{
+ /**
+ * SQL table name
+ *
+ * @var string
+ */
+ const TABLE = 'columns';
+
+ /**
+ * Get a column by the id
+ *
+ * @access public
+ * @param integer $column_id Column id
+ * @return array
+ */
+ public function getById($column_id)
+ {
+ return $this->db->table(self::TABLE)->eq('id', $column_id)->findOne();
+ }
+
+ /**
+ * Get the first column id for a given project
+ *
+ * @access public
+ * @param integer $project_id Project id
+ * @return integer
+ */
+ public function getFirstColumnId($project_id)
+ {
+ return $this->db->table(self::TABLE)->eq('project_id', $project_id)->asc('position')->findOneColumn('id');
+ }
+
+ /**
+ * Get the last column id for a given project
+ *
+ * @access public
+ * @param integer $project_id Project id
+ * @return integer
+ */
+ public function getLastColumnId($project_id)
+ {
+ return $this->db->table(self::TABLE)->eq('project_id', $project_id)->desc('position')->findOneColumn('id');
+ }
+
+ /**
+ * Get the position of the last column for a given project
+ *
+ * @access public
+ * @param integer $project_id Project id
+ * @return integer
+ */
+ public function getLastColumnPosition($project_id)
+ {
+ return (int) $this->db
+ ->table(self::TABLE)
+ ->eq('project_id', $project_id)
+ ->desc('position')
+ ->findOneColumn('position');
+ }
+
+ /**
+ * Get a column id by the name
+ *
+ * @access public
+ * @param integer $project_id
+ * @param string $title
+ * @return integer
+ */
+ public function getColumnIdByTitle($project_id, $title)
+ {
+ return (int) $this->db->table(self::TABLE)->eq('project_id', $project_id)->eq('title', $title)->findOneColumn('id');
+ }
+
+ /**
+ * Get a column title by the id
+ *
+ * @access public
+ * @param integer $column_id
+ * @return integer
+ */
+ public function getColumnTitleById($column_id)
+ {
+ return $this->db->table(self::TABLE)->eq('id', $column_id)->findOneColumn('title');
+ }
+
+ /**
+ * Get all columns sorted by position for a given project
+ *
+ * @access public
+ * @param integer $project_id Project id
+ * @return array
+ */
+ public function getAll($project_id)
+ {
+ return $this->db->table(self::TABLE)->eq('project_id', $project_id)->asc('position')->findAll();
+ }
+
+ /**
+ * Get the list of columns sorted by position [ column_id => title ]
+ *
+ * @access public
+ * @param integer $project_id Project id
+ * @param boolean $prepend Prepend a default value
+ * @return array
+ */
+ public function getList($project_id, $prepend = false)
+ {
+ $listing = $this->db->hashtable(self::TABLE)->eq('project_id', $project_id)->asc('position')->getAll('id', 'title');
+ return $prepend ? array(-1 => t('All columns')) + $listing : $listing;
+ }
+
+ /**
+ * Add a new column to the board
+ *
+ * @access public
+ * @param integer $project_id Project id
+ * @param string $title Column title
+ * @param integer $task_limit Task limit
+ * @param string $description Column description
+ * @return boolean|integer
+ */
+ public function create($project_id, $title, $task_limit = 0, $description = '')
+ {
+ $values = array(
+ 'project_id' => $project_id,
+ 'title' => $title,
+ 'task_limit' => intval($task_limit),
+ 'position' => $this->getLastColumnPosition($project_id) + 1,
+ 'description' => $description,
+ );
+
+ return $this->persist(self::TABLE, $values);
+ }
+
+ /**
+ * Update a column
+ *
+ * @access public
+ * @param integer $column_id Column id
+ * @param string $title Column title
+ * @param integer $task_limit Task limit
+ * @param string $description Optional description
+ * @return boolean
+ */
+ public function update($column_id, $title, $task_limit = 0, $description = '')
+ {
+ return $this->db->table(self::TABLE)->eq('id', $column_id)->update(array(
+ 'title' => $title,
+ 'task_limit' => intval($task_limit),
+ 'description' => $description,
+ ));
+ }
+
+ /**
+ * Remove a column and all tasks associated to this column
+ *
+ * @access public
+ * @param integer $column_id Column id
+ * @return boolean
+ */
+ public function remove($column_id)
+ {
+ return $this->db->table(self::TABLE)->eq('id', $column_id)->remove();
+ }
+
+ /**
+ * Change column position
+ *
+ * @access public
+ * @param integer $project_id
+ * @param integer $column_id
+ * @param integer $position
+ * @return boolean
+ */
+ public function changePosition($project_id, $column_id, $position)
+ {
+ if ($position < 1 || $position > $this->db->table(self::TABLE)->eq('project_id', $project_id)->count()) {
+ return false;
+ }
+
+ $column_ids = $this->db->table(self::TABLE)->eq('project_id', $project_id)->neq('id', $column_id)->asc('position')->findAllByColumn('id');
+ $offset = 1;
+ $results = array();
+
+ foreach ($column_ids as $current_column_id) {
+ if ($offset == $position) {
+ $offset++;
+ }
+
+ $results[] = $this->db->table(self::TABLE)->eq('id', $current_column_id)->update(array('position' => $offset));
+ $offset++;
+ }
+
+ $results[] = $this->db->table(self::TABLE)->eq('id', $column_id)->update(array('position' => $position));
+
+ return !in_array(false, $results, true);
+ }
+}
diff --git a/app/Model/Config.php b/app/Model/Config.php
index 55999310..7b254c8d 100644
--- a/app/Model/Config.php
+++ b/app/Model/Config.php
@@ -76,6 +76,7 @@ class Config extends Setting
'en_US' => 'English',
'es_ES' => 'Español',
'fr_FR' => 'Français',
+ 'el_GR' => 'Grec',
'it_IT' => 'Italiano',
'hu_HU' => 'Magyar',
'my_MY' => 'Melayu',
@@ -131,7 +132,8 @@ class Config extends Setting
'zh_CN' => 'zh-cn',
'ja_JP' => 'ja',
'th_TH' => 'th',
- 'id_ID' => 'id'
+ 'id_ID' => 'id',
+ 'el_GR' => 'el',
);
$lang = $this->getCurrentLanguage();
@@ -237,6 +239,7 @@ class Config extends Setting
* Prepare data before save
*
* @access public
+ * @param array $values
* @return array
*/
public function prepare(array $values)
diff --git a/app/Model/File.php b/app/Model/File.php
index be62cdb3..03ea691d 100644
--- a/app/Model/File.php
+++ b/app/Model/File.php
@@ -2,31 +2,44 @@
namespace Kanboard\Model;
+use Exception;
use Kanboard\Event\FileEvent;
use Kanboard\Core\Tool;
use Kanboard\Core\ObjectStorage\ObjectStorageException;
/**
- * File model
+ * Base File Model
*
* @package model
* @author Frederic Guillot
*/
-class File extends Base
+abstract class File extends Base
{
/**
- * SQL table name
- *
- * @var string
- */
- const TABLE = 'files';
-
- /**
- * Events
+ * Get PicoDb query to get all files
*
- * @var string
+ * @access protected
+ * @return \PicoDb\Table
*/
- const EVENT_CREATE = 'file.create';
+ protected function getQuery()
+ {
+ return $this->db
+ ->table(static::TABLE)
+ ->columns(
+ static::TABLE.'.id',
+ static::TABLE.'.name',
+ static::TABLE.'.path',
+ static::TABLE.'.is_image',
+ static::TABLE.'.'.static::FOREIGN_KEY,
+ static::TABLE.'.date',
+ static::TABLE.'.user_id',
+ static::TABLE.'.size',
+ User::TABLE.'.username',
+ User::TABLE.'.name as user_name'
+ )
+ ->join(User::TABLE, 'id', 'user_id')
+ ->asc(static::TABLE.'.name');
+ }
/**
* Get a file by the id
@@ -37,146 +50,120 @@ class File extends Base
*/
public function getById($file_id)
{
- return $this->db->table(self::TABLE)->eq('id', $file_id)->findOne();
+ return $this->db->table(static::TABLE)->eq('id', $file_id)->findOne();
}
/**
- * Remove a file
+ * Get all files
*
* @access public
- * @param integer $file_id File id
- * @return bool
+ * @param integer $id
+ * @return array
*/
- public function remove($file_id)
+ public function getAll($id)
{
- try {
- $file = $this->getbyId($file_id);
- $this->objectStorage->remove($file['path']);
-
- if ($file['is_image'] == 1) {
- $this->objectStorage->remove($this->getThumbnailPath($file['path']));
- }
-
- return $this->db->table(self::TABLE)->eq('id', $file['id'])->remove();
- } catch (ObjectStorageException $e) {
- $this->logger->error($e->getMessage());
- return false;
- }
+ return $this->getQuery()->eq(static::FOREIGN_KEY, $id)->findAll();
}
/**
- * Remove all files for a given task
+ * Get all images
*
* @access public
- * @param integer $task_id Task id
- * @return bool
+ * @param integer $id
+ * @return array
*/
- public function removeAll($task_id)
+ public function getAllImages($id)
{
- $file_ids = $this->db->table(self::TABLE)->eq('task_id', $task_id)->asc('id')->findAllByColumn('id');
- $results = array();
-
- foreach ($file_ids as $file_id) {
- $results[] = $this->remove($file_id);
- }
+ return $this->getQuery()->eq(static::FOREIGN_KEY, $id)->eq('is_image', 1)->findAll();
+ }
- return ! in_array(false, $results, true);
+ /**
+ * Get all files without images
+ *
+ * @access public
+ * @param integer $id
+ * @return array
+ */
+ public function getAllDocuments($id)
+ {
+ return $this->getQuery()->eq(static::FOREIGN_KEY, $id)->eq('is_image', 0)->findAll();
}
/**
* Create a file entry in the database
*
* @access public
- * @param integer $task_id Task id
+ * @param integer $id Foreign key
* @param string $name Filename
* @param string $path Path on the disk
* @param integer $size File size
* @return bool|integer
*/
- public function create($task_id, $name, $path, $size)
+ public function create($id, $name, $path, $size)
{
- $result = $this->db->table(self::TABLE)->save(array(
- 'task_id' => $task_id,
+ $values = array(
+ static::FOREIGN_KEY => $id,
'name' => substr($name, 0, 255),
'path' => $path,
'is_image' => $this->isImage($name) ? 1 : 0,
'size' => $size,
'user_id' => $this->userSession->getId() ?: 0,
'date' => time(),
- ));
+ );
- if ($result) {
- $this->container['dispatcher']->dispatch(
- self::EVENT_CREATE,
- new FileEvent(array('task_id' => $task_id, 'name' => $name))
- );
+ $result = $this->db->table(static::TABLE)->insert($values);
- return (int) $this->db->getLastId();
+ if ($result) {
+ $file_id = (int) $this->db->getLastId();
+ $event = new FileEvent($values + array('file_id' => $file_id));
+ $this->dispatcher->dispatch(static::EVENT_CREATE, $event);
+ return $file_id;
}
return false;
}
/**
- * Get PicoDb query to get all files
+ * Remove all files
*
* @access public
- * @return \PicoDb\Table
+ * @param integer $id
+ * @return bool
*/
- public function getQuery()
+ public function removeAll($id)
{
- return $this->db
- ->table(self::TABLE)
- ->columns(
- self::TABLE.'.id',
- self::TABLE.'.name',
- self::TABLE.'.path',
- self::TABLE.'.is_image',
- self::TABLE.'.task_id',
- self::TABLE.'.date',
- self::TABLE.'.user_id',
- self::TABLE.'.size',
- User::TABLE.'.username',
- User::TABLE.'.name as user_name'
- )
- ->join(User::TABLE, 'id', 'user_id')
- ->asc(self::TABLE.'.name');
- }
+ $file_ids = $this->db->table(static::TABLE)->eq(static::FOREIGN_KEY, $id)->asc('id')->findAllByColumn('id');
+ $results = array();
- /**
- * Get all files for a given task
- *
- * @access public
- * @param integer $task_id Task id
- * @return array
- */
- public function getAll($task_id)
- {
- return $this->getQuery()->eq('task_id', $task_id)->findAll();
- }
+ foreach ($file_ids as $file_id) {
+ $results[] = $this->remove($file_id);
+ }
- /**
- * Get all images for a given task
- *
- * @access public
- * @param integer $task_id Task id
- * @return array
- */
- public function getAllImages($task_id)
- {
- return $this->getQuery()->eq('task_id', $task_id)->eq('is_image', 1)->findAll();
+ return ! in_array(false, $results, true);
}
/**
- * Get all files without images for a given task
+ * Remove a file
*
* @access public
- * @param integer $task_id Task id
- * @return array
+ * @param integer $file_id File id
+ * @return bool
*/
- public function getAllDocuments($task_id)
+ public function remove($file_id)
{
- return $this->getQuery()->eq('task_id', $task_id)->eq('is_image', 0)->findAll();
+ try {
+ $file = $this->getById($file_id);
+ $this->objectStorage->remove($file['path']);
+
+ if ($file['is_image'] == 1) {
+ $this->objectStorage->remove($this->getThumbnailPath($file['path']));
+ }
+
+ return $this->db->table(static::TABLE)->eq('id', $file['id'])->remove();
+ } catch (ObjectStorageException $e) {
+ $this->logger->error($e->getMessage());
+ return false;
+ }
}
/**
@@ -202,127 +189,96 @@ class File extends Base
}
/**
- * Return the image mimetype based on the file extension
+ * Generate the path for a thumbnails
*
* @access public
- * @param $filename
+ * @param string $key Storage key
* @return string
*/
- public function getImageMimeType($filename)
+ public function getThumbnailPath($key)
{
- $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
-
- switch ($extension) {
- case 'jpeg':
- case 'jpg':
- return 'image/jpeg';
- case 'png':
- return 'image/png';
- case 'gif':
- return 'image/gif';
- default:
- return 'image/jpeg';
- }
+ return 'thumbnails'.DIRECTORY_SEPARATOR.$key;
}
/**
* Generate the path for a new filename
*
* @access public
- * @param integer $project_id Project id
- * @param integer $task_id Task id
+ * @param integer $id Foreign key
* @param string $filename Filename
* @return string
*/
- public function generatePath($project_id, $task_id, $filename)
+ public function generatePath($id, $filename)
{
- return $project_id.DIRECTORY_SEPARATOR.$task_id.DIRECTORY_SEPARATOR.hash('sha1', $filename.time());
+ return static::PATH_PREFIX.DIRECTORY_SEPARATOR.$id.DIRECTORY_SEPARATOR.hash('sha1', $filename.time());
}
/**
- * Generate the path for a thumbnails
+ * Upload multiple files
*
* @access public
- * @param string $key Storage key
- * @return string
- */
- public function getThumbnailPath($key)
- {
- return 'thumbnails'.DIRECTORY_SEPARATOR.$key;
- }
-
- /**
- * Handle file upload
- *
- * @access public
- * @param integer $project_id Project id
- * @param integer $task_id Task id
- * @param string $form_name File form name
+ * @param integer $id
+ * @param array $files
* @return bool
*/
- public function uploadFiles($project_id, $task_id, $form_name)
+ public function uploadFiles($id, array $files)
{
try {
- $file = $this->request->getFileInfo($form_name);
-
- if (empty($file)) {
+ if (empty($files)) {
return false;
}
- foreach ($file['error'] as $key => $error) {
- if ($error == UPLOAD_ERR_OK && $file['size'][$key] > 0) {
- $original_filename = $file['name'][$key];
- $uploaded_filename = $file['tmp_name'][$key];
- $destination_filename = $this->generatePath($project_id, $task_id, $original_filename);
-
- if ($this->isImage($original_filename)) {
- $this->generateThumbnailFromFile($uploaded_filename, $destination_filename);
- }
-
- $this->objectStorage->moveUploadedFile($uploaded_filename, $destination_filename);
-
- $this->create(
- $task_id,
- $original_filename,
- $destination_filename,
- $file['size'][$key]
- );
- }
+ foreach (array_keys($files['error']) as $key) {
+ $file = array(
+ 'name' => $files['name'][$key],
+ 'tmp_name' => $files['tmp_name'][$key],
+ 'size' => $files['size'][$key],
+ 'error' => $files['error'][$key],
+ );
+
+ $this->uploadFile($id, $file);
}
return true;
- } catch (ObjectStorageException $e) {
+ } catch (Exception $e) {
$this->logger->error($e->getMessage());
return false;
}
}
/**
- * Handle screenshot upload
+ * Upload a file
*
* @access public
- * @param integer $project_id Project id
- * @param integer $task_id Task id
- * @param string $blob Base64 encoded image
- * @return bool|integer
+ * @param integer $id
+ * @param array $file
*/
- public function uploadScreenshot($project_id, $task_id, $blob)
+ public function uploadFile($id, array $file)
{
- $original_filename = e('Screenshot taken %s', dt('%B %e, %Y at %k:%M %p', time())).'.png';
- return $this->uploadContent($project_id, $task_id, $original_filename, $blob);
+ if ($file['error'] == UPLOAD_ERR_OK && $file['size'] > 0) {
+ $destination_filename = $this->generatePath($id, $file['name']);
+
+ if ($this->isImage($file['name'])) {
+ $this->generateThumbnailFromFile($file['tmp_name'], $destination_filename);
+ }
+
+ $this->objectStorage->moveUploadedFile($file['tmp_name'], $destination_filename);
+ $this->create($id, $file['name'], $destination_filename, $file['size']);
+ } else {
+ throw new Exception('File not uploaded: '.var_export($file['error'], true));
+ }
}
/**
* Handle file upload (base64 encoded content)
*
* @access public
- * @param integer $project_id Project id
- * @param integer $task_id Task id
- * @param string $original_filename Filename
- * @param string $blob Base64 encoded file
+ * @param integer $id
+ * @param string $original_filename
+ * @param string $blob
* @return bool|integer
*/
- public function uploadContent($project_id, $task_id, $original_filename, $blob)
+ public function uploadContent($id, $original_filename, $blob)
{
try {
$data = base64_decode($blob);
@@ -331,7 +287,7 @@ class File extends Base
return false;
}
- $destination_filename = $this->generatePath($project_id, $task_id, $original_filename);
+ $destination_filename = $this->generatePath($id, $original_filename);
$this->objectStorage->put($destination_filename, $data);
if ($this->isImage($original_filename)) {
@@ -339,7 +295,7 @@ class File extends Base
}
return $this->create(
- $task_id,
+ $id,
$original_filename,
$destination_filename,
strlen($data)
diff --git a/app/Model/LastLogin.php b/app/Model/LastLogin.php
index f5be020e..feb5f5a3 100644
--- a/app/Model/LastLogin.php
+++ b/app/Model/LastLogin.php
@@ -39,14 +39,14 @@ class LastLogin extends Base
$this->cleanup($user_id);
return $this->db
- ->table(self::TABLE)
- ->insert(array(
- 'auth_type' => $auth_type,
- 'user_id' => $user_id,
- 'ip' => $ip,
- 'user_agent' => substr($user_agent, 0, 255),
- 'date_creation' => time(),
- ));
+ ->table(self::TABLE)
+ ->insert(array(
+ 'auth_type' => $auth_type,
+ 'user_id' => $user_id,
+ 'ip' => $ip,
+ 'user_agent' => substr($user_agent, 0, 255),
+ 'date_creation' => time(),
+ ));
}
/**
@@ -65,9 +65,9 @@ class LastLogin extends Base
if (count($connections) >= self::NB_LOGINS) {
$this->db->table(self::TABLE)
- ->eq('user_id', $user_id)
- ->notin('id', array_slice($connections, 0, self::NB_LOGINS - 1))
- ->remove();
+ ->eq('user_id', $user_id)
+ ->notin('id', array_slice($connections, 0, self::NB_LOGINS - 1))
+ ->remove();
}
}
diff --git a/app/Model/Link.php b/app/Model/Link.php
index 7b81a237..903a98d6 100644
--- a/app/Model/Link.php
+++ b/app/Model/Link.php
@@ -90,7 +90,7 @@ class Link extends Base
*
* @access public
* @param integer $exclude_id Exclude this link
- * @param booelan $prepend Prepend default value
+ * @param boolean $prepend Prepend default value
* @return array
*/
public function getList($exclude_id = 0, $prepend = true)
diff --git a/app/Model/Metadata.php b/app/Model/Metadata.php
index 690b2265..cb66c717 100644
--- a/app/Model/Metadata.php
+++ b/app/Model/Metadata.php
@@ -76,6 +76,7 @@ abstract class Metadata extends Base
* @access public
* @param integer $entity_id
* @param array $values
+ * @return boolean
*/
public function save($entity_id, array $values)
{
diff --git a/app/Model/Notification.php b/app/Model/Notification.php
index 87c1a796..c252aa31 100644
--- a/app/Model/Notification.php
+++ b/app/Model/Notification.php
@@ -72,7 +72,7 @@ class Notification extends Base
return e('%s updated a comment on the task #%d', $event_author, $event_data['task']['id']);
case Comment::EVENT_CREATE:
return e('%s commented on the task #%d', $event_author, $event_data['task']['id']);
- case File::EVENT_CREATE:
+ case TaskFile::EVENT_CREATE:
return e('%s attached a file to the task #%d', $event_author, $event_data['task']['id']);
case Task::EVENT_USER_MENTION:
return e('%s mentioned you in the task #%d', $event_author, $event_data['task']['id']);
@@ -94,7 +94,7 @@ class Notification extends Base
public function getTitleWithoutAuthor($event_name, array $event_data)
{
switch ($event_name) {
- case File::EVENT_CREATE:
+ case TaskFile::EVENT_CREATE:
return e('New attachment on task #%d: %s', $event_data['file']['task_id'], $event_data['file']['name']);
case Comment::EVENT_CREATE:
return e('New comment on task #%d', $event_data['comment']['task_id']);
diff --git a/app/Model/NotificationType.php b/app/Model/NotificationType.php
index 9d8317b7..289aae9c 100644
--- a/app/Model/NotificationType.php
+++ b/app/Model/NotificationType.php
@@ -80,7 +80,7 @@ abstract class NotificationType extends Base
*
* @access public
* @param string $type
- * @return NotificationInterface
+ * @return \Kanboard\Notification\NotificationInterface
*/
public function getType($type)
{
diff --git a/app/Model/OverdueNotification.php b/app/Model/OverdueNotification.php
deleted file mode 100644
index 84565548..00000000
--- a/app/Model/OverdueNotification.php
+++ /dev/null
@@ -1,58 +0,0 @@
-<?php
-
-namespace Kanboard\Model;
-
-/**
- * Task Overdue Notification model
- *
- * @package model
- * @author Frederic Guillot
- */
-class OverdueNotification extends Base
-{
- /**
- * Send overdue tasks
- *
- * @access public
- */
- public function sendOverdueTaskNotifications()
- {
- $tasks = $this->taskFinder->getOverdueTasks();
-
- foreach ($this->groupByColumn($tasks, 'project_id') as $project_id => $project_tasks) {
- $users = $this->userNotification->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();
-
- foreach ($tasks as $task) {
- if ($this->userNotificationFilter->shouldReceiveNotification($user, array('task' => $task))) {
- $user_tasks[] = $task;
- }
- }
-
- if (! empty($user_tasks)) {
- $this->userNotification->sendUserNotification(
- $user,
- Task::EVENT_OVERDUE,
- array('tasks' => $user_tasks, 'project_name' => $tasks[0]['project_name'])
- );
- }
- }
-}
diff --git a/app/Model/PasswordReset.php b/app/Model/PasswordReset.php
index c2d7dde9..5cfd3c97 100644
--- a/app/Model/PasswordReset.php
+++ b/app/Model/PasswordReset.php
@@ -20,7 +20,7 @@ class PasswordReset extends Base
/**
* Token duration (30 minutes)
*
- * @var string
+ * @var integer
*/
const DURATION = 1800;
diff --git a/app/Model/Project.php b/app/Model/Project.php
index d0a8bfc8..d2e5b7ce 100644
--- a/app/Model/Project.php
+++ b/app/Model/Project.php
@@ -241,7 +241,7 @@ class Project extends Base
{
$stats = array();
$stats['nb_active_tasks'] = 0;
- $columns = $this->board->getColumns($project_id);
+ $columns = $this->column->getAll($project_id);
$column_stats = $this->board->getColumnStats($project_id);
foreach ($columns as &$column) {
@@ -265,7 +265,7 @@ class Project extends Base
*/
public function getColumnStats(array &$project)
{
- $project['columns'] = $this->board->getColumns($project['id']);
+ $project['columns'] = $this->column->getAll($project['id']);
$stats = $this->board->getColumnStats($project['id']);
foreach ($project['columns'] as &$column) {
@@ -334,7 +334,7 @@ class Project extends Base
$values['identifier'] = strtoupper($values['identifier']);
}
- $this->convertIntegerFields($values, array('priority_default', 'priority_start', 'priority_end'));
+ $this->helper->model->convertIntegerFields($values, array('priority_default', 'priority_start', 'priority_end'));
if (! $this->db->table(self::TABLE)->save($values)) {
$this->db->cancelTransaction();
@@ -402,7 +402,7 @@ class Project extends Base
$values['identifier'] = strtoupper($values['identifier']);
}
- $this->convertIntegerFields($values, array('priority_default', 'priority_start', 'priority_end'));
+ $this->helper->model->convertIntegerFields($values, array('priority_default', 'priority_start', 'priority_end'));
return $this->exists($values['id']) &&
$this->db->table(self::TABLE)->eq('id', $values['id'])->save($values);
diff --git a/app/Model/ProjectDailyColumnStats.php b/app/Model/ProjectDailyColumnStats.php
index cf79be84..2bcc4d55 100644
--- a/app/Model/ProjectDailyColumnStats.php
+++ b/app/Model/ProjectDailyColumnStats.php
@@ -84,7 +84,7 @@ class ProjectDailyColumnStats extends Base
*/
public function getAggregatedMetrics($project_id, $from, $to, $field = 'total')
{
- $columns = $this->board->getColumnsList($project_id);
+ $columns = $this->column->getList($project_id);
$metrics = $this->getMetrics($project_id, $from, $to);
return $this->buildAggregate($metrics, $columns, $field);
}
diff --git a/app/Model/ProjectDuplication.php b/app/Model/ProjectDuplication.php
index f0c66834..9c5f80ad 100644
--- a/app/Model/ProjectDuplication.php
+++ b/app/Model/ProjectDuplication.php
@@ -2,6 +2,8 @@
namespace Kanboard\Model;
+use Kanboard\Core\Security\Role;
+
/**
* Project Duplication
*
@@ -12,6 +14,28 @@ namespace Kanboard\Model;
class ProjectDuplication extends Base
{
/**
+ * Get list of optional models to duplicate
+ *
+ * @access public
+ * @return string[]
+ */
+ public function getOptionalSelection()
+ {
+ return array('category', 'projectPermission', 'action', 'swimlane', 'task');
+ }
+
+ /**
+ * Get list of all possible models to duplicate
+ *
+ * @access public
+ * @return string[]
+ */
+ public function getPossibleSelection()
+ {
+ return array('board', 'category', 'projectPermission', 'action', 'swimlane', 'task');
+ }
+
+ /**
* Get a valid project name for the duplication
*
* @access public
@@ -31,78 +55,106 @@ class ProjectDuplication extends Base
}
/**
- * Create a project from another one
- *
- * @param integer $project_id Project Id
- * @return integer Cloned Project Id
- */
- public function copy($project_id)
- {
- $project = $this->project->getById($project_id);
-
- $values = array(
- 'name' => $this->getClonedProjectName($project['name']),
- 'is_active' => true,
- 'last_modified' => 0,
- 'token' => '',
- 'is_public' => 0,
- 'is_private' => empty($project['is_private']) ? 0 : 1,
- );
-
- if (! $this->db->table(Project::TABLE)->save($values)) {
- return 0;
- }
-
- return $this->db->getLastId();
- }
-
- /**
* Clone a project with all settings
*
- * @param integer $project_id Project Id
- * @param array $part_selection Selection of optional project parts to duplicate. Possible options: 'swimlane', 'action', 'category', 'task'
- * @return integer Cloned Project Id
+ * @param integer $src_project_id Project Id
+ * @param array $selection Selection of optional project parts to duplicate
+ * @param integer $owner_id Owner of the project
+ * @param string $name Name of the project
+ * @param boolean $private Force the project to be private
+ * @return integer Cloned Project Id
*/
- public function duplicate($project_id, $part_selection = array('category', 'action'))
+ public function duplicate($src_project_id, $selection = array('projectPermission', 'category', 'action'), $owner_id = 0, $name = null, $private = null)
{
$this->db->startTransaction();
// Get the cloned project Id
- $clone_project_id = $this->copy($project_id);
+ $dst_project_id = $this->copy($src_project_id, $owner_id, $name, $private);
- if (! $clone_project_id) {
+ if ($dst_project_id === false) {
$this->db->cancelTransaction();
return false;
}
// Clone Columns, Categories, Permissions and Actions
- $optional_parts = array('swimlane', 'action', 'category');
- foreach (array('board', 'category', 'projectPermission', 'action', 'swimlane') as $model) {
+ foreach ($this->getPossibleSelection() as $model) {
// Skip if optional part has not been selected
- if (in_array($model, $optional_parts) && ! in_array($model, $part_selection)) {
+ if (in_array($model, $this->getOptionalSelection()) && ! in_array($model, $selection)) {
continue;
}
- if (! $this->$model->duplicate($project_id, $clone_project_id)) {
+ // Skip permissions for private projects
+ if ($private && $model === 'projectPermission') {
+ continue;
+ }
+
+ if (! $this->$model->duplicate($src_project_id, $dst_project_id)) {
$this->db->cancelTransaction();
return false;
}
}
+ if (! $this->makeOwnerManager($dst_project_id, $owner_id)) {
+ $this->db->cancelTransaction();
+ return false;
+ }
+
$this->db->closeTransaction();
- // Clone Tasks if in $part_selection
- if (in_array('task', $part_selection)) {
- $tasks = $this->taskFinder->getAll($project_id);
+ return (int) $dst_project_id;
+ }
+
+ /**
+ * Create a project from another one
+ *
+ * @access private
+ * @param integer $src_project_id
+ * @param integer $owner_id
+ * @param string $name
+ * @param boolean $private
+ * @return integer
+ */
+ private function copy($src_project_id, $owner_id = 0, $name = null, $private = null)
+ {
+ $project = $this->project->getById($src_project_id);
+ $is_private = empty($project['is_private']) ? 0 : 1;
+
+ $values = array(
+ 'name' => $name ?: $this->getClonedProjectName($project['name']),
+ 'is_active' => 1,
+ 'last_modified' => time(),
+ 'token' => '',
+ 'is_public' => 0,
+ 'is_private' => $private ? 1 : $is_private,
+ 'owner_id' => $owner_id,
+ );
+
+ if (! $this->db->table(Project::TABLE)->save($values)) {
+ return false;
+ }
+
+ return $this->db->getLastId();
+ }
+
+ /**
+ * Make sure that the creator of the duplicated project is alsp owner
+ *
+ * @access private
+ * @param integer $dst_project_id
+ * @param integer $owner_id
+ * @return boolean
+ */
+ private function makeOwnerManager($dst_project_id, $owner_id)
+ {
+ if ($owner_id > 0) {
+ $this->projectUserRole->removeUser($dst_project_id, $owner_id);
- foreach ($tasks as $task) {
- if (! $this->taskDuplication->duplicateToProject($task['id'], $clone_project_id)) {
- return false;
- }
+ if (! $this->projectUserRole->addUser($dst_project_id, $owner_id, Role::PROJECT_MANAGER)) {
+ return false;
}
}
- return (int) $clone_project_id;
+ return true;
}
}
diff --git a/app/Model/ProjectFile.php b/app/Model/ProjectFile.php
new file mode 100644
index 00000000..aa9bf15b
--- /dev/null
+++ b/app/Model/ProjectFile.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace Kanboard\Model;
+
+/**
+ * Project File Model
+ *
+ * @package model
+ * @author Frederic Guillot
+ */
+class ProjectFile extends File
+{
+ /**
+ * SQL table name
+ *
+ * @var string
+ */
+ const TABLE = 'project_has_files';
+
+ /**
+ * SQL foreign key
+ *
+ * @var string
+ */
+ const FOREIGN_KEY = 'project_id';
+
+ /**
+ * Path prefix
+ *
+ * @var string
+ */
+ const PATH_PREFIX = 'projects';
+
+ /**
+ * Events
+ *
+ * @var string
+ */
+ const EVENT_CREATE = 'project.file.create';
+}
diff --git a/app/Model/ProjectGroupRole.php b/app/Model/ProjectGroupRole.php
index 591b28c6..afad4a44 100644
--- a/app/Model/ProjectGroupRole.php
+++ b/app/Model/ProjectGroupRole.php
@@ -106,6 +106,7 @@ class ProjectGroupRole extends Base
->join(GroupMember::TABLE, 'user_id', 'id', User::TABLE)
->join(self::TABLE, 'group_id', 'group_id', GroupMember::TABLE)
->eq(self::TABLE.'.project_id', $project_id)
+ ->eq(User::TABLE.'.is_active', 1)
->in(self::TABLE.'.role', array(Role::PROJECT_MANAGER, Role::PROJECT_MEMBER))
->asc(User::TABLE.'.username')
->findAll();
@@ -165,7 +166,7 @@ class ProjectGroupRole extends Base
* Copy group access from a project to another one
*
* @param integer $project_src_id Project Template
- * @return integer $project_dst_id Project that receives the copy
+ * @param integer $project_dst_id Project that receives the copy
* @return boolean
*/
public function duplicate($project_src_id, $project_dst_id)
diff --git a/app/Model/ProjectPermission.php b/app/Model/ProjectPermission.php
index cea62e13..db1573ae 100644
--- a/app/Model/ProjectPermission.php
+++ b/app/Model/ProjectPermission.php
@@ -107,7 +107,8 @@ class ProjectPermission extends Base
*/
public function isAssignable($project_id, $user_id)
{
- return in_array($this->projectUserRole->getUserRole($project_id, $user_id), array(Role::PROJECT_MEMBER, Role::PROJECT_MANAGER));
+ return $this->user->isActive($user_id) &&
+ in_array($this->projectUserRole->getUserRole($project_id, $user_id), array(Role::PROJECT_MEMBER, Role::PROJECT_MANAGER));
}
/**
diff --git a/app/Model/ProjectUserRole.php b/app/Model/ProjectUserRole.php
index 8149a253..56da679c 100644
--- a/app/Model/ProjectUserRole.php
+++ b/app/Model/ProjectUserRole.php
@@ -152,13 +152,14 @@ class ProjectUserRole extends Base
public function getAssignableUsers($project_id)
{
if ($this->projectPermission->isEverybodyAllowed($project_id)) {
- return $this->user->getList();
+ return $this->user->getActiveUsersList();
}
$userMembers = $this->db->table(self::TABLE)
->columns(User::TABLE.'.id', User::TABLE.'.username', User::TABLE.'.name')
->join(User::TABLE, 'id', 'user_id')
- ->eq('project_id', $project_id)
+ ->eq(User::TABLE.'.is_active', 1)
+ ->eq(self::TABLE.'.project_id', $project_id)
->in(self::TABLE.'.role', array(Role::PROJECT_MANAGER, Role::PROJECT_MEMBER))
->findAll();
diff --git a/app/Model/Setting.php b/app/Model/Setting.php
index 44e6c065..6d29c6ec 100644
--- a/app/Model/Setting.php
+++ b/app/Model/Setting.php
@@ -75,6 +75,7 @@ abstract class Setting extends Base
*
* @access public
* @param array $values
+ * @return boolean
*/
public function save(array $values)
{
diff --git a/app/Model/Subtask.php b/app/Model/Subtask.php
index 0e039bb3..3f5cfe83 100644
--- a/app/Model/Subtask.php
+++ b/app/Model/Subtask.php
@@ -168,8 +168,8 @@ class Subtask extends Base
*/
public function prepare(array &$values)
{
- $this->removeFields($values, array('another_subtask'));
- $this->resetFields($values, array('time_estimated', 'time_spent'));
+ $this->helper->model->removeFields($values, array('another_subtask'));
+ $this->helper->model->resetFields($values, array('time_estimated', 'time_spent'));
}
/**
@@ -263,89 +263,36 @@ class Subtask extends Base
}
/**
- * Get subtasks with consecutive positions
- *
- * If you remove a subtask, the positions are not anymore consecutives
- *
- * @access public
- * @param integer $task_id
- * @return array
- */
- public function getNormalizedPositions($task_id)
- {
- $subtasks = $this->db->hashtable(self::TABLE)->eq('task_id', $task_id)->asc('position')->getAll('id', 'position');
- $position = 1;
-
- foreach ($subtasks as $subtask_id => $subtask_position) {
- $subtasks[$subtask_id] = $position++;
- }
-
- return $subtasks;
- }
-
- /**
- * Save the new positions for a set of subtasks
- *
- * @access public
- * @param array $subtasks Hashmap of column_id/column_position
- * @return boolean
- */
- public function savePositions(array $subtasks)
- {
- return $this->db->transaction(function (Database $db) use ($subtasks) {
-
- foreach ($subtasks as $subtask_id => $position) {
- if (! $db->table(Subtask::TABLE)->eq('id', $subtask_id)->update(array('position' => $position))) {
- return false;
- }
- }
- });
- }
-
- /**
- * Move a subtask down, increment the position value
+ * Save subtask position
*
* @access public
* @param integer $task_id
* @param integer $subtask_id
+ * @param integer $position
* @return boolean
*/
- public function moveDown($task_id, $subtask_id)
+ public function changePosition($task_id, $subtask_id, $position)
{
- $subtasks = $this->getNormalizedPositions($task_id);
- $positions = array_flip($subtasks);
-
- if (isset($subtasks[$subtask_id]) && $subtasks[$subtask_id] < count($subtasks)) {
- $position = ++$subtasks[$subtask_id];
- $subtasks[$positions[$position]]--;
-
- return $this->savePositions($subtasks);
+ if ($position < 1 || $position > $this->db->table(self::TABLE)->eq('task_id', $task_id)->count()) {
+ return false;
}
- return false;
- }
+ $subtask_ids = $this->db->table(self::TABLE)->eq('task_id', $task_id)->neq('id', $subtask_id)->asc('position')->findAllByColumn('id');
+ $offset = 1;
+ $results = array();
- /**
- * Move a subtask up, decrement the position value
- *
- * @access public
- * @param integer $task_id
- * @param integer $subtask_id
- * @return boolean
- */
- public function moveUp($task_id, $subtask_id)
- {
- $subtasks = $this->getNormalizedPositions($task_id);
- $positions = array_flip($subtasks);
-
- if (isset($subtasks[$subtask_id]) && $subtasks[$subtask_id] > 1) {
- $position = --$subtasks[$subtask_id];
- $subtasks[$positions[$position]]++;
+ foreach ($subtask_ids as $current_subtask_id) {
+ if ($offset == $position) {
+ $offset++;
+ }
- return $this->savePositions($subtasks);
+ $results[] = $this->db->table(self::TABLE)->eq('id', $current_subtask_id)->update(array('position' => $offset));
+ $offset++;
}
- return false;
+ $results[] = $this->db->table(self::TABLE)->eq('id', $subtask_id)->update(array('position' => $position));
+
+ return !in_array(false, $results, true);
}
/**
@@ -353,15 +300,16 @@ class Subtask extends Base
*
* @access public
* @param integer $subtask_id
- * @return bool
+ * @return boolean|integer
*/
public function toggleStatus($subtask_id)
{
$subtask = $this->getById($subtask_id);
+ $status = ($subtask['status'] + 1) % 3;
$values = array(
'id' => $subtask['id'],
- 'status' => ($subtask['status'] + 1) % 3,
+ 'status' => $status,
'task_id' => $subtask['task_id'],
);
@@ -369,7 +317,7 @@ class Subtask extends Base
$values['user_id'] = $this->userSession->getId();
}
- return $this->update($values);
+ return $this->update($values) ? $status : false;
}
/**
@@ -435,10 +383,10 @@ class Subtask extends Base
return $this->db->transaction(function (Database $db) use ($src_task_id, $dst_task_id) {
$subtasks = $db->table(Subtask::TABLE)
- ->columns('title', 'time_estimated', 'position')
- ->eq('task_id', $src_task_id)
- ->asc('position')
- ->findAll();
+ ->columns('title', 'time_estimated', 'position')
+ ->eq('task_id', $src_task_id)
+ ->asc('position')
+ ->findAll();
foreach ($subtasks as &$subtask) {
$subtask['task_id'] = $dst_task_id;
diff --git a/app/Model/SubtaskExport.php b/app/Model/SubtaskExport.php
deleted file mode 100644
index 7c4e941d..00000000
--- a/app/Model/SubtaskExport.php
+++ /dev/null
@@ -1,119 +0,0 @@
-<?php
-
-namespace Kanboard\Model;
-
-/**
- * Subtask Export
- *
- * @package model
- * @author Frederic Guillot
- */
-class SubtaskExport extends Base
-{
- /**
- * Subtask statuses
- *
- * @access private
- * @var array
- */
- private $subtask_status = array();
-
- /**
- * Fetch subtasks and return the prepared CSV
- *
- * @access public
- * @param integer $project_id Project id
- * @param mixed $from Start date (timestamp or user formatted date)
- * @param mixed $to End date (timestamp or user formatted date)
- * @return array
- */
- public function export($project_id, $from, $to)
- {
- $this->subtask_status = $this->subtask->getStatusList();
- $subtasks = $this->getSubtasks($project_id, $from, $to);
- $results = array($this->getColumns());
-
- foreach ($subtasks as $subtask) {
- $results[] = $this->format($subtask);
- }
-
- return $results;
- }
-
- /**
- * Get column titles
- *
- * @access public
- * @return string[]
- */
- public function getColumns()
- {
- return array(
- e('Subtask Id'),
- e('Title'),
- e('Status'),
- e('Assignee'),
- e('Time estimated'),
- e('Time spent'),
- e('Task Id'),
- e('Task Title'),
- );
- }
-
- /**
- * Format the output of a subtask array
- *
- * @access public
- * @param array $subtask Subtask properties
- * @return array
- */
- public function format(array $subtask)
- {
- $values = array();
- $values[] = $subtask['id'];
- $values[] = $subtask['title'];
- $values[] = $this->subtask_status[$subtask['status']];
- $values[] = $subtask['assignee_name'] ?: $subtask['assignee_username'];
- $values[] = $subtask['time_estimated'];
- $values[] = $subtask['time_spent'];
- $values[] = $subtask['task_id'];
- $values[] = $subtask['task_title'];
-
- return $values;
- }
-
- /**
- * Get all subtasks for a given project
- *
- * @access public
- * @param integer $project_id Project id
- * @param mixed $from Start date (timestamp or user formatted date)
- * @param mixed $to End date (timestamp or user formatted date)
- * @return array
- */
- public function getSubtasks($project_id, $from, $to)
- {
- if (! is_numeric($from)) {
- $from = $this->dateParser->removeTimeFromTimestamp($this->dateParser->getTimestamp($from));
- }
-
- if (! is_numeric($to)) {
- $to = $this->dateParser->removeTimeFromTimestamp(strtotime('+1 day', $this->dateParser->getTimestamp($to)));
- }
-
- return $this->db->table(Subtask::TABLE)
- ->eq('project_id', $project_id)
- ->columns(
- Subtask::TABLE.'.*',
- User::TABLE.'.username AS assignee_username',
- User::TABLE.'.name AS assignee_name',
- Task::TABLE.'.title AS task_title'
- )
- ->gte('date_creation', $from)
- ->lte('date_creation', $to)
- ->join(Task::TABLE, 'id', 'task_id')
- ->join(User::TABLE, 'id', 'user_id')
- ->asc(Subtask::TABLE.'.id')
- ->findAll();
- }
-}
diff --git a/app/Model/SubtaskTimeTracking.php b/app/Model/SubtaskTimeTracking.php
index a741dbb5..b766b542 100644
--- a/app/Model/SubtaskTimeTracking.php
+++ b/app/Model/SubtaskTimeTracking.php
@@ -302,11 +302,11 @@ class SubtaskTimeTracking extends Base
{
$hook = 'model:subtask-time-tracking:calculate:time-spent';
$start_time = $this->db
- ->table(self::TABLE)
- ->eq('subtask_id', $subtask_id)
- ->eq('user_id', $user_id)
- ->eq('end', 0)
- ->findOneColumn('start');
+ ->table(self::TABLE)
+ ->eq('subtask_id', $subtask_id)
+ ->eq('user_id', $user_id)
+ ->eq('end', 0)
+ ->findOneColumn('start');
if (empty($start_time)) {
return 0;
diff --git a/app/Model/Swimlane.php b/app/Model/Swimlane.php
index e5124e8e..721f20d3 100644
--- a/app/Model/Swimlane.php
+++ b/app/Model/Swimlane.php
@@ -96,10 +96,11 @@ class Swimlane extends Base
*/
public function getDefault($project_id)
{
- $result = $this->db->table(Project::TABLE)
- ->eq('id', $project_id)
- ->columns('id', 'default_swimlane', 'show_default_swimlane')
- ->findOne();
+ $result = $this->db
+ ->table(Project::TABLE)
+ ->eq('id', $project_id)
+ ->columns('id', 'default_swimlane', 'show_default_swimlane')
+ ->findOne();
if ($result['default_swimlane'] === 'Default swimlane') {
$result['default_swimlane'] = t($result['default_swimlane']);
@@ -117,10 +118,11 @@ class Swimlane extends Base
*/
public function getAll($project_id)
{
- return $this->db->table(self::TABLE)
- ->eq('project_id', $project_id)
- ->orderBy('position', 'asc')
- ->findAll();
+ return $this->db
+ ->table(self::TABLE)
+ ->eq('project_id', $project_id)
+ ->orderBy('position', 'asc')
+ ->findAll();
}
/**
@@ -133,9 +135,10 @@ class Swimlane extends Base
*/
public function getAllByStatus($project_id, $status = self::ACTIVE)
{
- $query = $this->db->table(self::TABLE)
- ->eq('project_id', $project_id)
- ->eq('is_active', $status);
+ $query = $this->db
+ ->table(self::TABLE)
+ ->eq('project_id', $project_id)
+ ->eq('is_active', $status);
if ($status == self::ACTIVE) {
$query->asc('position');
@@ -155,17 +158,19 @@ class Swimlane extends Base
*/
public function getSwimlanes($project_id)
{
- $swimlanes = $this->db->table(self::TABLE)
- ->columns('id', 'name', 'description')
- ->eq('project_id', $project_id)
- ->eq('is_active', self::ACTIVE)
- ->orderBy('position', 'asc')
- ->findAll();
-
- $default_swimlane = $this->db->table(Project::TABLE)
- ->eq('id', $project_id)
- ->eq('show_default_swimlane', 1)
- ->findOneColumn('default_swimlane');
+ $swimlanes = $this->db
+ ->table(self::TABLE)
+ ->columns('id', 'name', 'description')
+ ->eq('project_id', $project_id)
+ ->eq('is_active', self::ACTIVE)
+ ->orderBy('position', 'asc')
+ ->findAll();
+
+ $default_swimlane = $this->db
+ ->table(Project::TABLE)
+ ->eq('id', $project_id)
+ ->eq('show_default_swimlane', 1)
+ ->findOneColumn('default_swimlane');
if ($default_swimlane) {
if ($default_swimlane === 'Default swimlane') {
@@ -200,11 +205,12 @@ class Swimlane extends Base
$swimlanes[0] = $default === 'Default swimlane' ? t($default) : $default;
}
- return $swimlanes + $this->db->hashtable(self::TABLE)
- ->eq('project_id', $project_id)
- ->in('is_active', $only_active ? array(self::ACTIVE) : array(self::ACTIVE, self::INACTIVE))
- ->orderBy('position', 'asc')
- ->getAll('id', 'name');
+ return $swimlanes + $this->db
+ ->hashtable(self::TABLE)
+ ->eq('project_id', $project_id)
+ ->in('is_active', $only_active ? array(self::ACTIVE) : array(self::ACTIVE, self::INACTIVE))
+ ->orderBy('position', 'asc')
+ ->getAll('id', 'name');
}
/**
@@ -232,9 +238,10 @@ class Swimlane extends Base
*/
public function update(array $values)
{
- return $this->db->table(self::TABLE)
- ->eq('id', $values['id'])
- ->update($values);
+ return $this->db
+ ->table(self::TABLE)
+ ->eq('id', $values['id'])
+ ->update($values);
}
/**
@@ -247,12 +254,46 @@ class Swimlane extends Base
public function updateDefault(array $values)
{
return $this->db
- ->table(Project::TABLE)
- ->eq('id', $values['id'])
- ->update(array(
- 'default_swimlane' => $values['default_swimlane'],
- 'show_default_swimlane' => $values['show_default_swimlane'],
- ));
+ ->table(Project::TABLE)
+ ->eq('id', $values['id'])
+ ->update(array(
+ 'default_swimlane' => $values['default_swimlane'],
+ 'show_default_swimlane' => $values['show_default_swimlane'],
+ ));
+ }
+
+ /**
+ * Enable the default swimlane
+ *
+ * @access public
+ * @param integer $project_id
+ * @return bool
+ */
+ public function enableDefault($project_id)
+ {
+ return $this->db
+ ->table(Project::TABLE)
+ ->eq('id', $project_id)
+ ->update(array(
+ 'show_default_swimlane' => 1,
+ ));
+ }
+
+ /**
+ * Disable the default swimlane
+ *
+ * @access public
+ * @param integer $project_id
+ * @return bool
+ */
+ public function disableDefault($project_id)
+ {
+ return $this->db
+ ->table(Project::TABLE)
+ ->eq('id', $project_id)
+ ->update(array(
+ 'show_default_swimlane' => 0,
+ ));
}
/**
@@ -264,10 +305,11 @@ class Swimlane extends Base
*/
public function getLastPosition($project_id)
{
- return $this->db->table(self::TABLE)
- ->eq('project_id', $project_id)
- ->eq('is_active', 1)
- ->count() + 1;
+ return $this->db
+ ->table(self::TABLE)
+ ->eq('project_id', $project_id)
+ ->eq('is_active', 1)
+ ->count() + 1;
}
/**
@@ -281,12 +323,12 @@ class Swimlane extends Base
public function disable($project_id, $swimlane_id)
{
$result = $this->db
- ->table(self::TABLE)
- ->eq('id', $swimlane_id)
- ->update(array(
- 'is_active' => self::INACTIVE,
- 'position' => 0,
- ));
+ ->table(self::TABLE)
+ ->eq('id', $swimlane_id)
+ ->update(array(
+ 'is_active' => self::INACTIVE,
+ 'position' => 0,
+ ));
if ($result) {
// Re-order positions
@@ -307,12 +349,12 @@ class Swimlane extends Base
public function enable($project_id, $swimlane_id)
{
return $this->db
- ->table(self::TABLE)
- ->eq('id', $swimlane_id)
- ->update(array(
- 'is_active' => self::ACTIVE,
- 'position' => $this->getLastPosition($project_id),
- ));
+ ->table(self::TABLE)
+ ->eq('id', $swimlane_id)
+ ->update(array(
+ 'is_active' => self::ACTIVE,
+ 'position' => $this->getLastPosition($project_id),
+ ));
}
/**
@@ -353,11 +395,13 @@ class Swimlane extends Base
public function updatePositions($project_id)
{
$position = 0;
- $swimlanes = $this->db->table(self::TABLE)
- ->eq('project_id', $project_id)
- ->eq('is_active', 1)
- ->asc('position')
- ->findAllByColumn('id');
+ $swimlanes = $this->db
+ ->table(self::TABLE)
+ ->eq('project_id', $project_id)
+ ->eq('is_active', 1)
+ ->asc('position')
+ ->asc('id')
+ ->findAllByColumn('id');
if (! $swimlanes) {
return false;
@@ -365,77 +409,50 @@ class Swimlane extends Base
foreach ($swimlanes as $swimlane_id) {
$this->db->table(self::TABLE)
- ->eq('id', $swimlane_id)
- ->update(array('position' => ++$position));
+ ->eq('id', $swimlane_id)
+ ->update(array('position' => ++$position));
}
return true;
}
/**
- * Move a swimlane down, increment the position value
+ * Change swimlane position
*
* @access public
- * @param integer $project_id Project id
- * @param integer $swimlane_id Swimlane id
+ * @param integer $project_id
+ * @param integer $swimlane_id
+ * @param integer $position
* @return boolean
*/
- public function moveDown($project_id, $swimlane_id)
+ public function changePosition($project_id, $swimlane_id, $position)
{
- $swimlanes = $this->db->hashtable(self::TABLE)
- ->eq('project_id', $project_id)
- ->eq('is_active', self::ACTIVE)
- ->asc('position')
- ->getAll('id', 'position');
-
- $positions = array_flip($swimlanes);
-
- if (isset($swimlanes[$swimlane_id]) && $swimlanes[$swimlane_id] < count($swimlanes)) {
- $position = ++$swimlanes[$swimlane_id];
- $swimlanes[$positions[$position]]--;
-
- $this->db->startTransaction();
- $this->db->table(self::TABLE)->eq('id', $swimlane_id)->update(array('position' => $position));
- $this->db->table(self::TABLE)->eq('id', $positions[$position])->update(array('position' => $swimlanes[$positions[$position]]));
- $this->db->closeTransaction();
-
- return true;
+ if ($position < 1 || $position > $this->db->table(self::TABLE)->eq('project_id', $project_id)->count()) {
+ return false;
}
- return false;
- }
+ $swimlane_ids = $this->db->table(self::TABLE)
+ ->eq('is_active', 1)
+ ->eq('project_id', $project_id)
+ ->neq('id', $swimlane_id)
+ ->asc('position')
+ ->findAllByColumn('id');
- /**
- * Move a swimlane up, decrement the position value
- *
- * @access public
- * @param integer $project_id Project id
- * @param integer $swimlane_id Swimlane id
- * @return boolean
- */
- public function moveUp($project_id, $swimlane_id)
- {
- $swimlanes = $this->db->hashtable(self::TABLE)
- ->eq('project_id', $project_id)
- ->eq('is_active', self::ACTIVE)
- ->asc('position')
- ->getAll('id', 'position');
-
- $positions = array_flip($swimlanes);
-
- if (isset($swimlanes[$swimlane_id]) && $swimlanes[$swimlane_id] > 1) {
- $position = --$swimlanes[$swimlane_id];
- $swimlanes[$positions[$position]]++;
+ $offset = 1;
+ $results = array();
- $this->db->startTransaction();
- $this->db->table(self::TABLE)->eq('id', $swimlane_id)->update(array('position' => $position));
- $this->db->table(self::TABLE)->eq('id', $positions[$position])->update(array('position' => $swimlanes[$positions[$position]]));
- $this->db->closeTransaction();
+ foreach ($swimlane_ids as $current_swimlane_id) {
+ if ($offset == $position) {
+ $offset++;
+ }
- return true;
+ $results[] = $this->db->table(self::TABLE)->eq('id', $current_swimlane_id)->update(array('position' => $offset));
+ $offset++;
}
- return false;
+ $results[] = $this->db->table(self::TABLE)->eq('id', $swimlane_id)->update(array('position' => $position));
+
+ return !in_array(false, $results, true);
}
/**
diff --git a/app/Model/Task.php b/app/Model/Task.php
index 7aa9e312..f8b41b9f 100644
--- a/app/Model/Task.php
+++ b/app/Model/Task.php
@@ -42,6 +42,7 @@ class Task extends Base
const EVENT_ASSIGNEE_CHANGE = 'task.assignee_change';
const EVENT_OVERDUE = 'task.overdue';
const EVENT_USER_MENTION = 'task.user.mention';
+ const EVENT_DAILY_CRONJOB = 'task.cronjob.daily';
/**
* Recurrence: status
@@ -91,7 +92,7 @@ class Task extends Base
return false;
}
- $this->file->removeAll($task_id);
+ $this->taskFile->removeAll($task_id);
return $this->db->table(self::TABLE)->eq('id', $task_id)->remove();
}
@@ -198,4 +199,25 @@ class Task extends Base
return round(($position * 100) / count($columns), 1);
}
+
+ /**
+ * Helper method to duplicate all tasks to another project
+ *
+ * @access public
+ * @param integer $src_project_id
+ * @param integer $dst_project_id
+ * @return boolean
+ */
+ public function duplicate($src_project_id, $dst_project_id)
+ {
+ $task_ids = $this->taskFinder->getAllIds($src_project_id, array(Task::STATUS_OPEN, Task::STATUS_CLOSED));
+
+ foreach ($task_ids as $task_id) {
+ if (! $this->taskDuplication->duplicateToProject($task_id, $dst_project_id)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
}
diff --git a/app/Model/TaskAnalytic.php b/app/Model/TaskAnalytic.php
index bdfec3cb..cff56744 100644
--- a/app/Model/TaskAnalytic.php
+++ b/app/Model/TaskAnalytic.php
@@ -48,7 +48,7 @@ class TaskAnalytic extends Base
public function getTimeSpentByColumn(array $task)
{
$result = array();
- $columns = $this->board->getColumnsList($task['project_id']);
+ $columns = $this->column->getList($task['project_id']);
$sums = $this->transition->getTimeSpentByTask($task['id']);
foreach ($columns as $column_id => $column_title) {
diff --git a/app/Model/TaskCreation.php b/app/Model/TaskCreation.php
index 975cc7a3..2d2e5504 100644
--- a/app/Model/TaskCreation.php
+++ b/app/Model/TaskCreation.php
@@ -49,13 +49,14 @@ class TaskCreation extends Base
*/
public function prepare(array &$values)
{
- $this->dateParser->convert($values, array('date_due'));
- $this->dateParser->convert($values, array('date_started'), true);
- $this->removeFields($values, array('another_task'));
- $this->resetFields($values, array('date_started', 'creator_id', 'owner_id', 'swimlane_id', 'date_due', 'score', 'category_id', 'time_estimated'));
+ $values = $this->dateParser->convert($values, array('date_due'));
+ $values = $this->dateParser->convert($values, array('date_started'), true);
+
+ $this->helper->model->removeFields($values, array('another_task'));
+ $this->helper->model->resetFields($values, array('date_started', 'creator_id', 'owner_id', 'swimlane_id', 'date_due', 'score', 'category_id', 'time_estimated'));
if (empty($values['column_id'])) {
- $values['column_id'] = $this->board->getFirstColumn($values['project_id']);
+ $values['column_id'] = $this->column->getFirstColumnId($values['project_id']);
}
if (empty($values['color_id'])) {
diff --git a/app/Model/TaskDuplication.php b/app/Model/TaskDuplication.php
index e81fb232..ebdd4d29 100644
--- a/app/Model/TaskDuplication.php
+++ b/app/Model/TaskDuplication.php
@@ -64,7 +64,7 @@ class TaskDuplication extends Base
if ($values['recurrence_status'] == Task::RECURRING_STATUS_PENDING) {
$values['recurrence_parent'] = $task_id;
- $values['column_id'] = $this->board->getFirstColumn($values['project_id']);
+ $values['column_id'] = $this->column->getFirstColumnId($values['project_id']);
$this->calculateRecurringTaskDueDate($values);
$recurring_task_id = $this->save($task_id, $values);
@@ -155,6 +155,7 @@ class TaskDuplication extends Base
*
* @access public
* @param array $values
+ * @return array
*/
public function checkDestinationProjectValues(array &$values)
{
@@ -181,12 +182,12 @@ class TaskDuplication extends Base
// Check if the column exists for the destination project
if ($values['column_id'] > 0) {
- $values['column_id'] = $this->board->getColumnIdByTitle(
+ $values['column_id'] = $this->column->getColumnIdByTitle(
$values['project_id'],
- $this->board->getColumnTitleById($values['column_id'])
+ $this->column->getColumnTitleById($values['column_id'])
);
- $values['column_id'] = $values['column_id'] ?: $this->board->getFirstColumn($values['project_id']);
+ $values['column_id'] = $values['column_id'] ?: $this->column->getFirstColumnId($values['project_id']);
}
return $values;
diff --git a/app/Model/TaskExport.php b/app/Model/TaskExport.php
deleted file mode 100644
index 278c0897..00000000
--- a/app/Model/TaskExport.php
+++ /dev/null
@@ -1,145 +0,0 @@
-<?php
-
-namespace Kanboard\Model;
-
-use PDO;
-
-/**
- * Task Export model
- *
- * @package model
- * @author Frederic Guillot
- */
-class TaskExport extends Base
-{
- /**
- * Fetch tasks and return the prepared CSV
- *
- * @access public
- * @param integer $project_id Project id
- * @param mixed $from Start date (timestamp or user formatted date)
- * @param mixed $to End date (timestamp or user formatted date)
- * @return array
- */
- public function export($project_id, $from, $to)
- {
- $tasks = $this->getTasks($project_id, $from, $to);
- $swimlanes = $this->swimlane->getList($project_id);
- $results = array($this->getColumns());
-
- foreach ($tasks as &$task) {
- $results[] = array_values($this->format($task, $swimlanes));
- }
-
- return $results;
- }
-
- /**
- * Get the list of tasks for a given project and date range
- *
- * @access public
- * @param integer $project_id Project id
- * @param mixed $from Start date (timestamp or user formatted date)
- * @param mixed $to End date (timestamp or user formatted date)
- * @return array
- */
- public function getTasks($project_id, $from, $to)
- {
- $sql = '
- SELECT
- tasks.id,
- projects.name AS project_name,
- tasks.is_active,
- project_has_categories.name AS category_name,
- tasks.swimlane_id,
- columns.title AS column_title,
- tasks.position,
- tasks.color_id,
- tasks.date_due,
- creators.username AS creator_username,
- users.username AS assignee_username,
- users.name AS assignee_name,
- tasks.score,
- tasks.title,
- tasks.date_creation,
- tasks.date_modification,
- tasks.date_completed,
- tasks.date_started,
- tasks.time_estimated,
- tasks.time_spent
- FROM tasks
- LEFT JOIN users ON users.id = tasks.owner_id
- LEFT JOIN users AS creators ON creators.id = tasks.creator_id
- LEFT JOIN project_has_categories ON project_has_categories.id = tasks.category_id
- LEFT JOIN columns ON columns.id = tasks.column_id
- LEFT JOIN projects ON projects.id = tasks.project_id
- WHERE tasks.date_creation >= ? AND tasks.date_creation <= ? AND tasks.project_id = ?
- ORDER BY tasks.id ASC
- ';
-
- if (! is_numeric($from)) {
- $from = $this->dateParser->removeTimeFromTimestamp($this->dateParser->getTimestamp($from));
- }
-
- if (! is_numeric($to)) {
- $to = $this->dateParser->removeTimeFromTimestamp(strtotime('+1 day', $this->dateParser->getTimestamp($to)));
- }
-
- $rq = $this->db->execute($sql, array($from, $to, $project_id));
- return $rq->fetchAll(PDO::FETCH_ASSOC);
- }
-
- /**
- * Format the output of a task array
- *
- * @access public
- * @param array $task Task properties
- * @param array $swimlanes List of swimlanes
- * @return array
- */
- public function format(array &$task, array &$swimlanes)
- {
- $colors = $this->color->getList();
-
- $task['is_active'] = $task['is_active'] == Task::STATUS_OPEN ? e('Open') : e('Closed');
- $task['color_id'] = $colors[$task['color_id']];
- $task['score'] = $task['score'] ?: 0;
- $task['swimlane_id'] = isset($swimlanes[$task['swimlane_id']]) ? $swimlanes[$task['swimlane_id']] : '?';
-
- $this->dateParser->format($task, array('date_due', 'date_modification', 'date_creation', 'date_started', 'date_completed'), 'Y-m-d');
-
- return $task;
- }
-
- /**
- * Get column titles
- *
- * @access public
- * @return string[]
- */
- public function getColumns()
- {
- return array(
- e('Task Id'),
- e('Project'),
- e('Status'),
- e('Category'),
- e('Swimlane'),
- e('Column'),
- e('Position'),
- e('Color'),
- e('Due date'),
- e('Creator'),
- e('Assignee Username'),
- e('Assignee Name'),
- e('Complexity'),
- e('Title'),
- e('Creation date'),
- e('Modification date'),
- e('Completion date'),
- e('Start date'),
- e('Time estimated'),
- e('Time spent'),
- );
- }
-}
diff --git a/app/Model/TaskExternalLink.php b/app/Model/TaskExternalLink.php
new file mode 100644
index 00000000..f2c756b4
--- /dev/null
+++ b/app/Model/TaskExternalLink.php
@@ -0,0 +1,99 @@
+<?php
+
+namespace Kanboard\Model;
+
+/**
+ * Task External Link Model
+ *
+ * @package model
+ * @author Frederic Guillot
+ */
+class TaskExternalLink extends Base
+{
+ /**
+ * SQL table name
+ *
+ * @var string
+ */
+ const TABLE = 'task_has_external_links';
+
+ /**
+ * Get all links
+ *
+ * @access public
+ * @param integer $task_id
+ * @return array
+ */
+ public function getAll($task_id)
+ {
+ $types = $this->externalLinkManager->getTypes();
+
+ $links = $this->db->table(self::TABLE)
+ ->columns(self::TABLE.'.*', User::TABLE.'.name AS creator_name', User::TABLE.'.username AS creator_username')
+ ->eq('task_id', $task_id)
+ ->asc('title')
+ ->join(User::TABLE, 'id', 'creator_id')
+ ->findAll();
+
+ foreach ($links as &$link) {
+ $link['dependency_label'] = $this->externalLinkManager->getDependencyLabel($link['link_type'], $link['dependency']);
+ $link['type'] = isset($types[$link['link_type']]) ? $types[$link['link_type']] : t('Unknown');
+ }
+
+ return $links;
+ }
+
+ /**
+ * Get link
+ *
+ * @access public
+ * @param integer $link_id
+ * @return array
+ */
+ public function getById($link_id)
+ {
+ return $this->db->table(self::TABLE)->eq('id', $link_id)->findOne();
+ }
+
+ /**
+ * Add a new link in the database
+ *
+ * @access public
+ * @param array $values Form values
+ * @return boolean|integer
+ */
+ public function create(array $values)
+ {
+ unset($values['id']);
+ $values['creator_id'] = $this->userSession->getId();
+ $values['date_creation'] = time();
+ $values['date_modification'] = $values['date_creation'];
+
+ return $this->persist(self::TABLE, $values);
+ }
+
+ /**
+ * Modify external link
+ *
+ * @access public
+ * @param array $values Form values
+ * @return boolean
+ */
+ public function update(array $values)
+ {
+ $values['date_modification'] = time();
+ return $this->db->table(self::TABLE)->eq('id', $values['id'])->update($values);
+ }
+
+ /**
+ * Remove a link
+ *
+ * @access public
+ * @param integer $link_id
+ * @return boolean
+ */
+ public function remove($link_id)
+ {
+ return $this->db->table(self::TABLE)->eq('id', $link_id)->remove();
+ }
+}
diff --git a/app/Model/TaskFile.php b/app/Model/TaskFile.php
new file mode 100644
index 00000000..45a3b97f
--- /dev/null
+++ b/app/Model/TaskFile.php
@@ -0,0 +1,54 @@
+<?php
+
+namespace Kanboard\Model;
+
+/**
+ * Task File Model
+ *
+ * @package model
+ * @author Frederic Guillot
+ */
+class TaskFile extends File
+{
+ /**
+ * SQL table name
+ *
+ * @var string
+ */
+ const TABLE = 'task_has_files';
+
+ /**
+ * SQL foreign key
+ *
+ * @var string
+ */
+ const FOREIGN_KEY = 'task_id';
+
+ /**
+ * Path prefix
+ *
+ * @var string
+ */
+ const PATH_PREFIX = 'tasks';
+
+ /**
+ * Events
+ *
+ * @var string
+ */
+ const EVENT_CREATE = 'task.file.create';
+
+ /**
+ * Handle screenshot upload
+ *
+ * @access public
+ * @param integer $task_id Task id
+ * @param string $blob Base64 encoded image
+ * @return bool|integer
+ */
+ public function uploadScreenshot($task_id, $blob)
+ {
+ $original_filename = e('Screenshot taken %s', $this->helper->dt->datetime(time())).'.png';
+ return $this->uploadContent($task_id, $original_filename, $blob);
+ }
+}
diff --git a/app/Model/TaskFilter.php b/app/Model/TaskFilter.php
index 7ceb4a97..1883298d 100644
--- a/app/Model/TaskFilter.php
+++ b/app/Model/TaskFilter.php
@@ -469,7 +469,7 @@ class TaskFilter extends Base
$this->query->beginOr();
foreach ($values as $project) {
- $this->query->ilike(Board::TABLE.'.title', $project);
+ $this->query->ilike(Column::TABLE.'.title', $project);
}
$this->query->closeOr();
diff --git a/app/Model/TaskFinder.php b/app/Model/TaskFinder.php
index 1c83136b..0492a9bf 100644
--- a/app/Model/TaskFinder.php
+++ b/app/Model/TaskFinder.php
@@ -38,14 +38,14 @@ class TaskFinder extends Base
Task::TABLE.'.time_spent',
Task::TABLE.'.time_estimated',
Project::TABLE.'.name AS project_name',
- Board::TABLE.'.title AS column_name',
+ Column::TABLE.'.title AS column_name',
User::TABLE.'.username AS assignee_username',
User::TABLE.'.name AS assignee_name'
)
->eq(Task::TABLE.'.is_active', $is_active)
->in(Project::TABLE.'.id', $project_ids)
->join(Project::TABLE, 'id', 'project_id')
- ->join(Board::TABLE, 'id', 'column_id', Task::TABLE)
+ ->join(Column::TABLE, 'id', 'column_id', Task::TABLE)
->join(User::TABLE, 'id', 'owner_id', Task::TABLE);
}
@@ -88,11 +88,12 @@ class TaskFinder extends Base
return $this->db
->table(Task::TABLE)
->columns(
- '(SELECT count(*) FROM '.Comment::TABLE.' WHERE task_id=tasks.id) AS nb_comments',
- '(SELECT count(*) FROM '.File::TABLE.' WHERE task_id=tasks.id) AS nb_files',
- '(SELECT count(*) FROM '.Subtask::TABLE.' WHERE '.Subtask::TABLE.'.task_id=tasks.id) AS nb_subtasks',
- '(SELECT count(*) FROM '.Subtask::TABLE.' WHERE '.Subtask::TABLE.'.task_id=tasks.id AND status=2) AS nb_completed_subtasks',
- '(SELECT count(*) FROM '.TaskLink::TABLE.' WHERE '.TaskLink::TABLE.'.task_id = tasks.id) AS nb_links',
+ '(SELECT COUNT(*) FROM '.Comment::TABLE.' WHERE task_id=tasks.id) AS nb_comments',
+ '(SELECT COUNT(*) FROM '.TaskFile::TABLE.' WHERE task_id=tasks.id) AS nb_files',
+ '(SELECT COUNT(*) FROM '.Subtask::TABLE.' WHERE '.Subtask::TABLE.'.task_id=tasks.id) AS nb_subtasks',
+ '(SELECT COUNT(*) FROM '.Subtask::TABLE.' WHERE '.Subtask::TABLE.'.task_id=tasks.id AND status=2) AS nb_completed_subtasks',
+ '(SELECT COUNT(*) FROM '.TaskLink::TABLE.' WHERE '.TaskLink::TABLE.'.task_id = tasks.id) AS nb_links',
+ '(SELECT COUNT(*) FROM '.TaskExternalLink::TABLE.' WHERE '.TaskExternalLink::TABLE.'.task_id = tasks.id) AS nb_external_links',
'(SELECT DISTINCT 1 FROM '.TaskLink::TABLE.' WHERE '.TaskLink::TABLE.'.task_id = tasks.id AND '.TaskLink::TABLE.'.link_id = 9) AS is_milestone',
'tasks.id',
'tasks.reference',
@@ -128,15 +129,15 @@ class TaskFinder extends Base
User::TABLE.'.name AS assignee_name',
Category::TABLE.'.name AS category_name',
Category::TABLE.'.description AS category_description',
- Board::TABLE.'.title AS column_name',
- Board::TABLE.'.position AS column_position',
+ Column::TABLE.'.title AS column_name',
+ Column::TABLE.'.position AS column_position',
Swimlane::TABLE.'.name AS swimlane_name',
Project::TABLE.'.default_swimlane',
Project::TABLE.'.name AS project_name'
)
->join(User::TABLE, 'id', 'owner_id', Task::TABLE)
->join(Category::TABLE, 'id', 'category_id', Task::TABLE)
- ->join(Board::TABLE, 'id', 'column_id', Task::TABLE)
+ ->join(Column::TABLE, 'id', 'column_id', Task::TABLE)
->join(Swimlane::TABLE, 'id', 'swimlane_id', Task::TABLE)
->join(Project::TABLE, 'id', 'project_id', Task::TABLE);
}
@@ -179,6 +180,23 @@ class TaskFinder extends Base
}
/**
+ * Get all tasks for a given project and status
+ *
+ * @access public
+ * @param integer $project_id
+ * @param array $status
+ * @return array
+ */
+ public function getAllIds($project_id, array $status = array(Task::STATUS_OPEN))
+ {
+ return $this->db
+ ->table(Task::TABLE)
+ ->eq(Task::TABLE.'.project_id', $project_id)
+ ->in(Task::TABLE.'.is_active', $status)
+ ->findAllByColumn('id');
+ }
+
+ /**
* Get overdue tasks query
*
* @access public
diff --git a/app/Model/TaskImport.php b/app/Model/TaskImport.php
deleted file mode 100644
index e8dd1946..00000000
--- a/app/Model/TaskImport.php
+++ /dev/null
@@ -1,156 +0,0 @@
-<?php
-
-namespace Kanboard\Model;
-
-use Kanboard\Core\Csv;
-use SimpleValidator\Validator;
-use SimpleValidator\Validators;
-
-/**
- * Task Import
- *
- * @package model
- * @author Frederic Guillot
- */
-class TaskImport extends Base
-{
- /**
- * Number of successful import
- *
- * @access public
- * @var integer
- */
- public $counter = 0;
-
- /**
- * Project id to import tasks
- *
- * @access public
- * @var integer
- */
- public $projectId;
-
- /**
- * Get mapping between CSV header and SQL columns
- *
- * @access public
- * @return array
- */
- public function getColumnMapping()
- {
- return array(
- 'reference' => 'Reference',
- 'title' => 'Title',
- 'description' => 'Description',
- 'assignee' => 'Assignee Username',
- 'creator' => 'Creator Username',
- 'color' => 'Color Name',
- 'column' => 'Column Name',
- 'category' => 'Category Name',
- 'swimlane' => 'Swimlane Name',
- 'score' => 'Complexity',
- 'time_estimated' => 'Time Estimated',
- 'time_spent' => 'Time Spent',
- 'date_due' => 'Due Date',
- 'is_active' => 'Closed',
- );
- }
-
- /**
- * Import a single row
- *
- * @access public
- * @param array $row
- * @param integer $line_number
- */
- public function import(array $row, $line_number)
- {
- $row = $this->prepare($row);
-
- if ($this->validateCreation($row)) {
- if ($this->taskCreation->create($row) > 0) {
- $this->logger->debug('TaskImport: imported successfully line '.$line_number);
- $this->counter++;
- } else {
- $this->logger->error('TaskImport: creation error at line '.$line_number);
- }
- } else {
- $this->logger->error('TaskImport: validation error at line '.$line_number);
- }
- }
-
- /**
- * Format row before validation
- *
- * @access public
- * @param array $row
- * @return array
- */
- public function prepare(array $row)
- {
- $values = array();
- $values['project_id'] = $this->projectId;
- $values['reference'] = $row['reference'];
- $values['title'] = $row['title'];
- $values['description'] = $row['description'];
- $values['is_active'] = Csv::getBooleanValue($row['is_active']) == 1 ? 0 : 1;
- $values['score'] = (int) $row['score'];
- $values['time_estimated'] = (float) $row['time_estimated'];
- $values['time_spent'] = (float) $row['time_spent'];
-
- if (! empty($row['assignee'])) {
- $values['owner_id'] = $this->user->getIdByUsername($row['assignee']);
- }
-
- if (! empty($row['creator'])) {
- $values['creator_id'] = $this->user->getIdByUsername($row['creator']);
- }
-
- if (! empty($row['color'])) {
- $values['color_id'] = $this->color->find($row['color']);
- }
-
- if (! empty($row['column'])) {
- $values['column_id'] = $this->board->getColumnIdByTitle($this->projectId, $row['column']);
- }
-
- if (! empty($row['category'])) {
- $values['category_id'] = $this->category->getIdByName($this->projectId, $row['category']);
- }
-
- if (! empty($row['swimlane'])) {
- $values['swimlane_id'] = $this->swimlane->getIdByName($this->projectId, $row['swimlane']);
- }
-
- if (! empty($row['date_due'])) {
- $values['date_due'] = $this->dateParser->getTimestampFromIsoFormat($row['date_due']);
- }
-
- $this->removeEmptyFields(
- $values,
- array('owner_id', 'creator_id', 'color_id', 'column_id', 'category_id', 'swimlane_id', 'date_due')
- );
-
- return $values;
- }
-
- /**
- * Validate user creation
- *
- * @access public
- * @param array $values
- * @return boolean
- */
- public function validateCreation(array $values)
- {
- $v = new Validator($values, array(
- new Validators\Integer('project_id', t('This value must be an integer')),
- new Validators\Required('project_id', t('The project is required')),
- new Validators\Required('title', t('The title is required')),
- new Validators\MaxLength('title', t('The maximum length is %d characters', 200), 200),
- new Validators\MaxLength('reference', t('The maximum length is %d characters', 50), 50),
- ));
-
- return $v->execute();
- }
-}
diff --git a/app/Model/TaskLink.php b/app/Model/TaskLink.php
index 87aae55e..e46ea476 100644
--- a/app/Model/TaskLink.php
+++ b/app/Model/TaskLink.php
@@ -75,22 +75,24 @@ class TaskLink extends Base
Task::TABLE.'.title',
Task::TABLE.'.is_active',
Task::TABLE.'.project_id',
+ Task::TABLE.'.column_id',
+ Task::TABLE.'.color_id',
Task::TABLE.'.time_spent AS task_time_spent',
Task::TABLE.'.time_estimated AS task_time_estimated',
Task::TABLE.'.owner_id AS task_assignee_id',
User::TABLE.'.username AS task_assignee_username',
User::TABLE.'.name AS task_assignee_name',
- Board::TABLE.'.title AS column_title',
+ Column::TABLE.'.title AS column_title',
Project::TABLE.'.name AS project_name'
)
->eq(self::TABLE.'.task_id', $task_id)
->join(Link::TABLE, 'id', 'link_id')
->join(Task::TABLE, 'id', 'opposite_task_id')
- ->join(Board::TABLE, 'id', 'column_id', Task::TABLE)
+ ->join(Column::TABLE, 'id', 'column_id', Task::TABLE)
->join(User::TABLE, 'id', 'owner_id', Task::TABLE)
->join(Project::TABLE, 'id', 'project_id', Task::TABLE)
->asc(Link::TABLE.'.id')
- ->desc(Board::TABLE.'.position')
+ ->desc(Column::TABLE.'.position')
->desc(Task::TABLE.'.is_active')
->asc(Task::TABLE.'.position')
->asc(Task::TABLE.'.id')
diff --git a/app/Model/TaskModification.php b/app/Model/TaskModification.php
index eee7b2e0..a77b78a4 100644
--- a/app/Model/TaskModification.php
+++ b/app/Model/TaskModification.php
@@ -84,11 +84,12 @@ class TaskModification extends Base
*/
public function prepare(array &$values)
{
- $this->dateParser->convert($values, array('date_due'));
- $this->dateParser->convert($values, array('date_started'), true);
- $this->removeFields($values, array('another_task', 'id'));
- $this->resetFields($values, array('date_due', 'date_started', 'score', 'category_id', 'time_estimated', 'time_spent'));
- $this->convertIntegerFields($values, array('priority', 'is_active', 'recurrence_status', 'recurrence_trigger', 'recurrence_factor', 'recurrence_timeframe', 'recurrence_basedate'));
+ $values = $this->dateParser->convert($values, array('date_due'));
+ $values = $this->dateParser->convert($values, array('date_started'), true);
+
+ $this->helper->model->removeFields($values, array('another_task', 'id'));
+ $this->helper->model->resetFields($values, array('date_due', 'date_started', 'score', 'category_id', 'time_estimated', 'time_spent'));
+ $this->helper->model->convertIntegerFields($values, array('priority', 'is_active', 'recurrence_status', 'recurrence_trigger', 'recurrence_factor', 'recurrence_timeframe', 'recurrence_basedate'));
$values['date_modification'] = time();
}
diff --git a/app/Model/TaskPermission.php b/app/Model/TaskPermission.php
index fac2153e..b1e02589 100644
--- a/app/Model/TaskPermission.php
+++ b/app/Model/TaskPermission.php
@@ -18,7 +18,8 @@ class TaskPermission extends Base
* Regular users can't remove tasks from other people
*
* @public
- * @return boolean
+ * @param array $task
+ * @return bool
*/
public function canRemoveTask(array $task)
{
diff --git a/app/Model/Transition.php b/app/Model/Transition.php
index b1f8f678..870d95fd 100644
--- a/app/Model/Transition.php
+++ b/app/Model/Transition.php
@@ -3,7 +3,7 @@
namespace Kanboard\Model;
/**
- * Transition model
+ * Transition
*
* @package model
* @author Frederic Guillot
@@ -21,20 +21,22 @@ class Transition extends Base
* Save transition event
*
* @access public
- * @param integer $user_id
- * @param array $task
- * @return boolean
+ * @param integer $user_id
+ * @param array $task_event
+ * @return bool
*/
- public function save($user_id, array $task)
+ public function save($user_id, array $task_event)
{
+ $time = time();
+
return $this->db->table(self::TABLE)->insert(array(
'user_id' => $user_id,
- 'project_id' => $task['project_id'],
- 'task_id' => $task['task_id'],
- 'src_column_id' => $task['src_column_id'],
- 'dst_column_id' => $task['dst_column_id'],
- 'date' => time(),
- 'time_spent' => time() - $task['date_moved']
+ 'project_id' => $task_event['project_id'],
+ 'task_id' => $task_event['task_id'],
+ 'src_column_id' => $task_event['src_column_id'],
+ 'dst_column_id' => $task_event['dst_column_id'],
+ 'date' => $time,
+ 'time_spent' => $time - $task_event['date_moved']
));
}
@@ -76,8 +78,8 @@ class Transition extends Base
->eq('task_id', $task_id)
->desc('date')
->join(User::TABLE, 'id', 'user_id')
- ->join(Board::TABLE.' as src', 'id', 'src_column_id', self::TABLE, 'src')
- ->join(Board::TABLE.' as dst', 'id', 'dst_column_id', self::TABLE, 'dst')
+ ->join(Column::TABLE.' as src', 'id', 'src_column_id', self::TABLE, 'src')
+ ->join(Column::TABLE.' as dst', 'id', 'dst_column_id', self::TABLE, 'dst')
->findAll();
}
@@ -116,71 +118,11 @@ class Transition extends Base
->lte('date', $to)
->eq(self::TABLE.'.project_id', $project_id)
->desc('date')
+ ->desc(self::TABLE.'.id')
->join(Task::TABLE, 'id', 'task_id')
->join(User::TABLE, 'id', 'user_id')
- ->join(Board::TABLE.' as src', 'id', 'src_column_id', self::TABLE, 'src')
- ->join(Board::TABLE.' as dst', 'id', 'dst_column_id', self::TABLE, 'dst')
+ ->join(Column::TABLE.' as src', 'id', 'src_column_id', self::TABLE, 'src')
+ ->join(Column::TABLE.' as dst', 'id', 'dst_column_id', self::TABLE, 'dst')
->findAll();
}
-
- /**
- * Get project export
- *
- * @access public
- * @param integer $project_id Project id
- * @param mixed $from Start date (timestamp or user formatted date)
- * @param mixed $to End date (timestamp or user formatted date)
- * @return array
- */
- public function export($project_id, $from, $to)
- {
- $results = array($this->getColumns());
- $transitions = $this->getAllByProjectAndDate($project_id, $from, $to);
-
- foreach ($transitions as $transition) {
- $results[] = $this->format($transition);
- }
-
- return $results;
- }
-
- /**
- * Get column titles
- *
- * @access public
- * @return string[]
- */
- public function getColumns()
- {
- return array(
- e('Id'),
- e('Task Title'),
- e('Source column'),
- e('Destination column'),
- e('Executer'),
- e('Date'),
- e('Time spent'),
- );
- }
-
- /**
- * Format the output of a transition array
- *
- * @access public
- * @param array $transition
- * @return array
- */
- public function format(array $transition)
- {
- $values = array();
- $values[] = $transition['id'];
- $values[] = $transition['title'];
- $values[] = $transition['src_column'];
- $values[] = $transition['dst_column'];
- $values[] = $transition['name'] ?: $transition['username'];
- $values[] = date('Y-m-d H:i', $transition['date']);
- $values[] = round($transition['time_spent'] / 3600, 2);
-
- return $values;
- }
}
diff --git a/app/Model/User.php b/app/Model/User.php
index ac0e7491..0e11422b 100644
--- a/app/Model/User.php
+++ b/app/Model/User.php
@@ -41,6 +41,18 @@ class User extends Base
}
/**
+ * Return true if the user is active
+ *
+ * @access public
+ * @param integer $user_id User id
+ * @return boolean
+ */
+ public function isActive($user_id)
+ {
+ return $this->db->table(self::TABLE)->eq('id', $user_id)->eq('is_active', 1)->exists();
+ }
+
+ /**
* Get query to fetch all users
*
* @access public
@@ -48,20 +60,7 @@ class User extends Base
*/
public function getQuery()
{
- return $this->db
- ->table(self::TABLE)
- ->columns(
- 'id',
- 'username',
- 'name',
- 'email',
- 'role',
- 'is_ldap_user',
- 'notifications_enabled',
- 'google_id',
- 'github_id',
- 'twofactor_activated'
- );
+ return $this->db->table(self::TABLE);
}
/**
@@ -138,7 +137,7 @@ class User extends Base
*
* @access public
* @param string $username Username
- * @return array
+ * @return integer
*/
public function getIdByUsername($username)
{
@@ -206,9 +205,9 @@ class User extends Base
* @param boolean $prepend Prepend "All users"
* @return array
*/
- public function getList($prepend = false)
+ public function getActiveUsersList($prepend = false)
{
- $users = $this->db->table(self::TABLE)->columns('id', 'username', 'name')->findAll();
+ $users = $this->db->table(self::TABLE)->eq('is_active', 1)->columns('id', 'username', 'name')->findAll();
$listing = $this->prepareList($users);
if ($prepend) {
@@ -254,10 +253,10 @@ class User extends Base
}
}
- $this->removeFields($values, array('confirmation', 'current_password'));
- $this->resetFields($values, array('is_ldap_user', 'disable_login_form'));
- $this->convertNullFields($values, array('gitlab_id'));
- $this->convertIntegerFields($values, array('gitlab_id'));
+ $this->helper->model->removeFields($values, array('confirmation', 'current_password'));
+ $this->helper->model->resetFields($values, array('is_ldap_user', 'disable_login_form'));
+ $this->helper->model->convertNullFields($values, array('gitlab_id'));
+ $this->helper->model->convertIntegerFields($values, array('gitlab_id'));
}
/**
@@ -278,7 +277,7 @@ class User extends Base
*
* @access public
* @param array $values Form values
- * @return array
+ * @return boolean
*/
public function update(array $values)
{
@@ -294,6 +293,30 @@ class User extends Base
}
/**
+ * Disable a specific user
+ *
+ * @access public
+ * @param integer $user_id
+ * @return boolean
+ */
+ public function disable($user_id)
+ {
+ return $this->db->table(self::TABLE)->eq('id', $user_id)->update(array('is_active' => 0));
+ }
+
+ /**
+ * Enable a specific user
+ *
+ * @access public
+ * @param integer $user_id
+ * @return boolean
+ */
+ public function enable($user_id)
+ {
+ return $this->db->table(self::TABLE)->eq('id', $user_id)->update(array('is_active' => 1));
+ }
+
+ /**
* Remove a specific user
*
* @access public
diff --git a/app/Model/UserImport.php b/app/Model/UserImport.php
deleted file mode 100644
index 0ec4e802..00000000
--- a/app/Model/UserImport.php
+++ /dev/null
@@ -1,118 +0,0 @@
-<?php
-
-namespace Kanboard\Model;
-
-use SimpleValidator\Validator;
-use SimpleValidator\Validators;
-use Kanboard\Core\Security\Role;
-use Kanboard\Core\Csv;
-
-/**
- * User Import
- *
- * @package model
- * @author Frederic Guillot
- */
-class UserImport extends Base
-{
- /**
- * Number of successful import
- *
- * @access public
- * @var integer
- */
- public $counter = 0;
-
- /**
- * Get mapping between CSV header and SQL columns
- *
- * @access public
- * @return array
- */
- public function getColumnMapping()
- {
- return array(
- 'username' => 'Username',
- 'password' => 'Password',
- 'email' => 'Email',
- 'name' => 'Full Name',
- 'is_admin' => 'Administrator',
- 'is_manager' => 'Manager',
- 'is_ldap_user' => 'Remote User',
- );
- }
-
- /**
- * Import a single row
- *
- * @access public
- * @param array $row
- * @param integer $line_number
- */
- public function import(array $row, $line_number)
- {
- $row = $this->prepare($row);
-
- if ($this->validateCreation($row)) {
- if ($this->user->create($row)) {
- $this->logger->debug('UserImport: imported successfully line '.$line_number);
- $this->counter++;
- } else {
- $this->logger->error('UserImport: creation error at line '.$line_number);
- }
- } else {
- $this->logger->error('UserImport: validation error at line '.$line_number);
- }
- }
-
- /**
- * Format row before validation
- *
- * @access public
- * @param array $row
- * @return array
- */
- public function prepare(array $row)
- {
- $row['username'] = strtolower($row['username']);
-
- foreach (array('is_admin', 'is_manager', 'is_ldap_user') as $field) {
- $row[$field] = Csv::getBooleanValue($row[$field]);
- }
-
- if ($row['is_admin'] == 1) {
- $row['role'] = Role::APP_ADMIN;
- } elseif ($row['is_manager'] == 1) {
- $row['role'] = Role::APP_MANAGER;
- } else {
- $row['role'] = Role::APP_USER;
- }
-
- unset($row['is_admin']);
- unset($row['is_manager']);
-
- $this->removeEmptyFields($row, array('password', 'email', 'name'));
-
- return $row;
- }
-
- /**
- * Validate user creation
- *
- * @access public
- * @param array $values
- * @return boolean
- */
- public function validateCreation(array $values)
- {
- $v = new Validator($values, array(
- new Validators\MaxLength('username', t('The maximum length is %d characters', 50), 50),
- new Validators\Unique('username', t('The username must be unique'), $this->db->getConnection(), User::TABLE, 'id'),
- new Validators\MinLength('password', t('The minimum length is %d characters', 6), 6),
- new Validators\Email('email', t('Email address invalid')),
- new Validators\Integer('is_ldap_user', t('This value must be an integer')),
- ));
-
- return $v->execute();
- }
-}