summaryrefslogtreecommitdiff
path: root/app/Filter
diff options
context:
space:
mode:
authorFrederic Guillot <fred@kanboard.net>2016-04-09 22:42:17 -0400
committerFrederic Guillot <fred@kanboard.net>2016-04-09 22:42:17 -0400
commit11858be4e8d5aba983700c6cba1c4d0a33ea8e9d (patch)
tree38427e86d52185df95e2b891e4bcd50aeb56eeb9 /app/Filter
parent42813d702d1c3e5659301bc771f8dbb37a6d15cd (diff)
Filter refactoring
Diffstat (limited to 'app/Filter')
-rw-r--r--app/Filter/BaseFilter.php119
-rw-r--r--app/Filter/ProjectGroupRoleProjectFilter.php38
-rw-r--r--app/Filter/ProjectGroupRoleUsernameFilter.php44
-rw-r--r--app/Filter/ProjectIdsFilter.php43
-rw-r--r--app/Filter/ProjectStatusFilter.php45
-rw-r--r--app/Filter/ProjectTypeFilter.php45
-rw-r--r--app/Filter/ProjectUserRoleProjectFilter.php38
-rw-r--r--app/Filter/ProjectUserRoleUsernameFilter.php41
-rw-r--r--app/Filter/TaskAssigneeFilter.php75
-rw-r--r--app/Filter/TaskCategoryFilter.php46
-rw-r--r--app/Filter/TaskColorFilter.php60
-rw-r--r--app/Filter/TaskColumnFilter.php44
-rw-r--r--app/Filter/TaskCompletionDateFilter.php38
-rw-r--r--app/Filter/TaskCreationDateFilter.php38
-rw-r--r--app/Filter/TaskDescriptionFilter.php38
-rw-r--r--app/Filter/TaskDueDateFilter.php41
-rw-r--r--app/Filter/TaskDueDateRangeFilter.php39
-rw-r--r--app/Filter/TaskIdExclusionFilter.php38
-rw-r--r--app/Filter/TaskIdFilter.php38
-rw-r--r--app/Filter/TaskLinkFilter.php85
-rw-r--r--app/Filter/TaskModificationDateFilter.php38
-rw-r--r--app/Filter/TaskProjectFilter.php44
-rw-r--r--app/Filter/TaskProjectsFilter.php38
-rw-r--r--app/Filter/TaskReferenceFilter.php38
-rw-r--r--app/Filter/TaskStartDateFilter.php38
-rw-r--r--app/Filter/TaskStatusFilter.php43
-rw-r--r--app/Filter/TaskSubtaskAssigneeFilter.php140
-rw-r--r--app/Filter/TaskSwimlaneFilter.php50
-rw-r--r--app/Filter/TaskTitleFilter.php46
-rw-r--r--app/Filter/UserNameFilter.php35
30 files changed, 1503 insertions, 0 deletions
diff --git a/app/Filter/BaseFilter.php b/app/Filter/BaseFilter.php
new file mode 100644
index 00000000..a7e6a61a
--- /dev/null
+++ b/app/Filter/BaseFilter.php
@@ -0,0 +1,119 @@
+<?php
+
+namespace Kanboard\Filter;
+
+use PicoDb\Table;
+
+/**
+ * Base filter class
+ *
+ * @package filter
+ * @author Frederic Guillot
+ */
+abstract class BaseFilter
+{
+ /**
+ * @var Table
+ */
+ protected $query;
+
+ /**
+ * @var mixed
+ */
+ protected $value;
+
+ /**
+ * BaseFilter constructor
+ *
+ * @access public
+ * @param mixed $value
+ */
+ public function __construct($value = null)
+ {
+ $this->value = $value;
+ }
+
+ /**
+ * Get object instance
+ *
+ * @static
+ * @access public
+ * @param mixed $value
+ * @return static
+ */
+ public static function getInstance($value = null)
+ {
+ $self = new static($value);
+ return $self;
+ }
+
+ /**
+ * Set query
+ *
+ * @access public
+ * @param Table $query
+ * @return \Kanboard\Core\Filter\FilterInterface
+ */
+ public function withQuery(Table $query)
+ {
+ $this->query = $query;
+ return $this;
+ }
+
+ /**
+ * Set the value
+ *
+ * @access public
+ * @param string $value
+ * @return \Kanboard\Core\Filter\FilterInterface
+ */
+ public function withValue($value)
+ {
+ $this->value = $value;
+ return $this;
+ }
+
+ /**
+ * Parse operator in the input string
+ *
+ * @access protected
+ * @return string
+ */
+ protected function parseOperator()
+ {
+ $operators = array(
+ '<=' => 'lte',
+ '>=' => 'gte',
+ '<' => 'lt',
+ '>' => 'gt',
+ );
+
+ foreach ($operators as $operator => $method) {
+ if (strpos($this->value, $operator) === 0) {
+ $this->value = substr($this->value, strlen($operator));
+ return $method;
+ }
+ }
+
+ return '';
+ }
+
+ /**
+ * Apply a date filter
+ *
+ * @access protected
+ * @param string $field
+ */
+ protected function applyDateFilter($field)
+ {
+ $timestamp = strtotime($this->value);
+ $method = $this->parseOperator();
+
+ if ($method !== '') {
+ $this->query->$method($field, $timestamp);
+ } else {
+ $this->query->gte($field, $timestamp);
+ $this->query->lte($field, $timestamp + 86399);
+ }
+ }
+}
diff --git a/app/Filter/ProjectGroupRoleProjectFilter.php b/app/Filter/ProjectGroupRoleProjectFilter.php
new file mode 100644
index 00000000..b0950868
--- /dev/null
+++ b/app/Filter/ProjectGroupRoleProjectFilter.php
@@ -0,0 +1,38 @@
+<?php
+
+namespace Kanboard\Filter;
+
+use Kanboard\Core\Filter\FilterInterface;
+use Kanboard\Model\ProjectGroupRole;
+
+/**
+ * Filter ProjectGroupRole users by project
+ *
+ * @package filter
+ * @author Frederic Guillot
+ */
+class ProjectGroupRoleProjectFilter extends BaseFilter implements FilterInterface
+{
+ /**
+ * Get search attribute
+ *
+ * @access public
+ * @return string[]
+ */
+ public function getAttributes()
+ {
+ return array();
+ }
+
+ /**
+ * Apply filter
+ *
+ * @access public
+ * @return FilterInterface
+ */
+ public function apply()
+ {
+ $this->query->eq(ProjectGroupRole::TABLE.'.project_id', $this->value);
+ return $this;
+ }
+}
diff --git a/app/Filter/ProjectGroupRoleUsernameFilter.php b/app/Filter/ProjectGroupRoleUsernameFilter.php
new file mode 100644
index 00000000..c10855bc
--- /dev/null
+++ b/app/Filter/ProjectGroupRoleUsernameFilter.php
@@ -0,0 +1,44 @@
+<?php
+
+namespace Kanboard\Filter;
+
+use Kanboard\Core\Filter\FilterInterface;
+use Kanboard\Model\GroupMember;
+use Kanboard\Model\ProjectGroupRole;
+use Kanboard\Model\User;
+
+/**
+ * Filter ProjectGroupRole users by username
+ *
+ * @package filter
+ * @author Frederic Guillot
+ */
+class ProjectGroupRoleUsernameFilter extends BaseFilter implements FilterInterface
+{
+ /**
+ * Get search attribute
+ *
+ * @access public
+ * @return string[]
+ */
+ public function getAttributes()
+ {
+ return array();
+ }
+
+ /**
+ * Apply filter
+ *
+ * @access public
+ * @return FilterInterface
+ */
+ public function apply()
+ {
+ $this->query
+ ->join(GroupMember::TABLE, 'group_id', 'group_id', ProjectGroupRole::TABLE)
+ ->join(User::TABLE, 'id', 'user_id', GroupMember::TABLE)
+ ->ilike(User::TABLE.'.username', $this->value.'%');
+
+ return $this;
+ }
+}
diff --git a/app/Filter/ProjectIdsFilter.php b/app/Filter/ProjectIdsFilter.php
new file mode 100644
index 00000000..641f7f18
--- /dev/null
+++ b/app/Filter/ProjectIdsFilter.php
@@ -0,0 +1,43 @@
+<?php
+
+namespace Kanboard\Filter;
+
+use Kanboard\Core\Filter\FilterInterface;
+use Kanboard\Model\Project;
+
+/**
+ * Filter project by ids
+ *
+ * @package filter
+ * @author Frederic Guillot
+ */
+class ProjectIdsFilter extends BaseFilter implements FilterInterface
+{
+ /**
+ * Get search attribute
+ *
+ * @access public
+ * @return string[]
+ */
+ public function getAttributes()
+ {
+ return array('project_ids');
+ }
+
+ /**
+ * Apply filter
+ *
+ * @access public
+ * @return FilterInterface
+ */
+ public function apply()
+ {
+ if (empty($this->value)) {
+ $this->query->eq(Project::TABLE.'.id', 0);
+ } else {
+ $this->query->in(Project::TABLE.'.id', $this->value);
+ }
+
+ return $this;
+ }
+}
diff --git a/app/Filter/ProjectStatusFilter.php b/app/Filter/ProjectStatusFilter.php
new file mode 100644
index 00000000..a994600c
--- /dev/null
+++ b/app/Filter/ProjectStatusFilter.php
@@ -0,0 +1,45 @@
+<?php
+
+namespace Kanboard\Filter;
+
+use Kanboard\Core\Filter\FilterInterface;
+use Kanboard\Model\Project;
+
+/**
+ * Filter project by status
+ *
+ * @package filter
+ * @author Frederic Guillot
+ */
+class ProjectStatusFilter extends BaseFilter implements FilterInterface
+{
+ /**
+ * Get search attribute
+ *
+ * @access public
+ * @return string[]
+ */
+ public function getAttributes()
+ {
+ return array('status');
+ }
+
+ /**
+ * Apply filter
+ *
+ * @access public
+ * @return FilterInterface
+ */
+ public function apply()
+ {
+ if (is_int($this->value) || ctype_digit($this->value)) {
+ $this->query->eq(Project::TABLE.'.is_active', $this->value);
+ } elseif ($this->value === 'inactive' || $this->value === 'closed' || $this->value === 'disabled') {
+ $this->query->eq(Project::TABLE.'.is_active', 0);
+ } else {
+ $this->query->eq(Project::TABLE.'.is_active', 1);
+ }
+
+ return $this;
+ }
+}
diff --git a/app/Filter/ProjectTypeFilter.php b/app/Filter/ProjectTypeFilter.php
new file mode 100644
index 00000000..e085e2f6
--- /dev/null
+++ b/app/Filter/ProjectTypeFilter.php
@@ -0,0 +1,45 @@
+<?php
+
+namespace Kanboard\Filter;
+
+use Kanboard\Core\Filter\FilterInterface;
+use Kanboard\Model\Project;
+
+/**
+ * Filter project by type
+ *
+ * @package filter
+ * @author Frederic Guillot
+ */
+class ProjectTypeFilter extends BaseFilter implements FilterInterface
+{
+ /**
+ * Get search attribute
+ *
+ * @access public
+ * @return string[]
+ */
+ public function getAttributes()
+ {
+ return array('type');
+ }
+
+ /**
+ * Apply filter
+ *
+ * @access public
+ * @return FilterInterface
+ */
+ public function apply()
+ {
+ if (is_int($this->value) || ctype_digit($this->value)) {
+ $this->query->eq(Project::TABLE.'.is_private', $this->value);
+ } elseif ($this->value === 'private') {
+ $this->query->eq(Project::TABLE.'.is_private', Project::TYPE_PRIVATE);
+ } else {
+ $this->query->eq(Project::TABLE.'.is_private', Project::TYPE_TEAM);
+ }
+
+ return $this;
+ }
+}
diff --git a/app/Filter/ProjectUserRoleProjectFilter.php b/app/Filter/ProjectUserRoleProjectFilter.php
new file mode 100644
index 00000000..3b880df5
--- /dev/null
+++ b/app/Filter/ProjectUserRoleProjectFilter.php
@@ -0,0 +1,38 @@
+<?php
+
+namespace Kanboard\Filter;
+
+use Kanboard\Core\Filter\FilterInterface;
+use Kanboard\Model\ProjectUserRole;
+
+/**
+ * Filter ProjectUserRole users by project
+ *
+ * @package filter
+ * @author Frederic Guillot
+ */
+class ProjectUserRoleProjectFilter extends BaseFilter implements FilterInterface
+{
+ /**
+ * Get search attribute
+ *
+ * @access public
+ * @return string[]
+ */
+ public function getAttributes()
+ {
+ return array();
+ }
+
+ /**
+ * Apply filter
+ *
+ * @access public
+ * @return FilterInterface
+ */
+ public function apply()
+ {
+ $this->query->eq(ProjectUserRole::TABLE.'.project_id', $this->value);
+ return $this;
+ }
+}
diff --git a/app/Filter/ProjectUserRoleUsernameFilter.php b/app/Filter/ProjectUserRoleUsernameFilter.php
new file mode 100644
index 00000000..c00493a3
--- /dev/null
+++ b/app/Filter/ProjectUserRoleUsernameFilter.php
@@ -0,0 +1,41 @@
+<?php
+
+namespace Kanboard\Filter;
+
+use Kanboard\Core\Filter\FilterInterface;
+use Kanboard\Model\User;
+
+/**
+ * Filter ProjectUserRole users by username
+ *
+ * @package filter
+ * @author Frederic Guillot
+ */
+class ProjectUserRoleUsernameFilter extends BaseFilter implements FilterInterface
+{
+ /**
+ * Get search attribute
+ *
+ * @access public
+ * @return string[]
+ */
+ public function getAttributes()
+ {
+ return array();
+ }
+
+ /**
+ * Apply filter
+ *
+ * @access public
+ * @return FilterInterface
+ */
+ public function apply()
+ {
+ $this->query
+ ->join(User::TABLE, 'id', 'user_id')
+ ->ilike(User::TABLE.'.username', $this->value.'%');
+
+ return $this;
+ }
+}
diff --git a/app/Filter/TaskAssigneeFilter.php b/app/Filter/TaskAssigneeFilter.php
new file mode 100644
index 00000000..783d6a12
--- /dev/null
+++ b/app/Filter/TaskAssigneeFilter.php
@@ -0,0 +1,75 @@
+<?php
+
+namespace Kanboard\Filter;
+
+use Kanboard\Core\Filter\FilterInterface;
+use Kanboard\Model\Task;
+use Kanboard\Model\User;
+
+/**
+ * Filter tasks by assignee
+ *
+ * @package filter
+ * @author Frederic Guillot
+ */
+class TaskAssigneeFilter extends BaseFilter implements FilterInterface
+{
+ /**
+ * Current user id
+ *
+ * @access private
+ * @var int
+ */
+ private $currentUserId = 0;
+
+ /**
+ * Set current user id
+ *
+ * @access public
+ * @param integer $userId
+ * @return TaskAssigneeFilter
+ */
+ public function setCurrentUserId($userId)
+ {
+ $this->currentUserId = $userId;
+ return $this;
+ }
+
+ /**
+ * Get search attribute
+ *
+ * @access public
+ * @return string[]
+ */
+ public function getAttributes()
+ {
+ return array('assignee');
+ }
+
+ /**
+ * Apply filter
+ *
+ * @access public
+ * @return string
+ */
+ public function apply()
+ {
+ if (is_int($this->value) || ctype_digit($this->value)) {
+ $this->query->eq(Task::TABLE.'.owner_id', $this->value);
+ } else {
+ switch ($this->value) {
+ case 'me':
+ $this->query->eq(Task::TABLE.'.owner_id', $this->currentUserId);
+ break;
+ case 'nobody':
+ $this->query->eq(Task::TABLE.'.owner_id', 0);
+ break;
+ default:
+ $this->query->beginOr();
+ $this->query->ilike(User::TABLE.'.username', '%'.$this->value.'%');
+ $this->query->ilike(User::TABLE.'.name', '%'.$this->value.'%');
+ $this->query->closeOr();
+ }
+ }
+ }
+}
diff --git a/app/Filter/TaskCategoryFilter.php b/app/Filter/TaskCategoryFilter.php
new file mode 100644
index 00000000..517f24d9
--- /dev/null
+++ b/app/Filter/TaskCategoryFilter.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace Kanboard\Filter;
+
+use Kanboard\Core\Filter\FilterInterface;
+use Kanboard\Model\Category;
+use Kanboard\Model\Task;
+
+/**
+ * Filter tasks by category
+ *
+ * @package filter
+ * @author Frederic Guillot
+ */
+class TaskCategoryFilter extends BaseFilter implements FilterInterface
+{
+ /**
+ * Get search attribute
+ *
+ * @access public
+ * @return string[]
+ */
+ public function getAttributes()
+ {
+ return array('category');
+ }
+
+ /**
+ * Apply filter
+ *
+ * @access public
+ * @return FilterInterface
+ */
+ public function apply()
+ {
+ if (is_int($this->value) || ctype_digit($this->value)) {
+ $this->query->eq(Task::TABLE.'.category_id', $this->value);
+ } elseif ($this->value === 'none') {
+ $this->query->eq(Task::TABLE.'.category_id', 0);
+ } else {
+ $this->query->eq(Category::TABLE.'.name', $this->value);
+ }
+
+ return $this;
+ }
+}
diff --git a/app/Filter/TaskColorFilter.php b/app/Filter/TaskColorFilter.php
new file mode 100644
index 00000000..784162d4
--- /dev/null
+++ b/app/Filter/TaskColorFilter.php
@@ -0,0 +1,60 @@
+<?php
+
+namespace Kanboard\Filter;
+
+use Kanboard\Core\Filter\FilterInterface;
+use Kanboard\Model\Color;
+use Kanboard\Model\Task;
+
+/**
+ * Filter tasks by color
+ *
+ * @package filter
+ * @author Frederic Guillot
+ */
+class TaskColorFilter extends BaseFilter implements FilterInterface
+{
+ /**
+ * Color object
+ *
+ * @access private
+ * @var Color
+ */
+ private $colorModel;
+
+ /**
+ * Set color model object
+ *
+ * @access public
+ * @param Color $colorModel
+ * @return TaskColorFilter
+ */
+ public function setColorModel(Color $colorModel)
+ {
+ $this->colorModel = $colorModel;
+ return $this;
+ }
+
+ /**
+ * Get search attribute
+ *
+ * @access public
+ * @return string[]
+ */
+ public function getAttributes()
+ {
+ return array('color', 'colour');
+ }
+
+ /**
+ * Apply filter
+ *
+ * @access public
+ * @return FilterInterface
+ */
+ public function apply()
+ {
+ $this->query->eq(Task::TABLE.'.color_id', $this->colorModel->find($this->value));
+ return $this;
+ }
+}
diff --git a/app/Filter/TaskColumnFilter.php b/app/Filter/TaskColumnFilter.php
new file mode 100644
index 00000000..9a4d4253
--- /dev/null
+++ b/app/Filter/TaskColumnFilter.php
@@ -0,0 +1,44 @@
+<?php
+
+namespace Kanboard\Filter;
+
+use Kanboard\Core\Filter\FilterInterface;
+use Kanboard\Model\Column;
+use Kanboard\Model\Task;
+
+/**
+ * Filter tasks by column
+ *
+ * @package filter
+ * @author Frederic Guillot
+ */
+class TaskColumnFilter extends BaseFilter implements FilterInterface
+{
+ /**
+ * Get search attribute
+ *
+ * @access public
+ * @return string[]
+ */
+ public function getAttributes()
+ {
+ return array('column');
+ }
+
+ /**
+ * Apply filter
+ *
+ * @access public
+ * @return FilterInterface
+ */
+ public function apply()
+ {
+ if (is_int($this->value) || ctype_digit($this->value)) {
+ $this->query->eq(Task::TABLE.'.column_id', $this->value);
+ } else {
+ $this->query->eq(Column::TABLE.'.title', $this->value);
+ }
+
+ return $this;
+ }
+}
diff --git a/app/Filter/TaskCompletionDateFilter.php b/app/Filter/TaskCompletionDateFilter.php
new file mode 100644
index 00000000..5166bebf
--- /dev/null
+++ b/app/Filter/TaskCompletionDateFilter.php
@@ -0,0 +1,38 @@
+<?php
+
+namespace Kanboard\Filter;
+
+use Kanboard\Core\Filter\FilterInterface;
+use Kanboard\Model\Task;
+
+/**
+ * Filter tasks by completion date
+ *
+ * @package filter
+ * @author Frederic Guillot
+ */
+class TaskCompletionDateFilter extends BaseFilter implements FilterInterface
+{
+ /**
+ * Get search attribute
+ *
+ * @access public
+ * @return string[]
+ */
+ public function getAttributes()
+ {
+ return array('completed');
+ }
+
+ /**
+ * Apply filter
+ *
+ * @access public
+ * @return FilterInterface
+ */
+ public function apply()
+ {
+ $this->applyDateFilter(Task::TABLE.'.date_completed');
+ return $this;
+ }
+}
diff --git a/app/Filter/TaskCreationDateFilter.php b/app/Filter/TaskCreationDateFilter.php
new file mode 100644
index 00000000..26318b3e
--- /dev/null
+++ b/app/Filter/TaskCreationDateFilter.php
@@ -0,0 +1,38 @@
+<?php
+
+namespace Kanboard\Filter;
+
+use Kanboard\Core\Filter\FilterInterface;
+use Kanboard\Model\Task;
+
+/**
+ * Filter tasks by creation date
+ *
+ * @package filter
+ * @author Frederic Guillot
+ */
+class TaskCreationDateFilter extends BaseFilter implements FilterInterface
+{
+ /**
+ * Get search attribute
+ *
+ * @access public
+ * @return string[]
+ */
+ public function getAttributes()
+ {
+ return array('created');
+ }
+
+ /**
+ * Apply filter
+ *
+ * @access public
+ * @return FilterInterface
+ */
+ public function apply()
+ {
+ $this->applyDateFilter(Task::TABLE.'.date_creation');
+ return $this;
+ }
+}
diff --git a/app/Filter/TaskDescriptionFilter.php b/app/Filter/TaskDescriptionFilter.php
new file mode 100644
index 00000000..6dda58ae
--- /dev/null
+++ b/app/Filter/TaskDescriptionFilter.php
@@ -0,0 +1,38 @@
+<?php
+
+namespace Kanboard\Filter;
+
+use Kanboard\Core\Filter\FilterInterface;
+use Kanboard\Model\Task;
+
+/**
+ * Filter tasks by description
+ *
+ * @package filter
+ * @author Frederic Guillot
+ */
+class TaskDescriptionFilter extends BaseFilter implements FilterInterface
+{
+ /**
+ * Get search attribute
+ *
+ * @access public
+ * @return string[]
+ */
+ public function getAttributes()
+ {
+ return array('description', 'desc');
+ }
+
+ /**
+ * Apply filter
+ *
+ * @access public
+ * @return FilterInterface
+ */
+ public function apply()
+ {
+ $this->query->ilike(Task::TABLE.'.description', '%'.$this->value.'%');
+ return $this;
+ }
+}
diff --git a/app/Filter/TaskDueDateFilter.php b/app/Filter/TaskDueDateFilter.php
new file mode 100644
index 00000000..6ba55eb9
--- /dev/null
+++ b/app/Filter/TaskDueDateFilter.php
@@ -0,0 +1,41 @@
+<?php
+
+namespace Kanboard\Filter;
+
+use Kanboard\Core\Filter\FilterInterface;
+use Kanboard\Model\Task;
+
+/**
+ * Filter tasks by due date
+ *
+ * @package filter
+ * @author Frederic Guillot
+ */
+class TaskDueDateFilter extends BaseFilter implements FilterInterface
+{
+ /**
+ * Get search attribute
+ *
+ * @access public
+ * @return string[]
+ */
+ public function getAttributes()
+ {
+ return array('due');
+ }
+
+ /**
+ * Apply filter
+ *
+ * @access public
+ * @return FilterInterface
+ */
+ public function apply()
+ {
+ $this->query->neq(Task::TABLE.'.date_due', 0);
+ $this->query->notNull(Task::TABLE.'.date_due');
+ $this->applyDateFilter(Task::TABLE.'.date_due');
+
+ return $this;
+ }
+}
diff --git a/app/Filter/TaskDueDateRangeFilter.php b/app/Filter/TaskDueDateRangeFilter.php
new file mode 100644
index 00000000..10deb0d3
--- /dev/null
+++ b/app/Filter/TaskDueDateRangeFilter.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace Kanboard\Filter;
+
+use Kanboard\Core\Filter\FilterInterface;
+use Kanboard\Model\Task;
+
+/**
+ * Filter tasks by due date range
+ *
+ * @package filter
+ * @author Frederic Guillot
+ */
+class TaskDueDateRangeFilter extends BaseFilter implements FilterInterface
+{
+ /**
+ * Get search attribute
+ *
+ * @access public
+ * @return string[]
+ */
+ public function getAttributes()
+ {
+ return array();
+ }
+
+ /**
+ * Apply filter
+ *
+ * @access public
+ * @return FilterInterface
+ */
+ public function apply()
+ {
+ $this->query->gte(Task::TABLE.'.date_due', is_numeric($this->value[0]) ? $this->value[0] : strtotime($this->value[0]));
+ $this->query->lte(Task::TABLE.'.date_due', is_numeric($this->value[1]) ? $this->value[1] : strtotime($this->value[1]));
+ return $this;
+ }
+}
diff --git a/app/Filter/TaskIdExclusionFilter.php b/app/Filter/TaskIdExclusionFilter.php
new file mode 100644
index 00000000..8bfefb2b
--- /dev/null
+++ b/app/Filter/TaskIdExclusionFilter.php
@@ -0,0 +1,38 @@
+<?php
+
+namespace Kanboard\Filter;
+
+use Kanboard\Core\Filter\FilterInterface;
+use Kanboard\Model\Task;
+
+/**
+ * Exclude task ids
+ *
+ * @package filter
+ * @author Frederic Guillot
+ */
+class TaskIdExclusionFilter extends BaseFilter implements FilterInterface
+{
+ /**
+ * Get search attribute
+ *
+ * @access public
+ * @return string[]
+ */
+ public function getAttributes()
+ {
+ return array('exclude');
+ }
+
+ /**
+ * Apply filter
+ *
+ * @access public
+ * @return FilterInterface
+ */
+ public function apply()
+ {
+ $this->query->notin(Task::TABLE.'.id', $this->value);
+ return $this;
+ }
+}
diff --git a/app/Filter/TaskIdFilter.php b/app/Filter/TaskIdFilter.php
new file mode 100644
index 00000000..87bac794
--- /dev/null
+++ b/app/Filter/TaskIdFilter.php
@@ -0,0 +1,38 @@
+<?php
+
+namespace Kanboard\Filter;
+
+use Kanboard\Core\Filter\FilterInterface;
+use Kanboard\Model\Task;
+
+/**
+ * Filter tasks by id
+ *
+ * @package filter
+ * @author Frederic Guillot
+ */
+class TaskIdFilter extends BaseFilter implements FilterInterface
+{
+ /**
+ * Get search attribute
+ *
+ * @access public
+ * @return string[]
+ */
+ public function getAttributes()
+ {
+ return array('id');
+ }
+
+ /**
+ * Apply filter
+ *
+ * @access public
+ * @return FilterInterface
+ */
+ public function apply()
+ {
+ $this->query->eq(Task::TABLE.'.id', $this->value);
+ return $this;
+ }
+}
diff --git a/app/Filter/TaskLinkFilter.php b/app/Filter/TaskLinkFilter.php
new file mode 100644
index 00000000..18a13a09
--- /dev/null
+++ b/app/Filter/TaskLinkFilter.php
@@ -0,0 +1,85 @@
+<?php
+
+namespace Kanboard\Filter;
+
+use Kanboard\Core\Filter\FilterInterface;
+use Kanboard\Model\Link;
+use Kanboard\Model\Task;
+use Kanboard\Model\TaskLink;
+use PicoDb\Database;
+use PicoDb\Table;
+
+/**
+ * Filter tasks by link name
+ *
+ * @package filter
+ * @author Frederic Guillot
+ */
+class TaskLinkFilter extends BaseFilter implements FilterInterface
+{
+ /**
+ * Database object
+ *
+ * @access private
+ * @var Database
+ */
+ private $db;
+
+ /**
+ * Set database object
+ *
+ * @access public
+ * @param Database $db
+ * @return TaskLinkFilter
+ */
+ public function setDatabase(Database $db)
+ {
+ $this->db = $db;
+ return $this;
+ }
+
+ /**
+ * Get search attribute
+ *
+ * @access public
+ * @return string[]
+ */
+ public function getAttributes()
+ {
+ return array('link');
+ }
+
+ /**
+ * Apply filter
+ *
+ * @access public
+ * @return string
+ */
+ public function apply()
+ {
+ $task_ids = $this->getSubQuery()->findAllByColumn('task_id');
+
+ if (! empty($task_ids)) {
+ $this->query->in(Task::TABLE.'.id', $task_ids);
+ } else {
+ $this->query->eq(Task::TABLE.'.id', 0); // No match
+ }
+ }
+
+ /**
+ * Get subquery
+ *
+ * @access protected
+ * @return Table
+ */
+ protected function getSubQuery()
+ {
+ return $this->db->table(TaskLink::TABLE)
+ ->columns(
+ TaskLink::TABLE.'.task_id',
+ Link::TABLE.'.label'
+ )
+ ->join(Link::TABLE, 'id', 'link_id', TaskLink::TABLE)
+ ->ilike(Link::TABLE.'.label', $this->value);
+ }
+}
diff --git a/app/Filter/TaskModificationDateFilter.php b/app/Filter/TaskModificationDateFilter.php
new file mode 100644
index 00000000..d8838bce
--- /dev/null
+++ b/app/Filter/TaskModificationDateFilter.php
@@ -0,0 +1,38 @@
+<?php
+
+namespace Kanboard\Filter;
+
+use Kanboard\Core\Filter\FilterInterface;
+use Kanboard\Model\Task;
+
+/**
+ * Filter tasks by modification date
+ *
+ * @package filter
+ * @author Frederic Guillot
+ */
+class TaskModificationDateFilter extends BaseFilter implements FilterInterface
+{
+ /**
+ * Get search attribute
+ *
+ * @access public
+ * @return string[]
+ */
+ public function getAttributes()
+ {
+ return array('updated', 'modified');
+ }
+
+ /**
+ * Apply filter
+ *
+ * @access public
+ * @return FilterInterface
+ */
+ public function apply()
+ {
+ $this->applyDateFilter(Task::TABLE.'.date_modification');
+ return $this;
+ }
+}
diff --git a/app/Filter/TaskProjectFilter.php b/app/Filter/TaskProjectFilter.php
new file mode 100644
index 00000000..e432efee
--- /dev/null
+++ b/app/Filter/TaskProjectFilter.php
@@ -0,0 +1,44 @@
+<?php
+
+namespace Kanboard\Filter;
+
+use Kanboard\Core\Filter\FilterInterface;
+use Kanboard\Model\Project;
+use Kanboard\Model\Task;
+
+/**
+ * Filter tasks by project
+ *
+ * @package filter
+ * @author Frederic Guillot
+ */
+class TaskProjectFilter extends BaseFilter implements FilterInterface
+{
+ /**
+ * Get search attribute
+ *
+ * @access public
+ * @return string[]
+ */
+ public function getAttributes()
+ {
+ return array('project');
+ }
+
+ /**
+ * Apply filter
+ *
+ * @access public
+ * @return FilterInterface
+ */
+ public function apply()
+ {
+ if (is_int($this->value) || ctype_digit($this->value)) {
+ $this->query->eq(Task::TABLE.'.project_id', $this->value);
+ } else {
+ $this->query->ilike(Project::TABLE.'.name', $this->value);
+ }
+
+ return $this;
+ }
+}
diff --git a/app/Filter/TaskProjectsFilter.php b/app/Filter/TaskProjectsFilter.php
new file mode 100644
index 00000000..e0fc09cf
--- /dev/null
+++ b/app/Filter/TaskProjectsFilter.php
@@ -0,0 +1,38 @@
+<?php
+
+namespace Kanboard\Filter;
+
+use Kanboard\Core\Filter\FilterInterface;
+use Kanboard\Model\Task;
+
+/**
+ * Filter tasks by project ids
+ *
+ * @package filter
+ * @author Frederic Guillot
+ */
+class TaskProjectsFilter extends BaseFilter implements FilterInterface
+{
+ /**
+ * Get search attribute
+ *
+ * @access public
+ * @return string[]
+ */
+ public function getAttributes()
+ {
+ return array('projects');
+ }
+
+ /**
+ * Apply filter
+ *
+ * @access public
+ * @return FilterInterface
+ */
+ public function apply()
+ {
+ $this->query->in(Task::TABLE.'.project_id', $this->value);
+ return $this;
+ }
+}
diff --git a/app/Filter/TaskReferenceFilter.php b/app/Filter/TaskReferenceFilter.php
new file mode 100644
index 00000000..4ad47dd5
--- /dev/null
+++ b/app/Filter/TaskReferenceFilter.php
@@ -0,0 +1,38 @@
+<?php
+
+namespace Kanboard\Filter;
+
+use Kanboard\Core\Filter\FilterInterface;
+use Kanboard\Model\Task;
+
+/**
+ * Filter tasks by reference
+ *
+ * @package filter
+ * @author Frederic Guillot
+ */
+class TaskReferenceFilter extends BaseFilter implements FilterInterface
+{
+ /**
+ * Get search attribute
+ *
+ * @access public
+ * @return string[]
+ */
+ public function getAttributes()
+ {
+ return array('reference', 'ref');
+ }
+
+ /**
+ * Apply filter
+ *
+ * @access public
+ * @return FilterInterface
+ */
+ public function apply()
+ {
+ $this->query->eq(Task::TABLE.'.reference', $this->value);
+ return $this;
+ }
+}
diff --git a/app/Filter/TaskStartDateFilter.php b/app/Filter/TaskStartDateFilter.php
new file mode 100644
index 00000000..d45bc0d4
--- /dev/null
+++ b/app/Filter/TaskStartDateFilter.php
@@ -0,0 +1,38 @@
+<?php
+
+namespace Kanboard\Filter;
+
+use Kanboard\Core\Filter\FilterInterface;
+use Kanboard\Model\Task;
+
+/**
+ * Filter tasks by start date
+ *
+ * @package filter
+ * @author Frederic Guillot
+ */
+class TaskStartDateFilter extends BaseFilter implements FilterInterface
+{
+ /**
+ * Get search attribute
+ *
+ * @access public
+ * @return string[]
+ */
+ public function getAttributes()
+ {
+ return array('started');
+ }
+
+ /**
+ * Apply filter
+ *
+ * @access public
+ * @return FilterInterface
+ */
+ public function apply()
+ {
+ $this->applyDateFilter(Task::TABLE.'.date_started');
+ return $this;
+ }
+}
diff --git a/app/Filter/TaskStatusFilter.php b/app/Filter/TaskStatusFilter.php
new file mode 100644
index 00000000..0ba4361e
--- /dev/null
+++ b/app/Filter/TaskStatusFilter.php
@@ -0,0 +1,43 @@
+<?php
+
+namespace Kanboard\Filter;
+
+use Kanboard\Core\Filter\FilterInterface;
+use Kanboard\Model\Task;
+
+/**
+ * Filter tasks by status
+ *
+ * @package filter
+ * @author Frederic Guillot
+ */
+class TaskStatusFilter extends BaseFilter implements FilterInterface
+{
+ /**
+ * Get search attribute
+ *
+ * @access public
+ * @return string[]
+ */
+ public function getAttributes()
+ {
+ return array('status');
+ }
+
+ /**
+ * Apply filter
+ *
+ * @access public
+ * @return FilterInterface
+ */
+ public function apply()
+ {
+ if ($this->value === 'open' || $this->value === 'closed') {
+ $this->query->eq(Task::TABLE.'.is_active', $this->value === 'open' ? Task::STATUS_OPEN : Task::STATUS_CLOSED);
+ } else {
+ $this->query->eq(Task::TABLE.'.is_active', $this->value);
+ }
+
+ return $this;
+ }
+}
diff --git a/app/Filter/TaskSubtaskAssigneeFilter.php b/app/Filter/TaskSubtaskAssigneeFilter.php
new file mode 100644
index 00000000..4c757315
--- /dev/null
+++ b/app/Filter/TaskSubtaskAssigneeFilter.php
@@ -0,0 +1,140 @@
+<?php
+
+namespace Kanboard\Filter;
+
+use Kanboard\Core\Filter\FilterInterface;
+use Kanboard\Model\Subtask;
+use Kanboard\Model\Task;
+use Kanboard\Model\User;
+use PicoDb\Database;
+use PicoDb\Table;
+
+/**
+ * Filter tasks by subtasks assignee
+ *
+ * @package filter
+ * @author Frederic Guillot
+ */
+class TaskSubtaskAssigneeFilter extends BaseFilter implements FilterInterface
+{
+ /**
+ * Database object
+ *
+ * @access private
+ * @var Database
+ */
+ private $db;
+
+ /**
+ * Current user id
+ *
+ * @access private
+ * @var int
+ */
+ private $currentUserId = 0;
+
+ /**
+ * Set current user id
+ *
+ * @access public
+ * @param integer $userId
+ * @return TaskSubtaskAssigneeFilter
+ */
+ public function setCurrentUserId($userId)
+ {
+ $this->currentUserId = $userId;
+ return $this;
+ }
+
+ /**
+ * Set database object
+ *
+ * @access public
+ * @param Database $db
+ * @return TaskSubtaskAssigneeFilter
+ */
+ public function setDatabase(Database $db)
+ {
+ $this->db = $db;
+ return $this;
+ }
+
+ /**
+ * Get search attribute
+ *
+ * @access public
+ * @return string[]
+ */
+ public function getAttributes()
+ {
+ return array('subtask:assignee');
+ }
+
+ /**
+ * Apply filter
+ *
+ * @access public
+ * @return string
+ */
+ public function apply()
+ {
+ $task_ids = $this->getSubQuery()->findAllByColumn('task_id');
+
+ if (! empty($task_ids)) {
+ $this->query->in(Task::TABLE.'.id', $task_ids);
+ } else {
+ $this->query->eq(Task::TABLE.'.id', 0); // No match
+ }
+ }
+
+ /**
+ * Get subquery
+ *
+ * @access protected
+ * @return Table
+ */
+ protected function getSubQuery()
+ {
+ $subquery = $this->db->table(Subtask::TABLE)
+ ->columns(
+ Subtask::TABLE.'.user_id',
+ Subtask::TABLE.'.task_id',
+ User::TABLE.'.name',
+ User::TABLE.'.username'
+ )
+ ->join(User::TABLE, 'id', 'user_id', Subtask::TABLE)
+ ->neq(Subtask::TABLE.'.status', Subtask::STATUS_DONE);
+
+ return $this->applySubQueryFilter($subquery);
+ }
+
+ /**
+ * Apply subquery filter
+ *
+ * @access protected
+ * @param Table $subquery
+ * @return Table
+ */
+ protected function applySubQueryFilter(Table $subquery)
+ {
+ if (is_int($this->value) || ctype_digit($this->value)) {
+ $subquery->eq(Subtask::TABLE.'.user_id', $this->value);
+ } else {
+ switch ($this->value) {
+ case 'me':
+ $subquery->eq(Subtask::TABLE.'.user_id', $this->currentUserId);
+ break;
+ case 'nobody':
+ $subquery->eq(Subtask::TABLE.'.user_id', 0);
+ break;
+ default:
+ $subquery->beginOr();
+ $subquery->ilike(User::TABLE.'.username', $this->value.'%');
+ $subquery->ilike(User::TABLE.'.name', '%'.$this->value.'%');
+ $subquery->closeOr();
+ }
+ }
+
+ return $subquery;
+ }
+}
diff --git a/app/Filter/TaskSwimlaneFilter.php b/app/Filter/TaskSwimlaneFilter.php
new file mode 100644
index 00000000..4e030244
--- /dev/null
+++ b/app/Filter/TaskSwimlaneFilter.php
@@ -0,0 +1,50 @@
+<?php
+
+namespace Kanboard\Filter;
+
+use Kanboard\Core\Filter\FilterInterface;
+use Kanboard\Model\Project;
+use Kanboard\Model\Swimlane;
+use Kanboard\Model\Task;
+
+/**
+ * Filter tasks by swimlane
+ *
+ * @package filter
+ * @author Frederic Guillot
+ */
+class TaskSwimlaneFilter extends BaseFilter implements FilterInterface
+{
+ /**
+ * Get search attribute
+ *
+ * @access public
+ * @return string[]
+ */
+ public function getAttributes()
+ {
+ return array('swimlane');
+ }
+
+ /**
+ * Apply filter
+ *
+ * @access public
+ * @return FilterInterface
+ */
+ public function apply()
+ {
+ if (is_int($this->value) || ctype_digit($this->value)) {
+ $this->query->eq(Task::TABLE.'.swimlane_id', $this->value);
+ } elseif ($this->value === 'default') {
+ $this->query->eq(Task::TABLE.'.swimlane_id', 0);
+ } else {
+ $this->query->beginOr();
+ $this->query->ilike(Swimlane::TABLE.'.name', $this->value);
+ $this->query->ilike(Project::TABLE.'.default_swimlane', $this->value);
+ $this->query->closeOr();
+ }
+
+ return $this;
+ }
+}
diff --git a/app/Filter/TaskTitleFilter.php b/app/Filter/TaskTitleFilter.php
new file mode 100644
index 00000000..9853369c
--- /dev/null
+++ b/app/Filter/TaskTitleFilter.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace Kanboard\Filter;
+
+use Kanboard\Core\Filter\FilterInterface;
+use Kanboard\Model\Task;
+
+/**
+ * Filter tasks by title
+ *
+ * @package filter
+ * @author Frederic Guillot
+ */
+class TaskTitleFilter extends BaseFilter implements FilterInterface
+{
+ /**
+ * Get search attribute
+ *
+ * @access public
+ * @return string[]
+ */
+ public function getAttributes()
+ {
+ return array('title');
+ }
+
+ /**
+ * Apply filter
+ *
+ * @access public
+ * @return FilterInterface
+ */
+ public function apply()
+ {
+ if (ctype_digit($this->value) || (strlen($this->value) > 1 && $this->value{0} === '#' && ctype_digit(substr($this->value, 1)))) {
+ $this->query->beginOr();
+ $this->query->eq(Task::TABLE.'.id', str_replace('#', '', $this->value));
+ $this->query->ilike(Task::TABLE.'.title', '%'.$this->value.'%');
+ $this->query->closeOr();
+ } else {
+ $this->query->ilike(Task::TABLE.'.title', '%'.$this->value.'%');
+ }
+
+ return $this;
+ }
+}
diff --git a/app/Filter/UserNameFilter.php b/app/Filter/UserNameFilter.php
new file mode 100644
index 00000000..dfb07fdd
--- /dev/null
+++ b/app/Filter/UserNameFilter.php
@@ -0,0 +1,35 @@
+<?php
+
+namespace Kanboard\Filter;
+
+use Kanboard\Core\Filter\FilterInterface;
+
+class UserNameFilter extends BaseFilter implements FilterInterface
+{
+ /**
+ * Get search attribute
+ *
+ * @access public
+ * @return string[]
+ */
+ public function getAttributes()
+ {
+ return array('name');
+ }
+
+ /**
+ * Apply filter
+ *
+ * @access public
+ * @return FilterInterface
+ */
+ public function apply()
+ {
+ $this->query->beginOr()
+ ->ilike('username', '%'.$this->value.'%')
+ ->ilike('name', '%'.$this->value.'%')
+ ->closeOr();
+
+ return $this;
+ }
+}