diff options
author | Frederic Guillot <fred@kanboard.net> | 2015-07-12 10:38:42 -0400 |
---|---|---|
committer | Frederic Guillot <fred@kanboard.net> | 2015-07-12 10:38:42 -0400 |
commit | bc98748c0d5b1e1c4d08f16b58db8aea9b5fb5ce (patch) | |
tree | b8eca28ff1546fe21f5d0ebb48f93e4b5988d92b | |
parent | 7e94d0ca233d15d6124c0adf3f956a119c82ccae (diff) |
Start to implement url rewrite (nice urls)
35 files changed, 543 insertions, 204 deletions
diff --git a/.htaccess b/.htaccess new file mode 100644 index 00000000..0d873f58 --- /dev/null +++ b/.htaccess @@ -0,0 +1,9 @@ +<IfModule mod_rewrite.c> + Options -MultiViews + + SetEnv HTTP_MOD_REWRITE On + + RewriteEngine On + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^ index.php [QSA,L] +</IfModule> diff --git a/app/Api/Project.php b/app/Api/Project.php index faf2a3da..4e4e10b8 100644 --- a/app/Api/Project.php +++ b/app/Api/Project.php @@ -87,9 +87,9 @@ class Project extends Base { if (! empty($project)) { $project['url'] = array( - 'board' => $this->helper->url->base().$this->helper->url->to('board', 'show', array('project_id' => $project['id'])), - 'calendar' => $this->helper->url->base().$this->helper->url->to('calendar', 'show', array('project_id' => $project['id'])), - 'list' => $this->helper->url->base().$this->helper->url->to('listing', 'show', array('project_id' => $project['id'])), + 'board' => $this->helper->url->to('board', 'show', array('project_id' => $project['id']), '', true), + 'calendar' => $this->helper->url->to('calendar', 'show', array('project_id' => $project['id']), '', true), + 'list' => $this->helper->url->to('listing', 'show', array('project_id' => $project['id']), '', true), ); } diff --git a/app/Api/Task.php b/app/Api/Task.php index ade49a6d..3b8c1ec8 100644 --- a/app/Api/Task.php +++ b/app/Api/Task.php @@ -119,7 +119,7 @@ class Task extends Base private function formatTask($task) { if (! empty($task)) { - $task['url'] = $this->helper->url->base().$this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])); + $task['url'] = $this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']), '', true); } return $task; diff --git a/app/Auth/RememberMe.php b/app/Auth/RememberMe.php index eebf4f4b..54e60422 100644 --- a/app/Auth/RememberMe.php +++ b/app/Auth/RememberMe.php @@ -282,7 +282,7 @@ class RememberMe extends Base self::COOKIE_NAME, $this->encodeCookie($token, $sequence), $expiration, - BASE_URL_DIRECTORY, + $this->helper->url->dir(), null, Request::isHTTPS(), true @@ -315,7 +315,7 @@ class RememberMe extends Base self::COOKIE_NAME, '', time() - 3600, - BASE_URL_DIRECTORY, + $this->helper->url->dir(), null, Request::isHTTPS(), true diff --git a/app/Controller/Action.php b/app/Controller/Action.php index cd24453a..74a5326d 100644 --- a/app/Controller/Action.php +++ b/app/Controller/Action.php @@ -46,7 +46,7 @@ class Action extends Base $values = $this->request->getValues(); if (empty($values['action_name']) || empty($values['project_id'])) { - $this->response->redirect('?controller=action&action=index&project_id='.$project['id']); + $this->response->redirect($this->helper->url->to('action', 'index', array('project_id' => $project['id']))); } $this->response->html($this->projectLayout('action/event', array( @@ -68,7 +68,7 @@ class Action extends Base $values = $this->request->getValues(); if (empty($values['action_name']) || empty($values['project_id']) || empty($values['event_name'])) { - $this->response->redirect('?controller=action&action=index&project_id='.$project['id']); + $this->response->redirect($this->helper->url->to('action', 'index', array('project_id' => $project['id']))); } $action = $this->action->load($values['action_name'], $values['project_id'], $values['event_name']); @@ -125,7 +125,7 @@ class Action extends Base } } - $this->response->redirect('?controller=action&action=index&project_id='.$project['id']); + $this->response->redirect($this->helper->url->to('action', 'index', array('project_id' => $project['id']))); } /** @@ -163,6 +163,6 @@ class Action extends Base $this->session->flashError(t('Unable to remove this action.')); } - $this->response->redirect('?controller=action&action=index&project_id='.$project['id']); + $this->response->redirect($this->helper->url->to('action', 'index', array('project_id' => $project['id']))); } } diff --git a/app/Controller/Base.php b/app/Controller/Base.php index 9f5d6dc6..18187162 100644 --- a/app/Controller/Base.php +++ b/app/Controller/Base.php @@ -101,7 +101,7 @@ abstract class Base extends \Core\Base public function beforeAction($controller, $action) { // Start the session - $this->session->open(BASE_URL_DIRECTORY); + $this->session->open($this->helper->url->dir()); $this->sendHeaders($action); $this->container['dispatcher']->dispatch('session.bootstrap', new Event); @@ -223,17 +223,6 @@ abstract class Base extends \Core\Base } /** - * Redirection when there is no project in the database - * - * @access protected - */ - protected function redirectNoProject() - { - $this->session->flash(t('There is no active project, the first step is to create a new project.')); - $this->response->redirect('?controller=project&action=create'); - } - - /** * Common layout for task views * * @access protected @@ -301,7 +290,7 @@ abstract class Base extends \Core\Base if (empty($project)) { $this->session->flashError(t('Project not found.')); - $this->response->redirect('?controller=project'); + $this->response->redirect($this->helper->url->to('project', 'index')); } return $project; @@ -344,10 +333,10 @@ abstract class Base extends \Core\Base 'controller' => $controller, 'action' => $action, 'project_id' => $project['id'], - 'search' => $search, + 'search' => urldecode($search), ); - $this->userSession->setFilters($project['id'], $search); + $this->userSession->setFilters($project['id'], $filters['search']); return array( 'project' => $project, diff --git a/app/Controller/Category.php b/app/Controller/Category.php index 515cc9c8..e8d83f2d 100644 --- a/app/Controller/Category.php +++ b/app/Controller/Category.php @@ -23,7 +23,7 @@ class Category extends Base if (empty($category)) { $this->session->flashError(t('Category not found.')); - $this->response->redirect('?controller=category&action=index&project_id='.$project_id); + $this->response->redirect($this->helper->url->to('category', 'index', array('project_id' => $project_id))); } return $category; @@ -63,7 +63,7 @@ class Category extends Base if ($this->category->create($values)) { $this->session->flash(t('Your category have been created successfully.')); - $this->response->redirect('?controller=category&action=index&project_id='.$project['id']); + $this->response->redirect($this->helper->url->to('category', 'index', array('project_id' => $project['id']))); } else { $this->session->flashError(t('Unable to create your category.')); @@ -107,7 +107,7 @@ class Category extends Base if ($this->category->update($values)) { $this->session->flash(t('Your category have been updated successfully.')); - $this->response->redirect('?controller=category&action=index&project_id='.$project['id']); + $this->response->redirect($this->helper->url->to('category', 'index', array('project_id' => $project['id']))); } else { $this->session->flashError(t('Unable to update your category.')); @@ -151,6 +151,6 @@ class Category extends Base $this->session->flashError(t('Unable to remove this category.')); } - $this->response->redirect('?controller=category&action=index&project_id='.$project['id']); + $this->response->redirect($this->helper->url->to('category', 'index', array('project_id' => $project['id']))); } } diff --git a/app/Controller/Comment.php b/app/Controller/Comment.php index a5f6b1f8..ca701a88 100644 --- a/app/Controller/Comment.php +++ b/app/Controller/Comment.php @@ -90,10 +90,10 @@ class Comment extends Base } if ($ajax) { - $this->response->redirect('?controller=board&action=show&project_id='.$task['project_id']); + $this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $task['project_id']))); } - $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'&project_id='.$task['project_id'].'#comments'); + $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])), 'comments'); } $this->create($values, $errors); @@ -140,7 +140,7 @@ class Comment extends Base $this->session->flashError(t('Unable to update your comment.')); } - $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'&project_id='.$task['project_id'].'#comment-'.$comment['id']); + $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])), 'comment-'.$comment['id']); } $this->edit($values, $errors); @@ -181,6 +181,6 @@ class Comment extends Base $this->session->flashError(t('Unable to remove this comment.')); } - $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'&project_id='.$task['project_id'].'#comments'); + $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])), 'comments'); } } diff --git a/app/Controller/Config.php b/app/Controller/Config.php index 19bc2767..1e6b3dc8 100644 --- a/app/Controller/Config.php +++ b/app/Controller/Config.php @@ -60,7 +60,7 @@ class Config extends Base $this->session->flashError(t('Unable to save your settings.')); } - $this->response->redirect('?controller=config&action='.$redirect); + $this->response->redirect($this->helper->url->to('config', $redirect)); } } @@ -199,7 +199,7 @@ class Config extends Base $this->checkCSRFParam(); $this->config->optimizeDatabase(); $this->session->flash(t('Database optimization done.')); - $this->response->redirect('?controller=config'); + $this->response->redirect($this->helper->url->to('config', 'index')); } /** @@ -215,6 +215,6 @@ class Config extends Base $this->config->regenerateToken($type.'_token'); $this->session->flash(t('Token regenerated.')); - $this->response->redirect('?controller=config&action='.$type); + $this->response->redirect($this->helper->url->to('config', $type)); } } diff --git a/app/Controller/Project.php b/app/Controller/Project.php index faebac38..45bc2a46 100644 --- a/app/Controller/Project.php +++ b/app/Controller/Project.php @@ -73,11 +73,12 @@ class Project extends Base if ($this->project->{$switch.'PublicAccess'}($project['id'])) { $this->session->flash(t('Project updated successfully.')); - } else { + } + else { $this->session->flashError(t('Unable to update this project.')); } - $this->response->redirect('?controller=project&action=share&project_id='.$project['id']); + $this->response->redirect($this->helper->url->to('project', 'share', array('project_id' => $project['id']))); } $this->response->html($this->projectLayout('project/share', array( @@ -150,7 +151,7 @@ class Project extends Base if ($this->project->update($values)) { $this->session->flash(t('Project updated successfully.')); - $this->response->redirect('?controller=project&action=edit&project_id='.$project['id']); + $this->response->redirect($this->helper->url->to('project', 'edit', array('project_id' => $project['id']))); } else { $this->session->flashError(t('Unable to update this project.')); @@ -197,7 +198,7 @@ class Project extends Base } } - $this->response->redirect('?controller=project&action=users&project_id='.$project['id']); + $this->response->redirect($this->helper->url->to('project', 'users', array('project_id' => $project['id']))); } /** @@ -220,7 +221,7 @@ class Project extends Base } } - $this->response->redirect('?controller=project&action=users&project_id='.$values['project_id']); + $this->response->redirect($this->helper->url->to('project', 'users', array('project_id' => $values['project_id']))); } /** @@ -250,7 +251,7 @@ class Project extends Base } } - $this->response->redirect('?controller=project&action=users&project_id='.$values['project_id']); + $this->response->redirect($this->helper->url->to('project', 'users', array('project_id' => $values['project_id']))); } /** @@ -279,7 +280,7 @@ class Project extends Base } } - $this->response->redirect('?controller=project&action=users&project_id='.$values['project_id']); + $this->response->redirect($this->helper->url->to('project', 'users', array('project_id' => $values['project_id']))); } /** @@ -301,7 +302,7 @@ class Project extends Base $this->session->flashError(t('Unable to remove this project.')); } - $this->response->redirect('?controller=project'); + $this->response->redirect($this->helper->url->to('project', 'index')); } $this->response->html($this->projectLayout('project/remove', array( @@ -329,7 +330,7 @@ class Project extends Base $this->session->flashError(t('Unable to clone this project.')); } - $this->response->redirect('?controller=project'); + $this->response->redirect($this->helper->url->to('project', 'index')); } $this->response->html($this->projectLayout('project/duplicate', array( @@ -357,7 +358,7 @@ class Project extends Base $this->session->flashError(t('Unable to disable this project.')); } - $this->response->redirect('?controller=project&action=show&project_id='.$project['id']); + $this->response->redirect($this->helper->url->to('project', 'show', array('project_id' => $project['id']))); } $this->response->html($this->projectLayout('project/disable', array( @@ -385,7 +386,7 @@ class Project extends Base $this->session->flashError(t('Unable to activate this project.')); } - $this->response->redirect('?controller=project&action=show&project_id='.$project['id']); + $this->response->redirect($this->helper->url->to('project', 'show', array('project_id' => $project['id']))); } $this->response->html($this->projectLayout('project/enable', array( @@ -428,7 +429,7 @@ class Project extends Base if ($project_id > 0) { $this->session->flash(t('Your project have been created successfully.')); - $this->response->redirect('?controller=project&action=show&project_id='.$project_id); + $this->response->redirect($this->helper->url->to('project', 'show', array('project_id' => $project_id))); } $this->session->flashError(t('Unable to create your project.')); diff --git a/app/Controller/Search.php b/app/Controller/Search.php index 519f9ce4..f6dc7a32 100644 --- a/app/Controller/Search.php +++ b/app/Controller/Search.php @@ -13,7 +13,7 @@ class Search extends Base public function index() { $projects = $this->projectPermission->getAllowedProjects($this->userSession->getId()); - $search = $this->request->getStringParam('search'); + $search = urldecode($this->request->getStringParam('search')); $nb_tasks = 0; $paginator = $this->paginator diff --git a/app/Controller/Subtask.php b/app/Controller/Subtask.php index 6ee94333..87f3fcb4 100644 --- a/app/Controller/Subtask.php +++ b/app/Controller/Subtask.php @@ -75,10 +75,10 @@ class Subtask extends Base } if (isset($values['another_subtask']) && $values['another_subtask'] == 1) { - $this->response->redirect('?controller=subtask&action=create&task_id='.$task['id'].'&another_subtask=1&project_id='.$task['project_id']); + $this->response->redirect($this->helper->url->to('subtask', 'create', array('project_id' => $task['project_id'], 'task_id' => $task['id'], 'another_subtask' => 1))); } - $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'&project_id='.$task['project_id'].'#subtasks'); + $this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $task['project_id'], 'task_id' => $task['id']), 'subtasks')); } $this->create($values, $errors); @@ -126,7 +126,7 @@ class Subtask extends Base $this->session->flashError(t('Unable to update your sub-task.')); } - $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'&project_id='.$task['project_id'].'#subtasks'); + $this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $task['project_id'], 'task_id' => $task['id']), 'subtasks')); } $this->edit($values, $errors); @@ -166,7 +166,7 @@ class Subtask extends Base $this->session->flashError(t('Unable to remove this sub-task.')); } - $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'&project_id='.$task['project_id'].'#subtasks'); + $this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $task['project_id'], 'task_id' => $task['id']), 'subtasks')); } /** @@ -256,7 +256,7 @@ class Subtask extends Base case 'dashboard': $this->response->redirect($this->helper->url->to('app', 'index')); default: - $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])).'#subtasks'); + $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']), 'subtasks')); } } @@ -275,6 +275,6 @@ class Subtask extends Base $method = $direction === 'up' ? 'moveUp' : 'moveDown'; $this->subtask->$method($task_id, $subtask_id); - $this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $project_id, 'task_id' => $task_id)).'#subtasks'); + $this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $project_id, 'task_id' => $task_id), 'subtasks')); } } diff --git a/app/Controller/Swimlane.php b/app/Controller/Swimlane.php index c6862d47..054fa4ba 100644 --- a/app/Controller/Swimlane.php +++ b/app/Controller/Swimlane.php @@ -25,7 +25,7 @@ class Swimlane extends Base if (empty($swimlane)) { $this->session->flashError(t('Swimlane not found.')); - $this->response->redirect('?controller=swimlane&action=index&project_id='.$project_id); + $this->response->redirect($this->helper->url->to('swimlane', 'index', array('project_id' => $project_id))); } return $swimlane; @@ -67,7 +67,7 @@ class Swimlane extends Base if ($this->swimlane->create($project['id'], $values['name'])) { $this->session->flash(t('Your swimlane have been created successfully.')); - $this->response->redirect('?controller=swimlane&action=index&project_id='.$project['id']); + $this->response->redirect($this->helper->url->to('swimlane', 'index', array('project_id' => $project['id']))); } else { $this->session->flashError(t('Unable to create your swimlane.')); @@ -93,7 +93,7 @@ class Swimlane extends Base if ($this->swimlane->updateDefault($values)) { $this->session->flash(t('The default swimlane have been updated successfully.')); - $this->response->redirect('?controller=swimlane&action=index&project_id='.$project['id']); + $this->response->redirect($this->helper->url->to('swimlane', 'index', array('project_id' => $project['id']))); } else { $this->session->flashError(t('Unable to update this swimlane.')); @@ -137,7 +137,7 @@ class Swimlane extends Base if ($this->swimlane->rename($values['id'], $values['name'])) { $this->session->flash(t('Swimlane updated successfully.')); - $this->response->redirect('?controller=swimlane&action=index&project_id='.$project['id']); + $this->response->redirect($this->helper->url->to('swimlane', 'index', array('project_id' => $project['id']))); } else { $this->session->flashError(t('Unable to update this swimlane.')); @@ -181,7 +181,7 @@ class Swimlane extends Base $this->session->flashError(t('Unable to remove this swimlane.')); } - $this->response->redirect('?controller=swimlane&action=index&project_id='.$project['id']); + $this->response->redirect($this->helper->url->to('swimlane', 'index', array('project_id' => $project['id']))); } /** @@ -201,7 +201,7 @@ class Swimlane extends Base $this->session->flashError(t('Unable to update this swimlane.')); } - $this->response->redirect('?controller=swimlane&action=index&project_id='.$project['id']); + $this->response->redirect($this->helper->url->to('swimlane', 'index', array('project_id' => $project['id']))); } /** @@ -221,7 +221,7 @@ class Swimlane extends Base $this->session->flashError(t('Unable to update this swimlane.')); } - $this->response->redirect('?controller=swimlane&action=index&project_id='.$project['id']); + $this->response->redirect($this->helper->url->to('swimlane', 'index', array('project_id' => $project['id']))); } /** @@ -236,7 +236,7 @@ class Swimlane extends Base $swimlane_id = $this->request->getIntegerParam('swimlane_id'); $this->swimlane->moveUp($project['id'], $swimlane_id); - $this->response->redirect('?controller=swimlane&action=index&project_id='.$project['id']); + $this->response->redirect($this->helper->url->to('swimlane', 'index', array('project_id' => $project['id']))); } /** @@ -251,6 +251,6 @@ class Swimlane extends Base $swimlane_id = $this->request->getIntegerParam('swimlane_id'); $this->swimlane->moveDown($project['id'], $swimlane_id); - $this->response->redirect('?controller=swimlane&action=index&project_id='.$project['id']); + $this->response->redirect($this->helper->url->to('swimlane', 'index', array('project_id' => $project['id']))); } } diff --git a/app/Controller/Task.php b/app/Controller/Task.php index 0d85f411..16bb408f 100644 --- a/app/Controller/Task.php +++ b/app/Controller/Task.php @@ -163,10 +163,10 @@ class Task extends Base if (isset($values['another_task']) && $values['another_task'] == 1) { unset($values['title']); unset($values['description']); - $this->response->redirect('?controller=task&action=create&'.http_build_query($values)); + $this->response->redirect($this->helper->url->to('task', 'create', $values)); } else { - $this->response->redirect('?controller=board&action=show&project_id='.$project['id']); + $this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $project['id']))); } } else { @@ -231,10 +231,10 @@ class Task extends Base $this->session->flash(t('Task updated successfully.')); if ($this->request->getIntegerParam('ajax')) { - $this->response->redirect('?controller=board&action=show&project_id='.$task['project_id']); + $this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $task['project_id']))); } else { - $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'&project_id='.$task['project_id']); + $this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $task['project_id'], 'task_id' => $task['id']))); } } else { @@ -264,7 +264,7 @@ class Task extends Base $this->session->flashError(t('Unable to update your task.')); } - $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'&project_id='.$task['project_id']); + $this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $task['project_id'], 'task_id' => $task['id']))); } /** @@ -326,7 +326,7 @@ class Task extends Base $this->session->flashError(t('Unable to open this task.')); } - $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'&project_id='.$task['project_id']); + $this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $task['project_id'], 'task_id' => $task['id']))); } $this->response->html($this->taskLayout('task/open', array( @@ -357,7 +357,7 @@ class Task extends Base $this->session->flashError(t('Unable to remove this task.')); } - $this->response->redirect('?controller=board&action=show&project_id='.$task['project_id']); + $this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $task['project_id']))); } $this->response->html($this->taskLayout('task/remove', array( @@ -381,10 +381,10 @@ class Task extends Base if ($task_id) { $this->session->flash(t('Task created successfully.')); - $this->response->redirect('?controller=task&action=show&task_id='.$task_id.'&project_id='.$task['project_id']); + $this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $task['project_id'], 'task_id' => $task['id']))); } else { $this->session->flashError(t('Unable to create this task.')); - $this->response->redirect('?controller=task&action=duplicate&task_id='.$task['id'].'&project_id='.$task['project_id']); + $this->response->redirect($this->helper->url->to('task', 'duplicate', array('project_id' => $task['project_id'], 'task_id' => $task['id']))); } } @@ -419,10 +419,10 @@ class Task extends Base } if ($ajax) { - $this->response->redirect('?controller=board&action=show&project_id='.$task['project_id']); + $this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $task['project_id']))); } else { - $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'&project_id='.$task['project_id']); + $this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $task['project_id'], 'task_id' => $task['id']))); } } } @@ -472,10 +472,10 @@ class Task extends Base } if ($ajax) { - $this->response->redirect('?controller=board&action=show&project_id='.$task['project_id']); + $this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $task['project_id']))); } else { - $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'&project_id='.$task['project_id']); + $this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $task['project_id'], 'task_id' => $task['id']))); } } } @@ -526,7 +526,7 @@ class Task extends Base if ($this->taskDuplication->moveToProject($task['id'], $values['project_id'])) { $this->session->flash(t('Task updated successfully.')); - $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'&project_id='.$values['project_id']); + $this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $task['project_id'], 'task_id' => $task['id']))); } else { $this->session->flashError(t('Unable to update your task.')); @@ -565,7 +565,7 @@ class Task extends Base $task_id = $this->taskDuplication->duplicateToProject($task['id'], $values['project_id']); if ($task_id) { $this->session->flash(t('Task created successfully.')); - $this->response->redirect('?controller=task&action=show&task_id='.$task_id.'&project_id='.$values['project_id']); + $this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $task['project_id'], 'task_id' => $task['id']))); } else { $this->session->flashError(t('Unable to create your task.')); diff --git a/app/Controller/User.php b/app/Controller/User.php index 119041e5..edebf5ba 100644 --- a/app/Controller/User.php +++ b/app/Controller/User.php @@ -178,7 +178,7 @@ class User extends Base $this->checkCSRFParam(); $user = $this->getUser(); $this->authentication->backend('rememberMe')->remove($this->request->getIntegerParam('id')); - $this->response->redirect('?controller=user&action=sessions&user_id='.$user['id']); + $this->response->redirect($this->helper->url->to('user', 'session', array('user_id' => $user['id']))); } /** @@ -194,7 +194,7 @@ class User extends Base $values = $this->request->getValues(); $this->notification->saveSettings($user['id'], $values); $this->session->flash(t('User updated successfully.')); - $this->response->redirect('?controller=user&action=notifications&user_id='.$user['id']); + $this->response->redirect($this->helper->url->to('user', 'notifications', array('user_id' => $user['id']))); } $this->response->html($this->layout('user/notifications', array( @@ -272,7 +272,7 @@ class User extends Base $this->session->flashError(t('Unable to change the password.')); } - $this->response->redirect('?controller=user&action=show&user_id='.$user['id']); + $this->response->redirect($this->helper->url->to('user', 'show', array('user_id' => $user['id']))); } } @@ -321,7 +321,7 @@ class User extends Base $this->session->flashError(t('Unable to update your user.')); } - $this->response->redirect('?controller=user&action=show&user_id='.$user['id']); + $this->response->redirect($this->helper->url->to('user', 'show', array('user_id' => $user['id']))); } } @@ -353,7 +353,7 @@ class User extends Base $this->session->flashError(t('Unable to remove this user.')); } - $this->response->redirect('?controller=user'); + $this->response->redirect($this->helper->url->to('user', 'index')); } $this->response->html($this->layout('user/remove', array( @@ -386,10 +386,10 @@ class User extends Base $this->session->flashError(t('Unable to link your Google Account.')); } - $this->response->redirect('?controller=user&action=external&user_id='.$this->userSession->getId()); + $this->response->redirect($this->helper->url->to('user', 'external', array('user_id' => $this->userSession->getId()))); } else if ($this->authentication->backend('google')->authenticate($profile['id'])) { - $this->response->redirect('?controller=app'); + $this->response->redirect($this->helper->url->to('app', 'index')); } else { $this->response->html($this->template->layout('auth/index', array( @@ -421,7 +421,7 @@ class User extends Base $this->session->flashError(t('Unable to unlink your Google Account.')); } - $this->response->redirect('?controller=user&action=external&user_id='.$this->userSession->getId()); + $this->response->redirect($this->helper->url->to('user', 'external', array('user_id' => $this->userSession->getId()))); } /** @@ -448,10 +448,10 @@ class User extends Base $this->session->flashError(t('Unable to link your GitHub Account.')); } - $this->response->redirect('?controller=user&action=external&user_id='.$this->userSession->getId()); + $this->response->redirect($this->helper->url->to('user', 'external', array('user_id' => $this->userSession->getId()))); } else if ($this->authentication->backend('gitHub')->authenticate($profile['id'])) { - $this->response->redirect('?controller=app'); + $this->response->redirect($this->helper->url->to('app', 'index')); } else { $this->response->html($this->template->layout('auth/index', array( @@ -486,6 +486,6 @@ class User extends Base $this->session->flashError(t('Unable to unlink your GitHub Account.')); } - $this->response->redirect('?controller=user&action=external&user_id='.$this->userSession->getId()); + $this->response->redirect($this->helper->url->to('user', 'external', array('user_id' => $this->userSession->getId()))); } } diff --git a/app/Core/Router.php b/app/Core/Router.php index 36c11a0a..ae989de5 100644 --- a/app/Core/Router.php +++ b/app/Core/Router.php @@ -2,53 +2,151 @@ namespace Core; -use Pimple\Container; - /** * Router class * * @package core * @author Frederic Guillot */ -class Router +class Router extends Base { /** - * Controller name + * Store routes for path lookup * * @access private - * @var string + * @var array */ - private $controller = ''; + private $paths = array(); /** - * Action name + * Store routes for url lookup * * @access private - * @var string + * @var array */ - private $action = ''; + private $urls = array(); /** - * Container instance + * Get the path to compare patterns * - * @access private - * @var \Pimple\Container + * @access public + * @param string $uri + * @param string $query_string + * @return string */ - private $container; + public function getPath($uri, $query_string = '') + { + $path = substr($uri, strlen($this->helper->url->dir())); + + if (! empty($query_string)) { + $path = substr($path, 0, - strlen($query_string) - 1); + } + + if ($path{0} === '/') { + $path = substr($path, 1); + } + + return $path; + } + + /** + * Add route + * + * @access public + * @param string $path + * @param string $controller + * @param string $action + * @param array $params + */ + public function addRoute($path, $controller, $action, array $params = array()) + { + $pattern = explode('/', $path); + + $this->paths[] = array( + 'pattern' => $pattern, + 'count' => count($pattern), + 'controller' => $controller, + 'action' => $action, + ); + + $this->urls[$controller][$action][] = array( + 'path' => $path, + 'params' => array_flip($params), + 'count' => count($params), + ); + } + + /** + * Find a route according to the given path + * + * @access public + * @param string $path + * @return array + */ + public function findRoute($path) + { + $parts = explode('/', $path); + $count = count($parts); + + foreach ($this->paths as $route) { + + if ($count === $route['count']) { + + $params = array(); + + for ($i = 0; $i < $count; $i++) { + + if ($route['pattern'][$i]{0} === ':') { + $params[substr($route['pattern'][$i], 1)] = $parts[$i]; + } + else if ($route['pattern'][$i] !== $parts[$i]) { + break; + } + } + + if ($i === $count) { + $_GET = array_merge($_GET, $params); + return array($route['controller'], $route['action']); + } + } + } + + return array('app', 'index'); + } /** - * Constructor + * Find route url * * @access public - * @param \Pimple\Container $container Container instance - * @param string $controller Controller name - * @param string $action Action name + * @param string $controller + * @param string $action + * @param array $params + * @return string */ - public function __construct(Container $container, $controller = '', $action = '') + public function findUrl($controller, $action, array $params = array()) { - $this->container = $container; - $this->controller = empty($_GET['controller']) ? $controller : $_GET['controller']; - $this->action = empty($_GET['action']) ? $action : $_GET['action']; + if (! isset($this->urls[$controller][$action])) { + return ''; + } + + foreach ($this->urls[$controller][$action] as $pattern) { + + if (array_diff_key($params, $pattern['params']) === array()) { + $url = $pattern['path']; + $i = 0; + + foreach ($params as $variable => $value) { + $url = str_replace(':'.$variable, $value, $url); + $i++; + } + + if ($i === $pattern['count']) { + return $url; + } + } + } + + return ''; } /** @@ -65,15 +163,42 @@ class Router } /** - * Load a controller and execute the action + * Find controller/action from the route table or from get arguments * * @access public - * @param string $filename Controller filename - * @param string $class Class name - * @param string $method Method name + * @param string $uri + * @param string $query_string + * @return boolean + */ + public function dispatch($uri, $query_string = '') + { + if (! empty($_GET['controller']) && ! empty($_GET['action'])) { + $controller = $this->sanitize($_GET['controller'], 'app'); + $action = $this->sanitize($_GET['action'], 'index'); + } + else { + list($controller, $action) = $this->findRoute($this->getPath($uri, $query_string)); + } + + return $this->load( + __DIR__.'/../Controller/'.ucfirst($controller).'.php', + $controller, + '\Controller\\'.ucfirst($controller), + $action + ); + } + + /** + * Load a controller and execute the action + * + * @access private + * @param string $filename + * @param string $controller + * @param string $class + * @param string $method * @return bool */ - public function load($filename, $class, $method) + private function load($filename, $controller, $class, $method) { if (file_exists($filename)) { @@ -84,7 +209,7 @@ class Router } $instance = new $class($this->container); - $instance->beforeAction($this->controller, $this->action); + $instance->beforeAction($controller, $method); $instance->$method(); return true; @@ -92,20 +217,4 @@ class Router return false; } - - /** - * Find a route - * - * @access public - */ - public function execute() - { - $this->controller = $this->sanitize($this->controller, 'app'); - $this->action = $this->sanitize($this->action, 'index'); - $filename = __DIR__.'/../Controller/'.ucfirst($this->controller).'.php'; - - if (! $this->load($filename, '\Controller\\'.$this->controller, $this->action)) { - die('Page not found!'); - } - } } diff --git a/app/Core/Session.php b/app/Core/Session.php index c35014cd..0e5f7426 100644 --- a/app/Core/Session.php +++ b/app/Core/Session.php @@ -41,8 +41,6 @@ class Session implements ArrayAccess */ public function open($base_path = '/') { - $base_path = str_replace('\\', '/', $base_path); - // HttpOnly and secure flags for session cookie session_set_cookie_params( self::SESSION_LIFETIME, diff --git a/app/Helper/Asset.php b/app/Helper/Asset.php index 1b1e47c5..fd555e07 100644 --- a/app/Helper/Asset.php +++ b/app/Helper/Asset.php @@ -18,7 +18,7 @@ class Asset extends \Core\Base */ public function js($filename, $async = false) { - return '<script '.($async ? 'async' : '').' type="text/javascript" src="'.$filename.'?'.filemtime($filename).'"></script>'; + return '<script '.($async ? 'async' : '').' type="text/javascript" src="'.$this->helper->url->dir().$filename.'?'.filemtime($filename).'"></script>'; } /** @@ -31,7 +31,7 @@ class Asset extends \Core\Base */ public function css($filename, $is_file = true, $media = 'screen') { - return '<link rel="stylesheet" href="'.$filename.($is_file ? '?'.filemtime($filename) : '').'" media="'.$media.'">'; + return '<link rel="stylesheet" href="'.$this->helper->url->dir().$filename.($is_file ? '?'.filemtime($filename) : '').'" media="'.$media.'">'; } /** diff --git a/app/Helper/Url.php b/app/Helper/Url.php index 8de63f8d..964e0762 100644 --- a/app/Helper/Url.php +++ b/app/Helper/Url.php @@ -13,6 +13,9 @@ use Core\Security; */ class Url extends \Core\Base { + private $base = ''; + private $directory = ''; + /** * HTML Link tag * @@ -33,7 +36,7 @@ class Url extends \Core\Base } /** - * Hyperlink + * HTML Hyperlink * * @access public * @param string $controller Controller name @@ -41,22 +44,12 @@ class Url extends \Core\Base * @param array $params Url parameters * @param boolean $csrf Add a CSRF token * @param string $anchor Link Anchor + * @param boolean $absolute Absolute or relative link * @return string */ - public function href($controller, $action, array $params = array(), $csrf = false, $anchor = '') + public function href($controller, $action, array $params = array(), $csrf = false, $anchor = '', $absolute = false) { - $values = array( - 'controller' => $controller, - 'action' => $action, - ); - - if ($csrf) { - $params['csrf_token'] = Security::getCSRFToken(); - } - - $values += $params; - - return '?'.http_build_query($values, '', '&').(empty($anchor) ? '' : '#'.$anchor); + return $this->build('&', $controller, $action, $params, $csrf, $anchor, $absolute); } /** @@ -66,18 +59,13 @@ class Url extends \Core\Base * @param string $controller Controller name * @param string $action Action name * @param array $params Url parameters + * @param string $anchor Link Anchor + * @param boolean $absolute Absolute or relative link * @return string */ - public function to($controller, $action, array $params = array()) + public function to($controller, $action, array $params = array(), $anchor = '', $absolute = false) { - $values = array( - 'controller' => $controller, - 'action' => $action, - ); - - $values += $params; - - return '?'.http_build_query($values, '', '&'); + return $this->build('&', $controller, $action, $params, false, $anchor, $absolute); } /** @@ -88,7 +76,28 @@ class Url extends \Core\Base */ public function base() { - return $this->config->get('application_url') ?: $this->server(); + if (empty($this->base)) { + $this->base = $this->config->get('application_url') ?: $this->server(); + } + + return $this->base; + } + + /** + * Get application base directory + * + * @access public + * @return string + */ + public function dir() + { + if (empty($this->directory) && isset($_SERVER['REQUEST_METHOD'])) { + $this->directory = str_replace('\\', '/', dirname($_SERVER['PHP_SELF'])); + $this->directory = $this->directory !== '/' ? $this->directory.'/' : '/'; + $this->directory = str_replace('//', '/', $this->directory); + } + + return $this->directory; } /** @@ -103,13 +112,46 @@ class Url extends \Core\Base return 'http://localhost/'; } - $self = str_replace('\\', '/', dirname($_SERVER['PHP_SELF'])); - $url = Request::isHTTPS() ? 'https://' : 'http://'; $url .= $_SERVER['SERVER_NAME']; $url .= $_SERVER['SERVER_PORT'] == 80 || $_SERVER['SERVER_PORT'] == 443 ? '' : ':'.$_SERVER['SERVER_PORT']; - $url .= $self !== '/' ? $self.'/' : '/'; + $url .= $this->dir() ?: '/'; return $url; } + + /** + * Build relative url + * + * @access private + * @param string $separator Querystring argument separator + * @param string $controller Controller name + * @param string $action Action name + * @param array $params Url parameters + * @param boolean $csrf Add a CSRF token + * @param string $anchor Link Anchor + * @param boolean $absolute Absolute or relative link + * @return string + */ + private function build($separator, $controller, $action, array $params = array(), $csrf = false, $anchor = '', $absolute = false) + { + $path = $this->router->findUrl($controller, $action, $params); + $qs = array(); + + if (empty($path)) { + $qs['controller'] = $controller; + $qs['action'] = $action; + $qs += $params; + } + + if ($csrf) { + $qs['csrf_token'] = Security::getCSRFToken(); + } + + if (! empty($qs)) { + $path .= '?'.http_build_query($qs, '', $separator); + } + + return ($absolute ? $this->base() : $this->dir()).$path.(empty($anchor) ? '' : '#'.$anchor); + } } diff --git a/app/ServiceProvider/ClassProvider.php b/app/ServiceProvider/ClassProvider.php index 8c55457d..ef772f58 100644 --- a/app/ServiceProvider/ClassProvider.php +++ b/app/ServiceProvider/ClassProvider.php @@ -72,6 +72,7 @@ class ClassProvider implements ServiceProviderInterface 'Lexer', 'MemoryCache', 'Request', + 'Router', 'Session', 'Template', ), diff --git a/app/Template/app/overview.php b/app/Template/app/overview.php index fa7866af..1b160496 100644 --- a/app/Template/app/overview.php +++ b/app/Template/app/overview.php @@ -1,7 +1,7 @@ <div class="search"> - <form method="get" action="?" class="search"> + <form method="get" action="<?= $this->url->dir() ?>" class="search"> <?= $this->form->hidden('controller', array('controller' => 'search')) ?> - <?= $this->form->hidden('action', array('controller' => 'index')) ?> + <?= $this->form->hidden('action', array('action' => 'index')) ?> <?= $this->form->text('search', array(), array(), array('placeholder="'.t('Search').'"'), 'form-input-large') ?> </form> diff --git a/app/Template/config/integrations.php b/app/Template/config/integrations.php index 9c80b499..44cfc7d1 100644 --- a/app/Template/config/integrations.php +++ b/app/Template/config/integrations.php @@ -6,30 +6,30 @@ <?= $this->form->csrf() ?> - <h3><img src="assets/img/mailgun-icon.png"/> <?= t('Mailgun (incoming emails)') ?></h3> + <h3><img src="<?= $this->url->dir() ?>assets/img/mailgun-icon.png"/> <?= t('Mailgun (incoming emails)') ?></h3> <div class="listing"> - <input type="text" class="auto-select" readonly="readonly" value="<?= $this->url->base().$this->url->href('webhook', 'mailgun', array('token' => $values['webhook_token'])) ?>"/><br/> + <input type="text" class="auto-select" readonly="readonly" value="<?= $this->url->href('webhook', 'mailgun', array('token' => $values['webhook_token']), false, '', true) ?>"/><br/> <p class="form-help"><a href="http://kanboard.net/documentation/mailgun" target="_blank"><?= t('Help on Mailgun integration') ?></a></p> </div> - <h3><img src="assets/img/sendgrid-icon.png"/> <?= t('Sendgrid (incoming emails)') ?></h3> + <h3><img src="<?= $this->url->dir() ?>assets/img/sendgrid-icon.png"/> <?= t('Sendgrid (incoming emails)') ?></h3> <div class="listing"> - <input type="text" class="auto-select" readonly="readonly" value="<?= $this->url->base().$this->url->href('webhook', 'sendgrid', array('token' => $values['webhook_token'])) ?>"/><br/> + <input type="text" class="auto-select" readonly="readonly" value="<?= $this->url->href('webhook', 'sendgrid', array('token' => $values['webhook_token']), false, '', true) ?>"/><br/> <p class="form-help"><a href="http://kanboard.net/documentation/sendgrid" target="_blank"><?= t('Help on Sendgrid integration') ?></a></p> </div> - <h3><img src="assets/img/postmark-icon.png"/> <?= t('Postmark (incoming emails)') ?></h3> + <h3><img src="<?= $this->url->dir() ?>assets/img/postmark-icon.png"/> <?= t('Postmark (incoming emails)') ?></h3> <div class="listing"> - <input type="text" class="auto-select" readonly="readonly" value="<?= $this->url->base().$this->url->href('webhook', 'postmark', array('token' => $values['webhook_token'])) ?>"/><br/> + <input type="text" class="auto-select" readonly="readonly" value="<?= $this->url->href('webhook', 'postmark', array('token' => $values['webhook_token']), false, '', true) ?>"/><br/> <p class="form-help"><a href="http://kanboard.net/documentation/postmark" target="_blank"><?= t('Help on Postmark integration') ?></a></p> </div> - <h3><img src="assets/img/gravatar-icon.png"/> <?= t('Gravatar') ?></h3> + <h3><img src="<?= $this->url->dir() ?>assets/img/gravatar-icon.png"/> <?= t('Gravatar') ?></h3> <div class="listing"> <?= $this->form->checkbox('integration_gravatar', t('Enable Gravatar images'), 1, $values['integration_gravatar'] == 1) ?> </div> - <h3><img src="assets/img/jabber-icon.png"/> <?= t('Jabber (XMPP)') ?></h3> + <h3><img src="<?= $this->url->dir() ?>assets/img/jabber-icon.png"/> <?= t('Jabber (XMPP)') ?></h3> <div class="listing"> <?= $this->form->checkbox('integration_jabber', t('Send notifications to Jabber'), 1, $values['integration_jabber'] == 1) ?> @@ -55,7 +55,7 @@ <p class="form-help"><a href="http://kanboard.net/documentation/jabber" target="_blank"><?= t('Help on Jabber integration') ?></a></p> </div> - <h3><img src="assets/img/hipchat-icon.png"/> <?= t('Hipchat') ?></h3> + <h3><img src="<?= $this->url->dir() ?>assets/img/hipchat-icon.png"/> <?= t('Hipchat') ?></h3> <div class="listing"> <?= $this->form->checkbox('integration_hipchat', t('Send notifications to Hipchat'), 1, $values['integration_hipchat'] == 1) ?> diff --git a/app/Template/config/webhook.php b/app/Template/config/webhook.php index 73ca3598..f1a98f8b 100644 --- a/app/Template/config/webhook.php +++ b/app/Template/config/webhook.php @@ -26,7 +26,7 @@ </li> <li> <?= t('URL for task creation:') ?> - <input type="text" class="auto-select" readonly="readonly" value="<?= $this->url->base().$this->url->href('webhook', 'task', array('token' => $values['webhook_token'])) ?>"> + <input type="text" class="auto-select" readonly="readonly" value="<?= $this->url->href('webhook', 'task', array('token' => $values['webhook_token']), false, '', true) ?>"> </li> <li> <?= $this->url->link(t('Reset token'), 'config', 'token', array('type' => 'webhook'), true) ?> diff --git a/app/Template/feed/project.php b/app/Template/feed/project.php index 60b7ee96..76cf6cf0 100644 --- a/app/Template/feed/project.php +++ b/app/Template/feed/project.php @@ -2,15 +2,15 @@ <feed xml:lang="en-US" xmlns="http://www.w3.org/2005/Atom"> <title><?= t('%s\'s activity', $project['name']) ?></title> <link rel="alternate" type="text/html" href="<?= $this->url->base() ?>"/> - <link rel="self" type="application/atom+xml" href="<?= $this->url->base().$this->url->href('feed', 'project', array('token' => $project['token'])) ?>"/> + <link rel="self" type="application/atom+xml" href="<?= $this->url->href('feed', 'project', array('token' => $project['token']), false, '', true) ?>"/> <updated><?= date(DATE_ATOM) ?></updated> - <id><?= $this->url->base().$this->url->href('feed', 'project', array('token' => $project['token'])) ?></id> + <id><?= $this->url->href('feed', 'project', array('token' => $project['token']), false, '', true) ?></id> <icon><?= $this->url->base() ?>assets/img/favicon.png</icon> <?php foreach ($events as $e): ?> <entry> <title type="text"><?= $e['event_title'] ?></title> - <link rel="alternate" href="<?= $this->url->base().$this->url->href('task', 'show', array('task_id' => $e['task_id'])) ?>"/> + <link rel="alternate" href="<?= $this->url->href('task', 'show', array('task_id' => $e['task_id']), false, '', true) ?>"/> <id><?= $e['id'].'-'.$e['event_name'].'-'.$e['task_id'].'-'.$e['date_creation'] ?></id> <published><?= date(DATE_ATOM, $e['date_creation']) ?></published> <updated><?= date(DATE_ATOM, $e['date_creation']) ?></updated> diff --git a/app/Template/feed/user.php b/app/Template/feed/user.php index b3279a0c..3e9606c6 100644 --- a/app/Template/feed/user.php +++ b/app/Template/feed/user.php @@ -2,15 +2,15 @@ <feed xml:lang="en-US" xmlns="http://www.w3.org/2005/Atom"> <title><?= t('Project activities for %s', $user['name'] ?: $user['username']) ?></title> <link rel="alternate" type="text/html" href="<?= $this->url->base() ?>"/> - <link rel="self" type="application/atom+xml" href="<?= $this->url->base().$this->url->href('feed', 'user', array('token' => $user['token'])) ?>"/> + <link rel="self" type="application/atom+xml" href="<?= $this->url->href('feed', 'user', array('token' => $user['token']), false, '', true) ?>"/> <updated><?= date(DATE_ATOM) ?></updated> - <id><?= $this->url->base().$this->url->href('feed', 'user', array('token' => $user['token'])) ?></id> + <id><?= $this->url->href('feed', 'user', array('token' => $user['token']), false, '', true) ?></id> <icon><?= $this->url->base() ?>assets/img/favicon.png</icon> <?php foreach ($events as $e): ?> <entry> <title type="text"><?= $e['event_title'] ?></title> - <link rel="alternate" href="<?= $this->url->base().$this->url->href('task', 'show', array('task_id' => $e['task_id'])) ?>"/> + <link rel="alternate" href="<?= $this->url->href('task', 'show', array('task_id' => $e['task_id']), false, '', true) ?>"/> <id><?= $e['id'].'-'.$e['event_name'].'-'.$e['task_id'].'-'.$e['date_creation'] ?></id> <published><?= date(DATE_ATOM, $e['date_creation']) ?></published> <updated><?= date(DATE_ATOM, $e['date_creation']) ?></updated> diff --git a/app/Template/layout.php b/app/Template/layout.php index a9f1cbc3..d02ba08d 100644 --- a/app/Template/layout.php +++ b/app/Template/layout.php @@ -20,11 +20,11 @@ <?= $this->asset->css('assets/css/print.css', true, 'print') ?> <?= $this->asset->customCss() ?> - <link rel="icon" type="image/png" href="assets/img/favicon.png"> - <link rel="apple-touch-icon" href="assets/img/touch-icon-iphone.png"> - <link rel="apple-touch-icon" sizes="72x72" href="assets/img/touch-icon-ipad.png"> - <link rel="apple-touch-icon" sizes="114x114" href="assets/img/touch-icon-iphone-retina.png"> - <link rel="apple-touch-icon" sizes="144x144" href="assets/img/touch-icon-ipad-retina.png"> + <link rel="icon" type="image/png" href="<?= $this->url->dir() ?>assets/img/favicon.png"> + <link rel="apple-touch-icon" href="<?= $this->url->dir() ?>assets/img/touch-icon-iphone.png"> + <link rel="apple-touch-icon" sizes="72x72" href="<?= $this->url->dir() ?>assets/img/touch-icon-ipad.png"> + <link rel="apple-touch-icon" sizes="114x114" href="<?= $this->url->dir() ?>assets/img/touch-icon-iphone-retina.png"> + <link rel="apple-touch-icon" sizes="144x144" href="<?= $this->url->dir() ?>assets/img/touch-icon-ipad-retina.png"> <title><?= isset($title) ? $this->e($title) : 'Kanboard' ?></title> </head> diff --git a/app/Template/project/filters.php b/app/Template/project/filters.php index 3beb2f44..cb6f11d3 100644 --- a/app/Template/project/filters.php +++ b/app/Template/project/filters.php @@ -40,10 +40,10 @@ <?= $this->url->link(t('List'), 'listing', 'show', array('project_id' => $project['id'], 'search' => $filters['search']), false, 'view-listing', t('Keyboard shortcut: "%s"', 'v l')) ?> </li> </ul> - <form method="get" action="?" class="search"> - <?= $this->form->hidden('project_id', $filters) ?> + <form method="get" action="<?= $this->url->dir() ?>" class="search"> <?= $this->form->hidden('controller', $filters) ?> <?= $this->form->hidden('action', $filters) ?> + <?= $this->form->hidden('project_id', $filters) ?> <?= $this->form->text('search', $filters, array(), array('placeholder="'.t('Filter').'"'), 'form-input-large') ?> </form> <?= $this->render('app/filters_helper', array('reset' => 'status:open')) ?> diff --git a/app/Template/project/integrations.php b/app/Template/project/integrations.php index 445e7bfb..12a7ee4e 100644 --- a/app/Template/project/integrations.php +++ b/app/Template/project/integrations.php @@ -8,26 +8,26 @@ <h3><i class="fa fa-github fa-fw"></i> <?= t('Github webhooks') ?></h3> <div class="listing"> - <input type="text" class="auto-select" readonly="readonly" value="<?= $this->url->base().$this->url->href('webhook', 'github', array('token' => $webhook_token, 'project_id' => $project['id'])) ?>"/><br/> + <input type="text" class="auto-select" readonly="readonly" value="<?= $this->url->href('webhook', 'github', array('token' => $webhook_token, 'project_id' => $project['id']), false, '', true) ?>"/><br/> <p class="form-help"><a href="http://kanboard.net/documentation/github-webhooks" target="_blank"><?= t('Help on Github webhooks') ?></a></p> </div> - <h3><img src="assets/img/gitlab-icon.png"/> <?= t('Gitlab webhooks') ?></h3> + <h3><img src="<?= $this->url->dir() ?>assets/img/gitlab-icon.png"/> <?= t('Gitlab webhooks') ?></h3> <div class="listing"> - <input type="text" class="auto-select" readonly="readonly" value="<?= $this->url->base().$this->url->href('webhook', 'gitlab', array('token' => $webhook_token, 'project_id' => $project['id'])) ?>"/><br/> + <input type="text" class="auto-select" readonly="readonly" value="<?= $this->url->href('webhook', 'gitlab', array('token' => $webhook_token, 'project_id' => $project['id']), false, '', true) ?>"/><br/> <p class="form-help"><a href="http://kanboard.net/documentation/gitlab-webhooks" target="_blank"><?= t('Help on Gitlab webhooks') ?></a></p> </div> <h3><i class="fa fa-bitbucket fa-fw"></i> <?= t('Bitbucket webhooks') ?></h3> <div class="listing"> - <input type="text" class="auto-select" readonly="readonly" value="<?= $this->url->base().$this->url->href('webhook', 'bitbucket', array('token' => $webhook_token, 'project_id' => $project['id'])) ?>"/><br/> + <input type="text" class="auto-select" readonly="readonly" value="<?= $this->url->href('webhook', 'bitbucket', array('token' => $webhook_token, 'project_id' => $project['id']), false, '', true) ?>"/><br/> <p class="form-help"><a href="http://kanboard.net/documentation/bitbucket-webhooks" target="_blank"><?= t('Help on Bitbucket webhooks') ?></a></p> </div> - <h3><img src="assets/img/jabber-icon.png"/> <?= t('Jabber (XMPP)') ?></h3> + <h3><img src="<?= $this->url->dir() ?>assets/img/jabber-icon.png"/> <?= t('Jabber (XMPP)') ?></h3> <div class="listing"> <?= $this->form->checkbox('jabber', t('Send notifications to Jabber'), 1, isset($values['jabber']) && $values['jabber'] == 1) ?> @@ -58,7 +58,7 @@ </div> - <h3><img src="assets/img/hipchat-icon.png"/> <?= t('Hipchat') ?></h3> + <h3><img src="<?= $this->url->dir() ?>assets/img/hipchat-icon.png"/> <?= t('Hipchat') ?></h3> <div class="listing"> <?= $this->form->checkbox('hipchat', t('Send notifications to Hipchat'), 1, isset($values['hipchat']) && $values['hipchat'] == 1) ?> diff --git a/app/Template/search/index.php b/app/Template/search/index.php index 3d0d6ff9..8940a24e 100644 --- a/app/Template/search/index.php +++ b/app/Template/search/index.php @@ -9,7 +9,7 @@ </div> <div class="search"> - <form method="get" action="?" class="search"> + <form method="get" action="<?= $this->url->dir() ?>" class="search"> <?= $this->form->hidden('controller', $values) ?> <?= $this->form->hidden('action', $values) ?> <?= $this->form->text('search', $values, array(), array(empty($values['search']) ? 'autofocus' : '', 'placeholder="'.t('Search').'"'), 'form-input-large') ?> diff --git a/app/common.php b/app/common.php index b5871673..3acf9573 100644 --- a/app/common.php +++ b/app/common.php @@ -12,7 +12,7 @@ if (getenv('DATABASE_URL')) { define('DB_PASSWORD', $dbopts["pass"]); define('DB_HOSTNAME', $dbopts["host"]); define('DB_PORT', isset($dbopts["port"]) ? $dbopts["port"] : null); - define('DB_NAME', ltrim($dbopts["path"],'/')); + define('DB_NAME', ltrim($dbopts["path"], '/')); } // Include custom config file @@ -28,3 +28,93 @@ $container->register(new ServiceProvider\LoggingProvider); $container->register(new ServiceProvider\DatabaseProvider); $container->register(new ServiceProvider\ClassProvider); $container->register(new ServiceProvider\EventDispatcherProvider); + +if (ENABLE_URL_REWRITE) { + + // Dashboard + $container['router']->addRoute('dashboard', 'app', 'index'); + $container['router']->addRoute('dashboard/:user_id', 'app', 'index', array('user_id')); + $container['router']->addRoute('dashboard/:user_id/projects', 'app', 'projects', array('user_id')); + $container['router']->addRoute('dashboard/:user_id/tasks', 'app', 'tasks', array('user_id')); + $container['router']->addRoute('dashboard/:user_id/subtasks', 'app', 'subtasks', array('user_id')); + $container['router']->addRoute('dashboard/:user_id/calendar', 'app', 'calendar', array('user_id')); + $container['router']->addRoute('dashboard/:user_id/activity', 'app', 'activity', array('user_id')); + + // Search routes + $container['router']->addRoute('search', 'search', 'index'); + $container['router']->addRoute('search/:search', 'search', 'index', array('search')); + + // Project routes + $container['router']->addRoute('projects', 'project', 'index'); + $container['router']->addRoute('project/create', 'project', 'create'); + $container['router']->addRoute('project/create/:private', 'project', 'create', array('private')); + $container['router']->addRoute('project/:project_id', 'project', 'show', array('project_id')); + $container['router']->addRoute('p/:project_id', 'project', 'show', array('project_id')); + $container['router']->addRoute('project/:project_id/share', 'project', 'share', array('project_id')); + $container['router']->addRoute('project/:project_id/edit', 'project', 'edit', array('project_id')); + $container['router']->addRoute('project/:project_id/integration', 'project', 'integration', array('project_id')); + $container['router']->addRoute('project/:project_id/users', 'project', 'users', array('project_id')); + $container['router']->addRoute('project/:project_id/duplicate', 'project', 'duplicate', array('project_id')); + $container['router']->addRoute('project/:project_id/remove', 'project', 'remove', array('project_id')); + $container['router']->addRoute('project/:project_id/disable', 'project', 'disable', array('project_id')); + $container['router']->addRoute('project/:project_id/enable', 'project', 'enable', array('project_id')); + + // Action routes + $container['router']->addRoute('project/:project_id/actions', 'action', 'index', array('project_id')); + $container['router']->addRoute('project/:project_id/action/:action_id/confirm', 'action', 'confirm', array('project_id', 'action_id')); + + // Column routes + $container['router']->addRoute('project/:project_id/columns', 'column', 'index', array('project_id')); + $container['router']->addRoute('project/:project_id/column/:column_id/edit', 'column', 'edit', array('project_id', 'column_id')); + $container['router']->addRoute('project/:project_id/column/:column_id/confirm', 'column', 'confirm', array('project_id', 'column_id')); + $container['router']->addRoute('project/:project_id/column/:column_id/move/:direction', 'column', 'move', array('project_id', 'column_id', 'direction')); + + // Swimlane routes + $container['router']->addRoute('project/:project_id/swimlanes', 'swimlane', 'index', array('project_id')); + $container['router']->addRoute('project/:project_id/swimlane/:swimlane_id/edit', 'swimlane', 'edit', array('project_id', 'swimlane_id')); + $container['router']->addRoute('project/:project_id/swimlane/:swimlane_id/confirm', 'swimlane', 'confirm', array('project_id', 'swimlane_id')); + $container['router']->addRoute('project/:project_id/swimlane/:swimlane_id/disable', 'swimlane', 'disable', array('project_id', 'swimlane_id')); + $container['router']->addRoute('project/:project_id/swimlane/:swimlane_id/enable', 'swimlane', 'enable', array('project_id', 'swimlane_id')); + $container['router']->addRoute('project/:project_id/swimlane/:swimlane_id/up', 'swimlane', 'moveup', array('project_id', 'swimlane_id')); + $container['router']->addRoute('project/:project_id/swimlane/:swimlane_id/down', 'swimlane', 'movedown', array('project_id', 'swimlane_id')); + + // Category routes + $container['router']->addRoute('project/:project_id/categories', 'category', 'index', array('project_id')); + $container['router']->addRoute('project/:project_id/category/:category_id/edit', 'category', 'edit', array('project_id', 'category_id')); + $container['router']->addRoute('project/:project_id/category/:category_id/confirm', 'category', 'confirm', array('project_id', 'category_id')); + + // Task routes + $container['router']->addRoute('project/:project_id/task/:task_id', 'task', 'show', array('project_id', 'task_id')); + $container['router']->addRoute('t/:task_id', 'task', 'show', array('task_id')); + $container['router']->addRoute('project/:project_id/swimlane/:swimlane_id/column/:column_id', 'task', 'create', array('project_id', 'swimlane_id', 'column_id')); + $container['router']->addRoute('public/task/:task_id/:token', 'task', 'readonly', array('task_id', 'token')); + + // Board routes + $container['router']->addRoute('board/:project_id', 'board', 'show', array('project_id')); + $container['router']->addRoute('b/:project_id', 'board', 'show', array('project_id')); + $container['router']->addRoute('board/:project_id/:search', 'board', 'show', array('project_id', 'search')); + $container['router']->addRoute('board/collapse/:project_id', 'board', 'collapse', array('project_id')); + $container['router']->addRoute('board/expand/:project_id', 'board', 'expand', array('project_id')); + $container['router']->addRoute('public/board/:token', 'board', 'readonly', array('token')); + + // Calendar routes + $container['router']->addRoute('calendar/:project_id', 'calendar', 'show', array('project_id')); + $container['router']->addRoute('c/:project_id', 'calendar', 'show', array('project_id')); + $container['router']->addRoute('calendar/:project_id/:search', 'calendar', 'show', array('project_id', 'search')); + + // Listing routes + $container['router']->addRoute('list/:project_id', 'listing', 'show', array('project_id')); + $container['router']->addRoute('l/:project_id', 'listing', 'show', array('project_id')); + $container['router']->addRoute('list/:project_id/:search', 'listing', 'show', array('project_id', 'search')); + + // Subtask routes + $container['router']->addRoute('project/:project_id/task/:task_id/subtask/:subtask_id', 'subtask', 'remove', array('project_id', 'task_id', 'subtask_id')); + + // Feed routes + $container['router']->addRoute('feed/project/:token', 'feed', 'project', array('token')); + $container['router']->addRoute('feed/user/:token', 'feed', 'user', array('token')); + + // Ical routes + $container['router']->addRoute('ical/project/:token', 'ical', 'project', array('token')); + $container['router']->addRoute('ical/user/:token', 'ical', 'user', array('token')); +} diff --git a/app/constants.php b/app/constants.php index 9b66b746..cd1c0d1c 100644 --- a/app/constants.php +++ b/app/constants.php @@ -7,9 +7,6 @@ defined('DEBUG_FILE') or define('DEBUG_FILE', __DIR__.'/../data/debug.log'); // Application version defined('APP_VERSION') or define('APP_VERSION', 'master'); -// Base directory -define('BASE_URL_DIRECTORY', dirname($_SERVER['PHP_SELF'])); - // Database driver: sqlite, mysql or postgres defined('DB_DRIVER') or define('DB_DRIVER', 'sqlite'); @@ -84,3 +81,6 @@ defined('MARKDOWN_ESCAPE_HTML') or define('MARKDOWN_ESCAPE_HTML', true); // API alternative authentication header, the default is HTTP Basic Authentication defined in RFC2617 defined('API_AUTHENTICATION_HEADER') or define('API_AUTHENTICATION_HEADER', ''); + +// Enable/disable url rewrite +defined('ENABLE_URL_REWRITE') or define('ENABLE_URL_REWRITE', isset($_SERVER['HTTP_MOD_REWRITE'])); diff --git a/config.default.php b/config.default.php index 7c6955e8..514a8121 100644 --- a/config.default.php +++ b/config.default.php @@ -150,3 +150,7 @@ define('MARKDOWN_ESCAPE_HTML', true); // API alternative authentication header, the default is HTTP Basic Authentication defined in RFC2617 define('API_AUTHENTICATION_HEADER', ''); + +// Enable/disable url rewrite +define('ENABLE_URL_REWRITE', false); + @@ -2,7 +2,6 @@ require __DIR__.'/app/common.php'; -use Core\Router; - -$router = new Router($container); -$router->execute(); +if (! $container['router']->dispatch($_SERVER['REQUEST_URI'], $_SERVER['QUERY_STRING'])) { + die('Page not found!'); +} diff --git a/tests/units/RouterTest.php b/tests/units/RouterTest.php new file mode 100644 index 00000000..e4582121 --- /dev/null +++ b/tests/units/RouterTest.php @@ -0,0 +1,79 @@ +<?php + +require_once __DIR__.'/Base.php'; + +use Core\Router; + +class RouterTest extends Base +{ + public function testSanitize() + { + $r = new Router($this->container); + + $this->assertEquals('plop', $r->sanitize('PloP', 'default')); + $this->assertEquals('default', $r->sanitize('', 'default')); + $this->assertEquals('default', $r->sanitize('123-AB', 'default')); + $this->assertEquals('default', $r->sanitize('R&D', 'default')); + $this->assertEquals('default', $r->sanitize('Test123', 'default')); + } + + public function testPath() + { + $r = new Router($this->container); + + $this->assertEquals('a/b/c', $r->getPath('/a/b/c')); + $this->assertEquals('a/b/something', $r->getPath('/a/b/something?test=a', 'test=a')); + + $_SERVER['REQUEST_METHOD'] = 'GET'; + $_SERVER['PHP_SELF'] = '/a/index.php'; + + $this->assertEquals('b/c', $r->getPath('/a/b/c')); + $this->assertEquals('b/c', $r->getPath('/a/b/c?e=f', 'e=f')); + } + + public function testFindRouteWithEmptyTable() + { + $r = new Router($this->container); + $this->assertEquals(array('app', 'index'), $r->findRoute('')); + $this->assertEquals(array('app', 'index'), $r->findRoute('/')); + } + + public function testFindRouteWithoutPlaceholders() + { + $r = new Router($this->container); + $r->addRoute('a/b', 'controller', 'action'); + $this->assertEquals(array('app', 'index'), $r->findRoute('a/b/c')); + $this->assertEquals(array('controller', 'action'), $r->findRoute('a/b')); + } + + public function testFindRouteWithPlaceholders() + { + $r = new Router($this->container); + $r->addRoute('a/:myvar1/b/:myvar2', 'controller', 'action'); + $this->assertEquals(array('app', 'index'), $r->findRoute('a/123/b')); + $this->assertEquals(array('controller', 'action'), $r->findRoute('a/456/b/789')); + $this->assertEquals(array('myvar1' => 456, 'myvar2' => 789), $_GET); + } + + public function testFindMultipleRoutes() + { + $r = new Router($this->container); + $r->addRoute('a/b', 'controller1', 'action1'); + $r->addRoute('a/b', 'duplicate', 'duplicate'); + $r->addRoute('a', 'controller2', 'action2'); + $this->assertEquals(array('controller1', 'action1'), $r->findRoute('a/b')); + $this->assertEquals(array('controller2', 'action2'), $r->findRoute('a')); + } + + public function testFindUrl() + { + $r = new Router($this->container); + $r->addRoute('a/b', 'controller1', 'action1'); + $r->addRoute('a/:myvar1/b/:myvar2', 'controller2', 'action2', array('myvar1', 'myvar2')); + + $this->assertEquals('a/1/b/2', $r->findUrl('controller2', 'action2', array('myvar1' => 1, 'myvar2' => 2))); + $this->assertEquals('', $r->findUrl('controller2', 'action2', array('myvar1' => 1))); + $this->assertEquals('a/b', $r->findUrl('controller1', 'action1')); + $this->assertEquals('', $r->findUrl('controller1', 'action2')); + } +} diff --git a/tests/units/UrlHelperTest.php b/tests/units/UrlHelperTest.php index 3ef3402a..ebfe9c99 100644 --- a/tests/units/UrlHelperTest.php +++ b/tests/units/UrlHelperTest.php @@ -34,6 +34,22 @@ class UrlHelperTest extends Base ); } + public function testDir() + { + $h = new Url($this->container); + $this->assertEquals('', $h->dir()); + + $_SERVER['REQUEST_METHOD'] = 'GET'; + $_SERVER['PHP_SELF'] = '/plop/index.php'; + $h = new Url($this->container); + $this->assertEquals('/plop/', $h->dir()); + + $_SERVER['REQUEST_METHOD'] = 'GET'; + $_SERVER['PHP_SELF'] = ''; + $h = new Url($this->container); + $this->assertEquals('/', $h->dir()); + } + public function testServer() { $h = new Url($this->container); @@ -57,11 +73,13 @@ class UrlHelperTest extends Base $_SERVER['SERVER_NAME'] = 'kb'; $_SERVER['SERVER_PORT'] = 1234; + $h = new Url($this->container); $this->assertEquals('http://kb:1234/', $h->base()); $c = new Config($this->container); $c->save(array('application_url' => 'https://mykanboard/')); + $h = new Url($this->container); $this->assertEquals('https://mykanboard/', $c->get('application_url')); $this->assertEquals('https://mykanboard/', $h->base()); } |