diff options
Diffstat (limited to 'app/Controller')
-rw-r--r-- | app/Controller/Activity.php | 5 | ||||
-rw-r--r-- | app/Controller/Analytic.php | 8 | ||||
-rw-r--r-- | app/Controller/App.php | 2 | ||||
-rw-r--r-- | app/Controller/AvatarFile.php | 92 | ||||
-rw-r--r-- | app/Controller/Base.php | 56 | ||||
-rw-r--r-- | app/Controller/Board.php | 21 | ||||
-rw-r--r-- | app/Controller/Calendar.php | 46 | ||||
-rw-r--r-- | app/Controller/Doc.php | 87 | ||||
-rw-r--r-- | app/Controller/Feed.php | 4 | ||||
-rw-r--r-- | app/Controller/FileViewer.php | 26 | ||||
-rw-r--r-- | app/Controller/Gantt.php | 34 | ||||
-rw-r--r-- | app/Controller/GroupHelper.php | 8 | ||||
-rw-r--r-- | app/Controller/Ical.php | 59 | ||||
-rw-r--r-- | app/Controller/Listing.php | 21 | ||||
-rw-r--r-- | app/Controller/Oauth.php | 106 | ||||
-rw-r--r-- | app/Controller/ProjectOverview.php | 21 | ||||
-rw-r--r-- | app/Controller/ProjectPermission.php | 4 | ||||
-rw-r--r-- | app/Controller/Search.php | 32 | ||||
-rw-r--r-- | app/Controller/Task.php | 8 | ||||
-rw-r--r-- | app/Controller/TaskHelper.php | 38 | ||||
-rw-r--r-- | app/Controller/UserHelper.php | 13 |
21 files changed, 418 insertions, 273 deletions
diff --git a/app/Controller/Activity.php b/app/Controller/Activity.php index db520ebe..47a66e0a 100644 --- a/app/Controller/Activity.php +++ b/app/Controller/Activity.php @@ -20,7 +20,7 @@ class Activity extends Base $project = $this->getProject(); $this->response->html($this->helper->layout->app('activity/project', array( - 'events' => $this->projectActivity->getProject($project['id']), + 'events' => $this->helper->projectActivity->getProjectEvents($project['id']), 'project' => $project, 'title' => t('%s\'s activity', $project['name']) ))); @@ -38,7 +38,8 @@ class Activity extends Base $this->response->html($this->helper->layout->task('activity/task', array( 'title' => $task['title'], 'task' => $task, - 'events' => $this->projectActivity->getTask($task['id']), + 'project' => $this->project->getById($task['project_id']), + 'events' => $this->helper->projectActivity->getTaskEvents($task['id']), ))); } } diff --git a/app/Controller/Analytic.php b/app/Controller/Analytic.php index 6ce062c4..35bc3048 100644 --- a/app/Controller/Analytic.php +++ b/app/Controller/Analytic.php @@ -2,6 +2,7 @@ namespace Kanboard\Controller; +use Kanboard\Filter\TaskProjectFilter; use Kanboard\Model\Task as TaskModel; /** @@ -44,14 +45,15 @@ class Analytic extends Base public function compareHours() { $project = $this->getProject(); - $params = $this->getProjectFilters('analytic', 'compareHours'); - $query = $this->taskFilter->create()->filterByProject($params['project']['id'])->getQuery(); $paginator = $this->paginator ->setUrl('analytic', 'compareHours', array('project_id' => $project['id'])) ->setMax(30) ->setOrder(TaskModel::TABLE.'.id') - ->setQuery($query) + ->setQuery($this->taskQuery + ->withFilter(new TaskProjectFilter($project['id'])) + ->getQuery() + ) ->calculate(); $this->response->html($this->helper->layout->analytic('analytic/compare_hours', array( diff --git a/app/Controller/App.php b/app/Controller/App.php index df1d3c90..01f733ff 100644 --- a/app/Controller/App.php +++ b/app/Controller/App.php @@ -157,7 +157,7 @@ class App extends Base $this->response->html($this->helper->layout->dashboard('app/activity', array( 'title' => t('My activity stream'), - 'events' => $this->projectActivity->getProjects($this->projectPermission->getActiveProjectIds($user['id']), 100), + 'events' => $this->helper->projectActivity->getProjectsEvents($this->projectPermission->getActiveProjectIds($user['id']), 100), 'user' => $user, ))); } diff --git a/app/Controller/AvatarFile.php b/app/Controller/AvatarFile.php new file mode 100644 index 00000000..a47cca66 --- /dev/null +++ b/app/Controller/AvatarFile.php @@ -0,0 +1,92 @@ +<?php + +namespace Kanboard\Controller; + +use Kanboard\Core\ObjectStorage\ObjectStorageException; +use Kanboard\Core\Thumbnail; + +/** + * Avatar File Controller + * + * @package controller + * @author Frederic Guillot + */ +class AvatarFile extends Base +{ + /** + * Display avatar page + */ + public function show() + { + $user = $this->getUser(); + + $this->response->html($this->helper->layout->user('avatar_file/show', array( + 'user' => $user, + ))); + } + + /** + * Upload Avatar + */ + public function upload() + { + $user = $this->getUser(); + + if (! $this->avatarFile->uploadFile($user['id'], $this->request->getFileInfo('avatar'))) { + $this->flash->failure(t('Unable to upload the file.')); + } + + $this->response->redirect($this->helper->url->to('AvatarFile', 'show', array('user_id' => $user['id']))); + } + + /** + * Remove Avatar image + */ + public function remove() + { + $this->checkCSRFParam(); + $user = $this->getUser(); + $this->avatarFile->remove($user['id']); + $this->response->redirect($this->helper->url->to('AvatarFile', 'show', array('user_id' => $user['id']))); + } + + /** + * Show Avatar image (public) + */ + public function image() + { + $user_id = $this->request->getIntegerParam('user_id'); + $size = $this->request->getStringParam('size', 48); + $filename = $this->avatarFile->getFilename($user_id); + $etag = md5($filename.$size); + + $this->response->cache(365 * 86400, $etag); + $this->response->contentType('image/jpeg'); + + if ($this->request->getHeader('If-None-Match') !== '"'.$etag.'"') { + $this->render($filename, $size); + } else { + $this->response->status(304); + } + } + + /** + * Render thumbnail from object storage + * + * @access private + * @param string $filename + * @param integer $size + */ + private function render($filename, $size) + { + try { + $blob = $this->objectStorage->get($filename); + + Thumbnail::createFromString($blob) + ->resize($size, $size) + ->toOutput(); + } catch (ObjectStorageException $e) { + $this->logger->error($e->getMessage()); + } + } +} diff --git a/app/Controller/Base.php b/app/Controller/Base.php index 2453be18..beb56909 100644 --- a/app/Controller/Base.php +++ b/app/Controller/Base.php @@ -287,60 +287,4 @@ abstract class Base extends \Kanboard\Core\Base return $subtask; } - - /** - * Common method to get project filters - * - * @access protected - * @param string $controller - * @param string $action - * @return array - */ - protected function getProjectFilters($controller, $action) - { - $project = $this->getProject(); - $search = $this->request->getStringParam('search', $this->userSession->getFilters($project['id'])); - $board_selector = $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()); - unset($board_selector[$project['id']]); - - $filters = array( - 'controller' => $controller, - 'action' => $action, - 'project_id' => $project['id'], - 'search' => urldecode($search), - ); - - $this->userSession->setFilters($project['id'], $filters['search']); - - return array( - 'project' => $project, - 'board_selector' => $board_selector, - 'filters' => $filters, - 'title' => $project['name'], - 'description' => $this->getProjectDescription($project), - ); - } - - /** - * Get project description - * - * @access protected - * @param array &$project - * @return string - */ - protected function getProjectDescription(array &$project) - { - if ($project['owner_id'] > 0) { - $description = t('Project owner: ').'**'.$this->helper->text->e($project['owner_name'] ?: $project['owner_username']).'**'.PHP_EOL.PHP_EOL; - - if (! empty($project['description'])) { - $description .= '***'.PHP_EOL.PHP_EOL; - $description .= $project['description']; - } - } else { - $description = $project['description']; - } - - return $description; - } } diff --git a/app/Controller/Board.php b/app/Controller/Board.php index 199f1703..67e99b81 100644 --- a/app/Controller/Board.php +++ b/app/Controller/Board.php @@ -2,6 +2,8 @@ namespace Kanboard\Controller; +use Kanboard\Formatter\BoardFormatter; + /** * Board controller * @@ -47,16 +49,19 @@ class Board extends Base */ public function show() { - $params = $this->getProjectFilters('board', 'show'); + $project = $this->getProject(); + $search = $this->helper->projectHeader->getSearchQuery($project); $this->response->html($this->helper->layout->app('board/view_private', array( - 'categories_list' => $this->category->getList($params['project']['id'], false), - 'users_list' => $this->projectUserRole->getAssignableUsersList($params['project']['id'], false), - 'custom_filters_list' => $this->customFilter->getAll($params['project']['id'], $this->userSession->getId()), - 'swimlanes' => $this->taskFilter->search($params['filters']['search'])->getBoard($params['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'), - ) + $params)); + 'swimlanes' => $this->taskLexer + ->build($search) + ->format(BoardFormatter::getInstance($this->container)->setProjectId($project['id'])) + ))); } /** @@ -177,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)) )); } } diff --git a/app/Controller/Calendar.php b/app/Controller/Calendar.php index a0a25e41..2517286d 100644 --- a/app/Controller/Calendar.php +++ b/app/Controller/Calendar.php @@ -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; /** @@ -20,9 +23,14 @@ class Calendar extends Base */ public function show() { + $project = $this->getProject(); + $this->response->html($this->helper->layout->app('calendar/show', array( + 'project' => $project, + 'title' => $project['name'], + 'description' => $this->helper->projectHeader->getDescription($project), 'check_interval' => $this->config->get('board_private_refresh_interval'), - ) + $this->getProjectFilters('calendar', 'show'))); + ))); } /** @@ -35,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, @@ -70,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); - - // Task with due date - $events = $filter->copy()->filterByDueDateRange($start, $end)->setColumns('date_due')->setFullDay()->format(); + $queryBuilder = $this->taskQuery + ->withFilter(new TaskAssigneeFilter($user_id)) + ->withFilter(new TaskStatusFilter(TaskModel::STATUS_OPEN)); - // 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()); - } + $events = $this->helper->calendar->getTaskDateDueEvents(clone($queryBuilder), $start, $end); + $events = array_merge($events, $this->helper->calendar->getTaskEvents(clone($queryBuilder), $start, $end)); - // 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( diff --git a/app/Controller/Doc.php b/app/Controller/Doc.php index f85326ac..00b9e585 100644 --- a/app/Controller/Doc.php +++ b/app/Controller/Doc.php @@ -5,54 +5,32 @@ namespace Kanboard\Controller; use Parsedown; /** - * Documentation controller + * Documentation Viewer * * @package controller * @author Frederic Guillot */ class Doc extends Base { - private function readFile($filename) - { - $url = $this->helper->url; - $data = file_get_contents($filename); - list($title, ) = explode("\n", $data, 2); - - $replaceUrl = function (array $matches) use ($url) { - return '('.$url->to('doc', 'show', array('file' => str_replace('.markdown', '', $matches[1]))).')'; - }; - - $content = preg_replace_callback('/\((.*.markdown)\)/', $replaceUrl, $data); - - return array( - 'content' => Parsedown::instance()->text($content), - 'title' => $title !== 'Documentation' ? t('Documentation: %s', $title) : $title, - ); - } - public function show() { $page = $this->request->getStringParam('file', 'index'); - if (! preg_match('/^[a-z0-9\-]+/', $page)) { + if (!preg_match('/^[a-z0-9\-]+/', $page)) { $page = 'index'; } - $filenames = array(__DIR__.'/../../doc/'.$page.'.markdown'); - $filename = __DIR__.'/../../doc/index.markdown'; - if ($this->config->getCurrentLanguage() === 'fr_FR') { - array_unshift($filenames, __DIR__.'/../../doc/fr/'.$page.'.markdown'); + $filename = __DIR__.'/../../doc/fr/' . $page . '.markdown'; + } else { + $filename = __DIR__ . '/../../doc/' . $page . '.markdown'; } - foreach ($filenames as $file) { - if (file_exists($file)) { - $filename = $file; - break; - } + if (!file_exists($filename)) { + $filename = __DIR__.'/../../doc/index.markdown'; } - $this->response->html($this->helper->layout->app('doc/show', $this->readFile($filename))); + $this->response->html($this->helper->layout->app('doc/show', $this->render($filename))); } /** @@ -62,4 +40,53 @@ class Doc extends Base { $this->response->html($this->template->render('config/keyboard_shortcuts')); } + + /** + * Prepare Markdown file + * + * @access private + * @param string $filename + * @return array + */ + private function render($filename) + { + $data = file_get_contents($filename); + $content = preg_replace_callback('/\((.*.markdown)\)/', array($this, 'replaceMarkdownUrl'), $data); + $content = preg_replace_callback('/\((screenshots.*\.png)\)/', array($this, 'replaceImageUrl'), $content); + + list($title, ) = explode("\n", $data, 2); + + return array( + 'content' => Parsedown::instance()->text($content), + 'title' => $title !== 'Documentation' ? t('Documentation: %s', $title) : $title, + ); + } + + /** + * Regex callback to replace Markdown links + * + * @access public + * @param array $matches + * @return string + */ + public function replaceMarkdownUrl(array $matches) + { + return '('.$this->helper->url->to('doc', 'show', array('file' => str_replace('.markdown', '', $matches[1]))).')'; + } + + /** + * Regex callback to replace image links + * + * @access public + * @param array $matches + * @return string + */ + public function replaceImageUrl(array $matches) + { + if ($this->config->getCurrentLanguage() === 'fr_FR') { + return '('.$this->helper->url->base().'doc/fr/'.$matches[1].')'; + } + + return '('.$this->helper->url->base().'doc/'.$matches[1].')'; + } } diff --git a/app/Controller/Feed.php b/app/Controller/Feed.php index 8457c383..f8b3d320 100644 --- a/app/Controller/Feed.php +++ b/app/Controller/Feed.php @@ -26,7 +26,7 @@ class Feed extends Base } $this->response->xml($this->template->render('feed/user', array( - 'events' => $this->projectActivity->getProjects($this->projectPermission->getActiveProjectIds($user['id'])), + 'events' => $this->helper->projectActivity->getProjectsEvents($this->projectPermission->getActiveProjectIds($user['id'])), 'user' => $user, ))); } @@ -47,7 +47,7 @@ class Feed extends Base } $this->response->xml($this->template->render('feed/project', array( - 'events' => $this->projectActivity->getProject($project['id']), + 'events' => $this->helper->projectActivity->getProjectEvents($project['id']), 'project' => $project, ))); } diff --git a/app/Controller/FileViewer.php b/app/Controller/FileViewer.php index bc91c3d8..3be4ea14 100644 --- a/app/Controller/FileViewer.php +++ b/app/Controller/FileViewer.php @@ -66,9 +66,16 @@ class FileViewer extends Base */ public function image() { + $file = $this->getFile(); + $etag = md5($file['path']); + $this->response->contentType($this->helper->file->getImageMimeType($file['name'])); + $this->response->cache(5 * 86400, $etag); + + if ($this->request->getHeader('If-None-Match') === '"'.$etag.'"') { + return $this->response->status(304); + } + try { - $file = $this->getFile(); - $this->response->contentType($this->helper->file->getImageMimeType($file['name'])); $this->objectStorage->output($file['path']); } catch (ObjectStorageException $e) { $this->logger->error($e->getMessage()); @@ -82,12 +89,21 @@ class FileViewer extends Base */ public function thumbnail() { + $file = $this->getFile(); + $model = $file['model']; + $filename = $this->$model->getThumbnailPath($file['path']); + $etag = md5($filename); + + $this->response->cache(5 * 86400, $etag); $this->response->contentType('image/jpeg'); + if ($this->request->getHeader('If-None-Match') === '"'.$etag.'"') { + return $this->response->status(304); + } + try { - $file = $this->getFile(); - $model = $file['model']; - $this->objectStorage->output($this->$model->getThumbnailPath($file['path'])); + + $this->objectStorage->output($filename); } catch (ObjectStorageException $e) { $this->logger->error($e->getMessage()); diff --git a/app/Controller/Gantt.php b/app/Controller/Gantt.php index 9ffa277f..5e9ad55e 100644 --- a/app/Controller/Gantt.php +++ b/app/Controller/Gantt.php @@ -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'), ))); } @@ -54,9 +63,10 @@ class Gantt extends Base */ public function project() { - $params = $this->getProjectFilters('gantt', 'project'); - $filter = $this->taskFilterGanttFormatter->search($params['filters']['search'])->filterByProject($params['project']['id']); + $project = $this->getProject(); + $search = $this->helper->projectHeader->getSearchQuery($project); $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'); @@ -64,10 +74,12 @@ class Gantt extends Base $filter->getQuery()->asc('column_position')->asc(TaskModel::TABLE.'.position'); } - $this->response->html($this->helper->layout->app('gantt/project', $params + array( - 'users_list' => $this->projectUserRole->getAssignableUsersList($params['project']['id'], false), + $this->response->html($this->helper->layout->app('gantt/project', array( + 'project' => $project, + 'title' => $project['name'], + 'description' => $this->helper->projectHeader->getDescription($project), 'sorting' => $sorting, - 'tasks' => $filter->format(), + 'tasks' => $filter->format(new TaskGanttFormatter($this->container)), ))); } diff --git a/app/Controller/GroupHelper.php b/app/Controller/GroupHelper.php index 34f522a6..429614c2 100644 --- a/app/Controller/GroupHelper.php +++ b/app/Controller/GroupHelper.php @@ -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()); } } diff --git a/app/Controller/Ical.php b/app/Controller/Ical.php index f1ea6d8f..8fe97b46 100644 --- a/app/Controller/Ical.php +++ b/app/Controller/Ical.php @@ -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); - } - - // Tasks with due date - $filter - ->copy() - ->filterByDueDateRange($start, $end) - ->setColumns('date_due') - ->setCalendar($calendar) - ->addFullDayEvents($calendar); + $this->helper->ical->addTaskDateDueEvents($queryBuilder, $calendar, $start, $end); - $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()); } } diff --git a/app/Controller/Listing.php b/app/Controller/Listing.php index c784dd50..2024ff03 100644 --- a/app/Controller/Listing.php +++ b/app/Controller/Listing.php @@ -2,6 +2,7 @@ namespace Kanboard\Controller; +use Kanboard\Filter\TaskProjectFilter; use Kanboard\Model\Task as TaskModel; /** @@ -19,22 +20,26 @@ class Listing extends Base */ public function show() { - $params = $this->getProjectFilters('listing', 'show'); - $query = $this->taskFilter->search($params['filters']['search'])->filterByProject($params['project']['id'])->getQuery(); + $project = $this->getProject(); + $search = $this->helper->projectHeader->getSearchQuery($project); $paginator = $this->paginator - ->setUrl('listing', 'show', array('project_id' => $params['project']['id'])) + ->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', $params + array( + $this->response->html($this->helper->layout->app('listing/show', array( + 'project' => $project, + 'title' => $project['name'], + 'description' => $this->helper->projectHeader->getDescription($project), 'paginator' => $paginator, - 'categories_list' => $this->category->getList($params['project']['id'], false), - 'users_list' => $this->projectUserRole->getAssignableUsersList($params['project']['id'], false), - 'custom_filters_list' => $this->customFilter->getAll($params['project']['id'], $this->userSession->getId()), ))); } } diff --git a/app/Controller/Oauth.php b/app/Controller/Oauth.php index 452faecd..12b91144 100644 --- a/app/Controller/Oauth.php +++ b/app/Controller/Oauth.php @@ -2,6 +2,8 @@ namespace Kanboard\Controller; +use Kanboard\Core\Security\OAuthAuthenticationProviderInterface; + /** * OAuth controller * @@ -11,25 +13,6 @@ namespace Kanboard\Controller; class Oauth extends Base { /** - * Unlink external account - * - * @access public - */ - public function unlink() - { - $backend = $this->request->getStringParam('backend'); - $this->checkCSRFParam(); - - if ($this->authenticationManager->getProvider($backend)->unlink($this->userSession->getId())) { - $this->flash->success(t('Your external account is not linked anymore to your profile.')); - } else { - $this->flash->failure(t('Unable to unlink your external account.')); - } - - $this->response->redirect($this->helper->url->to('user', 'external', array('user_id' => $this->userSession->getId()))); - } - - /** * Redirect to the provider if no code received * * @access private @@ -38,9 +21,10 @@ class Oauth extends Base protected function step1($provider) { $code = $this->request->getStringParam('code'); + $state = $this->request->getStringParam('state'); if (! empty($code)) { - $this->step2($provider, $code); + $this->step2($provider, $code, $state); } else { $this->response->redirect($this->authenticationManager->getProvider($provider)->getService()->getAuthorizationUrl()); } @@ -50,34 +34,44 @@ class Oauth extends Base * Link or authenticate the user * * @access protected - * @param string $provider + * @param string $providerName * @param string $code + * @param string $state */ - protected function step2($provider, $code) + protected function step2($providerName, $code, $state) { - $this->authenticationManager->getProvider($provider)->setCode($code); + $provider = $this->authenticationManager->getProvider($providerName); + $provider->setCode($code); + $hasValidState = $provider->getService()->isValidateState($state); if ($this->userSession->isLogged()) { - $this->link($provider); + if ($hasValidState) { + $this->link($provider); + } else { + $this->flash->failure(t('The OAuth2 state parameter is invalid')); + $this->response->redirect($this->helper->url->to('user', 'external', array('user_id' => $this->userSession->getId()))); + } + } else { + if ($hasValidState) { + $this->authenticate($providerName); + } else { + $this->authenticationFailure(t('The OAuth2 state parameter is invalid')); + } } - - $this->authenticate($provider); } /** * Link the account * * @access protected - * @param string $provider + * @param OAuthAuthenticationProviderInterface $provider */ - protected function link($provider) + protected function link(OAuthAuthenticationProviderInterface $provider) { - $authProvider = $this->authenticationManager->getProvider($provider); - - if (! $authProvider->authenticate()) { + if (! $provider->authenticate()) { $this->flash->failure(t('External authentication failed')); } else { - $this->userProfile->assign($this->userSession->getId(), $authProvider->getUser()); + $this->userProfile->assign($this->userSession->getId(), $provider->getUser()); $this->flash->success(t('Your external account is linked to your profile successfully.')); } @@ -85,22 +79,52 @@ class Oauth extends Base } /** + * Unlink external account + * + * @access public + */ + public function unlink() + { + $backend = $this->request->getStringParam('backend'); + $this->checkCSRFParam(); + + if ($this->authenticationManager->getProvider($backend)->unlink($this->userSession->getId())) { + $this->flash->success(t('Your external account is not linked anymore to your profile.')); + } else { + $this->flash->failure(t('Unable to unlink your external account.')); + } + + $this->response->redirect($this->helper->url->to('user', 'external', array('user_id' => $this->userSession->getId()))); + } + + /** * Authenticate the account * * @access protected - * @param string $provider + * @param string $providerName */ - protected function authenticate($provider) + protected function authenticate($providerName) { - if ($this->authenticationManager->oauthAuthentication($provider)) { + if ($this->authenticationManager->oauthAuthentication($providerName)) { $this->response->redirect($this->helper->url->to('app', 'index')); } else { - $this->response->html($this->helper->layout->app('auth/index', array( - 'errors' => array('login' => t('External authentication failed')), - 'values' => array(), - 'no_layout' => true, - 'title' => t('Login') - ))); + $this->authenticationFailure(t('External authentication failed')); } } + + /** + * Show login failure page + * + * @access protected + * @param string $message + */ + protected function authenticationFailure($message) + { + $this->response->html($this->helper->layout->app('auth/index', array( + 'errors' => array('login' => $message), + 'values' => array(), + 'no_layout' => true, + 'title' => t('Login') + ))); + } } diff --git a/app/Controller/ProjectOverview.php b/app/Controller/ProjectOverview.php index b0687ed3..b2bb33d6 100644 --- a/app/Controller/ProjectOverview.php +++ b/app/Controller/ProjectOverview.php @@ -15,15 +15,18 @@ class ProjectOverview extends Base */ public function show() { - $params = $this->getProjectFilters('ProjectOverview', 'show'); - $params['users'] = $this->projectUserRole->getAllUsersGroupedByRole($params['project']['id']); - $params['roles'] = $this->role->getProjectRoles(); - $params['events'] = $this->projectActivity->getProject($params['project']['id'], 10); - $params['images'] = $this->projectFile->getAllImages($params['project']['id']); - $params['files'] = $this->projectFile->getAllDocuments($params['project']['id']); + $project = $this->getProject(); + $this->project->getColumnStats($project); - $this->project->getColumnStats($params['project']); - - $this->response->html($this->helper->layout->app('project_overview/show', $params)); + $this->response->html($this->helper->layout->app('project_overview/show', array( + 'project' => $project, + 'title' => $project['name'], + 'description' => $this->helper->projectHeader->getDescription($project), + 'users' => $this->projectUserRole->getAllUsersGroupedByRole($project['id']), + 'roles' => $this->role->getProjectRoles(), + 'events' => $this->helper->projectActivity->getProjectEvents($project['id'], 10), + 'images' => $this->projectFile->getAllImages($project['id']), + 'files' => $this->projectFile->getAllDocuments($project['id']), + ))); } } diff --git a/app/Controller/ProjectPermission.php b/app/Controller/ProjectPermission.php index 800da02f..e203c0db 100644 --- a/app/Controller/ProjectPermission.php +++ b/app/Controller/ProjectPermission.php @@ -83,7 +83,9 @@ class ProjectPermission extends Base $project = $this->getProject(); $values = $this->request->getValues(); - if ($this->projectUserRole->addUser($values['project_id'], $values['user_id'], $values['role'])) { + if (empty($values['user_id'])) { + $this->flash->failure(t('User not found.')); + } elseif ($this->projectUserRole->addUser($values['project_id'], $values['user_id'], $values['role'])) { $this->flash->success(t('Project updated successfully.')); } else { $this->flash->failure(t('Unable to update this project.')); diff --git a/app/Controller/Search.php b/app/Controller/Search.php index 9b9b9e65..a42e9d3d 100644 --- a/app/Controller/Search.php +++ b/app/Controller/Search.php @@ -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(); @@ -46,4 +46,22 @@ class Search extends Base 'title' => t('Search tasks').($nb_tasks > 0 ? ' ('.$nb_tasks.')' : '') ))); } + + public function activity() + { + $search = urldecode($this->request->getStringParam('search')); + $events = $this->helper->projectActivity->searchEvents($search); + $nb_events = count($events); + + $this->response->html($this->helper->layout->app('search/activity', array( + 'values' => array( + 'search' => $search, + 'controller' => 'search', + 'action' => 'activity', + ), + 'title' => t('Search in activity stream').($nb_events > 0 ? ' ('.$nb_events.')' : ''), + 'nb_events' => $nb_events, + 'events' => $events, + ))); + } } diff --git a/app/Controller/Task.php b/app/Controller/Task.php index dc10604e..902a32d6 100644 --- a/app/Controller/Task.php +++ b/app/Controller/Task.php @@ -71,17 +71,16 @@ class Task extends Base $values = $this->dateParser->format($values, array('date_started'), $this->config->get('application_datetime_format', DateParser::DATE_TIME_FORMAT)); $this->response->html($this->helper->layout->task('task/show', array( + 'task' => $task, 'project' => $this->project->getById($task['project_id']), + 'values' => $values, 'files' => $this->taskFile->getAllDocuments($task['id']), 'images' => $this->taskFile->getAllImages($task['id']), 'comments' => $this->comment->getAll($task['id'], $this->userSession->getCommentSorting()), 'subtasks' => $subtasks, 'internal_links' => $this->taskLink->getAllGroupedByLabel($task['id']), 'external_links' => $this->taskExternalLink->getAll($task['id']), - 'task' => $task, - 'values' => $values, 'link_label_list' => $this->link->getList(0, false), - 'users_list' => $this->projectUserRole->getAssignableUsersList($task['project_id'], true, false, false), ))); } @@ -96,6 +95,7 @@ class Task extends Base $this->response->html($this->helper->layout->task('task/analytics', array( 'task' => $task, + 'project' => $this->project->getById($task['project_id']), 'lead_time' => $this->taskAnalytic->getLeadTime($task), 'cycle_time' => $this->taskAnalytic->getCycleTime($task), 'time_spent_columns' => $this->taskAnalytic->getTimeSpentByColumn($task), @@ -121,6 +121,7 @@ class Task extends Base $this->response->html($this->helper->layout->task('task/time_tracking_details', array( 'task' => $task, + 'project' => $this->project->getById($task['project_id']), 'subtask_paginator' => $subtask_paginator, ))); } @@ -136,6 +137,7 @@ class Task extends Base $this->response->html($this->helper->layout->task('task/transitions', array( 'task' => $task, + 'project' => $this->project->getById($task['project_id']), 'transitions' => $this->transition->getAllByTask($task['id']), ))); } diff --git a/app/Controller/TaskHelper.php b/app/Controller/TaskHelper.php index 7e340a6a..6835ab2b 100644 --- a/app/Controller/TaskHelper.php +++ b/app/Controller/TaskHelper.php @@ -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()); - } + } else { - $filter = $this->taskFilterAutoCompleteFormatter - ->create() - ->filterByProjects($projects) - ->excludeTasks(array($this->request->getIntegerParam('exclude_task_id'))); + $filter = $this->taskQuery->withFilter(new TaskProjectsFilter($project_ids)); - // Search by task id or by title - if (ctype_digit($search)) { - $filter->filterById($search); - } else { - $filter->filterByTitle($search); - } + 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()); + $this->response->json($filter->format(new TaskAutoCompleteFormatter($this->container))); + } } } diff --git a/app/Controller/UserHelper.php b/app/Controller/UserHelper.php index 041ed2c8..47bbe554 100644 --- a/app/Controller/UserHelper.php +++ b/app/Controller/UserHelper.php @@ -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 */ |