Filter refactoring
This commit is contained in:
parent
42813d702d
commit
11858be4e8
|
|
@ -23,7 +23,6 @@ before_script:
|
|||
- phpenv config-rm xdebug.ini
|
||||
- phpenv config-add tests/php.ini
|
||||
- composer install
|
||||
- php -i
|
||||
|
||||
script:
|
||||
- phpunit -c tests/units.$DB.xml
|
||||
|
|
|
|||
|
|
@ -1,3 +1,10 @@
|
|||
Version 1.0.28 (unreleased)
|
||||
--------------
|
||||
|
||||
Improvements:
|
||||
|
||||
* Filter/Lexer/QueryBuilder refactoring
|
||||
|
||||
Version 1.0.27
|
||||
--------------
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Kanboard\Controller;
|
||||
|
||||
use Kanboard\Filter\TaskProjectFilter;
|
||||
use Kanboard\Model\Task as TaskModel;
|
||||
|
||||
/**
|
||||
|
|
@ -44,13 +45,15 @@ class Analytic extends Base
|
|||
public function compareHours()
|
||||
{
|
||||
$project = $this->getProject();
|
||||
$query = $this->taskFilter->create()->filterByProject($project['id'])->getQuery();
|
||||
|
||||
$paginator = $this->paginator
|
||||
->setUrl('analytic', 'compareHours', array('project_id' => $project['id']))
|
||||
->setMax(30)
|
||||
->setOrder(TaskModel::TABLE.'.id')
|
||||
->setQuery($query)
|
||||
->setQuery($this->taskQuery
|
||||
->withFilter(new TaskProjectFilter($project['id']))
|
||||
->getQuery()
|
||||
)
|
||||
->calculate();
|
||||
|
||||
$this->response->html($this->helper->layout->analytic('analytic/compare_hours', array(
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
namespace Kanboard\Controller;
|
||||
|
||||
use Kanboard\Formatter\BoardFormatter;
|
||||
|
||||
/**
|
||||
* Board controller
|
||||
*
|
||||
|
|
@ -51,12 +53,14 @@ class Board extends Base
|
|||
$search = $this->helper->projectHeader->getSearchQuery($project);
|
||||
|
||||
$this->response->html($this->helper->layout->app('board/view_private', array(
|
||||
'swimlanes' => $this->taskFilter->search($search)->getBoard($project['id']),
|
||||
'project' => $project,
|
||||
'title' => $project['name'],
|
||||
'description' => $this->helper->projectHeader->getDescription($project),
|
||||
'board_private_refresh_interval' => $this->config->get('board_private_refresh_interval'),
|
||||
'board_highlight_period' => $this->config->get('board_highlight_period'),
|
||||
'swimlanes' => $this->taskLexer
|
||||
->build($search)
|
||||
->format(BoardFormatter::getInstance($this->container)->setProjectId($project['id']))
|
||||
)));
|
||||
}
|
||||
|
||||
|
|
@ -178,9 +182,11 @@ class Board extends Base
|
|||
{
|
||||
return $this->template->render('board/table_container', array(
|
||||
'project' => $this->project->getById($project_id),
|
||||
'swimlanes' => $this->taskFilter->search($this->userSession->getFilters($project_id))->getBoard($project_id),
|
||||
'board_private_refresh_interval' => $this->config->get('board_private_refresh_interval'),
|
||||
'board_highlight_period' => $this->config->get('board_highlight_period'),
|
||||
'swimlanes' => $this->taskLexer
|
||||
->build($this->userSession->getFilters($project_id))
|
||||
->format(BoardFormatter::getInstance($this->container)->setProjectId($project_id))
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,9 @@
|
|||
|
||||
namespace Kanboard\Controller;
|
||||
|
||||
use Kanboard\Filter\TaskAssigneeFilter;
|
||||
use Kanboard\Filter\TaskProjectFilter;
|
||||
use Kanboard\Filter\TaskStatusFilter;
|
||||
use Kanboard\Model\Task as TaskModel;
|
||||
|
||||
/**
|
||||
|
|
@ -40,21 +43,11 @@ class Calendar extends Base
|
|||
$project_id = $this->request->getIntegerParam('project_id');
|
||||
$start = $this->request->getStringParam('start');
|
||||
$end = $this->request->getStringParam('end');
|
||||
$search = $this->userSession->getFilters($project_id);
|
||||
$queryBuilder = $this->taskLexer->build($search)->withFilter(new TaskProjectFilter($project_id));
|
||||
|
||||
// Common filter
|
||||
$filter = $this->taskFilterCalendarFormatter
|
||||
->search($this->userSession->getFilters($project_id))
|
||||
->filterByProject($project_id);
|
||||
|
||||
// Tasks
|
||||
if ($this->config->get('calendar_project_tasks', 'date_started') === 'date_creation') {
|
||||
$events = $filter->copy()->filterByCreationDateRange($start, $end)->setColumns('date_creation', 'date_completed')->format();
|
||||
} else {
|
||||
$events = $filter->copy()->filterByStartDateRange($start, $end)->setColumns('date_started', 'date_completed')->format();
|
||||
}
|
||||
|
||||
// Tasks with due date
|
||||
$events = array_merge($events, $filter->copy()->filterByDueDateRange($start, $end)->setColumns('date_due')->setFullDay()->format());
|
||||
$events = $this->helper->calendar->getTaskDateDueEvents(clone($queryBuilder), $start, $end);
|
||||
$events = array_merge($events, $this->helper->calendar->getTaskEvents(clone($queryBuilder), $start, $end));
|
||||
|
||||
$events = $this->hook->merge('controller:calendar:project:events', $events, array(
|
||||
'project_id' => $project_id,
|
||||
|
|
@ -75,21 +68,15 @@ class Calendar extends Base
|
|||
$user_id = $this->request->getIntegerParam('user_id');
|
||||
$start = $this->request->getStringParam('start');
|
||||
$end = $this->request->getStringParam('end');
|
||||
$filter = $this->taskFilterCalendarFormatter->create()->filterByOwner($user_id)->filterByStatus(TaskModel::STATUS_OPEN);
|
||||
$queryBuilder = $this->taskQuery
|
||||
->withFilter(new TaskAssigneeFilter($user_id))
|
||||
->withFilter(new TaskStatusFilter(TaskModel::STATUS_OPEN));
|
||||
|
||||
// Task with due date
|
||||
$events = $filter->copy()->filterByDueDateRange($start, $end)->setColumns('date_due')->setFullDay()->format();
|
||||
$events = $this->helper->calendar->getTaskDateDueEvents(clone($queryBuilder), $start, $end);
|
||||
$events = array_merge($events, $this->helper->calendar->getTaskEvents(clone($queryBuilder), $start, $end));
|
||||
|
||||
// Tasks
|
||||
if ($this->config->get('calendar_user_tasks', 'date_started') === 'date_creation') {
|
||||
$events = array_merge($events, $filter->copy()->filterByCreationDateRange($start, $end)->setColumns('date_creation', 'date_completed')->format());
|
||||
} else {
|
||||
$events = array_merge($events, $filter->copy()->filterByStartDateRange($start, $end)->setColumns('date_started', 'date_completed')->format());
|
||||
}
|
||||
|
||||
// Subtasks time tracking
|
||||
if ($this->config->get('calendar_user_subtasks_time_tracking') == 1) {
|
||||
$events = array_merge($events, $this->subtaskTimeTracking->getUserCalendarEvents($user_id, $start, $end));
|
||||
$events = array_merge($events, $this->helper->calendar->getSubtaskTimeTrackingEvents($user_id, $start, $end));
|
||||
}
|
||||
|
||||
$events = $this->hook->merge('controller:calendar:user:events', $events, array(
|
||||
|
|
|
|||
|
|
@ -2,7 +2,14 @@
|
|||
|
||||
namespace Kanboard\Controller;
|
||||
|
||||
use Kanboard\Filter\ProjectIdsFilter;
|
||||
use Kanboard\Filter\ProjectStatusFilter;
|
||||
use Kanboard\Filter\ProjectTypeFilter;
|
||||
use Kanboard\Filter\TaskProjectFilter;
|
||||
use Kanboard\Formatter\ProjectGanttFormatter;
|
||||
use Kanboard\Formatter\TaskGanttFormatter;
|
||||
use Kanboard\Model\Task as TaskModel;
|
||||
use Kanboard\Model\Project as ProjectModel;
|
||||
|
||||
/**
|
||||
* Gantt controller
|
||||
|
|
@ -17,14 +24,16 @@ class Gantt extends Base
|
|||
*/
|
||||
public function projects()
|
||||
{
|
||||
if ($this->userSession->isAdmin()) {
|
||||
$project_ids = $this->project->getAllIds();
|
||||
} else {
|
||||
$project_ids = $this->projectPermission->getActiveProjectIds($this->userSession->getId());
|
||||
}
|
||||
$project_ids = $this->projectPermission->getActiveProjectIds($this->userSession->getId());
|
||||
$filter = $this->projectQuery
|
||||
->withFilter(new ProjectTypeFilter(ProjectModel::TYPE_TEAM))
|
||||
->withFilter(new ProjectStatusFilter(ProjectModel::ACTIVE))
|
||||
->withFilter(new ProjectIdsFilter($project_ids));
|
||||
|
||||
$filter->getQuery()->asc(ProjectModel::TABLE.'.start_date');
|
||||
|
||||
$this->response->html($this->helper->layout->app('gantt/projects', array(
|
||||
'projects' => $this->projectGanttFormatter->filter($project_ids)->format(),
|
||||
'projects' => $filter->format(new ProjectGanttFormatter($this->container)),
|
||||
'title' => t('Gantt chart for all projects'),
|
||||
)));
|
||||
}
|
||||
|
|
@ -56,8 +65,8 @@ class Gantt extends Base
|
|||
{
|
||||
$project = $this->getProject();
|
||||
$search = $this->helper->projectHeader->getSearchQuery($project);
|
||||
$filter = $this->taskFilterGanttFormatter->search($search)->filterByProject($project['id']);
|
||||
$sorting = $this->request->getStringParam('sorting', 'board');
|
||||
$filter = $this->taskLexer->build($search)->withFilter(new TaskProjectFilter($project['id']));
|
||||
|
||||
if ($sorting === 'date') {
|
||||
$filter->getQuery()->asc(TaskModel::TABLE.'.date_started')->asc(TaskModel::TABLE.'.date_creation');
|
||||
|
|
@ -70,7 +79,7 @@ class Gantt extends Base
|
|||
'title' => $project['name'],
|
||||
'description' => $this->helper->projectHeader->getDescription($project),
|
||||
'sorting' => $sorting,
|
||||
'tasks' => $filter->format(),
|
||||
'tasks' => $filter->format(new TaskGanttFormatter($this->container)),
|
||||
)));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
namespace Kanboard\Controller;
|
||||
|
||||
use Kanboard\Formatter\GroupAutoCompleteFormatter;
|
||||
|
||||
/**
|
||||
* Group Helper
|
||||
*
|
||||
|
|
@ -11,14 +13,14 @@ namespace Kanboard\Controller;
|
|||
class GroupHelper extends Base
|
||||
{
|
||||
/**
|
||||
* Group autocompletion (Ajax)
|
||||
* Group auto-completion (Ajax)
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function autocomplete()
|
||||
{
|
||||
$search = $this->request->getStringParam('term');
|
||||
$groups = $this->groupManager->find($search);
|
||||
$this->response->json($this->groupAutoCompleteFormatter->setGroups($groups)->format());
|
||||
$formatter = new GroupAutoCompleteFormatter($this->groupManager->find($search));
|
||||
$this->response->json($formatter->format());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,11 @@
|
|||
|
||||
namespace Kanboard\Controller;
|
||||
|
||||
use Kanboard\Model\TaskFilter;
|
||||
use Kanboard\Core\Filter\QueryBuilder;
|
||||
use Kanboard\Filter\TaskAssigneeFilter;
|
||||
use Kanboard\Filter\TaskProjectFilter;
|
||||
use Kanboard\Filter\TaskStatusFilter;
|
||||
use Kanboard\Formatter\TaskICalFormatter;
|
||||
use Kanboard\Model\Task as TaskModel;
|
||||
use Eluceo\iCal\Component\Calendar as iCalendar;
|
||||
|
||||
|
|
@ -30,10 +34,11 @@ class Ical extends Base
|
|||
}
|
||||
|
||||
// Common filter
|
||||
$filter = $this->taskFilterICalendarFormatter
|
||||
->create()
|
||||
->filterByStatus(TaskModel::STATUS_OPEN)
|
||||
->filterByOwner($user['id']);
|
||||
$queryBuilder = new QueryBuilder();
|
||||
$queryBuilder
|
||||
->withQuery($this->taskFinder->getICalQuery())
|
||||
->withFilter(new TaskStatusFilter(TaskModel::STATUS_OPEN))
|
||||
->withFilter(new TaskAssigneeFilter($user['id']));
|
||||
|
||||
// Calendar properties
|
||||
$calendar = new iCalendar('Kanboard');
|
||||
|
|
@ -41,7 +46,7 @@ class Ical extends Base
|
|||
$calendar->setDescription($user['name'] ?: $user['username']);
|
||||
$calendar->setPublishedTTL('PT1H');
|
||||
|
||||
$this->renderCalendar($filter, $calendar);
|
||||
$this->renderCalendar($queryBuilder, $calendar);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -60,10 +65,11 @@ class Ical extends Base
|
|||
}
|
||||
|
||||
// Common filter
|
||||
$filter = $this->taskFilterICalendarFormatter
|
||||
->create()
|
||||
->filterByStatus(TaskModel::STATUS_OPEN)
|
||||
->filterByProject($project['id']);
|
||||
$queryBuilder = new QueryBuilder();
|
||||
$queryBuilder
|
||||
->withQuery($this->taskFinder->getICalQuery())
|
||||
->withFilter(new TaskStatusFilter(TaskModel::STATUS_OPEN))
|
||||
->withFilter(new TaskProjectFilter($project['id']));
|
||||
|
||||
// Calendar properties
|
||||
$calendar = new iCalendar('Kanboard');
|
||||
|
|
@ -71,7 +77,7 @@ class Ical extends Base
|
|||
$calendar->setDescription($project['name']);
|
||||
$calendar->setPublishedTTL('PT1H');
|
||||
|
||||
$this->renderCalendar($filter, $calendar);
|
||||
$this->renderCalendar($queryBuilder, $calendar);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -79,37 +85,14 @@ class Ical extends Base
|
|||
*
|
||||
* @access private
|
||||
*/
|
||||
private function renderCalendar(TaskFilter $filter, iCalendar $calendar)
|
||||
private function renderCalendar(QueryBuilder $queryBuilder, iCalendar $calendar)
|
||||
{
|
||||
$start = $this->request->getStringParam('start', strtotime('-2 month'));
|
||||
$end = $this->request->getStringParam('end', strtotime('+6 months'));
|
||||
|
||||
// Tasks
|
||||
if ($this->config->get('calendar_project_tasks', 'date_started') === 'date_creation') {
|
||||
$filter
|
||||
->copy()
|
||||
->filterByCreationDateRange($start, $end)
|
||||
->setColumns('date_creation', 'date_completed')
|
||||
->setCalendar($calendar)
|
||||
->addDateTimeEvents();
|
||||
} else {
|
||||
$filter
|
||||
->copy()
|
||||
->filterByStartDateRange($start, $end)
|
||||
->setColumns('date_started', 'date_completed')
|
||||
->setCalendar($calendar)
|
||||
->addDateTimeEvents($calendar);
|
||||
}
|
||||
$this->helper->ical->addTaskDateDueEvents($queryBuilder, $calendar, $start, $end);
|
||||
|
||||
// Tasks with due date
|
||||
$filter
|
||||
->copy()
|
||||
->filterByDueDateRange($start, $end)
|
||||
->setColumns('date_due')
|
||||
->setCalendar($calendar)
|
||||
->addFullDayEvents($calendar);
|
||||
|
||||
$this->response->contentType('text/calendar; charset=utf-8');
|
||||
echo $filter->setCalendar($calendar)->format();
|
||||
$formatter = new TaskICalFormatter($this->container);
|
||||
$this->response->ical($formatter->setCalendar($calendar)->format());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Kanboard\Controller;
|
||||
|
||||
use Kanboard\Filter\TaskProjectFilter;
|
||||
use Kanboard\Model\Task as TaskModel;
|
||||
|
||||
/**
|
||||
|
|
@ -21,14 +22,17 @@ class Listing extends Base
|
|||
{
|
||||
$project = $this->getProject();
|
||||
$search = $this->helper->projectHeader->getSearchQuery($project);
|
||||
$query = $this->taskFilter->search($search)->filterByProject($project['id'])->getQuery();
|
||||
|
||||
$paginator = $this->paginator
|
||||
->setUrl('listing', 'show', array('project_id' => $project['id']))
|
||||
->setMax(30)
|
||||
->setOrder(TaskModel::TABLE.'.id')
|
||||
->setDirection('DESC')
|
||||
->setQuery($query)
|
||||
->setQuery($this->taskLexer
|
||||
->build($search)
|
||||
->withFilter(new TaskProjectFilter($project['id']))
|
||||
->getQuery()
|
||||
)
|
||||
->calculate();
|
||||
|
||||
$this->response->html($this->helper->layout->app('listing/show', array(
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
namespace Kanboard\Controller;
|
||||
|
||||
use Kanboard\Filter\TaskProjectsFilter;
|
||||
|
||||
/**
|
||||
* Search controller
|
||||
*
|
||||
|
|
@ -23,14 +25,12 @@ class Search extends Base
|
|||
->setDirection('DESC');
|
||||
|
||||
if ($search !== '' && ! empty($projects)) {
|
||||
$query = $this
|
||||
->taskFilter
|
||||
->search($search)
|
||||
->filterByProjects(array_keys($projects))
|
||||
->getQuery();
|
||||
|
||||
$paginator
|
||||
->setQuery($query)
|
||||
->setQuery($this->taskLexer
|
||||
->build($search)
|
||||
->withFilter(new TaskProjectsFilter(array_keys($projects)))
|
||||
->getQuery()
|
||||
)
|
||||
->calculate();
|
||||
|
||||
$nb_tasks = $paginator->getTotal();
|
||||
|
|
|
|||
|
|
@ -2,6 +2,12 @@
|
|||
|
||||
namespace Kanboard\Controller;
|
||||
|
||||
use Kanboard\Filter\TaskIdExclusionFilter;
|
||||
use Kanboard\Filter\TaskIdFilter;
|
||||
use Kanboard\Filter\TaskProjectsFilter;
|
||||
use Kanboard\Filter\TaskTitleFilter;
|
||||
use Kanboard\Formatter\TaskAutoCompleteFormatter;
|
||||
|
||||
/**
|
||||
* Task Ajax Helper
|
||||
*
|
||||
|
|
@ -11,31 +17,33 @@ namespace Kanboard\Controller;
|
|||
class TaskHelper extends Base
|
||||
{
|
||||
/**
|
||||
* Task autocompletion (Ajax)
|
||||
* Task auto-completion (Ajax)
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function autocomplete()
|
||||
{
|
||||
$search = $this->request->getStringParam('term');
|
||||
$projects = $this->projectPermission->getActiveProjectIds($this->userSession->getId());
|
||||
$project_ids = $this->projectPermission->getActiveProjectIds($this->userSession->getId());
|
||||
$exclude_task_id = $this->request->getIntegerParam('exclude_task_id');
|
||||
|
||||
if (empty($projects)) {
|
||||
if (empty($project_ids)) {
|
||||
$this->response->json(array());
|
||||
}
|
||||
|
||||
$filter = $this->taskFilterAutoCompleteFormatter
|
||||
->create()
|
||||
->filterByProjects($projects)
|
||||
->excludeTasks(array($this->request->getIntegerParam('exclude_task_id')));
|
||||
|
||||
// Search by task id or by title
|
||||
if (ctype_digit($search)) {
|
||||
$filter->filterById($search);
|
||||
} else {
|
||||
$filter->filterByTitle($search);
|
||||
}
|
||||
|
||||
$this->response->json($filter->format());
|
||||
$filter = $this->taskQuery->withFilter(new TaskProjectsFilter($project_ids));
|
||||
|
||||
if (! empty($exclude_task_id)) {
|
||||
$filter->withFilter(new TaskIdExclusionFilter(array($exclude_task_id)));
|
||||
}
|
||||
|
||||
if (ctype_digit($search)) {
|
||||
$filter->withFilter(new TaskIdFilter($search));
|
||||
} else {
|
||||
$filter->withFilter(new TaskTitleFilter($search));
|
||||
}
|
||||
|
||||
$this->response->json($filter->format(new TaskAutoCompleteFormatter($this->container)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,10 @@
|
|||
|
||||
namespace Kanboard\Controller;
|
||||
|
||||
use Kanboard\Filter\UserNameFilter;
|
||||
use Kanboard\Formatter\UserAutoCompleteFormatter;
|
||||
use Kanboard\Model\User as UserModel;
|
||||
|
||||
/**
|
||||
* User Helper
|
||||
*
|
||||
|
|
@ -11,19 +15,20 @@ namespace Kanboard\Controller;
|
|||
class UserHelper extends Base
|
||||
{
|
||||
/**
|
||||
* User autocompletion (Ajax)
|
||||
* User auto-completion (Ajax)
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function autocomplete()
|
||||
{
|
||||
$search = $this->request->getStringParam('term');
|
||||
$users = $this->userFilterAutoCompleteFormatter->create($search)->filterByUsernameOrByName()->format();
|
||||
$this->response->json($users);
|
||||
$filter = $this->userQuery->withFilter(new UserNameFilter($search));
|
||||
$filter->getQuery()->asc(UserModel::TABLE.'.name')->asc(UserModel::TABLE.'.username');
|
||||
$this->response->json($filter->format(new UserAutoCompleteFormatter($this->container)));
|
||||
}
|
||||
|
||||
/**
|
||||
* User mention autocompletion (Ajax)
|
||||
* User mention auto-completion (Ajax)
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ class ActionManager extends Base
|
|||
* List of automatic actions
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
* @var ActionBase[]
|
||||
*/
|
||||
private $actions = array();
|
||||
|
||||
|
|
|
|||
|
|
@ -48,16 +48,8 @@ use Pimple\Container;
|
|||
* @property \Kanboard\Core\User\UserSession $userSession
|
||||
* @property \Kanboard\Core\DateParser $dateParser
|
||||
* @property \Kanboard\Core\Helper $helper
|
||||
* @property \Kanboard\Core\Lexer $lexer
|
||||
* @property \Kanboard\Core\Paginator $paginator
|
||||
* @property \Kanboard\Core\Template $template
|
||||
* @property \Kanboard\Formatter\ProjectGanttFormatter $projectGanttFormatter
|
||||
* @property \Kanboard\Formatter\TaskFilterGanttFormatter $taskFilterGanttFormatter
|
||||
* @property \Kanboard\Formatter\TaskFilterAutoCompleteFormatter $taskFilterAutoCompleteFormatter
|
||||
* @property \Kanboard\Formatter\TaskFilterCalendarFormatter $taskFilterCalendarFormatter
|
||||
* @property \Kanboard\Formatter\TaskFilterICalendarFormatter $taskFilterICalendarFormatter
|
||||
* @property \Kanboard\Formatter\UserFilterAutoCompleteFormatter $userFilterAutoCompleteFormatter
|
||||
* @property \Kanboard\Formatter\GroupAutoCompleteFormatter $groupAutoCompleteFormatter
|
||||
* @property \Kanboard\Model\Action $action
|
||||
* @property \Kanboard\Model\ActionParameter $actionParameter
|
||||
* @property \Kanboard\Model\AvatarFile $avatarFile
|
||||
|
|
@ -85,7 +77,6 @@ use Pimple\Container;
|
|||
* @property \Kanboard\Model\ProjectMetadata $projectMetadata
|
||||
* @property \Kanboard\Model\ProjectPermission $projectPermission
|
||||
* @property \Kanboard\Model\ProjectUserRole $projectUserRole
|
||||
* @property \Kanboard\Model\projectUserRoleFilter $projectUserRoleFilter
|
||||
* @property \Kanboard\Model\ProjectGroupRole $projectGroupRole
|
||||
* @property \Kanboard\Model\ProjectNotification $projectNotification
|
||||
* @property \Kanboard\Model\ProjectNotificationType $projectNotificationType
|
||||
|
|
@ -99,7 +90,6 @@ use Pimple\Container;
|
|||
* @property \Kanboard\Model\TaskDuplication $taskDuplication
|
||||
* @property \Kanboard\Model\TaskExternalLink $taskExternalLink
|
||||
* @property \Kanboard\Model\TaskFinder $taskFinder
|
||||
* @property \Kanboard\Model\TaskFilter $taskFilter
|
||||
* @property \Kanboard\Model\TaskLink $taskLink
|
||||
* @property \Kanboard\Model\TaskModification $taskModification
|
||||
* @property \Kanboard\Model\TaskPermission $taskPermission
|
||||
|
|
@ -137,6 +127,12 @@ use Pimple\Container;
|
|||
* @property \Kanboard\Export\SubtaskExport $subtaskExport
|
||||
* @property \Kanboard\Export\TaskExport $taskExport
|
||||
* @property \Kanboard\Export\TransitionExport $transitionExport
|
||||
* @property \Kanboard\Core\Filter\QueryBuilder $projectGroupRoleQuery
|
||||
* @property \Kanboard\Core\Filter\QueryBuilder $projectUserRoleQuery
|
||||
* @property \Kanboard\Core\Filter\QueryBuilder $userQuery
|
||||
* @property \Kanboard\Core\Filter\QueryBuilder $projectQuery
|
||||
* @property \Kanboard\Core\Filter\QueryBuilder $taskQuery
|
||||
* @property \Kanboard\Core\Filter\LexerBuilder $taskLexer
|
||||
* @property \Psr\Log\LoggerInterface $logger
|
||||
* @property \PicoDb\Database $db
|
||||
* @property \Symfony\Component\EventDispatcher\EventDispatcher $dispatcher
|
||||
|
|
@ -173,4 +169,18 @@ abstract class Base
|
|||
{
|
||||
return $this->container[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get object instance
|
||||
*
|
||||
* @static
|
||||
* @access public
|
||||
* @param Container $container
|
||||
* @return static
|
||||
*/
|
||||
public static function getInstance(Container $container)
|
||||
{
|
||||
$self = new static($container);
|
||||
return $self;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ class ExternalLinkManager extends Base
|
|||
* Registered providers
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
* @var ExternalLinkProviderInterface[]
|
||||
*/
|
||||
private $providers = array();
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Core\Filter;
|
||||
|
||||
use PicoDb\Table;
|
||||
|
||||
/**
|
||||
* Criteria Interface
|
||||
*
|
||||
* @package filter
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
interface CriteriaInterface
|
||||
{
|
||||
/**
|
||||
* Set the Query
|
||||
*
|
||||
* @access public
|
||||
* @param Table $query
|
||||
* @return CriteriaInterface
|
||||
*/
|
||||
public function withQuery(Table $query);
|
||||
|
||||
/**
|
||||
* Set filter
|
||||
*
|
||||
* @access public
|
||||
* @param FilterInterface $filter
|
||||
* @return CriteriaInterface
|
||||
*/
|
||||
public function withFilter(FilterInterface $filter);
|
||||
|
||||
/**
|
||||
* Apply condition
|
||||
*
|
||||
* @access public
|
||||
* @return CriteriaInterface
|
||||
*/
|
||||
public function apply();
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Core\Filter;
|
||||
|
||||
use PicoDb\Table;
|
||||
|
||||
/**
|
||||
* Filter Interface
|
||||
*
|
||||
* @package filter
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
interface FilterInterface
|
||||
{
|
||||
/**
|
||||
* BaseFilter constructor
|
||||
*
|
||||
* @access public
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function __construct($value = null);
|
||||
|
||||
/**
|
||||
* Set the value
|
||||
*
|
||||
* @access public
|
||||
* @param string $value
|
||||
* @return FilterInterface
|
||||
*/
|
||||
public function withValue($value);
|
||||
|
||||
/**
|
||||
* Set query
|
||||
*
|
||||
* @access public
|
||||
* @param Table $query
|
||||
* @return FilterInterface
|
||||
*/
|
||||
public function withQuery(Table $query);
|
||||
|
||||
/**
|
||||
* Get search attribute
|
||||
*
|
||||
* @access public
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAttributes();
|
||||
|
||||
/**
|
||||
* Apply filter
|
||||
*
|
||||
* @access public
|
||||
* @return FilterInterface
|
||||
*/
|
||||
public function apply();
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Core\Filter;
|
||||
|
||||
use PicoDb\Table;
|
||||
|
||||
/**
|
||||
* Formatter interface
|
||||
*
|
||||
* @package filter
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
interface FormatterInterface
|
||||
{
|
||||
/**
|
||||
* Set query
|
||||
*
|
||||
* @access public
|
||||
* @param Table $query
|
||||
* @return FormatterInterface
|
||||
*/
|
||||
public function withQuery(Table $query);
|
||||
|
||||
/**
|
||||
* Apply formatter
|
||||
*
|
||||
* @access public
|
||||
* @return mixed
|
||||
*/
|
||||
public function format();
|
||||
}
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Core\Filter;
|
||||
|
||||
/**
|
||||
* Lexer
|
||||
*
|
||||
* @package filter
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Lexer
|
||||
{
|
||||
/**
|
||||
* Current position
|
||||
*
|
||||
* @access private
|
||||
* @var integer
|
||||
*/
|
||||
private $offset = 0;
|
||||
|
||||
/**
|
||||
* Token map
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private $tokenMap = array(
|
||||
"/^(\s+)/" => 'T_WHITESPACE',
|
||||
'/^([<=>]{0,2}[0-9]{4}-[0-9]{2}-[0-9]{2})/' => 'T_DATE',
|
||||
'/^(yesterday|tomorrow|today)/' => 'T_DATE',
|
||||
'/^("(.*?)")/' => 'T_STRING',
|
||||
"/^(\w+)/" => 'T_STRING',
|
||||
"/^(#\d+)/" => 'T_STRING',
|
||||
);
|
||||
|
||||
/**
|
||||
* Default token
|
||||
*
|
||||
* @access private
|
||||
* @var string
|
||||
*/
|
||||
private $defaultToken = '';
|
||||
|
||||
/**
|
||||
* Add token
|
||||
*
|
||||
* @access public
|
||||
* @param string $regex
|
||||
* @param string $token
|
||||
* @return $this
|
||||
*/
|
||||
public function addToken($regex, $token)
|
||||
{
|
||||
$this->tokenMap = array($regex => $token) + $this->tokenMap;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set default token
|
||||
*
|
||||
* @access public
|
||||
* @param string $token
|
||||
* @return $this
|
||||
*/
|
||||
public function setDefaultToken($token)
|
||||
{
|
||||
$this->defaultToken = $token;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tokenize input string
|
||||
*
|
||||
* @access public
|
||||
* @param string $input
|
||||
* @return array
|
||||
*/
|
||||
public function tokenize($input)
|
||||
{
|
||||
$tokens = array();
|
||||
$this->offset = 0;
|
||||
|
||||
while (isset($input[$this->offset])) {
|
||||
$result = $this->match(substr($input, $this->offset));
|
||||
|
||||
if ($result === false) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$tokens[] = $result;
|
||||
}
|
||||
|
||||
return $this->map($tokens);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a token that match and move the offset
|
||||
*
|
||||
* @access protected
|
||||
* @param string $string
|
||||
* @return array|boolean
|
||||
*/
|
||||
protected function match($string)
|
||||
{
|
||||
foreach ($this->tokenMap as $pattern => $name) {
|
||||
if (preg_match($pattern, $string, $matches)) {
|
||||
$this->offset += strlen($matches[1]);
|
||||
|
||||
return array(
|
||||
'match' => trim($matches[1], '"'),
|
||||
'token' => $name,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build map of tokens and matches
|
||||
*
|
||||
* @access protected
|
||||
* @param array $tokens
|
||||
* @return array
|
||||
*/
|
||||
protected function map(array $tokens)
|
||||
{
|
||||
$map = array();
|
||||
$leftOver = '';
|
||||
|
||||
while (false !== ($token = current($tokens))) {
|
||||
if ($token['token'] === 'T_STRING' || $token['token'] === 'T_WHITESPACE') {
|
||||
$leftOver .= $token['match'];
|
||||
} else {
|
||||
$next = next($tokens);
|
||||
|
||||
if ($next !== false && in_array($next['token'], array('T_STRING', 'T_DATE'))) {
|
||||
$map[$token['token']][] = $next['match'];
|
||||
}
|
||||
}
|
||||
|
||||
next($tokens);
|
||||
}
|
||||
|
||||
$leftOver = trim($leftOver);
|
||||
|
||||
if ($this->defaultToken !== '' && $leftOver !== '') {
|
||||
$map[$this->defaultToken] = array($leftOver);
|
||||
}
|
||||
|
||||
return $map;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,151 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Core\Filter;
|
||||
|
||||
use PicoDb\Table;
|
||||
|
||||
/**
|
||||
* Lexer Builder
|
||||
*
|
||||
* @package filter
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class LexerBuilder
|
||||
{
|
||||
/**
|
||||
* Lexer object
|
||||
*
|
||||
* @access protected
|
||||
* @var Lexer
|
||||
*/
|
||||
protected $lexer;
|
||||
|
||||
/**
|
||||
* Query object
|
||||
*
|
||||
* @access protected
|
||||
* @var Table
|
||||
*/
|
||||
protected $query;
|
||||
|
||||
/**
|
||||
* List of filters
|
||||
*
|
||||
* @access protected
|
||||
* @var FilterInterface[]
|
||||
*/
|
||||
protected $filters;
|
||||
|
||||
/**
|
||||
* QueryBuilder object
|
||||
*
|
||||
* @access protected
|
||||
* @var QueryBuilder
|
||||
*/
|
||||
protected $queryBuilder;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->lexer = new Lexer;
|
||||
$this->queryBuilder = new QueryBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a filter
|
||||
*
|
||||
* @access public
|
||||
* @param FilterInterface $filter
|
||||
* @param bool $default
|
||||
* @return LexerBuilder
|
||||
*/
|
||||
public function withFilter(FilterInterface $filter, $default = false)
|
||||
{
|
||||
$attributes = $filter->getAttributes();
|
||||
|
||||
foreach ($attributes as $attribute) {
|
||||
$this->filters[$attribute] = $filter;
|
||||
$this->lexer->addToken(sprintf("/^(%s:)/", $attribute), $attribute);
|
||||
|
||||
if ($default) {
|
||||
$this->lexer->setDefaultToken($attribute);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the query
|
||||
*
|
||||
* @access public
|
||||
* @param Table $query
|
||||
* @return LexerBuilder
|
||||
*/
|
||||
public function withQuery(Table $query)
|
||||
{
|
||||
$this->query = $query;
|
||||
$this->queryBuilder->withQuery($this->query);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the input and build the query
|
||||
*
|
||||
* @access public
|
||||
* @param string $input
|
||||
* @return QueryBuilder
|
||||
*/
|
||||
public function build($input)
|
||||
{
|
||||
$tokens = $this->lexer->tokenize($input);
|
||||
|
||||
foreach ($tokens as $token => $values) {
|
||||
if (isset($this->filters[$token])) {
|
||||
$this->applyFilters($this->filters[$token], $values);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->queryBuilder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply filters to the query
|
||||
*
|
||||
* @access protected
|
||||
* @param FilterInterface $filter
|
||||
* @param array $values
|
||||
*/
|
||||
protected function applyFilters(FilterInterface $filter, array $values)
|
||||
{
|
||||
$len = count($values);
|
||||
|
||||
if ($len > 1) {
|
||||
$criteria = new OrCriteria();
|
||||
$criteria->withQuery($this->query);
|
||||
|
||||
foreach ($values as $value) {
|
||||
$currentFilter = clone($filter);
|
||||
$criteria->withFilter($currentFilter->withValue($value));
|
||||
}
|
||||
|
||||
$this->queryBuilder->withCriteria($criteria);
|
||||
} elseif ($len === 1) {
|
||||
$this->queryBuilder->withFilter($filter->withValue($values[0]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone object with deep copy
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
$this->lexer = clone $this->lexer;
|
||||
$this->query = clone $this->query;
|
||||
$this->queryBuilder = clone $this->queryBuilder;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Core\Filter;
|
||||
|
||||
use PicoDb\Table;
|
||||
|
||||
/**
|
||||
* OR criteria
|
||||
*
|
||||
* @package filter
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class OrCriteria implements CriteriaInterface
|
||||
{
|
||||
/**
|
||||
* @var Table
|
||||
*/
|
||||
protected $query;
|
||||
|
||||
/**
|
||||
* @var FilterInterface[]
|
||||
*/
|
||||
protected $filters = array();
|
||||
|
||||
/**
|
||||
* Set the Query
|
||||
*
|
||||
* @access public
|
||||
* @param Table $query
|
||||
* @return CriteriaInterface
|
||||
*/
|
||||
public function withQuery(Table $query)
|
||||
{
|
||||
$this->query = $query;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set filter
|
||||
*
|
||||
* @access public
|
||||
* @param FilterInterface $filter
|
||||
* @return CriteriaInterface
|
||||
*/
|
||||
public function withFilter(FilterInterface $filter)
|
||||
{
|
||||
$this->filters[] = $filter;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply condition
|
||||
*
|
||||
* @access public
|
||||
* @return CriteriaInterface
|
||||
*/
|
||||
public function apply()
|
||||
{
|
||||
$this->query->beginOr();
|
||||
|
||||
foreach ($this->filters as $filter) {
|
||||
$filter->withQuery($this->query)->apply();
|
||||
}
|
||||
|
||||
$this->query->closeOr();
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Core\Filter;
|
||||
|
||||
use PicoDb\Table;
|
||||
|
||||
/**
|
||||
* Class QueryBuilder
|
||||
*
|
||||
* @package filter
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class QueryBuilder
|
||||
{
|
||||
/**
|
||||
* Query object
|
||||
*
|
||||
* @access protected
|
||||
* @var Table
|
||||
*/
|
||||
protected $query;
|
||||
|
||||
/**
|
||||
* Set the query
|
||||
*
|
||||
* @access public
|
||||
* @param Table $query
|
||||
* @return QueryBuilder
|
||||
*/
|
||||
public function withQuery(Table $query)
|
||||
{
|
||||
$this->query = $query;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a filter
|
||||
*
|
||||
* @access public
|
||||
* @param FilterInterface $filter
|
||||
* @return QueryBuilder
|
||||
*/
|
||||
public function withFilter(FilterInterface $filter)
|
||||
{
|
||||
$filter->withQuery($this->query)->apply();
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a criteria
|
||||
*
|
||||
* @access public
|
||||
* @param CriteriaInterface $criteria
|
||||
* @return QueryBuilder
|
||||
*/
|
||||
public function withCriteria(CriteriaInterface $criteria)
|
||||
{
|
||||
$criteria->withQuery($this->query)->apply();
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a formatter
|
||||
*
|
||||
* @access public
|
||||
* @param FormatterInterface $formatter
|
||||
* @return string|array
|
||||
*/
|
||||
public function format(FormatterInterface $formatter)
|
||||
{
|
||||
return $formatter->withQuery($this->query)->format();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the query result as array
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function toArray()
|
||||
{
|
||||
return $this->query->findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Query object
|
||||
*
|
||||
* @access public
|
||||
* @return Table
|
||||
*/
|
||||
public function getQuery()
|
||||
{
|
||||
return $this->query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone object with deep copy
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
$this->query = clone $this->query;
|
||||
}
|
||||
}
|
||||
|
|
@ -12,10 +12,12 @@ use Pimple\Container;
|
|||
*
|
||||
* @property \Kanboard\Helper\AppHelper $app
|
||||
* @property \Kanboard\Helper\AssetHelper $asset
|
||||
* @property \Kanboard\Helper\CalendarHelper $calendar
|
||||
* @property \Kanboard\Helper\DateHelper $dt
|
||||
* @property \Kanboard\Helper\FileHelper $file
|
||||
* @property \Kanboard\Helper\FormHelper $form
|
||||
* @property \Kanboard\Helper\HookHelper $hook
|
||||
* @property \Kanboard\Helper\ICalHelper $ical
|
||||
* @property \Kanboard\Helper\ModelHelper $model
|
||||
* @property \Kanboard\Helper\SubtaskHelper $subtask
|
||||
* @property \Kanboard\Helper\TaskHelper $task
|
||||
|
|
|
|||
|
|
@ -231,6 +231,20 @@ class Response extends Base
|
|||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a iCal response
|
||||
*
|
||||
* @access public
|
||||
* @param string $data Raw data
|
||||
* @param integer $status_code HTTP status code
|
||||
*/
|
||||
public function ical($data, $status_code = 200)
|
||||
{
|
||||
$this->status($status_code);
|
||||
$this->contentType('text/calendar; charset=utf-8');
|
||||
echo $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the security header: Content-Security-Policy
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1,161 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Core;
|
||||
|
||||
/**
|
||||
* Lexer
|
||||
*
|
||||
* @package core
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Lexer
|
||||
{
|
||||
/**
|
||||
* Current position
|
||||
*
|
||||
* @access private
|
||||
* @var integer
|
||||
*/
|
||||
private $offset = 0;
|
||||
|
||||
/**
|
||||
* Token map
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private $tokenMap = array(
|
||||
"/^(assignee:)/" => 'T_ASSIGNEE',
|
||||
"/^(color:)/" => 'T_COLOR',
|
||||
"/^(due:)/" => 'T_DUE',
|
||||
"/^(updated:)/" => 'T_UPDATED',
|
||||
"/^(modified:)/" => 'T_UPDATED',
|
||||
"/^(created:)/" => 'T_CREATED',
|
||||
"/^(status:)/" => 'T_STATUS',
|
||||
"/^(description:)/" => 'T_DESCRIPTION',
|
||||
"/^(category:)/" => 'T_CATEGORY',
|
||||
"/^(column:)/" => 'T_COLUMN',
|
||||
"/^(project:)/" => 'T_PROJECT',
|
||||
"/^(swimlane:)/" => 'T_SWIMLANE',
|
||||
"/^(ref:)/" => 'T_REFERENCE',
|
||||
"/^(reference:)/" => 'T_REFERENCE',
|
||||
"/^(link:)/" => 'T_LINK',
|
||||
"/^(\s+)/" => 'T_WHITESPACE',
|
||||
'/^([<=>]{0,2}[0-9]{4}-[0-9]{2}-[0-9]{2})/' => 'T_DATE',
|
||||
'/^(yesterday|tomorrow|today)/' => 'T_DATE',
|
||||
'/^("(.*?)")/' => 'T_STRING',
|
||||
"/^(\w+)/" => 'T_STRING',
|
||||
"/^(#\d+)/" => 'T_STRING',
|
||||
);
|
||||
|
||||
/**
|
||||
* Tokenize input string
|
||||
*
|
||||
* @access public
|
||||
* @param string $input
|
||||
* @return array
|
||||
*/
|
||||
public function tokenize($input)
|
||||
{
|
||||
$tokens = array();
|
||||
$this->offset = 0;
|
||||
|
||||
while (isset($input[$this->offset])) {
|
||||
$result = $this->match(substr($input, $this->offset));
|
||||
|
||||
if ($result === false) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$tokens[] = $result;
|
||||
}
|
||||
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a token that match and move the offset
|
||||
*
|
||||
* @access public
|
||||
* @param string $string
|
||||
* @return array|boolean
|
||||
*/
|
||||
public function match($string)
|
||||
{
|
||||
foreach ($this->tokenMap as $pattern => $name) {
|
||||
if (preg_match($pattern, $string, $matches)) {
|
||||
$this->offset += strlen($matches[1]);
|
||||
|
||||
return array(
|
||||
'match' => trim($matches[1], '"'),
|
||||
'token' => $name,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the output of tokenizer to be easily parsed by the database filter
|
||||
*
|
||||
* Example: ['T_ASSIGNEE' => ['user1', 'user2'], 'T_TITLE' => 'task title']
|
||||
*
|
||||
* @access public
|
||||
* @param array $tokens
|
||||
* @return array
|
||||
*/
|
||||
public function map(array $tokens)
|
||||
{
|
||||
$map = array(
|
||||
'T_TITLE' => '',
|
||||
);
|
||||
|
||||
while (false !== ($token = current($tokens))) {
|
||||
switch ($token['token']) {
|
||||
case 'T_ASSIGNEE':
|
||||
case 'T_COLOR':
|
||||
case 'T_CATEGORY':
|
||||
case 'T_COLUMN':
|
||||
case 'T_PROJECT':
|
||||
case 'T_SWIMLANE':
|
||||
case 'T_LINK':
|
||||
$next = next($tokens);
|
||||
|
||||
if ($next !== false && $next['token'] === 'T_STRING') {
|
||||
$map[$token['token']][] = $next['match'];
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'T_STATUS':
|
||||
case 'T_DUE':
|
||||
case 'T_UPDATED':
|
||||
case 'T_CREATED':
|
||||
case 'T_DESCRIPTION':
|
||||
case 'T_REFERENCE':
|
||||
$next = next($tokens);
|
||||
|
||||
if ($next !== false && ($next['token'] === 'T_DATE' || $next['token'] === 'T_STRING')) {
|
||||
$map[$token['token']] = $next['match'];
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
$map['T_TITLE'] .= $token['match'];
|
||||
break;
|
||||
}
|
||||
|
||||
next($tokens);
|
||||
}
|
||||
|
||||
$map['T_TITLE'] = trim($map['T_TITLE']);
|
||||
|
||||
if (empty($map['T_TITLE'])) {
|
||||
unset($map['T_TITLE']);
|
||||
}
|
||||
|
||||
return $map;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Formatter;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
use Kanboard\Core\Filter\FormatterInterface;
|
||||
use PicoDb\Table;
|
||||
|
||||
/**
|
||||
* Class BaseFormatter
|
||||
*
|
||||
* @package formatter
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
abstract class BaseFormatter extends Base
|
||||
{
|
||||
/**
|
||||
* Query object
|
||||
*
|
||||
* @access protected
|
||||
* @var Table
|
||||
*/
|
||||
protected $query;
|
||||
|
||||
/**
|
||||
* Set query
|
||||
*
|
||||
* @access public
|
||||
* @param Table $query
|
||||
* @return FormatterInterface
|
||||
*/
|
||||
public function withQuery(Table $query)
|
||||
{
|
||||
$this->query = $query;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace Kanboard\Formatter;
|
||||
|
||||
use Kanboard\Model\TaskFilter;
|
||||
use Kanboard\Core\Filter\FormatterInterface;
|
||||
|
||||
/**
|
||||
* Common class to handle calendar events
|
||||
|
|
@ -10,7 +10,7 @@ use Kanboard\Model\TaskFilter;
|
|||
* @package formatter
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
abstract class TaskFilterCalendarEvent extends TaskFilter
|
||||
abstract class BaseTaskCalendarFormatter extends BaseFormatter
|
||||
{
|
||||
/**
|
||||
* Column used for event start date
|
||||
|
|
@ -28,21 +28,13 @@ abstract class TaskFilterCalendarEvent extends TaskFilter
|
|||
*/
|
||||
protected $endColumn = 'date_completed';
|
||||
|
||||
/**
|
||||
* Full day event flag
|
||||
*
|
||||
* @access private
|
||||
* @var boolean
|
||||
*/
|
||||
private $fullDay = false;
|
||||
|
||||
/**
|
||||
* Transform results to calendar events
|
||||
*
|
||||
* @access public
|
||||
* @param string $start_column Column name for the start date
|
||||
* @param string $end_column Column name for the end date
|
||||
* @return TaskFilterCalendarEvent
|
||||
* @return FormatterInterface
|
||||
*/
|
||||
public function setColumns($start_column, $end_column = '')
|
||||
{
|
||||
|
|
@ -50,27 +42,4 @@ abstract class TaskFilterCalendarEvent extends TaskFilter
|
|||
$this->endColumn = $end_column ?: $start_column;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* When called calendar events will be full day
|
||||
*
|
||||
* @access public
|
||||
* @return TaskFilterCalendarEvent
|
||||
*/
|
||||
public function setFullDay()
|
||||
{
|
||||
$this->fullDay = true;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the events are full day
|
||||
*
|
||||
* @access public
|
||||
* @return boolean
|
||||
*/
|
||||
public function isFullDay()
|
||||
{
|
||||
return $this->fullDay;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Formatter;
|
||||
|
||||
use Kanboard\Core\Filter\FormatterInterface;
|
||||
use Kanboard\Model\Task;
|
||||
|
||||
/**
|
||||
* Board Formatter
|
||||
*
|
||||
* @package formatter
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class BoardFormatter extends BaseFormatter implements FormatterInterface
|
||||
{
|
||||
/**
|
||||
* Project id
|
||||
*
|
||||
* @access protected
|
||||
* @var integer
|
||||
*/
|
||||
protected $projectId;
|
||||
|
||||
/**
|
||||
* Set ProjectId
|
||||
*
|
||||
* @access public
|
||||
* @param integer $projectId
|
||||
* @return $this
|
||||
*/
|
||||
public function setProjectId($projectId)
|
||||
{
|
||||
$this->projectId = $projectId;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply formatter
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function format()
|
||||
{
|
||||
$tasks = $this->query
|
||||
->eq(Task::TABLE.'.project_id', $this->projectId)
|
||||
->asc(Task::TABLE.'.position')
|
||||
->findAll();
|
||||
|
||||
return $this->board->getBoard($this->projectId, function ($project_id, $column_id, $swimlane_id) use ($tasks) {
|
||||
return array_filter($tasks, function (array $task) use ($column_id, $swimlane_id) {
|
||||
return $task['column_id'] == $column_id && $task['swimlane_id'] == $swimlane_id;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Formatter;
|
||||
|
||||
/**
|
||||
* Formatter Interface
|
||||
*
|
||||
* @package formatter
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
interface FormatterInterface
|
||||
{
|
||||
public function format();
|
||||
}
|
||||
|
|
@ -2,8 +2,12 @@
|
|||
|
||||
namespace Kanboard\Formatter;
|
||||
|
||||
use Kanboard\Core\Filter\FormatterInterface;
|
||||
use Kanboard\Core\Group\GroupProviderInterface;
|
||||
use PicoDb\Table;
|
||||
|
||||
/**
|
||||
* Autocomplete formatter for groups
|
||||
* Auto-complete formatter for groups
|
||||
*
|
||||
* @package formatter
|
||||
* @author Frederic Guillot
|
||||
|
|
@ -14,25 +18,35 @@ class GroupAutoCompleteFormatter implements FormatterInterface
|
|||
* Groups found
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
* @var GroupProviderInterface[]
|
||||
*/
|
||||
private $groups;
|
||||
|
||||
/**
|
||||
* Format groups for the ajax autocompletion
|
||||
* Format groups for the ajax auto-completion
|
||||
*
|
||||
* @access public
|
||||
* @param array $groups
|
||||
* @return GroupAutoCompleteFormatter
|
||||
* @param GroupProviderInterface[] $groups
|
||||
*/
|
||||
public function setGroups(array $groups)
|
||||
public function __construct(array $groups)
|
||||
{
|
||||
$this->groups = $groups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set query
|
||||
*
|
||||
* @access public
|
||||
* @param Table $query
|
||||
* @return FormatterInterface
|
||||
*/
|
||||
public function withQuery(Table $query)
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format groups for the ajax autocompletion
|
||||
* Format groups for the ajax auto-completion
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace Kanboard\Formatter;
|
||||
|
||||
use Kanboard\Model\Project;
|
||||
use Kanboard\Core\Filter\FormatterInterface;
|
||||
|
||||
/**
|
||||
* Gantt chart formatter for projects
|
||||
|
|
@ -10,40 +10,8 @@ use Kanboard\Model\Project;
|
|||
* @package formatter
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class ProjectGanttFormatter extends Project implements FormatterInterface
|
||||
class ProjectGanttFormatter extends BaseFormatter implements FormatterInterface
|
||||
{
|
||||
/**
|
||||
* List of projects
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private $projects = array();
|
||||
|
||||
/**
|
||||
* Filter projects to generate the Gantt chart
|
||||
*
|
||||
* @access public
|
||||
* @param int[] $project_ids
|
||||
* @return ProjectGanttFormatter
|
||||
*/
|
||||
public function filter(array $project_ids)
|
||||
{
|
||||
if (empty($project_ids)) {
|
||||
$this->projects = array();
|
||||
} else {
|
||||
$this->projects = $this->db
|
||||
->table(self::TABLE)
|
||||
->asc('start_date')
|
||||
->in('id', $project_ids)
|
||||
->eq('is_active', self::ACTIVE)
|
||||
->eq('is_private', 0)
|
||||
->findAll();
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format projects to be displayed in the Gantt chart
|
||||
*
|
||||
|
|
@ -52,10 +20,11 @@ class ProjectGanttFormatter extends Project implements FormatterInterface
|
|||
*/
|
||||
public function format()
|
||||
{
|
||||
$projects = $this->query->findAll();
|
||||
$colors = $this->color->getDefaultColors();
|
||||
$bars = array();
|
||||
|
||||
foreach ($this->projects as $project) {
|
||||
foreach ($projects as $project) {
|
||||
$start = empty($project['start_date']) ? time() : strtotime($project['start_date']);
|
||||
$end = empty($project['end_date']) ? $start : strtotime($project['end_date']);
|
||||
$color = next($colors) ?: reset($colors);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Formatter;
|
||||
|
||||
use Kanboard\Core\Filter\FormatterInterface;
|
||||
|
||||
class SubtaskTimeTrackingCalendarFormatter extends BaseFormatter implements FormatterInterface
|
||||
{
|
||||
/**
|
||||
* Format calendar events
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function format()
|
||||
{
|
||||
$events = array();
|
||||
|
||||
foreach ($this->query->findAll() as $row) {
|
||||
$user = isset($row['username']) ? ' ('.($row['user_fullname'] ?: $row['username']).')' : '';
|
||||
|
||||
$events[] = array(
|
||||
'id' => $row['id'],
|
||||
'subtask_id' => $row['subtask_id'],
|
||||
'title' => t('#%d', $row['task_id']).' '.$row['subtask_title'].$user,
|
||||
'start' => date('Y-m-d\TH:i:s', $row['start']),
|
||||
'end' => date('Y-m-d\TH:i:s', $row['end'] ?: time()),
|
||||
'backgroundColor' => $this->color->getBackgroundColor($row['color_id']),
|
||||
'borderColor' => $this->color->getBorderColor($row['color_id']),
|
||||
'textColor' => 'black',
|
||||
'url' => $this->helper->url->to('task', 'show', array('task_id' => $row['task_id'], 'project_id' => $row['project_id'])),
|
||||
'editable' => false,
|
||||
);
|
||||
}
|
||||
|
||||
return $events;
|
||||
}
|
||||
}
|
||||
|
|
@ -2,19 +2,19 @@
|
|||
|
||||
namespace Kanboard\Formatter;
|
||||
|
||||
use Kanboard\Core\Filter\FormatterInterface;
|
||||
use Kanboard\Model\Task;
|
||||
use Kanboard\Model\TaskFilter;
|
||||
|
||||
/**
|
||||
* Autocomplete formatter for task filter
|
||||
* Task AutoComplete Formatter
|
||||
*
|
||||
* @package formatter
|
||||
* @author Frederic Guillot
|
||||
* @package formatter
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class TaskFilterAutoCompleteFormatter extends TaskFilter implements FormatterInterface
|
||||
class TaskAutoCompleteFormatter extends BaseFormatter implements FormatterInterface
|
||||
{
|
||||
/**
|
||||
* Format the tasks for the ajax autocompletion
|
||||
* Apply formatter
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
|
|
@ -2,14 +2,36 @@
|
|||
|
||||
namespace Kanboard\Formatter;
|
||||
|
||||
use Kanboard\Core\Filter\FormatterInterface;
|
||||
|
||||
/**
|
||||
* Calendar event formatter for task filter
|
||||
*
|
||||
* @package formatter
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class TaskFilterCalendarFormatter extends TaskFilterCalendarEvent implements FormatterInterface
|
||||
class TaskCalendarFormatter extends BaseTaskCalendarFormatter implements FormatterInterface
|
||||
{
|
||||
/**
|
||||
* Full day event flag
|
||||
*
|
||||
* @access private
|
||||
* @var boolean
|
||||
*/
|
||||
private $fullDay = false;
|
||||
|
||||
/**
|
||||
* When called calendar events will be full day
|
||||
*
|
||||
* @access public
|
||||
* @return FormatterInterface
|
||||
*/
|
||||
public function setFullDay()
|
||||
{
|
||||
$this->fullDay = true;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform tasks to calendar events
|
||||
*
|
||||
|
|
@ -31,8 +53,8 @@ class TaskFilterCalendarFormatter extends TaskFilterCalendarEvent implements For
|
|||
'url' => $this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])),
|
||||
'start' => date($this->getDateTimeFormat(), $task[$this->startColumn]),
|
||||
'end' => date($this->getDateTimeFormat(), $task[$this->endColumn] ?: time()),
|
||||
'editable' => $this->isFullDay(),
|
||||
'allday' => $this->isFullDay(),
|
||||
'editable' => $this->fullDay,
|
||||
'allday' => $this->fullDay,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -47,6 +69,6 @@ class TaskFilterCalendarFormatter extends TaskFilterCalendarEvent implements For
|
|||
*/
|
||||
private function getDateTimeFormat()
|
||||
{
|
||||
return $this->isFullDay() ? 'Y-m-d' : 'Y-m-d\TH:i:s';
|
||||
return $this->fullDay ? 'Y-m-d' : 'Y-m-d\TH:i:s';
|
||||
}
|
||||
}
|
||||
|
|
@ -2,15 +2,15 @@
|
|||
|
||||
namespace Kanboard\Formatter;
|
||||
|
||||
use Kanboard\Model\TaskFilter;
|
||||
use Kanboard\Core\Filter\FormatterInterface;
|
||||
|
||||
/**
|
||||
* Gantt chart formatter for task filter
|
||||
* Task Gantt Formatter
|
||||
*
|
||||
* @package formatter
|
||||
* @author Frederic Guillot
|
||||
* @package formatter
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class TaskFilterGanttFormatter extends TaskFilter implements FormatterInterface
|
||||
class TaskGanttFormatter extends BaseFormatter implements FormatterInterface
|
||||
{
|
||||
/**
|
||||
* Local cache for project columns
|
||||
|
|
@ -19,9 +19,9 @@ class TaskFilterGanttFormatter extends TaskFilter implements FormatterInterface
|
|||
* @var array
|
||||
*/
|
||||
private $columns = array();
|
||||
|
||||
|
||||
/**
|
||||
* Format tasks to be displayed in the Gantt chart
|
||||
* Apply formatter
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
|
|
@ -6,14 +6,15 @@ use DateTime;
|
|||
use Eluceo\iCal\Component\Calendar;
|
||||
use Eluceo\iCal\Component\Event;
|
||||
use Eluceo\iCal\Property\Event\Attendees;
|
||||
use Kanboard\Core\Filter\FormatterInterface;
|
||||
|
||||
/**
|
||||
* iCal event formatter for task filter
|
||||
* iCal event formatter for tasks
|
||||
*
|
||||
* @package formatter
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class TaskFilterICalendarFormatter extends TaskFilterCalendarEvent implements FormatterInterface
|
||||
class TaskICalFormatter extends BaseTaskCalendarFormatter implements FormatterInterface
|
||||
{
|
||||
/**
|
||||
* Calendar object
|
||||
|
|
@ -39,7 +40,7 @@ class TaskFilterICalendarFormatter extends TaskFilterCalendarEvent implements Fo
|
|||
*
|
||||
* @access public
|
||||
* @param \Eluceo\iCal\Component\Calendar $vCalendar
|
||||
* @return TaskFilterICalendarFormatter
|
||||
* @return FormatterInterface
|
||||
*/
|
||||
public function setCalendar(Calendar $vCalendar)
|
||||
{
|
||||
|
|
@ -48,10 +49,10 @@ class TaskFilterICalendarFormatter extends TaskFilterCalendarEvent implements Fo
|
|||
}
|
||||
|
||||
/**
|
||||
* Transform results to ical events
|
||||
* Transform results to iCal events
|
||||
*
|
||||
* @access public
|
||||
* @return TaskFilterICalendarFormatter
|
||||
* @return FormatterInterface
|
||||
*/
|
||||
public function addDateTimeEvents()
|
||||
{
|
||||
|
|
@ -73,10 +74,10 @@ class TaskFilterICalendarFormatter extends TaskFilterCalendarEvent implements Fo
|
|||
}
|
||||
|
||||
/**
|
||||
* Transform results to all day ical events
|
||||
* Transform results to all day iCal events
|
||||
*
|
||||
* @access public
|
||||
* @return TaskFilterICalendarFormatter
|
||||
* @return FormatterInterface
|
||||
*/
|
||||
public function addFullDayEvents()
|
||||
{
|
||||
|
|
@ -96,7 +97,7 @@ class TaskFilterICalendarFormatter extends TaskFilterCalendarEvent implements Fo
|
|||
}
|
||||
|
||||
/**
|
||||
* Get common events for task ical events
|
||||
* Get common events for task iCal events
|
||||
*
|
||||
* @access protected
|
||||
* @param array $task
|
||||
|
|
@ -3,15 +3,15 @@
|
|||
namespace Kanboard\Formatter;
|
||||
|
||||
use Kanboard\Model\User;
|
||||
use Kanboard\Model\UserFilter;
|
||||
use Kanboard\Core\Filter\FormatterInterface;
|
||||
|
||||
/**
|
||||
* Autocomplete formatter for user filter
|
||||
* Auto-complete formatter for user filter
|
||||
*
|
||||
* @package formatter
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class UserFilterAutoCompleteFormatter extends UserFilter implements FormatterInterface
|
||||
class UserAutoCompleteFormatter extends BaseFormatter implements FormatterInterface
|
||||
{
|
||||
/**
|
||||
* Format the tasks for the ajax autocompletion
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Helper;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
use Kanboard\Core\Filter\QueryBuilder;
|
||||
use Kanboard\Filter\TaskDueDateRangeFilter;
|
||||
use Kanboard\Formatter\SubtaskTimeTrackingCalendarFormatter;
|
||||
use Kanboard\Formatter\TaskCalendarFormatter;
|
||||
|
||||
/**
|
||||
* Calendar Helper
|
||||
*
|
||||
* @package helper
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class CalendarHelper extends Base
|
||||
{
|
||||
/**
|
||||
* Get formatted calendar task due events
|
||||
*
|
||||
* @access public
|
||||
* @param QueryBuilder $queryBuilder
|
||||
* @param string $start
|
||||
* @param string $end
|
||||
* @return array
|
||||
*/
|
||||
public function getTaskDateDueEvents(QueryBuilder $queryBuilder, $start, $end)
|
||||
{
|
||||
$formatter = new TaskCalendarFormatter($this->container);
|
||||
$formatter->setFullDay();
|
||||
$formatter->setColumns('date_due');
|
||||
|
||||
return $queryBuilder
|
||||
->withFilter(new TaskDueDateRangeFilter(array($start, $end)))
|
||||
->format($formatter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get formatted calendar task events
|
||||
*
|
||||
* @access public
|
||||
* @param QueryBuilder $queryBuilder
|
||||
* @param string $start
|
||||
* @param string $end
|
||||
* @return array
|
||||
*/
|
||||
public function getTaskEvents(QueryBuilder $queryBuilder, $start, $end)
|
||||
{
|
||||
$startColumn = $this->config->get('calendar_project_tasks', 'date_started');
|
||||
|
||||
$queryBuilder->getQuery()->addCondition($this->getCalendarCondition(
|
||||
$this->dateParser->getTimestampFromIsoFormat($start),
|
||||
$this->dateParser->getTimestampFromIsoFormat($end),
|
||||
$startColumn,
|
||||
'date_due'
|
||||
));
|
||||
|
||||
$formatter = new TaskCalendarFormatter($this->container);
|
||||
$formatter->setColumns($startColumn, 'date_due');
|
||||
|
||||
return $queryBuilder->format($formatter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get formatted calendar subtask time tracking events
|
||||
*
|
||||
* @access public
|
||||
* @param integer $user_id
|
||||
* @param string $start
|
||||
* @param string $end
|
||||
* @return array
|
||||
*/
|
||||
public function getSubtaskTimeTrackingEvents($user_id, $start, $end)
|
||||
{
|
||||
$formatter = new SubtaskTimeTrackingCalendarFormatter($this->container);
|
||||
return $formatter
|
||||
->withQuery($this->subtaskTimeTracking->getUserQuery($user_id)
|
||||
->addCondition($this->getCalendarCondition(
|
||||
$this->dateParser->getTimestampFromIsoFormat($start),
|
||||
$this->dateParser->getTimestampFromIsoFormat($end),
|
||||
'start',
|
||||
'end'
|
||||
))
|
||||
)
|
||||
->format();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build SQL condition for a given time range
|
||||
*
|
||||
* @access public
|
||||
* @param string $start_time Start timestamp
|
||||
* @param string $end_time End timestamp
|
||||
* @param string $start_column Start column name
|
||||
* @param string $end_column End column name
|
||||
* @return string
|
||||
*/
|
||||
public function getCalendarCondition($start_time, $end_time, $start_column, $end_column)
|
||||
{
|
||||
$start_column = $this->db->escapeIdentifier($start_column);
|
||||
$end_column = $this->db->escapeIdentifier($end_column);
|
||||
|
||||
$conditions = array(
|
||||
"($start_column >= '$start_time' AND $start_column <= '$end_time')",
|
||||
"($start_column <= '$start_time' AND $end_column >= '$start_time')",
|
||||
"($start_column <= '$start_time' AND ($end_column = '0' OR $end_column IS NULL))",
|
||||
);
|
||||
|
||||
return $start_column.' IS NOT NULL AND '.$start_column.' > 0 AND ('.implode(' OR ', $conditions).')';
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Helper;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
use Kanboard\Core\Filter\QueryBuilder;
|
||||
use Kanboard\Filter\TaskDueDateRangeFilter;
|
||||
use Kanboard\Formatter\TaskICalFormatter;
|
||||
use Eluceo\iCal\Component\Calendar as iCalendar;
|
||||
|
||||
/**
|
||||
* ICal Helper
|
||||
*
|
||||
* @package helper
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class ICalHelper extends Base
|
||||
{
|
||||
/**
|
||||
* Get formatted calendar task due events
|
||||
*
|
||||
* @access public
|
||||
* @param QueryBuilder $queryBuilder
|
||||
* @param iCalendar $calendar
|
||||
* @param string $start
|
||||
* @param string $end
|
||||
*/
|
||||
public function addTaskDateDueEvents(QueryBuilder $queryBuilder, iCalendar $calendar, $start, $end)
|
||||
{
|
||||
$queryBuilder->withFilter(new TaskDueDateRangeFilter(array($start, $end)));
|
||||
|
||||
$formatter = new TaskICalFormatter($this->container);
|
||||
$formatter->setColumns('date_due');
|
||||
$formatter->setCalendar($calendar);
|
||||
$formatter->withQuery($queryBuilder->getQuery());
|
||||
$formatter->addFullDayEvents();
|
||||
}
|
||||
}
|
||||
|
|
@ -76,6 +76,7 @@ class AvatarFile extends Base
|
|||
* @access public
|
||||
* @param integer $user_id
|
||||
* @param array $file
|
||||
* @return boolean
|
||||
*/
|
||||
public function uploadFile($user_id, array $file)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -31,28 +31,4 @@ abstract class Base extends \Kanboard\Core\Base
|
|||
return (int) $db->getLastId();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Build SQL condition for a given time range
|
||||
*
|
||||
* @access protected
|
||||
* @param string $start_time Start timestamp
|
||||
* @param string $end_time End timestamp
|
||||
* @param string $start_column Start column name
|
||||
* @param string $end_column End column name
|
||||
* @return string
|
||||
*/
|
||||
protected function getCalendarCondition($start_time, $end_time, $start_column, $end_column)
|
||||
{
|
||||
$start_column = $this->db->escapeIdentifier($start_column);
|
||||
$end_column = $this->db->escapeIdentifier($end_column);
|
||||
|
||||
$conditions = array(
|
||||
"($start_column >= '$start_time' AND $start_column <= '$end_time')",
|
||||
"($start_column <= '$start_time' AND $end_column >= '$start_time')",
|
||||
"($start_column <= '$start_time' AND ($end_column = '0' OR $end_column IS NULL))",
|
||||
);
|
||||
|
||||
return $start_column.' IS NOT NULL AND '.$start_column.' > 0 AND ('.implode(' OR ', $conditions).')';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,20 @@ class Project extends Base
|
|||
*/
|
||||
const INACTIVE = 0;
|
||||
|
||||
/**
|
||||
* Value for private project
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
const TYPE_PRIVATE = 1;
|
||||
|
||||
/**
|
||||
* Value for team project
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
const TYPE_TEAM = 0;
|
||||
|
||||
/**
|
||||
* Get a project by the id
|
||||
*
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
namespace Kanboard\Model;
|
||||
|
||||
use PicoDb\Table;
|
||||
|
||||
/**
|
||||
* Project activity model
|
||||
*
|
||||
|
|
@ -133,12 +135,12 @@ class ProjectActivity extends Base
|
|||
* Common function to return events
|
||||
*
|
||||
* @access public
|
||||
* @param \PicoDb\Table $query PicoDb Query
|
||||
* @param Table $query PicoDb Query
|
||||
* @param integer $start Timestamp of earliest activity
|
||||
* @param integer $end Timestamp of latest activity
|
||||
* @return array
|
||||
*/
|
||||
private function getEvents(\PicoDb\Table $query, $start, $end)
|
||||
private function getEvents(Table $query, $start, $end)
|
||||
{
|
||||
if (! is_null($start)) {
|
||||
$query->gte('date_creation', $start);
|
||||
|
|
|
|||
|
|
@ -1,89 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Model;
|
||||
|
||||
/**
|
||||
* Project Group Role Filter
|
||||
*
|
||||
* @package model
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class ProjectGroupRoleFilter extends Base
|
||||
{
|
||||
/**
|
||||
* Query
|
||||
*
|
||||
* @access protected
|
||||
* @var \PicoDb\Table
|
||||
*/
|
||||
protected $query;
|
||||
|
||||
/**
|
||||
* Initialize filter
|
||||
*
|
||||
* @access public
|
||||
* @return UserFilter
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
$this->query = $this->db->table(ProjectGroupRole::TABLE);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all results of the filter
|
||||
*
|
||||
* @access public
|
||||
* @param string $column
|
||||
* @return array
|
||||
*/
|
||||
public function findAll($column = '')
|
||||
{
|
||||
if ($column !== '') {
|
||||
return $this->query->asc($column)->findAllByColumn($column);
|
||||
}
|
||||
|
||||
return $this->query->findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the PicoDb query
|
||||
*
|
||||
* @access public
|
||||
* @return \PicoDb\Table
|
||||
*/
|
||||
public function getQuery()
|
||||
{
|
||||
return $this->query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by project id
|
||||
*
|
||||
* @access public
|
||||
* @param integer $project_id
|
||||
* @return ProjectUserRoleFilter
|
||||
*/
|
||||
public function filterByProjectId($project_id)
|
||||
{
|
||||
$this->query->eq(ProjectGroupRole::TABLE.'.project_id', $project_id);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by username
|
||||
*
|
||||
* @access public
|
||||
* @param string $input
|
||||
* @return ProjectUserRoleFilter
|
||||
*/
|
||||
public function startWithUsername($input)
|
||||
{
|
||||
$this->query
|
||||
->join(GroupMember::TABLE, 'group_id', 'group_id', ProjectGroupRole::TABLE)
|
||||
->join(User::TABLE, 'id', 'user_id', GroupMember::TABLE)
|
||||
->ilike(User::TABLE.'.username', $input.'%');
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,10 @@
|
|||
namespace Kanboard\Model;
|
||||
|
||||
use Kanboard\Core\Security\Role;
|
||||
use Kanboard\Filter\ProjectGroupRoleProjectFilter;
|
||||
use Kanboard\Filter\ProjectGroupRoleUsernameFilter;
|
||||
use Kanboard\Filter\ProjectUserRoleProjectFilter;
|
||||
use Kanboard\Filter\ProjectUserRoleUsernameFilter;
|
||||
|
||||
/**
|
||||
* Project Permission
|
||||
|
|
@ -53,8 +57,18 @@ class ProjectPermission extends Base
|
|||
*/
|
||||
public function findUsernames($project_id, $input)
|
||||
{
|
||||
$userMembers = $this->projectUserRoleFilter->create()->filterByProjectId($project_id)->startWithUsername($input)->findAll('username');
|
||||
$groupMembers = $this->projectGroupRoleFilter->create()->filterByProjectId($project_id)->startWithUsername($input)->findAll('username');
|
||||
$userMembers = $this->projectUserRoleQuery
|
||||
->withFilter(new ProjectUserRoleProjectFilter($project_id))
|
||||
->withFilter(new ProjectUserRoleUsernameFilter($input))
|
||||
->getQuery()
|
||||
->findAllByColumn('username');
|
||||
|
||||
$groupMembers = $this->projectGroupRoleQuery
|
||||
->withFilter(new ProjectGroupRoleProjectFilter($project_id))
|
||||
->withFilter(new ProjectGroupRoleUsernameFilter($input))
|
||||
->getQuery()
|
||||
->findAllByColumn('username');
|
||||
|
||||
$members = array_unique(array_merge($userMembers, $groupMembers));
|
||||
|
||||
sort($members);
|
||||
|
|
|
|||
|
|
@ -251,8 +251,8 @@ class ProjectUserRole extends Base
|
|||
/**
|
||||
* Copy user 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_src_id
|
||||
* @param integer $project_dst_id
|
||||
* @return boolean
|
||||
*/
|
||||
public function duplicate($project_src_id, $project_dst_id)
|
||||
|
|
|
|||
|
|
@ -1,88 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Model;
|
||||
|
||||
/**
|
||||
* Project User Role Filter
|
||||
*
|
||||
* @package model
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class ProjectUserRoleFilter extends Base
|
||||
{
|
||||
/**
|
||||
* Query
|
||||
*
|
||||
* @access protected
|
||||
* @var \PicoDb\Table
|
||||
*/
|
||||
protected $query;
|
||||
|
||||
/**
|
||||
* Initialize filter
|
||||
*
|
||||
* @access public
|
||||
* @return UserFilter
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
$this->query = $this->db->table(ProjectUserRole::TABLE);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all results of the filter
|
||||
*
|
||||
* @access public
|
||||
* @param string $column
|
||||
* @return array
|
||||
*/
|
||||
public function findAll($column = '')
|
||||
{
|
||||
if ($column !== '') {
|
||||
return $this->query->asc($column)->findAllByColumn($column);
|
||||
}
|
||||
|
||||
return $this->query->findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the PicoDb query
|
||||
*
|
||||
* @access public
|
||||
* @return \PicoDb\Table
|
||||
*/
|
||||
public function getQuery()
|
||||
{
|
||||
return $this->query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by project id
|
||||
*
|
||||
* @access public
|
||||
* @param integer $project_id
|
||||
* @return ProjectUserRoleFilter
|
||||
*/
|
||||
public function filterByProjectId($project_id)
|
||||
{
|
||||
$this->query->eq(ProjectUserRole::TABLE.'.project_id', $project_id);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by username
|
||||
*
|
||||
* @access public
|
||||
* @param string $input
|
||||
* @return ProjectUserRoleFilter
|
||||
*/
|
||||
public function startWithUsername($input)
|
||||
{
|
||||
$this->query
|
||||
->join(User::TABLE, 'id', 'user_id')
|
||||
->ilike(User::TABLE.'.username', $input.'%');
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -22,6 +22,7 @@ abstract class Setting extends Base
|
|||
*
|
||||
* @abstract
|
||||
* @access public
|
||||
* @param array $values
|
||||
* @return array
|
||||
*/
|
||||
abstract public function prepare(array $values);
|
||||
|
|
|
|||
|
|
@ -145,94 +145,6 @@ class SubtaskTimeTracking extends Base
|
|||
->findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user calendar events
|
||||
*
|
||||
* @access public
|
||||
* @param integer $user_id
|
||||
* @param string $start ISO-8601 format
|
||||
* @param string $end
|
||||
* @return array
|
||||
*/
|
||||
public function getUserCalendarEvents($user_id, $start, $end)
|
||||
{
|
||||
$hook = 'model:subtask-time-tracking:calendar:events';
|
||||
$events = $this->getUserQuery($user_id)
|
||||
->addCondition($this->getCalendarCondition(
|
||||
$this->dateParser->getTimestampFromIsoFormat($start),
|
||||
$this->dateParser->getTimestampFromIsoFormat($end),
|
||||
'start',
|
||||
'end'
|
||||
))
|
||||
->findAll();
|
||||
|
||||
if ($this->hook->exists($hook)) {
|
||||
$events = $this->hook->first($hook, array(
|
||||
'user_id' => $user_id,
|
||||
'events' => $events,
|
||||
'start' => $start,
|
||||
'end' => $end,
|
||||
));
|
||||
}
|
||||
|
||||
return $this->toCalendarEvents($events);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get project calendar events
|
||||
*
|
||||
* @access public
|
||||
* @param integer $project_id
|
||||
* @param integer $start
|
||||
* @param integer $end
|
||||
* @return array
|
||||
*/
|
||||
public function getProjectCalendarEvents($project_id, $start, $end)
|
||||
{
|
||||
$result = $this
|
||||
->getProjectQuery($project_id)
|
||||
->addCondition($this->getCalendarCondition(
|
||||
$this->dateParser->getTimestampFromIsoFormat($start),
|
||||
$this->dateParser->getTimestampFromIsoFormat($end),
|
||||
'start',
|
||||
'end'
|
||||
))
|
||||
->findAll();
|
||||
|
||||
return $this->toCalendarEvents($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a record set to calendar events
|
||||
*
|
||||
* @access private
|
||||
* @param array $rows
|
||||
* @return array
|
||||
*/
|
||||
private function toCalendarEvents(array $rows)
|
||||
{
|
||||
$events = array();
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$user = isset($row['username']) ? ' ('.($row['user_fullname'] ?: $row['username']).')' : '';
|
||||
|
||||
$events[] = array(
|
||||
'id' => $row['id'],
|
||||
'subtask_id' => $row['subtask_id'],
|
||||
'title' => t('#%d', $row['task_id']).' '.$row['subtask_title'].$user,
|
||||
'start' => date('Y-m-d\TH:i:s', $row['start']),
|
||||
'end' => date('Y-m-d\TH:i:s', $row['end'] ?: time()),
|
||||
'backgroundColor' => $this->color->getBackgroundColor($row['color_id']),
|
||||
'borderColor' => $this->color->getBorderColor($row['color_id']),
|
||||
'textColor' => 'black',
|
||||
'url' => $this->helper->url->to('task', 'show', array('task_id' => $row['task_id'], 'project_id' => $row['project_id'])),
|
||||
'editable' => false,
|
||||
);
|
||||
}
|
||||
|
||||
return $events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if a timer is started for this use and subtask
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1,745 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Model;
|
||||
|
||||
/**
|
||||
* Task Filter
|
||||
*
|
||||
* @package model
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class TaskFilter extends Base
|
||||
{
|
||||
/**
|
||||
* Filters mapping
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private $filters = array(
|
||||
'T_ASSIGNEE' => 'filterByAssignee',
|
||||
'T_COLOR' => 'filterByColors',
|
||||
'T_DUE' => 'filterByDueDate',
|
||||
'T_UPDATED' => 'filterByModificationDate',
|
||||
'T_CREATED' => 'filterByCreationDate',
|
||||
'T_TITLE' => 'filterByTitle',
|
||||
'T_STATUS' => 'filterByStatusName',
|
||||
'T_DESCRIPTION' => 'filterByDescription',
|
||||
'T_CATEGORY' => 'filterByCategoryName',
|
||||
'T_PROJECT' => 'filterByProjectName',
|
||||
'T_COLUMN' => 'filterByColumnName',
|
||||
'T_REFERENCE' => 'filterByReference',
|
||||
'T_SWIMLANE' => 'filterBySwimlaneName',
|
||||
'T_LINK' => 'filterByLinkName',
|
||||
);
|
||||
|
||||
/**
|
||||
* Query
|
||||
*
|
||||
* @access public
|
||||
* @var \PicoDb\Table
|
||||
*/
|
||||
public $query;
|
||||
|
||||
/**
|
||||
* Apply filters according to the search input
|
||||
*
|
||||
* @access public
|
||||
* @param string $input
|
||||
* @return TaskFilter
|
||||
*/
|
||||
public function search($input)
|
||||
{
|
||||
$tree = $this->lexer->map($this->lexer->tokenize($input));
|
||||
$this->query = $this->taskFinder->getExtendedQuery();
|
||||
|
||||
if (empty($tree)) {
|
||||
$this->filterByTitle($input);
|
||||
}
|
||||
|
||||
foreach ($tree as $filter => $value) {
|
||||
$method = $this->filters[$filter];
|
||||
$this->$method($value);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new query
|
||||
*
|
||||
* @access public
|
||||
* @return TaskFilter
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
$this->query = $this->db->table(Task::TABLE);
|
||||
$this->query->left(User::TABLE, 'ua', 'id', Task::TABLE, 'owner_id');
|
||||
$this->query->left(User::TABLE, 'uc', 'id', Task::TABLE, 'creator_id');
|
||||
|
||||
$this->query->columns(
|
||||
Task::TABLE.'.*',
|
||||
'ua.email AS assignee_email',
|
||||
'ua.name AS assignee_name',
|
||||
'ua.username AS assignee_username',
|
||||
'uc.email AS creator_email',
|
||||
'uc.username AS creator_username'
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new subtask query
|
||||
*
|
||||
* @access public
|
||||
* @return \PicoDb\Table
|
||||
*/
|
||||
public function createSubtaskQuery()
|
||||
{
|
||||
return $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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new link query
|
||||
*
|
||||
* @access public
|
||||
* @return \PicoDb\Table
|
||||
*/
|
||||
public function createLinkQuery()
|
||||
{
|
||||
return $this->db->table(TaskLink::TABLE)
|
||||
->columns(
|
||||
TaskLink::TABLE.'.task_id',
|
||||
Link::TABLE.'.label'
|
||||
)
|
||||
->join(Link::TABLE, 'id', 'link_id', TaskLink::TABLE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone the filter
|
||||
*
|
||||
* @access public
|
||||
* @return TaskFilter
|
||||
*/
|
||||
public function copy()
|
||||
{
|
||||
$filter = new static($this->container);
|
||||
$filter->query = clone($this->query);
|
||||
$filter->query->condition = clone($this->query->condition);
|
||||
return $filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exclude a list of task_id
|
||||
*
|
||||
* @access public
|
||||
* @param integer[] $task_ids
|
||||
* @return TaskFilter
|
||||
*/
|
||||
public function excludeTasks(array $task_ids)
|
||||
{
|
||||
$this->query->notin(Task::TABLE.'.id', $task_ids);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by id
|
||||
*
|
||||
* @access public
|
||||
* @param integer $task_id
|
||||
* @return TaskFilter
|
||||
*/
|
||||
public function filterById($task_id)
|
||||
{
|
||||
if ($task_id > 0) {
|
||||
$this->query->eq(Task::TABLE.'.id', $task_id);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by reference
|
||||
*
|
||||
* @access public
|
||||
* @param string $reference
|
||||
* @return TaskFilter
|
||||
*/
|
||||
public function filterByReference($reference)
|
||||
{
|
||||
if (! empty($reference)) {
|
||||
$this->query->eq(Task::TABLE.'.reference', $reference);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by title
|
||||
*
|
||||
* @access public
|
||||
* @param string $title
|
||||
* @return TaskFilter
|
||||
*/
|
||||
public function filterByDescription($title)
|
||||
{
|
||||
$this->query->ilike(Task::TABLE.'.description', '%'.$title.'%');
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by title or id if the string is like #123 or an integer
|
||||
*
|
||||
* @access public
|
||||
* @param string $title
|
||||
* @return TaskFilter
|
||||
*/
|
||||
public function filterByTitle($title)
|
||||
{
|
||||
if (ctype_digit($title) || (strlen($title) > 1 && $title{0} === '#' && ctype_digit(substr($title, 1)))) {
|
||||
$this->query->beginOr();
|
||||
$this->query->eq(Task::TABLE.'.id', str_replace('#', '', $title));
|
||||
$this->query->ilike(Task::TABLE.'.title', '%'.$title.'%');
|
||||
$this->query->closeOr();
|
||||
} else {
|
||||
$this->query->ilike(Task::TABLE.'.title', '%'.$title.'%');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by a list of project id
|
||||
*
|
||||
* @access public
|
||||
* @param array $project_ids
|
||||
* @return TaskFilter
|
||||
*/
|
||||
public function filterByProjects(array $project_ids)
|
||||
{
|
||||
$this->query->in(Task::TABLE.'.project_id', $project_ids);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by project id
|
||||
*
|
||||
* @access public
|
||||
* @param integer $project_id
|
||||
* @return TaskFilter
|
||||
*/
|
||||
public function filterByProject($project_id)
|
||||
{
|
||||
if ($project_id > 0) {
|
||||
$this->query->eq(Task::TABLE.'.project_id', $project_id);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by project name
|
||||
*
|
||||
* @access public
|
||||
* @param array $values List of project name
|
||||
* @return TaskFilter
|
||||
*/
|
||||
public function filterByProjectName(array $values)
|
||||
{
|
||||
$this->query->beginOr();
|
||||
|
||||
foreach ($values as $project) {
|
||||
if (ctype_digit($project)) {
|
||||
$this->query->eq(Task::TABLE.'.project_id', $project);
|
||||
} else {
|
||||
$this->query->ilike(Project::TABLE.'.name', $project);
|
||||
}
|
||||
}
|
||||
|
||||
$this->query->closeOr();
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by swimlane name
|
||||
*
|
||||
* @access public
|
||||
* @param array $values List of swimlane name
|
||||
* @return TaskFilter
|
||||
*/
|
||||
public function filterBySwimlaneName(array $values)
|
||||
{
|
||||
$this->query->beginOr();
|
||||
|
||||
foreach ($values as $swimlane) {
|
||||
if ($swimlane === 'default') {
|
||||
$this->query->eq(Task::TABLE.'.swimlane_id', 0);
|
||||
} else {
|
||||
$this->query->ilike(Swimlane::TABLE.'.name', $swimlane);
|
||||
$this->query->addCondition(Task::TABLE.'.swimlane_id=0 AND '.Project::TABLE.'.default_swimlane '.$this->db->getDriver()->getOperator('ILIKE')." '$swimlane'");
|
||||
}
|
||||
}
|
||||
|
||||
$this->query->closeOr();
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by category id
|
||||
*
|
||||
* @access public
|
||||
* @param integer $category_id
|
||||
* @return TaskFilter
|
||||
*/
|
||||
public function filterByCategory($category_id)
|
||||
{
|
||||
if ($category_id >= 0) {
|
||||
$this->query->eq(Task::TABLE.'.category_id', $category_id);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by category
|
||||
*
|
||||
* @access public
|
||||
* @param array $values List of assignees
|
||||
* @return TaskFilter
|
||||
*/
|
||||
public function filterByCategoryName(array $values)
|
||||
{
|
||||
$this->query->beginOr();
|
||||
|
||||
foreach ($values as $category) {
|
||||
if ($category === 'none') {
|
||||
$this->query->eq(Task::TABLE.'.category_id', 0);
|
||||
} else {
|
||||
$this->query->eq(Category::TABLE.'.name', $category);
|
||||
}
|
||||
}
|
||||
|
||||
$this->query->closeOr();
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by assignee
|
||||
*
|
||||
* @access public
|
||||
* @param integer $owner_id
|
||||
* @return TaskFilter
|
||||
*/
|
||||
public function filterByOwner($owner_id)
|
||||
{
|
||||
if ($owner_id >= 0) {
|
||||
$this->query->eq(Task::TABLE.'.owner_id', $owner_id);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by assignee names
|
||||
*
|
||||
* @access public
|
||||
* @param array $values List of assignees
|
||||
* @return TaskFilter
|
||||
*/
|
||||
public function filterByAssignee(array $values)
|
||||
{
|
||||
$this->query->beginOr();
|
||||
|
||||
foreach ($values as $assignee) {
|
||||
switch ($assignee) {
|
||||
case 'me':
|
||||
$this->query->eq(Task::TABLE.'.owner_id', $this->userSession->getId());
|
||||
break;
|
||||
case 'nobody':
|
||||
$this->query->eq(Task::TABLE.'.owner_id', 0);
|
||||
break;
|
||||
default:
|
||||
$this->query->ilike(User::TABLE.'.username', '%'.$assignee.'%');
|
||||
$this->query->ilike(User::TABLE.'.name', '%'.$assignee.'%');
|
||||
}
|
||||
}
|
||||
|
||||
$this->filterBySubtaskAssignee($values);
|
||||
|
||||
$this->query->closeOr();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by subtask assignee names
|
||||
*
|
||||
* @access public
|
||||
* @param array $values List of assignees
|
||||
* @return TaskFilter
|
||||
*/
|
||||
public function filterBySubtaskAssignee(array $values)
|
||||
{
|
||||
$subtaskQuery = $this->createSubtaskQuery();
|
||||
$subtaskQuery->beginOr();
|
||||
|
||||
foreach ($values as $assignee) {
|
||||
if ($assignee === 'me') {
|
||||
$subtaskQuery->eq(Subtask::TABLE.'.user_id', $this->userSession->getId());
|
||||
} else {
|
||||
$subtaskQuery->ilike(User::TABLE.'.username', '%'.$assignee.'%');
|
||||
$subtaskQuery->ilike(User::TABLE.'.name', '%'.$assignee.'%');
|
||||
}
|
||||
}
|
||||
|
||||
$subtaskQuery->closeOr();
|
||||
|
||||
$this->query->in(Task::TABLE.'.id', $subtaskQuery->findAllByColumn('task_id'));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by color
|
||||
*
|
||||
* @access public
|
||||
* @param string $color_id
|
||||
* @return TaskFilter
|
||||
*/
|
||||
public function filterByColor($color_id)
|
||||
{
|
||||
if ($color_id !== '') {
|
||||
$this->query->eq(Task::TABLE.'.color_id', $color_id);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by colors
|
||||
*
|
||||
* @access public
|
||||
* @param array $colors
|
||||
* @return TaskFilter
|
||||
*/
|
||||
public function filterByColors(array $colors)
|
||||
{
|
||||
$this->query->beginOr();
|
||||
|
||||
foreach ($colors as $color) {
|
||||
$this->filterByColor($this->color->find($color));
|
||||
}
|
||||
|
||||
$this->query->closeOr();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by column
|
||||
*
|
||||
* @access public
|
||||
* @param integer $column_id
|
||||
* @return TaskFilter
|
||||
*/
|
||||
public function filterByColumn($column_id)
|
||||
{
|
||||
if ($column_id >= 0) {
|
||||
$this->query->eq(Task::TABLE.'.column_id', $column_id);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by column name
|
||||
*
|
||||
* @access public
|
||||
* @param array $values List of column name
|
||||
* @return TaskFilter
|
||||
*/
|
||||
public function filterByColumnName(array $values)
|
||||
{
|
||||
$this->query->beginOr();
|
||||
|
||||
foreach ($values as $project) {
|
||||
$this->query->ilike(Column::TABLE.'.title', $project);
|
||||
}
|
||||
|
||||
$this->query->closeOr();
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by swimlane
|
||||
*
|
||||
* @access public
|
||||
* @param integer $swimlane_id
|
||||
* @return TaskFilter
|
||||
*/
|
||||
public function filterBySwimlane($swimlane_id)
|
||||
{
|
||||
if ($swimlane_id >= 0) {
|
||||
$this->query->eq(Task::TABLE.'.swimlane_id', $swimlane_id);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by status name
|
||||
*
|
||||
* @access public
|
||||
* @param string $status
|
||||
* @return TaskFilter
|
||||
*/
|
||||
public function filterByStatusName($status)
|
||||
{
|
||||
if ($status === 'open' || $status === 'closed') {
|
||||
$this->filterByStatus($status === 'open' ? Task::STATUS_OPEN : Task::STATUS_CLOSED);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by status
|
||||
*
|
||||
* @access public
|
||||
* @param integer $is_active
|
||||
* @return TaskFilter
|
||||
*/
|
||||
public function filterByStatus($is_active)
|
||||
{
|
||||
if ($is_active >= 0) {
|
||||
$this->query->eq(Task::TABLE.'.is_active', $is_active);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by link
|
||||
*
|
||||
* @access public
|
||||
* @param array $values List of links
|
||||
* @return TaskFilter
|
||||
*/
|
||||
public function filterByLinkName(array $values)
|
||||
{
|
||||
$this->query->beginOr();
|
||||
|
||||
$link_query = $this->createLinkQuery()->in(Link::TABLE.'.label', $values);
|
||||
$matching_task_ids = $link_query->findAllByColumn('task_id');
|
||||
if (empty($matching_task_ids)) {
|
||||
$this->query->eq(Task::TABLE.'.id', 0);
|
||||
} else {
|
||||
$this->query->in(Task::TABLE.'.id', $matching_task_ids);
|
||||
}
|
||||
|
||||
$this->query->closeOr();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by due date
|
||||
*
|
||||
* @access public
|
||||
* @param string $date ISO8601 date format
|
||||
* @return TaskFilter
|
||||
*/
|
||||
public function filterByDueDate($date)
|
||||
{
|
||||
$this->query->neq(Task::TABLE.'.date_due', 0);
|
||||
$this->query->notNull(Task::TABLE.'.date_due');
|
||||
return $this->filterWithOperator(Task::TABLE.'.date_due', $date, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by due date (range)
|
||||
*
|
||||
* @access public
|
||||
* @param string $start
|
||||
* @param string $end
|
||||
* @return TaskFilter
|
||||
*/
|
||||
public function filterByDueDateRange($start, $end)
|
||||
{
|
||||
$this->query->gte('date_due', $this->dateParser->getTimestampFromIsoFormat($start));
|
||||
$this->query->lte('date_due', $this->dateParser->getTimestampFromIsoFormat($end));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by start date (range)
|
||||
*
|
||||
* @access public
|
||||
* @param string $start
|
||||
* @param string $end
|
||||
* @return TaskFilter
|
||||
*/
|
||||
public function filterByStartDateRange($start, $end)
|
||||
{
|
||||
$this->query->addCondition($this->getCalendarCondition(
|
||||
$this->dateParser->getTimestampFromIsoFormat($start),
|
||||
$this->dateParser->getTimestampFromIsoFormat($end),
|
||||
'date_started',
|
||||
'date_completed'
|
||||
));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by creation date
|
||||
*
|
||||
* @access public
|
||||
* @param string $date ISO8601 date format
|
||||
* @return TaskFilter
|
||||
*/
|
||||
public function filterByCreationDate($date)
|
||||
{
|
||||
if ($date === 'recently') {
|
||||
return $this->filterRecentlyDate(Task::TABLE.'.date_creation');
|
||||
}
|
||||
|
||||
return $this->filterWithOperator(Task::TABLE.'.date_creation', $date, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by creation date
|
||||
*
|
||||
* @access public
|
||||
* @param string $start
|
||||
* @param string $end
|
||||
* @return TaskFilter
|
||||
*/
|
||||
public function filterByCreationDateRange($start, $end)
|
||||
{
|
||||
$this->query->addCondition($this->getCalendarCondition(
|
||||
$this->dateParser->getTimestampFromIsoFormat($start),
|
||||
$this->dateParser->getTimestampFromIsoFormat($end),
|
||||
'date_creation',
|
||||
'date_completed'
|
||||
));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by modification date
|
||||
*
|
||||
* @access public
|
||||
* @param string $date ISO8601 date format
|
||||
* @return TaskFilter
|
||||
*/
|
||||
public function filterByModificationDate($date)
|
||||
{
|
||||
if ($date === 'recently') {
|
||||
return $this->filterRecentlyDate(Task::TABLE.'.date_modification');
|
||||
}
|
||||
|
||||
return $this->filterWithOperator(Task::TABLE.'.date_modification', $date, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all results of the filter
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function findAll()
|
||||
{
|
||||
return $this->query->asc(Task::TABLE.'.id')->findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the PicoDb query
|
||||
*
|
||||
* @access public
|
||||
* @return \PicoDb\Table
|
||||
*/
|
||||
public function getQuery()
|
||||
{
|
||||
return $this->query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get swimlanes and tasks to display the board
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getBoard($project_id)
|
||||
{
|
||||
$tasks = $this->filterByProject($project_id)->query->asc(Task::TABLE.'.position')->findAll();
|
||||
|
||||
return $this->board->getBoard($project_id, function ($project_id, $column_id, $swimlane_id) use ($tasks) {
|
||||
return array_filter($tasks, function (array $task) use ($column_id, $swimlane_id) {
|
||||
return $task['column_id'] == $column_id && $task['swimlane_id'] == $swimlane_id;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter with an operator
|
||||
*
|
||||
* @access public
|
||||
* @param string $field
|
||||
* @param string $value
|
||||
* @param boolean $is_date
|
||||
* @return TaskFilter
|
||||
*/
|
||||
private function filterWithOperator($field, $value, $is_date)
|
||||
{
|
||||
$operators = array(
|
||||
'<=' => 'lte',
|
||||
'>=' => 'gte',
|
||||
'<' => 'lt',
|
||||
'>' => 'gt',
|
||||
);
|
||||
|
||||
foreach ($operators as $operator => $method) {
|
||||
if (strpos($value, $operator) === 0) {
|
||||
$value = substr($value, strlen($operator));
|
||||
$this->query->$method($field, $is_date ? $this->dateParser->getTimestampFromIsoFormat($value) : $value);
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
if ($is_date) {
|
||||
$timestamp = $this->dateParser->getTimestampFromIsoFormat($value);
|
||||
$this->query->gte($field, $timestamp);
|
||||
$this->query->lte($field, $timestamp + 86399);
|
||||
} else {
|
||||
$this->query->eq($field, $value);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the board_highlight_period for the "recently" keyword
|
||||
*
|
||||
* @access private
|
||||
* @param string $field
|
||||
* @return TaskFilter
|
||||
*/
|
||||
private function filterRecentlyDate($field)
|
||||
{
|
||||
$duration = $this->config->get('board_highlight_period', 0);
|
||||
|
||||
if ($duration > 0) {
|
||||
$this->query->gte($field, time() - $duration);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -362,6 +362,27 @@ class TaskFinder extends Base
|
|||
return $rq->fetch(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get iCal query
|
||||
*
|
||||
* @access public
|
||||
* @return \PicoDb\Table
|
||||
*/
|
||||
public function getICalQuery()
|
||||
{
|
||||
return $this->db->table(Task::TABLE)
|
||||
->left(User::TABLE, 'ua', 'id', Task::TABLE, 'owner_id')
|
||||
->left(User::TABLE, 'uc', 'id', Task::TABLE, 'creator_id')
|
||||
->columns(
|
||||
Task::TABLE.'.*',
|
||||
'ua.email AS assignee_email',
|
||||
'ua.name AS assignee_name',
|
||||
'ua.username AS assignee_username',
|
||||
'uc.email AS creator_email',
|
||||
'uc.username AS creator_username'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Count all tasks for a given project and status
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1,80 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Model;
|
||||
|
||||
/**
|
||||
* User Filter
|
||||
*
|
||||
* @package model
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class UserFilter extends Base
|
||||
{
|
||||
/**
|
||||
* Search query
|
||||
*
|
||||
* @access private
|
||||
* @var string
|
||||
*/
|
||||
private $input;
|
||||
|
||||
/**
|
||||
* Query
|
||||
*
|
||||
* @access protected
|
||||
* @var \PicoDb\Table
|
||||
*/
|
||||
protected $query;
|
||||
|
||||
/**
|
||||
* Initialize filter
|
||||
*
|
||||
* @access public
|
||||
* @param string $input
|
||||
* @return UserFilter
|
||||
*/
|
||||
public function create($input)
|
||||
{
|
||||
$this->query = $this->db->table(User::TABLE);
|
||||
$this->input = $input;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter users by name or username
|
||||
*
|
||||
* @access public
|
||||
* @return UserFilter
|
||||
*/
|
||||
public function filterByUsernameOrByName()
|
||||
{
|
||||
$this->query->beginOr()
|
||||
->ilike('username', '%'.$this->input.'%')
|
||||
->ilike('name', '%'.$this->input.'%')
|
||||
->closeOr();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all results of the filter
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function findAll()
|
||||
{
|
||||
return $this->query->findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the PicoDb query
|
||||
*
|
||||
* @access public
|
||||
* @return \PicoDb\Table
|
||||
*/
|
||||
public function getQuery()
|
||||
{
|
||||
return $this->query;
|
||||
}
|
||||
}
|
||||
|
|
@ -49,9 +49,7 @@ class ClassProvider implements ServiceProviderInterface
|
|||
'ProjectNotification',
|
||||
'ProjectMetadata',
|
||||
'ProjectGroupRole',
|
||||
'ProjectGroupRoleFilter',
|
||||
'ProjectUserRole',
|
||||
'ProjectUserRoleFilter',
|
||||
'RememberMeSession',
|
||||
'Subtask',
|
||||
'SubtaskTimeTracking',
|
||||
|
|
@ -63,7 +61,6 @@ class ClassProvider implements ServiceProviderInterface
|
|||
'TaskExternalLink',
|
||||
'TaskFinder',
|
||||
'TaskFile',
|
||||
'TaskFilter',
|
||||
'TaskLink',
|
||||
'TaskModification',
|
||||
'TaskPermission',
|
||||
|
|
@ -79,15 +76,6 @@ class ClassProvider implements ServiceProviderInterface
|
|||
'UserUnreadNotification',
|
||||
'UserMetadata',
|
||||
),
|
||||
'Formatter' => array(
|
||||
'TaskFilterGanttFormatter',
|
||||
'TaskFilterAutoCompleteFormatter',
|
||||
'TaskFilterCalendarFormatter',
|
||||
'TaskFilterICalendarFormatter',
|
||||
'ProjectGanttFormatter',
|
||||
'UserFilterAutoCompleteFormatter',
|
||||
'GroupAutoCompleteFormatter',
|
||||
),
|
||||
'Validator' => array(
|
||||
'ActionValidator',
|
||||
'AuthValidator',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,112 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\ServiceProvider;
|
||||
|
||||
use Kanboard\Core\Filter\LexerBuilder;
|
||||
use Kanboard\Core\Filter\QueryBuilder;
|
||||
use Kanboard\Filter\TaskAssigneeFilter;
|
||||
use Kanboard\Filter\TaskCategoryFilter;
|
||||
use Kanboard\Filter\TaskColorFilter;
|
||||
use Kanboard\Filter\TaskColumnFilter;
|
||||
use Kanboard\Filter\TaskCreationDateFilter;
|
||||
use Kanboard\Filter\TaskDescriptionFilter;
|
||||
use Kanboard\Filter\TaskDueDateFilter;
|
||||
use Kanboard\Filter\TaskIdFilter;
|
||||
use Kanboard\Filter\TaskLinkFilter;
|
||||
use Kanboard\Filter\TaskModificationDateFilter;
|
||||
use Kanboard\Filter\TaskProjectFilter;
|
||||
use Kanboard\Filter\TaskReferenceFilter;
|
||||
use Kanboard\Filter\TaskStatusFilter;
|
||||
use Kanboard\Filter\TaskSubtaskAssigneeFilter;
|
||||
use Kanboard\Filter\TaskSwimlaneFilter;
|
||||
use Kanboard\Filter\TaskTitleFilter;
|
||||
use Kanboard\Model\Project;
|
||||
use Kanboard\Model\ProjectGroupRole;
|
||||
use Kanboard\Model\ProjectUserRole;
|
||||
use Kanboard\Model\User;
|
||||
use Pimple\Container;
|
||||
use Pimple\ServiceProviderInterface;
|
||||
|
||||
/**
|
||||
* Filter Provider
|
||||
*
|
||||
* @package serviceProvider
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class FilterProvider implements ServiceProviderInterface
|
||||
{
|
||||
/**
|
||||
* Register providers
|
||||
*
|
||||
* @access public
|
||||
* @param \Pimple\Container $container
|
||||
* @return \Pimple\Container
|
||||
*/
|
||||
public function register(Container $container)
|
||||
{
|
||||
$container['projectGroupRoleQuery'] = $container->factory(function ($c) {
|
||||
$builder = new QueryBuilder();
|
||||
$builder->withQuery($c['db']->table(ProjectGroupRole::TABLE));
|
||||
return $builder;
|
||||
});
|
||||
|
||||
$container['projectUserRoleQuery'] = $container->factory(function ($c) {
|
||||
$builder = new QueryBuilder();
|
||||
$builder->withQuery($c['db']->table(ProjectUserRole::TABLE));
|
||||
return $builder;
|
||||
});
|
||||
|
||||
$container['userQuery'] = $container->factory(function ($c) {
|
||||
$builder = new QueryBuilder();
|
||||
$builder->withQuery($c['db']->table(User::TABLE));
|
||||
return $builder;
|
||||
});
|
||||
|
||||
$container['projectQuery'] = $container->factory(function ($c) {
|
||||
$builder = new QueryBuilder();
|
||||
$builder->withQuery($c['db']->table(Project::TABLE));
|
||||
return $builder;
|
||||
});
|
||||
|
||||
$container['taskQuery'] = $container->factory(function ($c) {
|
||||
$builder = new QueryBuilder();
|
||||
$builder->withQuery($c['taskFinder']->getExtendedQuery());
|
||||
return $builder;
|
||||
});
|
||||
|
||||
$container['taskLexer'] = $container->factory(function ($c) {
|
||||
$builder = new LexerBuilder();
|
||||
|
||||
$builder
|
||||
->withQuery($c['taskFinder']->getExtendedQuery())
|
||||
->withFilter(TaskAssigneeFilter::getInstance()
|
||||
->setCurrentUserId($c['userSession']->getId())
|
||||
)
|
||||
->withFilter(new TaskCategoryFilter())
|
||||
->withFilter(TaskColorFilter::getInstance()->setColorModel($c['color']))
|
||||
->withFilter(new TaskColumnFilter())
|
||||
->withFilter(new TaskCreationDateFilter())
|
||||
->withFilter(new TaskDescriptionFilter())
|
||||
->withFilter(new TaskDueDateFilter())
|
||||
->withFilter(new TaskIdFilter())
|
||||
->withFilter(TaskLinkFilter::getInstance()
|
||||
->setDatabase($c['db'])
|
||||
)
|
||||
->withFilter(new TaskModificationDateFilter())
|
||||
->withFilter(new TaskProjectFilter())
|
||||
->withFilter(new TaskReferenceFilter())
|
||||
->withFilter(new TaskStatusFilter())
|
||||
->withFilter(TaskSubtaskAssigneeFilter::getInstance()
|
||||
->setCurrentUserId($c['userSession']->getId())
|
||||
->setDatabase($c['db'])
|
||||
)
|
||||
->withFilter(new TaskSwimlaneFilter())
|
||||
->withFilter(new TaskTitleFilter(), true)
|
||||
;
|
||||
|
||||
return $builder;
|
||||
});
|
||||
|
||||
return $container;
|
||||
}
|
||||
}
|
||||
|
|
@ -13,12 +13,14 @@ class HelperProvider implements ServiceProviderInterface
|
|||
{
|
||||
$container['helper'] = new Helper($container);
|
||||
$container['helper']->register('app', '\Kanboard\Helper\AppHelper');
|
||||
$container['helper']->register('calendar', '\Kanboard\Helper\CalendarHelper');
|
||||
$container['helper']->register('asset', '\Kanboard\Helper\AssetHelper');
|
||||
$container['helper']->register('board', '\Kanboard\Helper\BoardHelper');
|
||||
$container['helper']->register('dt', '\Kanboard\Helper\DateHelper');
|
||||
$container['helper']->register('file', '\Kanboard\Helper\FileHelper');
|
||||
$container['helper']->register('form', '\Kanboard\Helper\FormHelper');
|
||||
$container['helper']->register('hook', '\Kanboard\Helper\HookHelper');
|
||||
$container['helper']->register('ical', '\Kanboard\Helper\ICalHelper');
|
||||
$container['helper']->register('layout', '\Kanboard\Helper\LayoutHelper');
|
||||
$container['helper']->register('model', '\Kanboard\Helper\ModelHelper');
|
||||
$container['helper']->register('subtask', '\Kanboard\Helper\SubtaskHelper');
|
||||
|
|
|
|||
|
|
@ -39,4 +39,5 @@ $container->register(new Kanboard\ServiceProvider\RouteProvider);
|
|||
$container->register(new Kanboard\ServiceProvider\ActionProvider);
|
||||
$container->register(new Kanboard\ServiceProvider\ExternalLinkProvider);
|
||||
$container->register(new Kanboard\ServiceProvider\AvatarProvider);
|
||||
$container->register(new Kanboard\ServiceProvider\FilterProvider);
|
||||
$container->register(new Kanboard\ServiceProvider\PluginProvider);
|
||||
|
|
|
|||
|
|
@ -9,16 +9,16 @@
|
|||
"packages": [
|
||||
{
|
||||
"name": "christian-riesen/base32",
|
||||
"version": "1.2.2",
|
||||
"version": "1.3.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/ChristianRiesen/base32.git",
|
||||
"reference": "fbe67d49d45dc789f942ef828c787550ebb894bc"
|
||||
"reference": "fde061a370b0a97fdcd33d9d5f7b1b70ce1f79d4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/ChristianRiesen/base32/zipball/fbe67d49d45dc789f942ef828c787550ebb894bc",
|
||||
"reference": "fbe67d49d45dc789f942ef828c787550ebb894bc",
|
||||
"url": "https://api.github.com/repos/ChristianRiesen/base32/zipball/fde061a370b0a97fdcd33d9d5f7b1b70ce1f79d4",
|
||||
"reference": "fde061a370b0a97fdcd33d9d5f7b1b70ce1f79d4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -59,7 +59,7 @@
|
|||
"encode",
|
||||
"rfc4648"
|
||||
],
|
||||
"time": "2015-09-27 23:45:02"
|
||||
"time": "2016-04-07 07:45:31"
|
||||
},
|
||||
{
|
||||
"name": "christian-riesen/otp",
|
||||
|
|
@ -397,16 +397,16 @@
|
|||
},
|
||||
{
|
||||
"name": "paragonie/random_compat",
|
||||
"version": "v2.0.1",
|
||||
"version": "v2.0.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/paragonie/random_compat.git",
|
||||
"reference": "76e90f747b769b347fe584e8015a014549107d35"
|
||||
"reference": "088c04e2f261c33bed6ca5245491cfca69195ccf"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/paragonie/random_compat/zipball/76e90f747b769b347fe584e8015a014549107d35",
|
||||
"reference": "76e90f747b769b347fe584e8015a014549107d35",
|
||||
"url": "https://api.github.com/repos/paragonie/random_compat/zipball/088c04e2f261c33bed6ca5245491cfca69195ccf",
|
||||
"reference": "088c04e2f261c33bed6ca5245491cfca69195ccf",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -441,7 +441,7 @@
|
|||
"pseudorandom",
|
||||
"random"
|
||||
],
|
||||
"time": "2016-03-18 20:36:13"
|
||||
"time": "2016-04-03 06:00:07"
|
||||
},
|
||||
{
|
||||
"name": "pimple/pimple",
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ From the repository (development version)
|
|||
You must install [composer](https://getcomposer.org/) to use this method.
|
||||
|
||||
1. `git clone https://github.com/fguillot/kanboard.git`
|
||||
2. `composer install`
|
||||
2. `composer install --no-dev`
|
||||
3. Go to the third step just above
|
||||
|
||||
Note: This method will install the **current development version**, use at your own risk.
|
||||
|
|
|
|||
|
|
@ -28,15 +28,6 @@ Some hooks can have only one listener:
|
|||
- `$start` (DateTime)
|
||||
- `$end` (DateTime)
|
||||
|
||||
#### model:subtask-time-tracking:calendar:events
|
||||
|
||||
- Override subtask time tracking events to display the calendar
|
||||
- Arguments:
|
||||
- `$user_id` (integer)
|
||||
- `$events` (array)
|
||||
- `$start` (string, ISO-8601 format)
|
||||
- `$end` (string, ISO-8601 format)
|
||||
|
||||
### Merge hooks
|
||||
|
||||
"Merge hooks" act in the same way as the function `array_merge`. The hook callback must return an array. This array will be merged with the default one.
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ From the repository (development version)
|
|||
-----------------------------------------
|
||||
|
||||
1. `git pull`
|
||||
2. `composer install`
|
||||
2. `composer install --no-dev`
|
||||
3. Login and check if everything is ok
|
||||
|
||||
Note: This method will install the **current development version**, use at your own risk.
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ abstract class Base extends PHPUnit_Framework_TestCase
|
|||
$this->container->register(new Kanboard\ServiceProvider\NotificationProvider);
|
||||
$this->container->register(new Kanboard\ServiceProvider\RouteProvider);
|
||||
$this->container->register(new Kanboard\ServiceProvider\AvatarProvider);
|
||||
$this->container->register(new Kanboard\ServiceProvider\FilterProvider);
|
||||
|
||||
$this->container['dispatcher'] = new TraceableEventDispatcher(
|
||||
new EventDispatcher,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,106 @@
|
|||
<?php
|
||||
|
||||
require_once __DIR__.'/../../Base.php';
|
||||
|
||||
use Kanboard\Core\Filter\LexerBuilder;
|
||||
use Kanboard\Filter\TaskAssigneeFilter;
|
||||
use Kanboard\Filter\TaskTitleFilter;
|
||||
use Kanboard\Model\Project;
|
||||
use Kanboard\Model\TaskCreation;
|
||||
use Kanboard\Model\TaskFinder;
|
||||
|
||||
class LexerBuilderTest extends Base
|
||||
{
|
||||
public function testBuilderThatReturnResult()
|
||||
{
|
||||
$project = new Project($this->container);
|
||||
$taskCreation = new TaskCreation($this->container);
|
||||
$taskFinder = new TaskFinder($this->container);
|
||||
$query = $taskFinder->getExtendedQuery();
|
||||
|
||||
$this->assertEquals(1, $project->create(array('name' => 'Project')));
|
||||
$this->assertNotFalse($taskCreation->create(array('project_id' => 1, 'title' => 'Test')));
|
||||
|
||||
$builder = new LexerBuilder();
|
||||
$builder->withFilter(new TaskAssigneeFilter());
|
||||
$builder->withFilter(new TaskTitleFilter(), true);
|
||||
$builder->withQuery($query);
|
||||
$tasks = $builder->build('assignee:nobody')->toArray();
|
||||
|
||||
$this->assertCount(1, $tasks);
|
||||
$this->assertEquals('Test', $tasks[0]['title']);
|
||||
}
|
||||
|
||||
public function testBuilderThatReturnNothing()
|
||||
{
|
||||
$project = new Project($this->container);
|
||||
$taskCreation = new TaskCreation($this->container);
|
||||
$taskFinder = new TaskFinder($this->container);
|
||||
$query = $taskFinder->getExtendedQuery();
|
||||
|
||||
$this->assertEquals(1, $project->create(array('name' => 'Project')));
|
||||
$this->assertNotFalse($taskCreation->create(array('project_id' => 1, 'title' => 'Test')));
|
||||
|
||||
$builder = new LexerBuilder();
|
||||
$builder->withFilter(new TaskAssigneeFilter());
|
||||
$builder->withFilter(new TaskTitleFilter(), true);
|
||||
$builder->withQuery($query);
|
||||
$tasks = $builder->build('something')->toArray();
|
||||
|
||||
$this->assertCount(0, $tasks);
|
||||
}
|
||||
|
||||
public function testBuilderWithEmptyInput()
|
||||
{
|
||||
$project = new Project($this->container);
|
||||
$taskCreation = new TaskCreation($this->container);
|
||||
$taskFinder = new TaskFinder($this->container);
|
||||
$query = $taskFinder->getExtendedQuery();
|
||||
|
||||
$this->assertEquals(1, $project->create(array('name' => 'Project')));
|
||||
$this->assertNotFalse($taskCreation->create(array('project_id' => 1, 'title' => 'Test')));
|
||||
|
||||
$builder = new LexerBuilder();
|
||||
$builder->withFilter(new TaskAssigneeFilter());
|
||||
$builder->withFilter(new TaskTitleFilter(), true);
|
||||
$builder->withQuery($query);
|
||||
$tasks = $builder->build('')->toArray();
|
||||
|
||||
$this->assertCount(1, $tasks);
|
||||
}
|
||||
|
||||
public function testBuilderWithMultipleMatches()
|
||||
{
|
||||
$project = new Project($this->container);
|
||||
$taskCreation = new TaskCreation($this->container);
|
||||
$taskFinder = new TaskFinder($this->container);
|
||||
$query = $taskFinder->getExtendedQuery();
|
||||
|
||||
$this->assertEquals(1, $project->create(array('name' => 'Project')));
|
||||
$this->assertNotFalse($taskCreation->create(array('project_id' => 1, 'title' => 'ABC', 'owner_id' => 1)));
|
||||
$this->assertNotFalse($taskCreation->create(array('project_id' => 1, 'title' => 'DEF')));
|
||||
|
||||
$builder = new LexerBuilder();
|
||||
$builder->withFilter(new TaskAssigneeFilter());
|
||||
$builder->withFilter(new TaskTitleFilter(), true);
|
||||
$builder->withQuery($query);
|
||||
$tasks = $builder->build('assignee:nobody assignee:1')->toArray();
|
||||
|
||||
$this->assertCount(2, $tasks);
|
||||
}
|
||||
|
||||
public function testClone()
|
||||
{
|
||||
$taskFinder = new TaskFinder($this->container);
|
||||
$query = $taskFinder->getExtendedQuery();
|
||||
|
||||
$builder = new LexerBuilder();
|
||||
$builder->withFilter(new TaskAssigneeFilter());
|
||||
$builder->withFilter(new TaskTitleFilter());
|
||||
$builder->withQuery($query);
|
||||
|
||||
$clone = clone($builder);
|
||||
$this->assertFalse($builder === $clone);
|
||||
$this->assertFalse($builder->build('test')->getQuery() === $clone->build('test')->getQuery());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
<?php
|
||||
|
||||
require_once __DIR__.'/../../Base.php';
|
||||
|
||||
use Kanboard\Core\Filter\Lexer;
|
||||
|
||||
class LexerTest extends Base
|
||||
{
|
||||
public function testTokenizeWithNoDefaultToken()
|
||||
{
|
||||
$lexer = new Lexer();
|
||||
$this->assertSame(array(), $lexer->tokenize('This is Kanboard'));
|
||||
}
|
||||
|
||||
public function testTokenizeWithDefaultToken()
|
||||
{
|
||||
$lexer = new Lexer();
|
||||
$lexer->setDefaultToken('myDefaultToken');
|
||||
|
||||
$expected = array(
|
||||
'myDefaultToken' => array('This is Kanboard'),
|
||||
);
|
||||
|
||||
$this->assertSame($expected, $lexer->tokenize('This is Kanboard'));
|
||||
}
|
||||
|
||||
public function testTokenizeWithCustomToken()
|
||||
{
|
||||
$lexer = new Lexer();
|
||||
$lexer->addToken("/^(assignee:)/", 'T_USER');
|
||||
|
||||
$expected = array(
|
||||
'T_USER' => array('admin'),
|
||||
);
|
||||
|
||||
$this->assertSame($expected, $lexer->tokenize('assignee:admin something else'));
|
||||
}
|
||||
|
||||
public function testTokenizeWithCustomTokenAndDefaultToken()
|
||||
{
|
||||
$lexer = new Lexer();
|
||||
$lexer->setDefaultToken('myDefaultToken');
|
||||
$lexer->addToken("/^(assignee:)/", 'T_USER');
|
||||
|
||||
$expected = array(
|
||||
'T_USER' => array('admin'),
|
||||
'myDefaultToken' => array('something else'),
|
||||
);
|
||||
|
||||
$this->assertSame($expected, $lexer->tokenize('assignee:admin something else'));
|
||||
}
|
||||
|
||||
public function testTokenizeWithQuotedString()
|
||||
{
|
||||
$lexer = new Lexer();
|
||||
$lexer->addToken("/^(assignee:)/", 'T_USER');
|
||||
|
||||
$expected = array(
|
||||
'T_USER' => array('Foo Bar'),
|
||||
);
|
||||
|
||||
$this->assertSame($expected, $lexer->tokenize('assignee:"Foo Bar" something else'));
|
||||
}
|
||||
|
||||
public function testTokenizeWithNumber()
|
||||
{
|
||||
$lexer = new Lexer();
|
||||
$lexer->setDefaultToken('myDefaultToken');
|
||||
|
||||
$expected = array(
|
||||
'myDefaultToken' => array('#123'),
|
||||
);
|
||||
|
||||
$this->assertSame($expected, $lexer->tokenize('#123'));
|
||||
}
|
||||
|
||||
public function testTokenizeWithStringDate()
|
||||
{
|
||||
$lexer = new Lexer();
|
||||
$lexer->addToken("/^(date:)/", 'T_DATE');
|
||||
|
||||
$expected = array(
|
||||
'T_DATE' => array('today'),
|
||||
);
|
||||
|
||||
$this->assertSame($expected, $lexer->tokenize('date:today something else'));
|
||||
}
|
||||
|
||||
public function testTokenizeWithIsoDate()
|
||||
{
|
||||
$lexer = new Lexer();
|
||||
$lexer->addToken("/^(date:)/", 'T_DATE');
|
||||
|
||||
$expected = array(
|
||||
'T_DATE' => array('<=2016-01-01'),
|
||||
);
|
||||
|
||||
$this->assertSame($expected, $lexer->tokenize('date:<=2016-01-01 something else'));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
<?php
|
||||
|
||||
use Kanboard\Core\Filter\OrCriteria;
|
||||
use Kanboard\Filter\TaskAssigneeFilter;
|
||||
use Kanboard\Filter\TaskTitleFilter;
|
||||
use Kanboard\Model\Project;
|
||||
use Kanboard\Model\TaskCreation;
|
||||
use Kanboard\Model\TaskFinder;
|
||||
use Kanboard\Model\User;
|
||||
|
||||
require_once __DIR__.'/../../Base.php';
|
||||
|
||||
class OrCriteriaTest extends Base
|
||||
{
|
||||
public function testWithSameFilter()
|
||||
{
|
||||
$taskFinder = new TaskFinder($this->container);
|
||||
$taskCreation = new TaskCreation($this->container);
|
||||
$projectModel = new Project($this->container);
|
||||
$userModel = new User($this->container);
|
||||
$query = $taskFinder->getExtendedQuery();
|
||||
|
||||
$this->assertEquals(2, $userModel->create(array('username' => 'foobar', 'name' => 'Foo Bar')));
|
||||
$this->assertEquals(1, $projectModel->create(array('name' => 'Test')));
|
||||
$this->assertEquals(1, $taskCreation->create(array('title' => 'Test', 'project_id' => 1, 'owner_id' => 2)));
|
||||
$this->assertEquals(2, $taskCreation->create(array('title' => 'Test', 'project_id' => 1, 'owner_id' => 1)));
|
||||
|
||||
$criteria = new OrCriteria();
|
||||
$criteria->withQuery($query);
|
||||
$criteria->withFilter(TaskAssigneeFilter::getInstance(1));
|
||||
$criteria->withFilter(TaskAssigneeFilter::getInstance(2));
|
||||
$criteria->apply();
|
||||
|
||||
$this->assertCount(2, $query->findAll());
|
||||
}
|
||||
|
||||
public function testWithDifferentFilter()
|
||||
{
|
||||
$taskFinder = new TaskFinder($this->container);
|
||||
$taskCreation = new TaskCreation($this->container);
|
||||
$projectModel = new Project($this->container);
|
||||
$userModel = new User($this->container);
|
||||
$query = $taskFinder->getExtendedQuery();
|
||||
|
||||
$this->assertEquals(2, $userModel->create(array('username' => 'foobar', 'name' => 'Foo Bar')));
|
||||
$this->assertEquals(1, $projectModel->create(array('name' => 'Test')));
|
||||
$this->assertEquals(1, $taskCreation->create(array('title' => 'ABC', 'project_id' => 1, 'owner_id' => 2)));
|
||||
$this->assertEquals(2, $taskCreation->create(array('title' => 'DEF', 'project_id' => 1, 'owner_id' => 1)));
|
||||
|
||||
$criteria = new OrCriteria();
|
||||
$criteria->withQuery($query);
|
||||
$criteria->withFilter(TaskAssigneeFilter::getInstance(1));
|
||||
$criteria->withFilter(TaskTitleFilter::getInstance('ABC'));
|
||||
$criteria->apply();
|
||||
|
||||
$this->assertCount(2, $query->findAll());
|
||||
}
|
||||
}
|
||||
|
|
@ -1,468 +0,0 @@
|
|||
<?php
|
||||
|
||||
require_once __DIR__.'/../Base.php';
|
||||
|
||||
use Kanboard\Core\Lexer;
|
||||
|
||||
class LexerTest extends Base
|
||||
{
|
||||
public function testSwimlaneQuery()
|
||||
{
|
||||
$lexer = new Lexer;
|
||||
|
||||
$this->assertEquals(
|
||||
array(array('match' => 'swimlane:', 'token' => 'T_SWIMLANE'), array('match' => 'Version 42', 'token' => 'T_STRING')),
|
||||
$lexer->tokenize('swimlane:"Version 42"')
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array(array('match' => 'swimlane:', 'token' => 'T_SWIMLANE'), array('match' => 'v3', 'token' => 'T_STRING')),
|
||||
$lexer->tokenize('swimlane:v3')
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array('T_SWIMLANE' => array('v3')),
|
||||
$lexer->map($lexer->tokenize('swimlane:v3'))
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array('T_SWIMLANE' => array('Version 42', 'v3')),
|
||||
$lexer->map($lexer->tokenize('swimlane:"Version 42" swimlane:v3'))
|
||||
);
|
||||
}
|
||||
|
||||
public function testAssigneeQuery()
|
||||
{
|
||||
$lexer = new Lexer;
|
||||
|
||||
$this->assertEquals(
|
||||
array(array('match' => 'assignee:', 'token' => 'T_ASSIGNEE'), array('match' => 'me', 'token' => 'T_STRING')),
|
||||
$lexer->tokenize('assignee:me')
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array(array('match' => 'assignee:', 'token' => 'T_ASSIGNEE'), array('match' => 'everybody', 'token' => 'T_STRING')),
|
||||
$lexer->tokenize('assignee:everybody')
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array(array('match' => 'assignee:', 'token' => 'T_ASSIGNEE'), array('match' => 'nobody', 'token' => 'T_STRING')),
|
||||
$lexer->tokenize('assignee:nobody')
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array('T_ASSIGNEE' => array('nobody')),
|
||||
$lexer->map($lexer->tokenize('assignee:nobody'))
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array('T_ASSIGNEE' => array('John Doe', 'me')),
|
||||
$lexer->map($lexer->tokenize('assignee:"John Doe" assignee:me'))
|
||||
);
|
||||
}
|
||||
|
||||
public function testColorQuery()
|
||||
{
|
||||
$lexer = new Lexer;
|
||||
|
||||
$this->assertEquals(
|
||||
array(array('match' => 'color:', 'token' => 'T_COLOR'), array('match' => 'Blue', 'token' => 'T_STRING')),
|
||||
$lexer->tokenize('color:Blue')
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array(array('match' => 'color:', 'token' => 'T_COLOR'), array('match' => 'Dark Grey', 'token' => 'T_STRING')),
|
||||
$lexer->tokenize('color:"Dark Grey"')
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array('T_COLOR' => array('Blue')),
|
||||
$lexer->map($lexer->tokenize('color:Blue'))
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array('T_COLOR' => array('Dark Grey')),
|
||||
$lexer->map($lexer->tokenize('color:"Dark Grey"'))
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array(),
|
||||
$lexer->map($lexer->tokenize('color: '))
|
||||
);
|
||||
}
|
||||
|
||||
public function testCategoryQuery()
|
||||
{
|
||||
$lexer = new Lexer;
|
||||
|
||||
$this->assertEquals(
|
||||
array(array('match' => 'category:', 'token' => 'T_CATEGORY'), array('match' => 'Feature Request', 'token' => 'T_STRING')),
|
||||
$lexer->tokenize('category:"Feature Request"')
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array('T_CATEGORY' => array('Feature Request')),
|
||||
$lexer->map($lexer->tokenize('category:"Feature Request"'))
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array('T_CATEGORY' => array('Feature Request', 'Bug')),
|
||||
$lexer->map($lexer->tokenize('category:"Feature Request" category:Bug'))
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array(),
|
||||
$lexer->map($lexer->tokenize('category: '))
|
||||
);
|
||||
}
|
||||
|
||||
public function testLinkQuery()
|
||||
{
|
||||
$lexer = new Lexer;
|
||||
|
||||
$this->assertEquals(
|
||||
array(array('match' => 'link:', 'token' => 'T_LINK'), array('match' => 'is a milestone of', 'token' => 'T_STRING')),
|
||||
$lexer->tokenize('link:"is a milestone of"')
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array('T_LINK' => array('is a milestone of')),
|
||||
$lexer->map($lexer->tokenize('link:"is a milestone of"'))
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array('T_LINK' => array('is a milestone of', 'fixes')),
|
||||
$lexer->map($lexer->tokenize('link:"is a milestone of" link:fixes'))
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array(),
|
||||
$lexer->map($lexer->tokenize('link: '))
|
||||
);
|
||||
}
|
||||
|
||||
public function testColumnQuery()
|
||||
{
|
||||
$lexer = new Lexer;
|
||||
|
||||
$this->assertEquals(
|
||||
array(array('match' => 'column:', 'token' => 'T_COLUMN'), array('match' => 'Feature Request', 'token' => 'T_STRING')),
|
||||
$lexer->tokenize('column:"Feature Request"')
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array('T_COLUMN' => array('Feature Request')),
|
||||
$lexer->map($lexer->tokenize('column:"Feature Request"'))
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array('T_COLUMN' => array('Feature Request', 'Bug')),
|
||||
$lexer->map($lexer->tokenize('column:"Feature Request" column:Bug'))
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array(),
|
||||
$lexer->map($lexer->tokenize('column: '))
|
||||
);
|
||||
}
|
||||
|
||||
public function testProjectQuery()
|
||||
{
|
||||
$lexer = new Lexer;
|
||||
|
||||
$this->assertEquals(
|
||||
array(array('match' => 'project:', 'token' => 'T_PROJECT'), array('match' => 'My project', 'token' => 'T_STRING')),
|
||||
$lexer->tokenize('project:"My project"')
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array('T_PROJECT' => array('My project')),
|
||||
$lexer->map($lexer->tokenize('project:"My project"'))
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array('T_PROJECT' => array('My project', 'plop')),
|
||||
$lexer->map($lexer->tokenize('project:"My project" project:plop'))
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array(),
|
||||
$lexer->map($lexer->tokenize('project: '))
|
||||
);
|
||||
}
|
||||
|
||||
public function testStatusQuery()
|
||||
{
|
||||
$lexer = new Lexer;
|
||||
|
||||
$this->assertEquals(
|
||||
array(array('match' => 'status:', 'token' => 'T_STATUS'), array('match' => 'open', 'token' => 'T_STRING')),
|
||||
$lexer->tokenize('status:open')
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array(array('match' => 'status:', 'token' => 'T_STATUS'), array('match' => 'closed', 'token' => 'T_STRING')),
|
||||
$lexer->tokenize('status:closed')
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array('T_STATUS' => 'open'),
|
||||
$lexer->map($lexer->tokenize('status:open'))
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array('T_STATUS' => 'closed'),
|
||||
$lexer->map($lexer->tokenize('status:closed'))
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array(),
|
||||
$lexer->map($lexer->tokenize('status: '))
|
||||
);
|
||||
}
|
||||
|
||||
public function testReferenceQuery()
|
||||
{
|
||||
$lexer = new Lexer;
|
||||
|
||||
$this->assertEquals(
|
||||
array(array('match' => 'ref:', 'token' => 'T_REFERENCE'), array('match' => '123', 'token' => 'T_STRING')),
|
||||
$lexer->tokenize('ref:123')
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array(array('match' => 'reference:', 'token' => 'T_REFERENCE'), array('match' => '456', 'token' => 'T_STRING')),
|
||||
$lexer->tokenize('reference:456')
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array('T_REFERENCE' => '123'),
|
||||
$lexer->map($lexer->tokenize('reference:123'))
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array('T_REFERENCE' => '456'),
|
||||
$lexer->map($lexer->tokenize('ref:456'))
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array(),
|
||||
$lexer->map($lexer->tokenize('ref: '))
|
||||
);
|
||||
}
|
||||
|
||||
public function testDescriptionQuery()
|
||||
{
|
||||
$lexer = new Lexer;
|
||||
|
||||
$this->assertEquals(
|
||||
array(array('match' => 'description:', 'token' => 'T_DESCRIPTION'), array('match' => 'my text search', 'token' => 'T_STRING')),
|
||||
$lexer->tokenize('description:"my text search"')
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array('T_DESCRIPTION' => 'my text search'),
|
||||
$lexer->map($lexer->tokenize('description:"my text search"'))
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array(),
|
||||
$lexer->map($lexer->tokenize('description: '))
|
||||
);
|
||||
}
|
||||
|
||||
public function testDueDateQuery()
|
||||
{
|
||||
$lexer = new Lexer;
|
||||
|
||||
$this->assertEquals(
|
||||
array(array('match' => 'due:', 'token' => 'T_DUE'), array('match' => '2015-05-01', 'token' => 'T_DATE')),
|
||||
$lexer->tokenize('due:2015-05-01')
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array(array('match' => 'due:', 'token' => 'T_DUE'), array('match' => '<2015-05-01', 'token' => 'T_DATE')),
|
||||
$lexer->tokenize('due:<2015-05-01')
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array(array('match' => 'due:', 'token' => 'T_DUE'), array('match' => '>2015-05-01', 'token' => 'T_DATE')),
|
||||
$lexer->tokenize('due:>2015-05-01')
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array(array('match' => 'due:', 'token' => 'T_DUE'), array('match' => '<=2015-05-01', 'token' => 'T_DATE')),
|
||||
$lexer->tokenize('due:<=2015-05-01')
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array(array('match' => 'due:', 'token' => 'T_DUE'), array('match' => '>=2015-05-01', 'token' => 'T_DATE')),
|
||||
$lexer->tokenize('due:>=2015-05-01')
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array(array('match' => 'due:', 'token' => 'T_DUE'), array('match' => 'yesterday', 'token' => 'T_DATE')),
|
||||
$lexer->tokenize('due:yesterday')
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array(array('match' => 'due:', 'token' => 'T_DUE'), array('match' => 'tomorrow', 'token' => 'T_DATE')),
|
||||
$lexer->tokenize('due:tomorrow')
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array(),
|
||||
$lexer->tokenize('due:#2015-05-01')
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array(),
|
||||
$lexer->tokenize('due:01-05-1024')
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array('T_DUE' => '2015-05-01'),
|
||||
$lexer->map($lexer->tokenize('due:2015-05-01'))
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array('T_DUE' => '<2015-05-01'),
|
||||
$lexer->map($lexer->tokenize('due:<2015-05-01'))
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array('T_DUE' => 'today'),
|
||||
$lexer->map($lexer->tokenize('due:today'))
|
||||
);
|
||||
}
|
||||
|
||||
public function testModifiedQuery()
|
||||
{
|
||||
$lexer = new Lexer;
|
||||
|
||||
$this->assertEquals(
|
||||
array(array('match' => 'modified:', 'token' => 'T_UPDATED'), array('match' => '2015-05-01', 'token' => 'T_DATE')),
|
||||
$lexer->tokenize('modified:2015-05-01')
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array(array('match' => 'modified:', 'token' => 'T_UPDATED'), array('match' => '<2015-05-01', 'token' => 'T_DATE')),
|
||||
$lexer->tokenize('modified:<2015-05-01')
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array(array('match' => 'modified:', 'token' => 'T_UPDATED'), array('match' => '>2015-05-01', 'token' => 'T_DATE')),
|
||||
$lexer->tokenize('modified:>2015-05-01')
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array(array('match' => 'updated:', 'token' => 'T_UPDATED'), array('match' => '<=2015-05-01', 'token' => 'T_DATE')),
|
||||
$lexer->tokenize('updated:<=2015-05-01')
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array(array('match' => 'updated:', 'token' => 'T_UPDATED'), array('match' => '>=2015-05-01', 'token' => 'T_DATE')),
|
||||
$lexer->tokenize('updated:>=2015-05-01')
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array(array('match' => 'updated:', 'token' => 'T_UPDATED'), array('match' => 'yesterday', 'token' => 'T_DATE')),
|
||||
$lexer->tokenize('updated:yesterday')
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array(array('match' => 'updated:', 'token' => 'T_UPDATED'), array('match' => 'tomorrow', 'token' => 'T_DATE')),
|
||||
$lexer->tokenize('updated:tomorrow')
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array(),
|
||||
$lexer->tokenize('updated:#2015-05-01')
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array(),
|
||||
$lexer->tokenize('modified:01-05-1024')
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array('T_UPDATED' => '2015-05-01'),
|
||||
$lexer->map($lexer->tokenize('modified:2015-05-01'))
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array('T_UPDATED' => '<2015-05-01'),
|
||||
$lexer->map($lexer->tokenize('modified:<2015-05-01'))
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array('T_UPDATED' => 'today'),
|
||||
$lexer->map($lexer->tokenize('modified:today'))
|
||||
);
|
||||
}
|
||||
|
||||
public function testMultipleCriterias()
|
||||
{
|
||||
$lexer = new Lexer;
|
||||
|
||||
$this->assertEquals(
|
||||
array('T_COLOR' => array('Dark Grey'), 'T_ASSIGNEE' => array('Fred G'), 'T_TITLE' => 'my task title'),
|
||||
$lexer->map($lexer->tokenize('color:"Dark Grey" assignee:"Fred G" my task title'))
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array('T_TITLE' => 'my title', 'T_COLOR' => array('yellow')),
|
||||
$lexer->map($lexer->tokenize('my title color:yellow'))
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array('T_TITLE' => 'my title', 'T_DUE' => '2015-04-01'),
|
||||
$lexer->map($lexer->tokenize('my title due:2015-04-01'))
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array('T_TITLE' => 'awesome', 'T_DUE' => '<=2015-04-01'),
|
||||
$lexer->map($lexer->tokenize('due:<=2015-04-01 awesome'))
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array('T_TITLE' => 'awesome', 'T_DUE' => 'today'),
|
||||
$lexer->map($lexer->tokenize('due:today awesome'))
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array('T_TITLE' => 'my title', 'T_COLOR' => array('yellow'), 'T_DUE' => '2015-04-01'),
|
||||
$lexer->map($lexer->tokenize('my title color:yellow due:2015-04-01'))
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array('T_TITLE' => 'my title', 'T_COLOR' => array('yellow'), 'T_DUE' => '2015-04-01', 'T_ASSIGNEE' => array('John Doe')),
|
||||
$lexer->map($lexer->tokenize('my title color:yellow due:2015-04-01 assignee:"John Doe"'))
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array('T_TITLE' => 'my title'),
|
||||
$lexer->map($lexer->tokenize('my title color:'))
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array('T_TITLE' => 'my title'),
|
||||
$lexer->map($lexer->tokenize('my title color:assignee:'))
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array('T_TITLE' => 'my title'),
|
||||
$lexer->map($lexer->tokenize('my title '))
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array('T_TITLE' => '#123'),
|
||||
$lexer->map($lexer->tokenize('#123'))
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array(),
|
||||
$lexer->map($lexer->tokenize('color:assignee:'))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
<?php
|
||||
|
||||
use Kanboard\Filter\TaskAssigneeFilter;
|
||||
use Kanboard\Model\Project;
|
||||
use Kanboard\Model\TaskCreation;
|
||||
use Kanboard\Model\TaskFinder;
|
||||
use Kanboard\Model\User;
|
||||
|
||||
require_once __DIR__.'/../Base.php';
|
||||
|
||||
class TaskAssigneeFilterTest extends Base
|
||||
{
|
||||
public function testWithIntegerAssigneeId()
|
||||
{
|
||||
$taskFinder = new TaskFinder($this->container);
|
||||
$taskCreation = new TaskCreation($this->container);
|
||||
$projectModel = new Project($this->container);
|
||||
$query = $taskFinder->getExtendedQuery();
|
||||
|
||||
$this->assertEquals(1, $projectModel->create(array('name' => 'Test')));
|
||||
$this->assertEquals(1, $taskCreation->create(array('title' => 'Test', 'project_id' => 1, 'owner_id' => 1)));
|
||||
|
||||
$filter = new TaskAssigneeFilter();
|
||||
$filter->withQuery($query);
|
||||
$filter->withValue(1);
|
||||
$filter->apply();
|
||||
|
||||
$this->assertCount(1, $query->findAll());
|
||||
|
||||
$filter = new TaskAssigneeFilter();
|
||||
$filter->withQuery($query);
|
||||
$filter->withValue(123);
|
||||
$filter->apply();
|
||||
|
||||
$this->assertCount(0, $query->findAll());
|
||||
}
|
||||
|
||||
public function testWithStringAssigneeId()
|
||||
{
|
||||
$taskFinder = new TaskFinder($this->container);
|
||||
$taskCreation = new TaskCreation($this->container);
|
||||
$projectModel = new Project($this->container);
|
||||
$query = $taskFinder->getExtendedQuery();
|
||||
|
||||
$this->assertEquals(1, $projectModel->create(array('name' => 'Test')));
|
||||
$this->assertEquals(1, $taskCreation->create(array('title' => 'Test', 'project_id' => 1, 'owner_id' => 1)));
|
||||
|
||||
$filter = new TaskAssigneeFilter();
|
||||
$filter->withQuery($query);
|
||||
$filter->withValue('1');
|
||||
$filter->apply();
|
||||
|
||||
$this->assertCount(1, $query->findAll());
|
||||
|
||||
$filter = new TaskAssigneeFilter();
|
||||
$filter->withQuery($query);
|
||||
$filter->withValue("123");
|
||||
$filter->apply();
|
||||
|
||||
$this->assertCount(0, $query->findAll());
|
||||
}
|
||||
|
||||
public function testWithUsername()
|
||||
{
|
||||
$taskFinder = new TaskFinder($this->container);
|
||||
$taskCreation = new TaskCreation($this->container);
|
||||
$projectModel = new Project($this->container);
|
||||
$query = $taskFinder->getExtendedQuery();
|
||||
|
||||
$this->assertEquals(1, $projectModel->create(array('name' => 'Test')));
|
||||
$this->assertEquals(1, $taskCreation->create(array('title' => 'Test', 'project_id' => 1, 'owner_id' => 1)));
|
||||
|
||||
$filter = new TaskAssigneeFilter();
|
||||
$filter->withQuery($query);
|
||||
$filter->withValue('admin');
|
||||
$filter->apply();
|
||||
|
||||
$this->assertCount(1, $query->findAll());
|
||||
|
||||
$filter = new TaskAssigneeFilter();
|
||||
$filter->withQuery($query);
|
||||
$filter->withValue('foobar');
|
||||
$filter->apply();
|
||||
|
||||
$this->assertCount(0, $query->findAll());
|
||||
}
|
||||
|
||||
public function testWithName()
|
||||
{
|
||||
$taskFinder = new TaskFinder($this->container);
|
||||
$taskCreation = new TaskCreation($this->container);
|
||||
$projectModel = new Project($this->container);
|
||||
$userModel = new User($this->container);
|
||||
$query = $taskFinder->getExtendedQuery();
|
||||
|
||||
$this->assertEquals(2, $userModel->create(array('username' => 'foobar', 'name' => 'Foo Bar')));
|
||||
$this->assertEquals(1, $projectModel->create(array('name' => 'Test')));
|
||||
$this->assertEquals(1, $taskCreation->create(array('title' => 'Test', 'project_id' => 1, 'owner_id' => 2)));
|
||||
|
||||
$filter = new TaskAssigneeFilter();
|
||||
$filter->withQuery($query);
|
||||
$filter->withValue('foo bar');
|
||||
$filter->apply();
|
||||
|
||||
$this->assertCount(1, $query->findAll());
|
||||
|
||||
$filter = new TaskAssigneeFilter();
|
||||
$filter->withQuery($query);
|
||||
$filter->withValue('bob');
|
||||
$filter->apply();
|
||||
|
||||
$this->assertCount(0, $query->findAll());
|
||||
}
|
||||
|
||||
public function testWithNobody()
|
||||
{
|
||||
$taskFinder = new TaskFinder($this->container);
|
||||
$taskCreation = new TaskCreation($this->container);
|
||||
$projectModel = new Project($this->container);
|
||||
$query = $taskFinder->getExtendedQuery();
|
||||
|
||||
$this->assertEquals(1, $projectModel->create(array('name' => 'Test')));
|
||||
$this->assertEquals(1, $taskCreation->create(array('title' => 'Test', 'project_id' => 1)));
|
||||
|
||||
$filter = new TaskAssigneeFilter();
|
||||
$filter->withQuery($query);
|
||||
$filter->withValue('nobody');
|
||||
$filter->apply();
|
||||
|
||||
$this->assertCount(1, $query->findAll());
|
||||
}
|
||||
|
||||
public function testWithCurrentUser()
|
||||
{
|
||||
$taskFinder = new TaskFinder($this->container);
|
||||
$taskCreation = new TaskCreation($this->container);
|
||||
$projectModel = new Project($this->container);
|
||||
$query = $taskFinder->getExtendedQuery();
|
||||
|
||||
$this->assertEquals(1, $projectModel->create(array('name' => 'Test')));
|
||||
$this->assertEquals(1, $taskCreation->create(array('title' => 'Test', 'project_id' => 1, 'owner_id' => 1)));
|
||||
|
||||
$filter = new TaskAssigneeFilter();
|
||||
$filter->setCurrentUserId(1);
|
||||
$filter->withQuery($query);
|
||||
$filter->withValue('me');
|
||||
$filter->apply();
|
||||
|
||||
$this->assertCount(1, $query->findAll());
|
||||
|
||||
$filter = new TaskAssigneeFilter();
|
||||
$filter->setCurrentUserId(2);
|
||||
$filter->withQuery($query);
|
||||
$filter->withValue('me');
|
||||
$filter->apply();
|
||||
|
||||
$this->assertCount(0, $query->findAll());
|
||||
}
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
<?php
|
||||
|
||||
require_once __DIR__.'/../Base.php';
|
||||
|
||||
use Kanboard\Formatter\TaskFilterCalendarFormatter;
|
||||
|
||||
class TaskFilterCalendarFormatterTest extends Base
|
||||
{
|
||||
public function testCopy()
|
||||
{
|
||||
$tf = new TaskFilterCalendarFormatter($this->container);
|
||||
$filter1 = $tf->create()->setFullDay();
|
||||
$filter2 = $tf->copy();
|
||||
|
||||
$this->assertTrue($filter1 !== $filter2);
|
||||
$this->assertTrue($filter1->query !== $filter2->query);
|
||||
$this->assertTrue($filter1->query->condition !== $filter2->query->condition);
|
||||
$this->assertTrue($filter1->isFullDay());
|
||||
$this->assertFalse($filter2->isFullDay());
|
||||
}
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
<?php
|
||||
|
||||
require_once __DIR__.'/../Base.php';
|
||||
|
||||
use Kanboard\Formatter\TaskFilterGanttFormatter;
|
||||
use Kanboard\Model\Project;
|
||||
use Kanboard\Model\TaskCreation;
|
||||
use Kanboard\Core\DateParser;
|
||||
|
||||
class TaskFilterGanttFormatterTest extends Base
|
||||
{
|
||||
public function testFormat()
|
||||
{
|
||||
$dp = new DateParser($this->container);
|
||||
$p = new Project($this->container);
|
||||
$tc = new TaskCreation($this->container);
|
||||
$tf = new TaskFilterGanttFormatter($this->container);
|
||||
|
||||
$this->assertEquals(1, $p->create(array('name' => 'test')));
|
||||
$this->assertNotFalse($tc->create(array('project_id' => 1, 'title' => 'task1')));
|
||||
|
||||
$this->assertNotEmpty($tf->search('status:open')->format());
|
||||
}
|
||||
}
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
<?php
|
||||
|
||||
require_once __DIR__.'/../Base.php';
|
||||
|
||||
use Eluceo\iCal\Component\Calendar;
|
||||
use Kanboard\Formatter\TaskFilterICalendarFormatter;
|
||||
use Kanboard\Model\Project;
|
||||
use Kanboard\Model\User;
|
||||
use Kanboard\Model\TaskCreation;
|
||||
use Kanboard\Core\DateParser;
|
||||
use Kanboard\Model\Config;
|
||||
|
||||
class TaskFilterICalendarFormatterTest extends Base
|
||||
{
|
||||
public function testIcalEventsWithCreatorAndDueDate()
|
||||
{
|
||||
$dp = new DateParser($this->container);
|
||||
$p = new Project($this->container);
|
||||
$tc = new TaskCreation($this->container);
|
||||
$tf = new TaskFilterICalendarFormatter($this->container);
|
||||
|
||||
$this->assertEquals(1, $p->create(array('name' => 'test')));
|
||||
$this->assertNotFalse($tc->create(array('project_id' => 1, 'title' => 'task1', 'creator_id' => 1, 'date_due' => $dp->getTimestampFromIsoFormat('-2 days'))));
|
||||
|
||||
$ics = $tf->create()
|
||||
->filterByDueDateRange(strtotime('-1 month'), strtotime('+1 month'))
|
||||
->setFullDay()
|
||||
->setCalendar(new Calendar('Kanboard'))
|
||||
->setColumns('date_due')
|
||||
->addFullDayEvents()
|
||||
->format();
|
||||
|
||||
$this->assertContains('UID:task-#1-date_due', $ics);
|
||||
$this->assertContains('DTSTART;TZID=UTC;VALUE=DATE:'.date('Ymd', strtotime('-2 days')), $ics);
|
||||
$this->assertContains('DTEND;TZID=UTC;VALUE=DATE:'.date('Ymd', strtotime('-2 days')), $ics);
|
||||
$this->assertContains('URL:http://localhost/?controller=task&action=show&task_id=1&project_id=1', $ics);
|
||||
$this->assertContains('SUMMARY:#1 task1', $ics);
|
||||
$this->assertContains('ATTENDEE:MAILTO:admin@kanboard.local', $ics);
|
||||
$this->assertContains('X-MICROSOFT-CDO-ALLDAYEVENT:TRUE', $ics);
|
||||
}
|
||||
|
||||
public function testIcalEventsWithAssigneeAndDueDate()
|
||||
{
|
||||
$dp = new DateParser($this->container);
|
||||
$p = new Project($this->container);
|
||||
$tc = new TaskCreation($this->container);
|
||||
$tf = new TaskFilterICalendarFormatter($this->container);
|
||||
$u = new User($this->container);
|
||||
$c = new Config($this->container);
|
||||
|
||||
$this->assertNotFalse($c->save(array('application_url' => 'http://kb/')));
|
||||
$this->assertEquals('http://kb/', $c->get('application_url'));
|
||||
|
||||
$this->assertNotFalse($u->update(array('id' => 1, 'email' => 'bob@localhost')));
|
||||
$this->assertEquals(1, $p->create(array('name' => 'test')));
|
||||
$this->assertNotFalse($tc->create(array('project_id' => 1, 'title' => 'task1', 'owner_id' => 1, 'date_due' => $dp->getTimestampFromIsoFormat('+5 days'))));
|
||||
|
||||
$ics = $tf->create()
|
||||
->filterByDueDateRange(strtotime('-1 month'), strtotime('+1 month'))
|
||||
->setFullDay()
|
||||
->setCalendar(new Calendar('Kanboard'))
|
||||
->setColumns('date_due')
|
||||
->addFullDayEvents()
|
||||
->format();
|
||||
|
||||
$this->assertContains('UID:task-#1-date_due', $ics);
|
||||
$this->assertContains('DTSTART;TZID=UTC;VALUE=DATE:'.date('Ymd', strtotime('+5 days')), $ics);
|
||||
$this->assertContains('DTEND;TZID=UTC;VALUE=DATE:'.date('Ymd', strtotime('+5 days')), $ics);
|
||||
$this->assertContains('URL:http://kb/?controller=task&action=show&task_id=1&project_id=1', $ics);
|
||||
$this->assertContains('SUMMARY:#1 task1', $ics);
|
||||
$this->assertContains('ORGANIZER;CN=admin:MAILTO:bob@localhost', $ics);
|
||||
$this->assertContains('X-MICROSOFT-CDO-ALLDAYEVENT:TRUE', $ics);
|
||||
}
|
||||
}
|
||||
|
|
@ -240,81 +240,4 @@ class SubtaskTimeTrackingTest extends Base
|
|||
$this->assertEquals(0, $task['time_estimated']);
|
||||
$this->assertEquals(0, $task['time_spent']);
|
||||
}
|
||||
|
||||
public function testGetCalendarEvents()
|
||||
{
|
||||
$tf = new TaskFinder($this->container);
|
||||
$tc = new TaskCreation($this->container);
|
||||
$s = new Subtask($this->container);
|
||||
$st = new SubtaskTimeTracking($this->container);
|
||||
$p = new Project($this->container);
|
||||
|
||||
$this->assertEquals(1, $p->create(array('name' => 'test1')));
|
||||
$this->assertEquals(2, $p->create(array('name' => 'test2')));
|
||||
|
||||
$this->assertEquals(1, $tc->create(array('title' => 'test 1', 'project_id' => 1)));
|
||||
$this->assertEquals(2, $tc->create(array('title' => 'test 1', 'project_id' => 2)));
|
||||
|
||||
$this->assertEquals(1, $s->create(array('title' => 'subtask #1', 'task_id' => 1)));
|
||||
$this->assertEquals(2, $s->create(array('title' => 'subtask #2', 'task_id' => 1)));
|
||||
$this->assertEquals(3, $s->create(array('title' => 'subtask #3', 'task_id' => 1)));
|
||||
|
||||
$this->assertEquals(4, $s->create(array('title' => 'subtask #4', 'task_id' => 2)));
|
||||
$this->assertEquals(5, $s->create(array('title' => 'subtask #5', 'task_id' => 2)));
|
||||
$this->assertEquals(6, $s->create(array('title' => 'subtask #6', 'task_id' => 2)));
|
||||
$this->assertEquals(7, $s->create(array('title' => 'subtask #7', 'task_id' => 2)));
|
||||
$this->assertEquals(8, $s->create(array('title' => 'subtask #8', 'task_id' => 2)));
|
||||
|
||||
// Slot start before and finish inside the calendar time range
|
||||
$this->container['db']->table(SubtaskTimeTracking::TABLE)->insert(array('user_id' => 1, 'subtask_id' => 1, 'start' => strtotime('-1 day'), 'end' => strtotime('+1 hour')));
|
||||
|
||||
// Slot start inside time range and finish after the time range
|
||||
$this->container['db']->table(SubtaskTimeTracking::TABLE)->insert(array('user_id' => 1, 'subtask_id' => 2, 'start' => strtotime('+1 hour'), 'end' => strtotime('+2 days')));
|
||||
|
||||
// Start before time range and finish inside time range
|
||||
$this->container['db']->table(SubtaskTimeTracking::TABLE)->insert(array('user_id' => 1, 'subtask_id' => 3, 'start' => strtotime('-1 day'), 'end' => strtotime('+1.5 days')));
|
||||
|
||||
// Start and finish inside time range
|
||||
$this->container['db']->table(SubtaskTimeTracking::TABLE)->insert(array('user_id' => 1, 'subtask_id' => 4, 'start' => strtotime('+1 hour'), 'end' => strtotime('+2 hours')));
|
||||
|
||||
// Start and finish after the time range
|
||||
$this->container['db']->table(SubtaskTimeTracking::TABLE)->insert(array('user_id' => 1, 'subtask_id' => 5, 'start' => strtotime('+2 days'), 'end' => strtotime('+3 days')));
|
||||
|
||||
// Start and finish before the time range
|
||||
$this->container['db']->table(SubtaskTimeTracking::TABLE)->insert(array('user_id' => 1, 'subtask_id' => 6, 'start' => strtotime('-2 days'), 'end' => strtotime('-1 day')));
|
||||
|
||||
// Start before time range and not finished
|
||||
$this->container['db']->table(SubtaskTimeTracking::TABLE)->insert(array('user_id' => 1, 'subtask_id' => 7, 'start' => strtotime('-1 day')));
|
||||
|
||||
// Start inside time range and not finish
|
||||
$this->container['db']->table(SubtaskTimeTracking::TABLE)->insert(array('user_id' => 1, 'subtask_id' => 8, 'start' => strtotime('+3200 seconds')));
|
||||
|
||||
$timesheet = $st->getUserTimesheet(1);
|
||||
$this->assertNotEmpty($timesheet);
|
||||
$this->assertCount(8, $timesheet);
|
||||
|
||||
$events = $st->getUserCalendarEvents(1, date('Y-m-d'), date('Y-m-d', strtotime('+2 day')));
|
||||
$this->assertNotEmpty($events);
|
||||
$this->assertCount(6, $events);
|
||||
$this->assertEquals(1, $events[0]['subtask_id']);
|
||||
$this->assertEquals(2, $events[1]['subtask_id']);
|
||||
$this->assertEquals(3, $events[2]['subtask_id']);
|
||||
$this->assertEquals(4, $events[3]['subtask_id']);
|
||||
$this->assertEquals(7, $events[4]['subtask_id']);
|
||||
$this->assertEquals(8, $events[5]['subtask_id']);
|
||||
|
||||
$events = $st->getProjectCalendarEvents(1, date('Y-m-d'), date('Y-m-d', strtotime('+2 days')));
|
||||
$this->assertNotEmpty($events);
|
||||
$this->assertCount(3, $events);
|
||||
$this->assertEquals(1, $events[0]['subtask_id']);
|
||||
$this->assertEquals(2, $events[1]['subtask_id']);
|
||||
$this->assertEquals(3, $events[2]['subtask_id']);
|
||||
|
||||
$events = $st->getProjectCalendarEvents(2, date('Y-m-d'), date('Y-m-d', strtotime('+2 days')));
|
||||
$this->assertNotEmpty($events);
|
||||
$this->assertCount(3, $events);
|
||||
$this->assertEquals(4, $events[0]['subtask_id']);
|
||||
$this->assertEquals(7, $events[1]['subtask_id']);
|
||||
$this->assertEquals(8, $events[2]['subtask_id']);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue