diff --git a/conf/config.php b/conf/config.php index 7889ef3..21c5bf7 100644 --- a/conf/config.php +++ b/conf/config.php @@ -214,7 +214,7 @@ define('BRUTEFORCE_LOCKDOWN_DURATION', 15); // Session duration in second (0 = until the browser is closed) // See http://php.net/manual/en/session.configuration.php#ini.session.cookie-lifetime -define('SESSION_DURATION', 60); +define('SESSION_DURATION', 0); // HTTP client proxy define('HTTP_PROXY_HOSTNAME', ''); diff --git a/scripts/install b/scripts/install index c7748ba..fe67ab1 100644 --- a/scripts/install +++ b/scripts/install @@ -36,9 +36,9 @@ sudo sed -i "s/yuno_admin/$admin/g" $final_path/config.php sudo sed -i "s/yuno_email/$email/g" $final_path/config.php sudo sed -i "s/yuno_domain/$domain/g" $final_path/config.php -# Set permissions to data directory and config file +# Set permissions to kanboard and data directory +sudo chown -R root:root $final_path sudo chown -R www-data:www-data $final_path/data -sudo chown www-data:www-data $final_path/config.php # Use dedicated php pool sed -i "s@NAMETOCHANGE@$app@g" ../conf/php-fpm.conf diff --git a/scripts/restore b/scripts/restore index 42db852..fe2fe7f 100644 --- a/scripts/restore +++ b/scripts/restore @@ -9,6 +9,10 @@ backup_dir=$1/apps/$app # Restore sources & data sudo cp -a $backup_dir/sources/. /var/www/$app +# Restore permissions +sudo chown -R root:root $final_path +sudo chown -R www-data:www-data $final_path/data + # Restore mysql dump db_pwd=$(sudo yunohost app setting $app mysqlpwd) sudo mysql -u $app -p$db_pwd $app < $backup_dir/$app.dmp diff --git a/scripts/upgrade b/scripts/upgrade index a916d82..01135cf 100644 --- a/scripts/upgrade +++ b/scripts/upgrade @@ -26,9 +26,9 @@ sudo sed -i "s/yuno_admin/$admin/g" $final_path/config.php sudo sed -i "s/yuno_email/$email/g" $final_path/config.php sudo sed -i "s/yuno_domain/$domain/g" $final_path/config.php -# Set permissions to data directory and config file +# Set permissions to kanboard and data directory +sudo chown -R root:root $final_path sudo chown -R www-data:www-data $final_path/data -sudo chown www-data:www-data $final_path/config.php # Use dedicated php pool sed -i "s@NAMETOCHANGE@$app@g" ../conf/php-fpm.conf diff --git a/sources/ChangeLog b/sources/ChangeLog index 6b9f551..87bb379 100644 --- a/sources/ChangeLog +++ b/sources/ChangeLog @@ -1,3 +1,30 @@ +Version 1.0.20 +-------------- + +Breaking changes: + +- Add namespace Kanboard (update your plugins) +- Move Mailgun, Sendgrid, Postmark, Slack, Hipchat and Jabber to plugins +- ReverseProxy authentication check for each request that the username match the user session + +New features: + +* Add CSV import for users and tasks +* Add Task, User and Project metadata for plugin creators + +Improvements: + +* Allow to change comments sorting +* Add the possibility to append or not custom filters +* Make mail transports pluggable +* Do not show scroll-bars when a column is collapsed on Windows systems +* Regenerate thumbnails if missing + +Bug fixes: + +* People should not see any tasks during a search when they are not associated to a project +* Avoid to disable the default swimlane during renaming when there is no other activated swimlane + Version 1.0.19 -------------- diff --git a/sources/app/Action/Base.php b/sources/app/Action/Base.php index c8ff02a..4d2d6da 100644 --- a/sources/app/Action/Base.php +++ b/sources/app/Action/Base.php @@ -1,8 +1,8 @@ user->getById($this->getParam('user_id')); if (! empty($user['email'])) { - $task = $this->taskFinder->getDetails($data['task_id']); $this->emailClient->send( diff --git a/sources/app/Action/TaskLogMoveAnotherColumn.php b/sources/app/Action/TaskLogMoveAnotherColumn.php index 621e8e6..a699c4a 100644 --- a/sources/app/Action/TaskLogMoveAnotherColumn.php +++ b/sources/app/Action/TaskLogMoveAnotherColumn.php @@ -1,9 +1,8 @@ userSession->isLogged()) { return false; } - + $column = $this->board->getColumn($data['column_id']); return (bool) $this->comment->create(array( diff --git a/sources/app/Action/TaskMoveAnotherProject.php b/sources/app/Action/TaskMoveAnotherProject.php index ee21299..476e203 100644 --- a/sources/app/Action/TaskMoveAnotherProject.php +++ b/sources/app/Action/TaskMoveAnotherProject.php @@ -1,8 +1,8 @@ action->getAllByProject($project_id); foreach ($actions as $index => $action) { - $params = array(); - foreach($action['params'] as $param) { + foreach ($action['params'] as $param) { $params[$param['name']] = $param['value']; } @@ -57,7 +56,7 @@ class Action extends \Core\Base 'params' => $params, ); - list($valid,) = $this->action->validateCreation($values); + list($valid, ) = $this->action->validateCreation($values); if (! $valid) { return false; @@ -80,14 +79,14 @@ class Action extends \Core\Base $required_params = $action->getActionRequiredParameters(); // Check missing parameters - foreach($required_params as $param => $value) { + foreach ($required_params as $param => $value) { if (! isset($params[$param])) { return false; } } // Check extra parameters - foreach($params as $param => $value) { + foreach ($params as $param => $value) { if (! isset($required_params[$param])) { return false; } diff --git a/sources/app/Api/App.php b/sources/app/Api/App.php index 9b3ceb9..d082bcf 100644 --- a/sources/app/Api/App.php +++ b/sources/app/Api/App.php @@ -1,6 +1,6 @@ authentication->hasCaptcha($username) && $this->authentication->authenticate($username, $password)) { $this->checkProcedurePermission(true, $method); $this->userSession->refresh($this->user->getByUsername($username)); - } - else if ($username === 'jsonrpc' && $password === $this->config->get('api_token')) { + } elseif ($username === 'jsonrpc' && $password === $this->config->get('api_token')) { $this->checkProcedurePermission(false, $method); - } - else { + } else { throw new AuthenticationFailure('Wrong credentials'); } } diff --git a/sources/app/Api/Base.php b/sources/app/Api/Base.php index 0287e0e..0959817 100644 --- a/sources/app/Api/Base.php +++ b/sources/app/Api/Base.php @@ -1,8 +1,7 @@ $name, ); - list($valid,) = $this->category->validateCreation($values); + list($valid, ) = $this->category->validateCreation($values); return $valid ? $this->category->create($values) : false; } @@ -43,7 +43,7 @@ class Category extends \Core\Base 'name' => $name, ); - list($valid,) = $this->category->validateModification($values); + list($valid, ) = $this->category->validateModification($values); return $valid && $this->category->update($values); } } diff --git a/sources/app/Api/Comment.php b/sources/app/Api/Comment.php index e40968b..26b632e 100644 --- a/sources/app/Api/Comment.php +++ b/sources/app/Api/Comment.php @@ -1,6 +1,6 @@ $content, ); - list($valid,) = $this->comment->validateCreation($values); + list($valid, ) = $this->comment->validateCreation($values); return $valid ? $this->comment->create($values) : false; } @@ -45,7 +45,7 @@ class Comment extends \Core\Base 'comment' => $content, ); - list($valid,) = $this->comment->validateModification($values); + list($valid, ) = $this->comment->validateModification($values); return $valid && $this->comment->update($values); } } diff --git a/sources/app/Api/File.php b/sources/app/Api/File.php index ad736ad..be415ec 100644 --- a/sources/app/Api/File.php +++ b/sources/app/Api/File.php @@ -1,8 +1,8 @@ file->getById($file_id); if (! empty($file)) { return base64_encode($this->objectStorage->get($file['path'])); } - } - catch (ObjectStorageException $e) { + } catch (ObjectStorageException $e) { $this->logger->error($e->getMessage()); } diff --git a/sources/app/Api/Link.php b/sources/app/Api/Link.php index d883013..d4df18f 100644 --- a/sources/app/Api/Link.php +++ b/sources/app/Api/Link.php @@ -1,6 +1,6 @@ $opposite_label, ); - list($valid,) = $this->link->validateCreation($values); + list($valid, ) = $this->link->validateCreation($values); return $valid ? $this->link->create($label, $opposite_label) : false; } @@ -93,7 +93,7 @@ class Link extends \Core\Base 'label' => $label, ); - list($valid,) = $this->link->validateModification($values); + list($valid, ) = $this->link->validateModification($values); return $valid && $this->link->update($values); } diff --git a/sources/app/Api/Me.php b/sources/app/Api/Me.php index e761155..2c332a8 100644 --- a/sources/app/Api/Me.php +++ b/sources/app/Api/Me.php @@ -1,9 +1,8 @@ 1, ); - list($valid,) = $this->project->validateCreation($values); + list($valid, ) = $this->project->validateCreation($values); return $valid ? $this->project->create($values, $this->userSession->getId(), true) : false; } diff --git a/sources/app/Api/Project.php b/sources/app/Api/Project.php index 8ed382c..f934432 100644 --- a/sources/app/Api/Project.php +++ b/sources/app/Api/Project.php @@ -1,6 +1,6 @@ $description ); - list($valid,) = $this->project->validateCreation($values); + list($valid, ) = $this->project->validateCreation($values); return $valid ? $this->project->create($values) : false; } @@ -81,7 +81,7 @@ class Project extends Base 'description' => $description ); - list($valid,) = $this->project->validateModification($values); + list($valid, ) = $this->project->validateModification($values); return $valid && $this->project->update($values); } } diff --git a/sources/app/Api/ProjectPermission.php b/sources/app/Api/ProjectPermission.php index 7dd2dec..8032339 100644 --- a/sources/app/Api/ProjectPermission.php +++ b/sources/app/Api/ProjectPermission.php @@ -1,6 +1,6 @@ $status, ); - list($valid,) = $this->subtask->validateCreation($values); + list($valid, ) = $this->subtask->validateCreation($values); return $valid ? $this->subtask->create($values) : false; } @@ -58,7 +58,7 @@ class Subtask extends \Core\Base } } - list($valid,) = $this->subtask->validateApiModification($values); + list($valid, ) = $this->subtask->validateApiModification($values); return $valid && $this->subtask->update($values); } } diff --git a/sources/app/Api/Swimlane.php b/sources/app/Api/Swimlane.php index 13838d7..84c699a 100644 --- a/sources/app/Api/Swimlane.php +++ b/sources/app/Api/Swimlane.php @@ -1,6 +1,6 @@ taskFinder->getOverdueTasks(); } - + public function getOverdueTasksByProject($project_id) { $this->checkProjectPermission($project_id); @@ -91,7 +91,7 @@ class Task extends Base 'reference' => $reference, ); - list($valid,) = $this->taskValidator->validateCreation($values); + list($valid, ) = $this->taskValidator->validateCreation($values); return $valid ? $this->taskCreation->create($values) : false; } diff --git a/sources/app/Api/TaskLink.php b/sources/app/Api/TaskLink.php index 6b23d05..47d70d1 100644 --- a/sources/app/Api/TaskLink.php +++ b/sources/app/Api/TaskLink.php @@ -1,6 +1,6 @@ $is_project_admin, ); - list($valid,) = $this->user->validateCreation($values); + list($valid, ) = $this->user->validateCreation($values); return $valid ? $this->user->create($values) : false; } @@ -81,7 +81,7 @@ class User extends \Core\Base } } - list($valid,) = $this->user->validateApiModification($values); + list($valid, ) = $this->user->validateApiModification($values); return $valid && $this->user->update($values); } } diff --git a/sources/app/Auth/Base.php b/sources/app/Auth/Base.php deleted file mode 100644 index ebf6681..0000000 --- a/sources/app/Auth/Base.php +++ /dev/null @@ -1,34 +0,0 @@ -container = $container; - $this->db = $this->container['db']; - } -} diff --git a/sources/app/Auth/Database.php b/sources/app/Auth/Database.php index e69f18a..91b17a5 100644 --- a/sources/app/Auth/Database.php +++ b/sources/app/Auth/Database.php @@ -1,9 +1,10 @@ findUser($username, $password); if (is_array($result)) { - $user = $this->user->getByUsername($username); if (! empty($user)) { @@ -226,14 +226,12 @@ class Ldap extends Base if ($user['is_ldap_user'] == 0) { return false; } - } - else { + } else { // We create automatically a new user if ($this->isLdapAccountCreationEnabled() && $this->user->create($result) !== false) { $user = $this->user->getByUsername($username); - } - else { + } else { return false; } } @@ -319,12 +317,10 @@ class Ldap extends Base if ($this->getLdapBindType() === 'user') { $ldap_username = sprintf($this->getLdapUsername(), $username); $ldap_password = $password; - } - else if ($this->getLdapBindType() === 'proxy') { + } elseif ($this->getLdapBindType() === 'proxy') { $ldap_username = $this->getLdapUsername(); $ldap_password = $this->getLdapPassword(); - } - else { + } else { $ldap_username = null; $ldap_password = null; } @@ -486,11 +482,9 @@ class Ldap extends Base { if (! empty($username) && ! empty($email)) { return '(&('.$this->getLdapUserPattern($username).')('.$this->getLdapAccountEmail().'='.$email.'))'; - } - else if (! empty($username)) { + } elseif (! empty($username)) { return $this->getLdapUserPattern($username); - } - else if (! empty($email)) { + } elseif (! empty($email)) { return '('.$this->getLdapAccountEmail().'='.$email.')'; } @@ -508,7 +502,7 @@ class Ldap extends Base */ private function getEntry(array $entries, $key, $default = '') { - return isset($entries[0][$key][0]) ? $entries[0][$key][0] : $default; + return isset($entries[0][$key][0]) ? $entries[0][$key][0] : $default; } /** @@ -522,6 +516,6 @@ class Ldap extends Base */ private function getEntries(array $entries, $key, $default = array()) { - return isset($entries[0][$key]) ? $entries[0][$key] : $default; + return isset($entries[0][$key]) ? $entries[0][$key] : $default; } } diff --git a/sources/app/Auth/RememberMe.php b/sources/app/Auth/RememberMe.php index 54e6042..0290e36 100644 --- a/sources/app/Auth/RememberMe.php +++ b/sources/app/Auth/RememberMe.php @@ -1,10 +1,11 @@ readCookie(); if ($credentials !== false) { - $record = $this->find($credentials['token'], $credentials['sequence']); if ($record) { @@ -144,7 +144,6 @@ class RememberMe extends Base $credentials = $this->readCookie(); if ($credentials !== false) { - $this->deleteCookie(); $this->db diff --git a/sources/app/Auth/ReverseProxy.php b/sources/app/Auth/ReverseProxy.php index 7818254..1910ad3 100644 --- a/sources/app/Auth/ReverseProxy.php +++ b/sources/app/Auth/ReverseProxy.php @@ -1,8 +1,9 @@ rewind(); while ($it->valid()) { - if (! $it->isDot() && substr($it->key(), -4) === '.php') { $strings = array_merge($strings, $this->search($it->key())); } @@ -72,7 +71,7 @@ class LocaleComparator extends Base $strings = array_merge($strings, $matches[1]); } - array_walk($strings, function(&$value) { + array_walk($strings, function (&$value) { $value = trim($value, "'"); $value = str_replace("\'", "'", $value); }); diff --git a/sources/app/Console/LocaleSync.php b/sources/app/Console/LocaleSync.php index ab95651..d62b40b 100644 --- a/sources/app/Console/LocaleSync.php +++ b/sources/app/Console/LocaleSync.php @@ -1,6 +1,6 @@ isDot() && $fileInfo->isDir() && $fileInfo->getFilename() !== self::REF_LOCALE) { - $filename = 'app/Locale/'.$fileInfo->getFilename().'/translations.php'; echo $fileInfo->getFilename().' ('.$filename.')'.PHP_EOL; @@ -42,11 +40,9 @@ class LocaleSync extends Base $output .= 'return array('.PHP_EOL; foreach ($reference as $key => $value) { - if (! empty($outdated[$key])) { $output .= " '".str_replace("'", "\'", $key)."' => '".str_replace("'", "\'", $outdated[$key])."',\n"; - } - else { + } else { $output .= " // '".str_replace("'", "\'", $key)."' => '',\n"; } } diff --git a/sources/app/Console/ProjectDailyColumnStatsExport.php b/sources/app/Console/ProjectDailyColumnStatsExport.php index b983066..2513fbf 100644 --- a/sources/app/Console/ProjectDailyColumnStatsExport.php +++ b/sources/app/Console/ProjectDailyColumnStatsExport.php @@ -1,8 +1,8 @@ action->validateCreation($values); + list($valid, ) = $this->action->validateCreation($values); if ($valid) { - if ($this->action->create($values) !== false) { $this->session->flash(t('Your automatic action have been created successfully.')); - } - else { + } else { $this->session->flashError(t('Unable to create your automatic action.')); } } diff --git a/sources/app/Controller/Activity.php b/sources/app/Controller/Activity.php index 234e4be..24327c2 100644 --- a/sources/app/Controller/Activity.php +++ b/sources/app/Controller/Activity.php @@ -1,6 +1,6 @@ response->html($this->layout('app/notifications', array( 'title' => t('My notifications'), - 'notifications' => $this->webNotification->getAll($user['id']), + 'notifications' => $this->userUnreadNotification->getAll($user['id']), 'user' => $user, ))); } @@ -227,17 +227,21 @@ class App extends Base public function autocomplete() { $search = $this->request->getStringParam('term'); + $projects = $this->projectPermission->getActiveMemberProjectIds($this->userSession->getId()); + + if (empty($projects)) { + $this->response->json(array()); + } $filter = $this->taskFilterAutoCompleteFormatter ->create() - ->filterByProjects($this->projectPermission->getActiveMemberProjectIds($this->userSession->getId())) + ->filterByProjects($projects) ->excludeTasks(array($this->request->getIntegerParam('exclude_task_id'))); // Search by task id or by title if (ctype_digit($search)) { $filter->filterById($search); - } - else { + } else { $filter->filterByTitle($search); } diff --git a/sources/app/Controller/Auth.php b/sources/app/Controller/Auth.php index bb1154e..95ad8d9 100644 --- a/sources/app/Controller/Auth.php +++ b/sources/app/Controller/Auth.php @@ -1,6 +1,6 @@ authentication->validateForm($values); if ($valid) { - if (! empty($this->session['login_redirect']) && ! filter_var($this->session['login_redirect'], FILTER_VALIDATE_URL)) { $redirect = $this->session['login_redirect']; unset($this->session['login_redirect']); diff --git a/sources/app/Controller/Base.php b/sources/app/Controller/Base.php index e0fd59c..a955b12 100644 --- a/sources/app/Controller/Base.php +++ b/sources/app/Controller/Base.php @@ -1,14 +1,11 @@ container['db']->getLogMessages() as $message) { $this->container['logger']->debug($message); } @@ -123,7 +119,6 @@ abstract class Base extends \Core\Base public function handleAuthentication() { if (! $this->authentication->isAuthenticated()) { - if ($this->request->isAjax()) { $this->response->text('Not Authorized', 401); } @@ -143,7 +138,6 @@ abstract class Base extends \Core\Base $ignore = ($controller === 'twofactor' && in_array($action, array('code', 'check'))) || ($controller === 'auth' && $action === 'logout'); if ($ignore === false && $this->userSession->has2FA() && ! $this->userSession->check2FA()) { - if ($this->request->isAjax()) { $this->response->text('Not Authorized', 401); } diff --git a/sources/app/Controller/Board.php b/sources/app/Controller/Board.php index 840db05..2d75db8 100644 --- a/sources/app/Controller/Board.php +++ b/sources/app/Controller/Board.php @@ -1,6 +1,6 @@ getTask(); $this->response->html($this->template->render('board/tooltip_comments', array( - 'comments' => $this->comment->getAll($task['id']) + 'comments' => $this->comment->getAll($task['id'], $this->userSession->getCommentSorting()) ))); } @@ -239,12 +239,11 @@ class Board extends Base { $values = $this->request->getValues(); - list($valid,) = $this->taskValidator->validateAssigneeModification($values); + list($valid, ) = $this->taskValidator->validateAssigneeModification($values); if ($valid && $this->taskModification->update($values)) { $this->session->flash(t('Task updated successfully.')); - } - else { + } else { $this->session->flashError(t('Unable to update your task.')); } @@ -277,12 +276,11 @@ class Board extends Base { $values = $this->request->getValues(); - list($valid,) = $this->taskValidator->validateCategoryModification($values); + list($valid, ) = $this->taskValidator->validateCategoryModification($values); if ($valid && $this->taskModification->update($values)) { $this->session->flash(t('Task updated successfully.')); - } - else { + } else { $this->session->flashError(t('Unable to update your task.')); } @@ -365,8 +363,7 @@ class Board extends Base if ($this->request->isAjax()) { $this->response->html($this->renderBoard($project_id)); - } - else { + } else { $this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $project_id))); } } diff --git a/sources/app/Controller/Calendar.php b/sources/app/Controller/Calendar.php index 7050e54..67a402d 100644 --- a/sources/app/Controller/Calendar.php +++ b/sources/app/Controller/Calendar.php @@ -1,8 +1,8 @@ config->get('calendar_project_tasks', 'date_started') === 'date_creation') { $events = $filter->copy()->filterByCreationDateRange($start, $end)->setColumns('date_creation', 'date_completed')->format(); - } - else { + } else { $events = $filter->copy()->filterByStartDateRange($start, $end)->setColumns('date_started', 'date_completed')->format(); } @@ -79,8 +78,7 @@ class Calendar extends Base // 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 { + } else { $events = array_merge($events, $filter->copy()->filterByStartDateRange($start, $end)->setColumns('date_started', 'date_completed')->format()); } @@ -106,7 +104,6 @@ class Calendar extends Base public function save() { if ($this->request->isAjax() && $this->request->isPost()) { - $values = $this->request->getJson(); $this->taskModification->update(array( diff --git a/sources/app/Controller/Category.php b/sources/app/Controller/Category.php index e8d83f2..4aefd9f 100644 --- a/sources/app/Controller/Category.php +++ b/sources/app/Controller/Category.php @@ -1,6 +1,6 @@ category->validateCreation($values); if ($valid) { - if ($this->category->create($values)) { $this->session->flash(t('Your category have been created successfully.')); $this->response->redirect($this->helper->url->to('category', 'index', array('project_id' => $project['id']))); - } - else { + } else { $this->session->flashError(t('Unable to create your category.')); } } @@ -104,12 +102,10 @@ class Category extends Base list($valid, $errors) = $this->category->validateModification($values); if ($valid) { - if ($this->category->update($values)) { $this->session->flash(t('Your category have been updated successfully.')); $this->response->redirect($this->helper->url->to('category', 'index', array('project_id' => $project['id']))); - } - else { + } else { $this->session->flashError(t('Unable to update your category.')); } } diff --git a/sources/app/Controller/Column.php b/sources/app/Controller/Column.php index 89c495a..d28fb29 100644 --- a/sources/app/Controller/Column.php +++ b/sources/app/Controller/Column.php @@ -1,6 +1,6 @@ board->validateCreation($data); if ($valid) { - if ($this->board->addColumn($project['id'], $data['title'], $data['task_limit'], $data['description'])) { $this->session->flash(t('Board updated successfully.')); $this->response->redirect($this->helper->url->to('column', 'index', array('project_id' => $project['id']))); - } - else { + } else { $this->session->flashError(t('Unable to update this board.')); } } @@ -99,12 +97,10 @@ class Column extends Base list($valid, $errors) = $this->board->validateModification($values); if ($valid) { - if ($this->board->updateColumn($values['id'], $values['title'], $values['task_limit'], $values['description'])) { $this->session->flash(t('Board updated successfully.')); $this->response->redirect($this->helper->url->to('column', 'index', array('project_id' => $project['id']))); - } - else { + } else { $this->session->flashError(t('Unable to update this board.')); } } @@ -160,8 +156,7 @@ class Column extends Base if (! empty($column) && $this->board->removeColumn($column['id'])) { $this->session->flash(t('Column removed successfully.')); - } - else { + } else { $this->session->flashError(t('Unable to remove this column.')); } diff --git a/sources/app/Controller/Comment.php b/sources/app/Controller/Comment.php index 81fd721..d6cbbf1 100644 --- a/sources/app/Controller/Comment.php +++ b/sources/app/Controller/Comment.php @@ -1,6 +1,6 @@ comment->validateCreation($values); if ($valid) { - if ($this->comment->create($values)) { $this->session->flash(t('Comment added successfully.')); - } - else { + } else { $this->session->flashError(t('Unable to create your comment.')); } @@ -132,11 +130,9 @@ class Comment extends Base list($valid, $errors) = $this->comment->validateModification($values); if ($valid) { - if ($this->comment->update($values)) { $this->session->flash(t('Comment updated successfully.')); - } - else { + } else { $this->session->flashError(t('Unable to update your comment.')); } @@ -176,11 +172,25 @@ class Comment extends Base if ($this->comment->remove($comment['id'])) { $this->session->flash(t('Comment removed successfully.')); - } - else { + } else { $this->session->flashError(t('Unable to remove this comment.')); } $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']), 'comments')); } + + /** + * Toggle comment sorting + * + * @access public + */ + public function toggleSorting() + { + $task = $this->getTask(); + + $order = $this->userSession->getCommentSorting() === 'ASC' ? 'DESC' : 'ASC'; + $this->userSession->setCommentSorting($order); + + $this->response->redirect($this->helper->url->href('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'comments')); + } } diff --git a/sources/app/Controller/Config.php b/sources/app/Controller/Config.php index 1ae390c..47b844e 100644 --- a/sources/app/Controller/Config.php +++ b/sources/app/Controller/Config.php @@ -1,6 +1,6 @@ request->isPost()) { - $values = $this->request->getValues(); switch ($redirect) { @@ -45,7 +44,7 @@ class Config extends Base $values += array('subtask_restriction' => 0, 'subtask_time_tracking' => 0, 'cfd_include_closed_tasks' => 0); break; case 'integrations': - $values += array('integration_slack_webhook' => 0, 'integration_hipchat' => 0, 'integration_gravatar' => 0, 'integration_jabber' => 0); + $values += array('integration_gravatar' => 0); break; case 'calendar': $values += array('calendar_user_subtasks_time_tracking' => 0); @@ -55,8 +54,7 @@ class Config extends Base if ($this->config->save($values)) { $this->config->reload(); $this->session->flash(t('Settings saved successfully.')); - } - else { + } else { $this->session->flashError(t('Unable to save your settings.')); } diff --git a/sources/app/Controller/Currency.php b/sources/app/Controller/Currency.php index 10fb90d..9d6b024 100644 --- a/sources/app/Controller/Currency.php +++ b/sources/app/Controller/Currency.php @@ -1,6 +1,6 @@ currency->validate($values); if ($valid) { - if ($this->currency->create($values['currency'], $values['rate'])) { $this->session->flash(t('The currency rate have been added successfully.')); $this->response->redirect($this->helper->url->to('currency', 'index')); - } - else { + } else { $this->session->flashError(t('Unable to add this currency rate.')); } } @@ -79,8 +77,7 @@ class Currency extends Base if ($this->config->save($values)) { $this->config->reload(); $this->session->flash(t('Settings saved successfully.')); - } - else { + } else { $this->session->flashError(t('Unable to save your settings.')); } diff --git a/sources/app/Controller/Customfilter.php b/sources/app/Controller/Customfilter.php index c403cb4..a152c66 100644 --- a/sources/app/Controller/Customfilter.php +++ b/sources/app/Controller/Customfilter.php @@ -1,6 +1,6 @@ customFilter->create($values)) { $this->session->flash(t('Your custom filter have been created successfully.')); $this->response->redirect($this->helper->url->to('customfilter', 'index', array('project_id' => $project['id']))); - } - else { + } else { $this->session->flashError(t('Unable to create your custom filter.')); } } @@ -116,14 +115,17 @@ class Customfilter extends Base $values += array('is_shared' => 0); } + if (! isset($values['append'])) { + $values += array('append' => 0); + } + list($valid, $errors) = $this->customFilter->validateModification($values); if ($valid) { if ($this->customFilter->update($values)) { $this->session->flash(t('Your custom filter have been updated successfully.')); $this->response->redirect($this->helper->url->to('customfilter', 'index', array('project_id' => $project['id']))); - } - else { + } else { $this->session->flashError(t('Unable to update custom filter.')); } } diff --git a/sources/app/Controller/Doc.php b/sources/app/Controller/Doc.php index f9f0a67..3241304 100644 --- a/sources/app/Controller/Doc.php +++ b/sources/app/Controller/Doc.php @@ -1,6 +1,6 @@ helper->url; $data = file_get_contents($filename); - list($title,) = explode("\n", $data, 2); + list($title, ) = explode("\n", $data, 2); $replaceUrl = function (array $matches) use ($url) { return '('.$url->to('doc', 'show', array('file' => str_replace('.markdown', '', $matches[1]))).')'; diff --git a/sources/app/Controller/Export.php b/sources/app/Controller/Export.php index 8b558c0..cdedcb8 100644 --- a/sources/app/Controller/Export.php +++ b/sources/app/Controller/Export.php @@ -1,6 +1,6 @@ getTask(); if ($this->request->isPost() && $this->file->uploadScreenshot($task['project_id'], $task['id'], $this->request->getValue('screenshot')) !== false) { - $this->session->flash(t('Screenshot uploaded successfully.')); if ($this->request->getStringParam('redirect') === 'board') { @@ -77,7 +76,6 @@ class File extends Base public function download() { try { - $task = $this->getTask(); $file = $this->file->getById($this->request->getIntegerParam('file_id')); @@ -87,8 +85,7 @@ class File extends Base $this->response->forceDownload($file['name']); $this->objectStorage->output($file['path']); - } - catch (ObjectStorageException $e) { + } catch (ObjectStorageException $e) { $this->logger->error($e->getMessage()); } } @@ -112,50 +109,48 @@ class File extends Base } /** - * Return the file content (work only for images) + * Display image * * @access public */ public function image() { try { - $task = $this->getTask(); $file = $this->file->getById($this->request->getIntegerParam('file_id')); - if ($file['task_id'] != $task['id']) { - $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']))); + if ($file['task_id'] == $task['id']) { + $this->response->contentType($this->file->getImageMimeType($file['name'])); + $this->objectStorage->output($file['path']); } - - $this->response->contentType($this->file->getImageMimeType($file['name'])); - $this->objectStorage->output($file['path']); - } - catch (ObjectStorageException $e) { + } catch (ObjectStorageException $e) { $this->logger->error($e->getMessage()); } } /** - * Return image thumbnails + * Display image thumbnails * * @access public */ public function thumbnail() { - try { + $this->response->contentType('image/jpeg'); + try { $task = $this->getTask(); $file = $this->file->getById($this->request->getIntegerParam('file_id')); - if ($file['task_id'] != $task['id']) { - $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']))); + if ($file['task_id'] == $task['id']) { + $this->objectStorage->output($this->file->getThumbnailPath($file['path'])); } - - $this->response->contentType('image/jpeg'); - $this->objectStorage->output($this->file->getThumbnailPath($file['path'])); - } - catch (ObjectStorageException $e) { + } catch (ObjectStorageException $e) { $this->logger->error($e->getMessage()); + + // Try to generate thumbnail on the fly for images uploaded before Kanboard < 1.0.19 + $data = $this->objectStorage->get($file['path']); + $this->file->generateThumbnailFromData($file['path'], $data); + $this->objectStorage->output($this->file->getThumbnailPath($file['path'])); } } diff --git a/sources/app/Controller/Gantt.php b/sources/app/Controller/Gantt.php index 879ab37..24d94f0 100644 --- a/sources/app/Controller/Gantt.php +++ b/sources/app/Controller/Gantt.php @@ -1,8 +1,8 @@ userSession->isAdmin()) { $project_ids = $this->project->getAllIds(); - } - else { + } else { $project_ids = $this->projectPermission->getMemberProjectIds($this->userSession->getId()); } @@ -62,8 +61,7 @@ class Gantt extends Base if ($sorting === 'date') { $filter->getQuery()->asc(TaskModel::TABLE.'.date_started')->asc(TaskModel::TABLE.'.date_creation'); - } - else { + } else { $filter->getQuery()->asc('column_position')->asc(TaskModel::TABLE.'.position'); } @@ -134,14 +132,12 @@ class Gantt extends Base list($valid, $errors) = $this->taskValidator->validateCreation($values); if ($valid) { - $task_id = $this->taskCreation->create($values); if ($task_id !== false) { $this->session->flash(t('Task created successfully.')); $this->response->redirect($this->helper->url->to('gantt', 'project', array('project_id' => $project['id']))); - } - else { + } else { $this->session->flashError(t('Unable to create your task.')); } } diff --git a/sources/app/Controller/Ical.php b/sources/app/Controller/Ical.php index e89b7e3..f8e9e25 100644 --- a/sources/app/Controller/Ical.php +++ b/sources/app/Controller/Ical.php @@ -1,8 +1,8 @@ setColumns('date_creation', 'date_completed') ->setCalendar($calendar) ->addDateTimeEvents(); - } - else { + } else { $filter ->copy() ->filterByStartDateRange($start, $end) diff --git a/sources/app/Controller/Link.php b/sources/app/Controller/Link.php index 482e415..0eb3d67 100644 --- a/sources/app/Controller/Link.php +++ b/sources/app/Controller/Link.php @@ -1,6 +1,6 @@ link->validateCreation($values); if ($valid) { - if ($this->link->create($values['label'], $values['opposite_label']) !== false) { $this->session->flash(t('Link added successfully.')); $this->response->redirect($this->helper->url->to('link', 'index')); - } - else { + } else { $this->session->flashError(t('Unable to create your link.')); } } @@ -116,8 +114,7 @@ class Link extends Base if ($this->link->update($values)) { $this->session->flash(t('Link updated successfully.')); $this->response->redirect($this->helper->url->to('link', 'index')); - } - else { + } else { $this->session->flashError(t('Unable to update your link.')); } } @@ -152,8 +149,7 @@ class Link extends Base if ($this->link->remove($link['id'])) { $this->session->flash(t('Link removed successfully.')); - } - else { + } else { $this->session->flashError(t('Unable to remove this link.')); } diff --git a/sources/app/Controller/Listing.php b/sources/app/Controller/Listing.php index 2c197e3..b9c851f 100644 --- a/sources/app/Controller/Listing.php +++ b/sources/app/Controller/Listing.php @@ -1,8 +1,8 @@ authentication->backend($backend)->unlink($this->userSession->getId())) { $this->session->flash(t('Your external account is not linked anymore to your profile.')); - } - else { + } else { $this->session->flashError(t('Unable to unlink your external account.')); } @@ -71,8 +70,7 @@ class Oauth extends Base if (! empty($code)) { $this->step2($backend, $code); - } - else { + } else { $this->response->redirect($this->authentication->backend($backend)->getService()->getAuthorizationUrl()); } } @@ -102,8 +100,7 @@ class Oauth extends Base { if (empty($profile)) { $this->session->flashError(t('External authentication failed')); - } - else { + } else { $this->session->flash(t('Your external account is linked to your profile successfully.')); $this->authentication->backend($backend)->updateUser($this->userSession->getId(), $profile); } @@ -120,8 +117,7 @@ class Oauth extends Base { if (! empty($profile) && $this->authentication->backend($backend)->authenticate($profile['id'])) { $this->response->redirect($this->helper->url->to('app', 'index')); - } - else { + } else { $this->response->html($this->template->layout('auth/index', array( 'errors' => array('login' => t('External authentication failed')), 'values' => array(), diff --git a/sources/app/Controller/Project.php b/sources/app/Controller/Project.php index 3e3e47c..f30d70e 100644 --- a/sources/app/Controller/Project.php +++ b/sources/app/Controller/Project.php @@ -1,6 +1,6 @@ userSession->isAdmin()) { $project_ids = $this->project->getAllIds(); - } - else { + } else { $project_ids = $this->projectPermission->getMemberProjectIds($this->userSession->getId()); } @@ -68,13 +67,11 @@ class Project extends Base $switch = $this->request->getStringParam('switch'); if ($switch === 'enable' || $switch === 'disable') { - $this->checkCSRFParam(); 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.')); } @@ -92,28 +89,49 @@ class Project extends Base * * @access public */ - public function integration() + public function integrations() { $project = $this->getProject(); if ($this->request->isPost()) { - $params = $this->request->getValues(); - $params += array('hipchat' => 0, 'slack' => 0, 'jabber' => 0); - $this->projectIntegration->saveParameters($project['id'], $params); + $this->projectMetadata->save($project['id'], $this->request->getValues()); + $this->session->flash(t('Project updated successfully.')); + $this->response->redirect($this->helper->url->to('project', 'integrations', array('project_id' => $project['id']))); } - $values = $this->projectIntegration->getParameters($project['id']); - $values += array('hipchat_api_url' => 'https://api.hipchat.com'); - $this->response->html($this->projectLayout('project/integrations', array( 'project' => $project, 'title' => t('Integrations'), 'webhook_token' => $this->config->get('webhook_token'), - 'values' => $values, + 'values' => $this->projectMetadata->getAll($project['id']), 'errors' => array(), ))); } + /** + * Display project notifications + * + * @access public + */ + public function notifications() + { + $project = $this->getProject(); + + if ($this->request->isPost()) { + $values = $this->request->getValues(); + $this->projectNotification->saveSettings($project['id'], $values); + $this->session->flash(t('Project updated successfully.')); + $this->response->redirect($this->helper->url->to('project', 'notifications', array('project_id' => $project['id']))); + } + + $this->response->html($this->projectLayout('project/notifications', array( + 'notifications' => $this->projectNotification->readSettings($project['id']), + 'types' => $this->projectNotificationType->getTypes(), + 'project' => $project, + 'title' => t('Notifications'), + ))); + } + /** * Display a form to edit a project * @@ -145,8 +163,7 @@ class Project extends Base if (! $this->helper->user->isProjectAdministrationAllowed($project['id'])) { unset($values['is_private']); } - } - else if ($project['is_private'] == 1 && ! isset($values['is_private'])) { + } elseif ($project['is_private'] == 1 && ! isset($values['is_private'])) { if ($this->helper->user->isProjectAdministrationAllowed($project['id'])) { $values += array('is_private' => 0); } @@ -155,12 +172,10 @@ class Project extends Base list($valid, $errors) = $this->project->validateModification($values); if ($valid) { - if ($this->project->update($values)) { $this->session->flash(t('Project updated successfully.')); $this->response->redirect($this->helper->url->to('project', 'edit', array('project_id' => $project['id']))); - } - else { + } else { $this->session->flashError(t('Unable to update this project.')); } } @@ -193,14 +208,12 @@ class Project extends Base { $project = $this->getProject(); $values = $this->request->getValues() + array('is_everybody_allowed' => 0); - list($valid,) = $this->projectPermission->validateProjectModification($values); + list($valid, ) = $this->projectPermission->validateProjectModification($values); if ($valid) { - if ($this->project->update($values)) { $this->session->flash(t('Project updated successfully.')); - } - else { + } else { $this->session->flashError(t('Unable to update this project.')); } } @@ -216,14 +229,12 @@ class Project extends Base public function allow() { $values = $this->request->getValues(); - list($valid,) = $this->projectPermission->validateUserModification($values); + list($valid, ) = $this->projectPermission->validateUserModification($values); if ($valid) { - if ($this->projectPermission->addMember($values['project_id'], $values['user_id'])) { $this->session->flash(t('Project updated successfully.')); - } - else { + } else { $this->session->flashError(t('Unable to update this project.')); } } @@ -246,14 +257,12 @@ class Project extends Base 'is_owner' => $this->request->getIntegerParam('is_owner'), ); - list($valid,) = $this->projectPermission->validateUserModification($values); + list($valid, ) = $this->projectPermission->validateUserModification($values); if ($valid) { - if ($this->projectPermission->changeRole($values['project_id'], $values['user_id'], $values['is_owner'])) { $this->session->flash(t('Project updated successfully.')); - } - else { + } else { $this->session->flashError(t('Unable to update this project.')); } } @@ -275,14 +284,12 @@ class Project extends Base 'user_id' => $this->request->getIntegerParam('user_id'), ); - list($valid,) = $this->projectPermission->validateUserModification($values); + list($valid, ) = $this->projectPermission->validateUserModification($values); if ($valid) { - if ($this->projectPermission->revokeMember($values['project_id'], $values['user_id'])) { $this->session->flash(t('Project updated successfully.')); - } - else { + } else { $this->session->flashError(t('Unable to update this project.')); } } @@ -300,7 +307,6 @@ class Project extends Base $project = $this->getProject(); if ($this->request->getStringParam('remove') === 'yes') { - $this->checkCSRFParam(); if ($this->project->remove($project['id'])) { @@ -356,7 +362,6 @@ class Project extends Base $project = $this->getProject(); if ($this->request->getStringParam('disable') === 'yes') { - $this->checkCSRFParam(); if ($this->project->disable($project['id'])) { @@ -384,7 +389,6 @@ class Project extends Base $project = $this->getProject(); if ($this->request->getStringParam('enable') === 'yes') { - $this->checkCSRFParam(); if ($this->project->enable($project['id'])) { @@ -431,7 +435,6 @@ class Project extends Base list($valid, $errors) = $this->project->validateCreation($values); if ($valid) { - $project_id = $this->project->create($values, $this->userSession->getId(), true); if ($project_id > 0) { diff --git a/sources/app/Controller/Projectuser.php b/sources/app/Controller/Projectuser.php index 4456ce3..18829b3 100644 --- a/sources/app/Controller/Projectuser.php +++ b/sources/app/Controller/Projectuser.php @@ -1,9 +1,9 @@ userSession->isAdmin()) { $project_ids = $this->project->getAllIds(); - } - else { + } else { $project_ids = $this->projectPermission->getMemberProjectIds($this->userSession->getId()); } diff --git a/sources/app/Controller/Search.php b/sources/app/Controller/Search.php index f6dc7a3..0aff907 100644 --- a/sources/app/Controller/Search.php +++ b/sources/app/Controller/Search.php @@ -1,6 +1,6 @@ setOrder('tasks.id') ->setDirection('DESC'); - if ($search !== '') { - + if ($search !== '' && ! empty($projects)) { $query = $this ->taskFilter ->search($search) diff --git a/sources/app/Controller/Subtask.php b/sources/app/Controller/Subtask.php index 338918f..4ef3e74 100644 --- a/sources/app/Controller/Subtask.php +++ b/sources/app/Controller/Subtask.php @@ -1,8 +1,8 @@ subtask->validateCreation($values); if ($valid) { - if ($this->subtask->create($values)) { $this->session->flash(t('Sub-task added successfully.')); - } - else { + } else { $this->session->flashError(t('Unable to create your sub-task.')); } @@ -118,11 +116,9 @@ class Subtask extends Base list($valid, $errors) = $this->subtask->validateModification($values); if ($valid) { - if ($this->subtask->update($values)) { $this->session->flash(t('Sub-task updated successfully.')); - } - else { + } else { $this->session->flashError(t('Unable to update your sub-task.')); } @@ -161,8 +157,7 @@ class Subtask extends Base if ($this->subtask->remove($subtask['id'])) { $this->session->flash(t('Sub-task removed successfully.')); - } - else { + } else { $this->session->flashError(t('Unable to remove this sub-task.')); } @@ -183,7 +178,6 @@ class Subtask extends Base $this->subtask->toggleStatus($subtask['id']); if ($redirect === 'board') { - $this->session['has_subtask_inprogress'] = $this->subtask->hasSubtaskInProgress($this->userSession->getId()); $this->response->html($this->template->render('board/tooltip_subtasks', array( diff --git a/sources/app/Controller/Swimlane.php b/sources/app/Controller/Swimlane.php index f92e9fe..0b29f59 100644 --- a/sources/app/Controller/Swimlane.php +++ b/sources/app/Controller/Swimlane.php @@ -1,8 +1,8 @@ swimlane->validateCreation($values); if ($valid) { - if ($this->swimlane->create($values)) { $this->session->flash(t('Your swimlane have been created successfully.')); $this->response->redirect($this->helper->url->to('swimlane', 'index', array('project_id' => $project['id']))); - } - else { + } else { $this->session->flashError(t('Unable to create your swimlane.')); } } @@ -86,15 +84,13 @@ class Swimlane extends Base $project = $this->getProject(); $values = $this->request->getValues() + array('show_default_swimlane' => 0); - list($valid,) = $this->swimlane->validateDefaultModification($values); + list($valid, ) = $this->swimlane->validateDefaultModification($values); if ($valid) { - if ($this->swimlane->updateDefault($values)) { $this->session->flash(t('The default swimlane have been updated successfully.')); $this->response->redirect($this->helper->url->to('swimlane', 'index', array('project_id' => $project['id']))); - } - else { + } else { $this->session->flashError(t('Unable to update this swimlane.')); } } @@ -136,8 +132,7 @@ class Swimlane extends Base if ($this->swimlane->update($values)) { $this->session->flash(t('Swimlane updated successfully.')); $this->response->redirect($this->helper->url->to('swimlane', 'index', array('project_id' => $project['id']))); - } - else { + } else { $this->session->flashError(t('Unable to update this swimlane.')); } } diff --git a/sources/app/Controller/Task.php b/sources/app/Controller/Task.php index 0770fcd..894802d 100644 --- a/sources/app/Controller/Task.php +++ b/sources/app/Controller/Task.php @@ -1,6 +1,6 @@ $this->project->getById($task['project_id']), 'files' => $this->file->getAllDocuments($task['id']), 'images' => $this->file->getAllImages($task['id']), - 'comments' => $this->comment->getAll($task['id']), + 'comments' => $this->comment->getAll($task['id'], $this->userSession->getCommentSorting()), 'subtasks' => $subtasks, 'links' => $this->taskLink->getAllGroupedByLabel($task['id']), 'task' => $task, @@ -156,7 +156,6 @@ class Task extends Base } if ($this->request->getStringParam('confirmation') === 'yes') { - $this->checkCSRFParam(); if ($this->task->remove($task['id'])) { diff --git a/sources/app/Controller/TaskImport.php b/sources/app/Controller/TaskImport.php new file mode 100644 index 0000000..0e9d216 --- /dev/null +++ b/sources/app/Controller/TaskImport.php @@ -0,0 +1,72 @@ +getProject(); + + $this->response->html($this->projectLayout('task_import/step1', array( + 'project' => $project, + 'values' => $values, + 'errors' => $errors, + 'max_size' => ini_get('upload_max_filesize'), + 'delimiters' => Csv::getDelimiters(), + 'enclosures' => Csv::getEnclosures(), + 'title' => t('Import tasks from CSV file'), + ))); + } + + /** + * Process CSV file + * + */ + public function step2() + { + $project = $this->getProject(); + $values = $this->request->getValues(); + $filename = $this->request->getFilePath('file'); + + if (! file_exists($filename)) { + $this->step1($values, array('file' => array(t('Unable to read your file')))); + } + + $this->taskImport->projectId = $project['id']; + + $csv = new Csv($values['delimiter'], $values['enclosure']); + $csv->setColumnMapping($this->taskImport->getColumnMapping()); + $csv->read($filename, array($this->taskImport, 'import')); + + if ($this->taskImport->counter > 0) { + $this->session->flash(t('%d task(s) have been imported successfully.', $this->taskImport->counter)); + } else { + $this->session->flashError(t('Nothing have been imported!')); + } + + $this->response->redirect($this->helper->url->to('taskImport', 'step1', array('project_id' => $project['id']))); + } + + /** + * Generate template + * + */ + public function template() + { + $this->response->forceDownload('tasks.csv'); + $this->response->csv(array($this->taskImport->getColumnMapping())); + } +} diff --git a/sources/app/Controller/Taskcreation.php b/sources/app/Controller/Taskcreation.php index b9e9a33..e47cd1b 100644 --- a/sources/app/Controller/Taskcreation.php +++ b/sources/app/Controller/Taskcreation.php @@ -1,6 +1,6 @@ swimlane->getList($project['id'], false, true); if (empty($values)) { - $values = array( 'swimlane_id' => $this->request->getIntegerParam('swimlane_id', key($swimlanes_list)), 'column_id' => $this->request->getIntegerParam('column_id'), @@ -62,8 +61,7 @@ class Taskcreation extends Base if ($valid && $this->taskCreation->create($values)) { $this->session->flash(t('Task created successfully.')); $this->afterSave($project, $values); - } - else { + } else { $this->session->flashError(t('Unable to create your task.')); } @@ -79,8 +77,7 @@ class Taskcreation extends Base if (! $this->request->isAjax()) { $this->response->redirect($this->helper->url->to('taskcreation', 'create', $values)); } - } - else { + } else { $this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $project['id']))); } } diff --git a/sources/app/Controller/Taskduplication.php b/sources/app/Controller/Taskduplication.php index 8d329ca..79f498f 100644 --- a/sources/app/Controller/Taskduplication.php +++ b/sources/app/Controller/Taskduplication.php @@ -1,6 +1,6 @@ getTask(); if ($this->request->getStringParam('confirmation') === 'yes') { - $this->checkCSRFParam(); $task_id = $this->taskDuplication->duplicate($task['id']); @@ -48,9 +47,8 @@ class Taskduplication extends Base $task = $this->getTask(); if ($this->request->isPost()) { - $values = $this->request->getValues(); - list($valid,) = $this->taskValidator->validateProjectModification($values); + list($valid, ) = $this->taskValidator->validateProjectModification($values); if ($valid && $this->taskDuplication->moveToProject($task['id'], $values['project_id'], @@ -58,7 +56,6 @@ class Taskduplication extends Base $values['column_id'], $values['category_id'], $values['owner_id'])) { - $this->session->flash(t('Task updated successfully.')); $this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $values['project_id'], 'task_id' => $task['id']))); } @@ -79,9 +76,8 @@ class Taskduplication extends Base $task = $this->getTask(); if ($this->request->isPost()) { - $values = $this->request->getValues(); - list($valid,) = $this->taskValidator->validateProjectModification($values); + list($valid, ) = $this->taskValidator->validateProjectModification($values); if ($valid) { $task_id = $this->taskDuplication->duplicateToProject( @@ -125,8 +121,7 @@ class Taskduplication extends Base $values = $this->taskDuplication->checkDestinationProjectValues($task); $values['project_id'] = $dst_project_id; - } - else { + } else { $swimlanes_list = array(); $columns_list = array(); $categories_list = array(); diff --git a/sources/app/Controller/Tasklink.php b/sources/app/Controller/Tasklink.php index dd07680..587769e 100644 --- a/sources/app/Controller/Tasklink.php +++ b/sources/app/Controller/Tasklink.php @@ -1,6 +1,6 @@ taskLink->validateCreation($values); if ($valid) { - if ($this->taskLink->create($values['task_id'], $values['opposite_task_id'], $values['link_id'])) { $this->session->flash(t('Link added successfully.')); @@ -129,7 +128,6 @@ class Tasklink extends Base list($valid, $errors) = $this->taskLink->validateModification($values); if ($valid) { - if ($this->taskLink->update($values['id'], $values['task_id'], $values['opposite_task_id'], $values['link_id'])) { $this->session->flash(t('Link updated successfully.')); $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])).'#links'); @@ -162,15 +160,14 @@ class Tasklink extends Base * * @access public */ - public function remove() + public function remove() { $this->checkCSRFParam(); $task = $this->getTask(); if ($this->taskLink->remove($this->request->getIntegerParam('link_id'))) { $this->session->flash(t('Link removed successfully.')); - } - else { + } else { $this->session->flashError(t('Unable to remove this link.')); } diff --git a/sources/app/Controller/Taskmodification.php b/sources/app/Controller/Taskmodification.php index 638af59..b1105dc 100644 --- a/sources/app/Controller/Taskmodification.php +++ b/sources/app/Controller/Taskmodification.php @@ -1,6 +1,6 @@ getTask(); $values = $this->request->getValues(); - list($valid,) = $this->taskValidator->validateTimeModification($values); + list($valid, ) = $this->taskValidator->validateTimeModification($values); if ($valid && $this->taskModification->update($values)) { $this->session->flash(t('Task updated successfully.')); - } - else { + } else { $this->session->flashError(t('Unable to update your task.')); } @@ -55,29 +54,24 @@ class Taskmodification extends Base $ajax = $this->request->isAjax() || $this->request->getIntegerParam('ajax'); if ($this->request->isPost()) { - $values = $this->request->getValues(); list($valid, $errors) = $this->taskValidator->validateDescriptionCreation($values); if ($valid) { - if ($this->taskModification->update($values)) { $this->session->flash(t('Task updated successfully.')); - } - else { + } else { $this->session->flashError(t('Unable to update your task.')); } if ($ajax) { $this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $task['project_id']))); - } - else { + } else { $this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $task['project_id'], 'task_id' => $task['id']))); } } - } - else { + } else { $values = $task; $errors = array(); } @@ -91,8 +85,7 @@ class Taskmodification extends Base if ($ajax) { $this->response->html($this->template->render('task_modification/edit_description', $params)); - } - else { + } else { $this->response->html($this->taskLayout('task_modification/edit_description', $params)); } } @@ -127,8 +120,7 @@ class Taskmodification extends Base if ($ajax) { $html = $this->template->render('task_modification/edit_task', $params); - } - else { + } else { $html = $this->taskLayout('task_modification/edit_task', $params); } @@ -152,12 +144,10 @@ class Taskmodification extends Base if ($this->request->isAjax()) { $this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $task['project_id']))); - } - else { + } else { $this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $task['project_id'], 'task_id' => $task['id']))); } - } - else { + } else { $this->session->flashError(t('Unable to update your task.')); $this->edit($values, $errors); } @@ -173,24 +163,20 @@ class Taskmodification extends Base $task = $this->getTask(); if ($this->request->isPost()) { - $values = $this->request->getValues(); list($valid, $errors) = $this->taskValidator->validateEditRecurrence($values); if ($valid) { - if ($this->taskModification->update($values)) { $this->session->flash(t('Task updated successfully.')); - } - else { + } else { $this->session->flashError(t('Unable to update your task.')); } $this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $task['project_id'], 'task_id' => $task['id']))); } - } - else { + } else { $values = $task; $errors = array(); } diff --git a/sources/app/Controller/Taskstatus.php b/sources/app/Controller/Taskstatus.php index 1768b77..c0421ea 100644 --- a/sources/app/Controller/Taskstatus.php +++ b/sources/app/Controller/Taskstatus.php @@ -1,6 +1,6 @@ request->getStringParam('confirmation') === 'yes') { - $this->checkCSRFParam(); if ($this->taskStatus->$method($task['id'])) { diff --git a/sources/app/Controller/Timer.php b/sources/app/Controller/Timer.php index 2a4531b..0267fcd 100644 --- a/sources/app/Controller/Timer.php +++ b/sources/app/Controller/Timer.php @@ -1,6 +1,6 @@ subtaskTimeTracking->logStartTime($subtask_id, $this->userSession->getId()); - } - else if ($timer === 'stop') { + } elseif ($timer === 'stop') { $this->subtaskTimeTracking->logEndTime($subtask_id, $this->userSession->getId()); $this->subtaskTimeTracking->updateTaskTimeTracking($task_id); } diff --git a/sources/app/Controller/Twofactor.php b/sources/app/Controller/Twofactor.php index a8b0351..179241f 100644 --- a/sources/app/Controller/Twofactor.php +++ b/sources/app/Controller/Twofactor.php @@ -1,6 +1,6 @@ 1, 'twofactor_secret' => GoogleAuthenticator::generateRandom(), )); - } - else { + } else { $this->user->update(array( 'id' => $user['id'], 'twofactor_activated' => 0, @@ -94,8 +93,7 @@ class Twofactor extends User if (! empty($values['code']) && $otp->checkTotp(Base32::decode($user['twofactor_secret']), $values['code'])) { $this->session->flash(t('The two factor authentication code is valid.')); - } - else { + } else { $this->session->flashError(t('The two factor authentication code is not valid.')); } @@ -119,8 +117,7 @@ class Twofactor extends User $this->session['2fa_validated'] = true; $this->session->flash(t('The two factor authentication code is valid.')); $this->response->redirect($this->helper->url->to('app', 'index')); - } - else { + } else { $this->session->flashError(t('The two factor authentication code is not valid.')); $this->response->redirect($this->helper->url->to('twofactor', 'code')); } @@ -148,7 +145,6 @@ class Twofactor extends User $user = $this->getUser(); if ($this->request->getStringParam('disable') === 'yes') { - $this->checkCSRFParam(); $this->user->update(array( diff --git a/sources/app/Controller/User.php b/sources/app/Controller/User.php index 0b39619..b5f4ede 100644 --- a/sources/app/Controller/User.php +++ b/sources/app/Controller/User.php @@ -1,8 +1,8 @@ user->validateCreation($values); if ($valid) { - $project_id = empty($values['project_id']) ? 0 : $values['project_id']; unset($values['project_id']); @@ -96,13 +95,12 @@ class User extends Base $this->projectPermission->addMember($project_id, $user_id); if (! empty($values['notifications_enabled'])) { - $this->notificationType->saveUserSelectedTypes($user_id, array(NotificationType::TYPE_EMAIL)); + $this->userNotificationType->saveSelectedTypes($user_id, array(NotificationType::TYPE_EMAIL)); } $this->session->flash(t('User created successfully.')); $this->response->redirect($this->helper->url->to('user', 'show', array('user_id' => $user_id))); - } - else { + } else { $this->session->flashError(t('Unable to create your user.')); $values['project_id'] = $project_id; } @@ -201,20 +199,42 @@ class User extends Base if ($this->request->isPost()) { $values = $this->request->getValues(); - $this->notification->saveSettings($user['id'], $values); + $this->userNotification->saveSettings($user['id'], $values); $this->session->flash(t('User updated successfully.')); $this->response->redirect($this->helper->url->to('user', 'notifications', array('user_id' => $user['id']))); } $this->response->html($this->layout('user/notifications', array( 'projects' => $this->projectPermission->getMemberProjects($user['id']), - 'notifications' => $this->notification->readSettings($user['id']), - 'types' => $this->notificationType->getTypes(), - 'filters' => $this->notificationFilter->getFilters(), + 'notifications' => $this->userNotification->readSettings($user['id']), + 'types' => $this->userNotificationType->getTypes(), + 'filters' => $this->userNotificationFilter->getFilters(), 'user' => $user, ))); } + /** + * Display user integrations + * + * @access public + */ + public function integrations() + { + $user = $this->getUser(); + + if ($this->request->isPost()) { + $values = $this->request->getValues(); + $this->userMetadata->save($user['id'], $values); + $this->session->flash(t('User updated successfully.')); + $this->response->redirect($this->helper->url->to('user', 'integrations', array('user_id' => $user['id']))); + } + + $this->response->html($this->layout('user/integrations', array( + 'user' => $user, + 'values' => $this->userMetadata->getall($user['id']), + ))); + } + /** * Display external accounts * @@ -240,7 +260,6 @@ class User extends Base $switch = $this->request->getStringParam('switch'); if ($switch === 'enable' || $switch === 'disable') { - $this->checkCSRFParam(); if ($this->user->{$switch.'PublicAccess'}($user['id'])) { @@ -270,16 +289,13 @@ class User extends Base $errors = array(); if ($this->request->isPost()) { - $values = $this->request->getValues(); list($valid, $errors) = $this->user->validatePasswordModification($values); if ($valid) { - if ($this->user->update($values)) { $this->session->flash(t('Password modified successfully.')); - } - else { + } else { $this->session->flashError(t('Unable to change the password.')); } @@ -308,13 +324,11 @@ class User extends Base unset($values['password']); if ($this->request->isPost()) { - $values = $this->request->getValues(); if ($this->userSession->isAdmin()) { $values += array('is_admin' => 0, 'is_project_admin' => 0); - } - else { + } else { // Regular users can't be admin if (isset($values['is_admin'])) { unset($values['is_admin']); @@ -328,11 +342,9 @@ class User extends Base list($valid, $errors) = $this->user->validateModification($values); if ($valid) { - if ($this->user->update($values)) { $this->session->flash(t('User updated successfully.')); - } - else { + } else { $this->session->flashError(t('Unable to update your user.')); } @@ -363,16 +375,13 @@ class User extends Base unset($values['password']); if ($this->request->isPost()) { - $values = $this->request->getValues() + array('disable_login_form' => 0, 'is_ldap_user' => 0); list($valid, $errors) = $this->user->validateModification($values); if ($valid) { - if ($this->user->update($values)) { $this->session->flash(t('User updated successfully.')); - } - else { + } else { $this->session->flashError(t('Unable to update your user.')); } @@ -397,7 +406,6 @@ class User extends Base $user = $this->getUser(); if ($this->request->getStringParam('confirmation') === 'yes') { - $this->checkCSRFParam(); if ($this->user->remove($user['id'])) { diff --git a/sources/app/Controller/UserImport.php b/sources/app/Controller/UserImport.php new file mode 100644 index 0000000..32b9a86 --- /dev/null +++ b/sources/app/Controller/UserImport.php @@ -0,0 +1,66 @@ +response->html($this->template->layout('user_import/step1', array( + 'values' => $values, + 'errors' => $errors, + 'max_size' => ini_get('upload_max_filesize'), + 'delimiters' => Csv::getDelimiters(), + 'enclosures' => Csv::getEnclosures(), + 'title' => t('Import users from CSV file'), + ))); + } + + /** + * Process CSV file + * + */ + public function step2() + { + $values = $this->request->getValues(); + $filename = $this->request->getFilePath('file'); + + if (! file_exists($filename)) { + $this->step1($values, array('file' => array(t('Unable to read your file')))); + } + + $csv = new Csv($values['delimiter'], $values['enclosure']); + $csv->setColumnMapping($this->userImport->getColumnMapping()); + $csv->read($filename, array($this->userImport, 'import')); + + if ($this->userImport->counter > 0) { + $this->session->flash(t('%d user(s) have been imported successfully.', $this->userImport->counter)); + } else { + $this->session->flashError(t('Nothing have been imported!')); + } + + $this->response->redirect($this->helper->url->to('userImport', 'step1')); + } + + /** + * Generate template + * + */ + public function template() + { + $this->response->forceDownload('users.csv'); + $this->response->csv(array($this->userImport->getColumnMapping())); + } +} diff --git a/sources/app/Controller/Webnotification.php b/sources/app/Controller/WebNotification.php similarity index 53% rename from sources/app/Controller/Webnotification.php rename to sources/app/Controller/WebNotification.php index a481e9b..dca5cb4 100644 --- a/sources/app/Controller/Webnotification.php +++ b/sources/app/Controller/WebNotification.php @@ -1,6 +1,6 @@ userSession->getId(); + $user_id = $this->getUserId(); - $this->webNotification->markAllAsRead($user_id); + $this->userUnreadNotification->markAllAsRead($user_id); $this->response->redirect($this->helper->url->to('app', 'notifications', array('user_id' => $user_id))); } @@ -30,10 +30,21 @@ class Webnotification extends Base */ public function remove() { - $user_id = $this->userSession->getId(); + $user_id = $this->getUserId(); $notification_id = $this->request->getIntegerParam('notification_id'); - $this->webNotification->markAsRead($user_id, $notification_id); + $this->userUnreadNotification->markAsRead($user_id, $notification_id); $this->response->redirect($this->helper->url->to('app', 'notifications', array('user_id' => $user_id))); } + + private function getUserId() + { + $user_id = $this->request->getIntegerParam('user_id'); + + if (! $this->userSession->isAdmin() && $user_id != $this->userSession->getId()) { + $user_id = $this->userSession->getId(); + } + + return $user_id; + } } diff --git a/sources/app/Controller/Webhook.php b/sources/app/Controller/Webhook.php index 1a4cfe9..a7e9bde 100644 --- a/sources/app/Controller/Webhook.php +++ b/sources/app/Controller/Webhook.php @@ -1,6 +1,6 @@ $this->request->getIntegerParam('category_id'), ); - list($valid,) = $this->taskValidator->validateCreation($values); + list($valid, ) = $this->taskValidator->validateCreation($values); if ($valid && $this->taskCreation->create($values)) { $this->response->text('OK'); @@ -92,37 +92,4 @@ class Webhook extends Base echo $result ? 'PARSED' : 'IGNORED'; } - - /** - * Handle Postmark webhooks - * - * @access public - */ - public function postmark() - { - $this->checkWebhookToken(); - echo $this->postmark->receiveEmail($this->request->getJson()) ? 'PARSED' : 'IGNORED'; - } - - /** - * Handle Mailgun webhooks - * - * @access public - */ - public function mailgun() - { - $this->checkWebhookToken(); - echo $this->mailgun->receiveEmail($_POST) ? 'PARSED' : 'IGNORED'; - } - - /** - * Handle Sendgrid webhooks - * - * @access public - */ - public function sendgrid() - { - $this->checkWebhookToken(); - echo $this->sendgrid->receiveEmail($_POST) ? 'PARSED' : 'IGNORED'; - } } diff --git a/sources/app/Core/Base.php b/sources/app/Core/Base.php index 7503e84..d402fb3 100644 --- a/sources/app/Core/Base.php +++ b/sources/app/Core/Base.php @@ -1,6 +1,6 @@ delimiter = $delimiter; + $this->enclosure = $enclosure; + } + + /** + * Get list of delimiters + * + * @static + * @access public + * @return array + */ + public static function getDelimiters() + { + return array( + ',' => t('Comma'), + ';' => t('Semi-colon'), + '\t' => t('Tab'), + '|' => t('Vertical bar'), + ); + } + + /** + * Get list of enclosures + * + * @static + * @access public + * @return array + */ + public static function getEnclosures() + { + return array( + '"' => t('Double Quote'), + "'" => t('Single Quote'), + '' => t('None'), + ); + } + + /** + * Check boolean field value + * + * @static + * @access public + * @return integer + */ + public static function getBooleanValue($value) + { + if (! empty($value)) { + $value = trim(strtolower($value)); + return $value === '1' || $value{0} + === 't' ? 1 : 0; + } + + return 0; + } + + /** + * Output CSV file to standard output + * + * @static + * @access public + * @param array $rows + */ + public static function output(array $rows) + { + $csv = new static; + $csv->write('php://output', $rows); + } + + /** + * Define column mapping between CSV and SQL columns + * + * @access public + * @param array $columns + * @return Csv + */ + public function setColumnMapping(array $columns) + { + $this->columns = $columns; + return $this; + } + + /** + * Read CSV file + * + * @access public + * @param string $filename + * @param callable $callback Example: function(array $row, $line_number) + * @return Csv + */ + public function read($filename, $callback) + { + $file = new SplFileObject($filename); + $file->setFlags(SplFileObject::READ_CSV); + $file->setCsvControl($this->delimiter, $this->enclosure); + $line_number = 0; + + foreach ($file as $row) { + $row = $this->filterRow($row); + + if (! empty($row) && $line_number > 0) { + call_user_func_array($callback, array($this->associateColumns($row), $line_number)); + } + + $line_number++; + } + + return $this; + } + + /** + * Write CSV file + * + * @access public + * @param string $filename + * @param array $rows + * @return Csv + */ + public function write($filename, array $rows) + { + $file = new SplFileObject($filename, 'w'); + + foreach ($rows as $row) { + $file->fputcsv($row, $this->delimiter, $this->enclosure); + } + + return $this; + } + + /** + * Associate columns header with row values + * + * @access private + * @param array $row + * @return array + */ + private function associateColumns(array $row) + { + $line = array(); + $index = 0; + + foreach ($this->columns as $sql_name => $csv_name) { + if (isset($row[$index])) { + $line[$sql_name] = $row[$index]; + } else { + $line[$sql_name] = ''; + } + + $index++; + } + + return $line; + } + + /** + * Filter empty rows + * + * @access private + * @param array $row + * @return array + */ + private function filterRow(array $row) + { + return array_filter($row); + } +} diff --git a/sources/app/Model/DateParser.php b/sources/app/Core/DateParser.php similarity index 98% rename from sources/app/Model/DateParser.php rename to sources/app/Core/DateParser.php index 002fc03..6577af0 100644 --- a/sources/app/Model/DateParser.php +++ b/sources/app/Core/DateParser.php @@ -1,13 +1,13 @@ container['logger']->debug('Sending email to '.$email.' ('.MAIL_TRANSPORT.')'); - - $start_time = microtime(true); - $author = 'Kanboard'; - - if (Session::isOpen() && $this->userSession->isLogged()) { - $author = e('%s via Kanboard', $this->user->getFullname($this->session['user'])); - } - - switch (MAIL_TRANSPORT) { - case 'sendgrid': - $this->sendgrid->sendEmail($email, $name, $subject, $html, $author); - break; - case 'mailgun': - $this->mailgun->sendEmail($email, $name, $subject, $html, $author); - break; - case 'postmark': - $this->postmark->sendEmail($email, $name, $subject, $html, $author); - break; - default: - $this->smtp->sendEmail($email, $name, $subject, $html, $author); - } - - $this->container['logger']->debug('Email sent in '.round(microtime(true) - $start_time, 6).' seconds'); - } -} diff --git a/sources/app/Core/Helper.php b/sources/app/Core/Helper.php index 64eaed2..5edaa3f 100644 --- a/sources/app/Core/Helper.php +++ b/sources/app/Core/Helper.php @@ -1,6 +1,6 @@ helpers[$name])) { - $class = '\Helper\\'.ucfirst($name); + $class = '\Kanboard\Helper\\'.ucfirst($name); $this->helpers[$name] = new $class($this->container); } diff --git a/sources/app/Core/HttpClient.php b/sources/app/Core/HttpClient.php index 99534cf..7f4ea47 100644 --- a/sources/app/Core/HttpClient.php +++ b/sources/app/Core/HttpClient.php @@ -1,6 +1,6 @@ container['logger']->error('HttpClient: request failed'); } diff --git a/sources/app/Core/Lexer.php b/sources/app/Core/Lexer.php index d7e6fde..ca2ef89 100644 --- a/sources/app/Core/Lexer.php +++ b/sources/app/Core/Lexer.php @@ -1,6 +1,6 @@ offset = 0; while (isset($input[$this->offset])) { - $result = $this->match(substr($input, $this->offset)); if ($result === false) { @@ -84,7 +83,6 @@ class Lexer { foreach ($this->tokenMap as $pattern => $name) { if (preg_match($pattern, $string, $matches)) { - $this->offset += strlen($matches[1]); return array( @@ -113,7 +111,6 @@ class Lexer ); while (false !== ($token = current($tokens))) { - switch ($token['token']) { case 'T_ASSIGNEE': case 'T_COLOR': diff --git a/sources/app/Core/Mail/Client.php b/sources/app/Core/Mail/Client.php new file mode 100644 index 0000000..52caef7 --- /dev/null +++ b/sources/app/Core/Mail/Client.php @@ -0,0 +1,96 @@ +transports = new Container; + } + + /** + * Send a HTML email + * + * @access public + * @param string $email + * @param string $name + * @param string $subject + * @param string $html + * @return EmailClient + */ + public function send($email, $name, $subject, $html) + { + $this->container['logger']->debug('Sending email to '.$email.' ('.MAIL_TRANSPORT.')'); + + $start_time = microtime(true); + $author = 'Kanboard'; + + if ($this->userSession->isLogged()) { + $author = e('%s via Kanboard', $this->user->getFullname($this->session['user'])); + } + + $this->getTransport(MAIL_TRANSPORT)->sendEmail($email, $name, $subject, $html, $author); + + if (DEBUG) { + $this->logger->debug('Email sent in '.round(microtime(true) - $start_time, 6).' seconds'); + } + + return $this; + } + + /** + * Get mail transport instance + * + * @access public + * @param string $transport + * @return EmailClientInterface + */ + public function getTransport($transport) + { + return $this->transports[$transport]; + } + + /** + * Add a new mail transport + * + * @access public + * @param string $transport + * @param string $class + * @return EmailClient + */ + public function setTransport($transport, $class) + { + $container = $this->container; + + $this->transports[$transport] = function () use ($class, $container) { + return new $class($container); + }; + + return $this; + } +} diff --git a/sources/app/Core/Mail/ClientInterface.php b/sources/app/Core/Mail/ClientInterface.php new file mode 100644 index 0000000..66263a9 --- /dev/null +++ b/sources/app/Core/Mail/ClientInterface.php @@ -0,0 +1,24 @@ +setSubject($subject) + ->setFrom(array(MAIL_FROM => $author)) + ->setBody($html, 'text/html') + ->setTo(array($email => $name)); + + Swift_Mailer::newInstance($this->getTransport())->send($message); + } catch (Swift_TransportException $e) { + $this->logger->error($e->getMessage()); + } + } + + /** + * Get SwiftMailer transport + * + * @access protected + * @return \Swift_Transport + */ + protected function getTransport() + { + return Swift_MailTransport::newInstance(); + } +} diff --git a/sources/app/Core/Mail/Transport/Sendmail.php b/sources/app/Core/Mail/Transport/Sendmail.php new file mode 100644 index 0000000..849e338 --- /dev/null +++ b/sources/app/Core/Mail/Transport/Sendmail.php @@ -0,0 +1,25 @@ +setUsername(MAIL_SMTP_USERNAME); + $transport->setPassword(MAIL_SMTP_PASSWORD); + $transport->setEncryption(MAIL_SMTP_ENCRYPTION); + + return $transport; + } +} diff --git a/sources/app/Core/Markdown.php b/sources/app/Core/Markdown.php index fa4e808..f08c486 100644 --- a/sources/app/Core/Markdown.php +++ b/sources/app/Core/Markdown.php @@ -1,9 +1,9 @@ link) && preg_match('!#(\d+)!i', $Excerpt['text'], $matches)) { - $url = $this->helper->href( $this->link['controller'], $this->link['action'], diff --git a/sources/app/Core/NotificationInterface.php b/sources/app/Core/NotificationInterface.php deleted file mode 100644 index 5dca74e..0000000 --- a/sources/app/Core/NotificationInterface.php +++ /dev/null @@ -1,22 +0,0 @@ -accessToken) && ! empty($code)) { - $params = array( 'code' => $code, 'client_id' => $this->clientId, diff --git a/sources/app/Core/ObjectStorage/FileStorage.php b/sources/app/Core/ObjectStorage/FileStorage.php index 75160e7..dd049ca 100644 --- a/sources/app/Core/ObjectStorage/FileStorage.php +++ b/sources/app/Core/ObjectStorage/FileStorage.php @@ -1,6 +1,6 @@ path.DIRECTORY_SEPARATOR.dirname($key) : $this->path; + $folder = strpos($key, DIRECTORY_SEPARATOR) !== false ? $this->path.DIRECTORY_SEPARATOR.dirname($key) : $this->path; if (! is_dir($folder) && ! mkdir($folder, 0755, true)) { throw new ObjectStorageException('Unable to create folder: '.$folder); diff --git a/sources/app/Core/ObjectStorage/ObjectStorageException.php b/sources/app/Core/ObjectStorage/ObjectStorageException.php index e89aeb2..9e98ff5 100644 --- a/sources/app/Core/ObjectStorage/ObjectStorageException.php +++ b/sources/app/Core/ObjectStorage/ObjectStorageException.php @@ -1,6 +1,6 @@ action, $this->getUrlParams($this->page - 1, $this->order, $this->direction) ); - } - else { + } else { $html .= '← '.t('Previous'); } @@ -382,8 +381,7 @@ class Paginator $this->action, $this->getUrlParams($this->page + 1, $this->order, $this->direction) ); - } - else { + } else { $html .= t('Next').' →'; } diff --git a/sources/app/Core/Plugin/Base.php b/sources/app/Core/Plugin/Base.php index 1b7ac8f..1526537 100644 --- a/sources/app/Core/Plugin/Base.php +++ b/sources/app/Core/Plugin/Base.php @@ -1,6 +1,6 @@ container; - $this->container['dispatcher']->addListener($event, function() use ($container, $callback) { + $this->container['dispatcher']->addListener($event, function () use ($container, $callback) { call_user_func($callback, $container); }); } @@ -66,7 +66,7 @@ abstract class Base extends \Core\Base */ public function getPluginName() { - return ucfirst(substr(get_called_class(), 7, -7)); + return ucfirst(substr(get_called_class(), 16, -7)); } /** diff --git a/sources/app/Core/Plugin/Hook.php b/sources/app/Core/Plugin/Hook.php index fa14af1..a3bcd91 100644 --- a/sources/app/Core/Plugin/Hook.php +++ b/sources/app/Core/Plugin/Hook.php @@ -1,6 +1,6 @@ container); Tool::buildDic($this->container, $instance->getClasses()); @@ -90,16 +91,15 @@ class Loader extends \Core\Base */ public function migrateSchema($plugin) { - $last_version = constant('\Plugin\\'.$plugin.'\Schema\VERSION'); + $last_version = constant('\Kanboard\Plugin\\'.$plugin.'\Schema\VERSION'); $current_version = $this->getSchemaVersion($plugin); try { - $this->db->startTransaction(); $this->db->getDriver()->disableForeignKeys(); for ($i = $current_version + 1; $i <= $last_version; $i++) { - $function_name = '\Plugin\\'.$plugin.'\Schema\version_'.$i; + $function_name = '\Kanboard\Plugin\\'.$plugin.'\Schema\version_'.$i; if (function_exists($function_name)) { call_user_func($function_name, $this->db->getConnection()); @@ -109,11 +109,10 @@ class Loader extends \Core\Base $this->db->getDriver()->enableForeignKeys(); $this->db->closeTransaction(); $this->setSchemaVersion($plugin, $i - 1); - } - catch (PDOException $e) { + } catch (PDOException $e) { $this->db->cancelTransaction(); $this->db->getDriver()->enableForeignKeys(); - die('Unable to migrate schema for the plugin: '.$plugin.' => '.$e->getMessage()); + throw new RuntimeException('Unable to migrate schema for the plugin: '.$plugin.' => '.$e->getMessage()); } } diff --git a/sources/app/Core/Request.php b/sources/app/Core/Request.php index 1eff66f..5eda2d0 100644 --- a/sources/app/Core/Request.php +++ b/sources/app/Core/Request.php @@ -1,6 +1,6 @@ status($status_code); $this->nocache(); + header('Content-Type: text/csv'); - Tool::csv($data); + Csv::output($data); exit; } diff --git a/sources/app/Core/Router.php b/sources/app/Core/Router.php index 93d266b..843f513 100644 --- a/sources/app/Core/Router.php +++ b/sources/app/Core/Router.php @@ -1,6 +1,8 @@ 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]) { + } elseif ($route['pattern'][$i] !== $parts[$i]) { break; } } @@ -168,7 +166,6 @@ class Router extends Base } foreach ($this->urls[$controller][$action] as $pattern) { - if (array_diff_key($params, $pattern['params']) === array()) { $url = $pattern['path']; $i = 0; @@ -197,7 +194,7 @@ class Router extends Base */ public function sanitize($value, $default_value) { - return ! ctype_alpha($value) || empty($value) ? $default_value : strtolower($value); + return ! preg_match('/^[a-zA-Z_0-9]+$/', $value) ? $default_value : $value; } /** @@ -213,12 +210,17 @@ class Router extends Base $this->controller = $this->sanitize($_GET['controller'], 'app'); $this->action = $this->sanitize($_GET['action'], 'index'); $plugin = ! empty($_GET['plugin']) ? $this->sanitize($_GET['plugin'], '') : ''; - } - else { + } else { list($this->controller, $this->action) = $this->findRoute($this->getPath($uri, $query_string)); // TODO: add plugin for routes $plugin = ''; } - $class = empty($plugin) ? '\Controller\\'.ucfirst($this->controller) : '\Plugin\\'.ucfirst($plugin).'\Controller\\'.ucfirst($this->controller); + + $class = '\Kanboard\\'; + $class .= empty($plugin) ? 'Controller\\'.ucfirst($this->controller) : 'Plugin\\'.ucfirst($plugin).'\Controller\\'.ucfirst($this->controller); + + if (! class_exists($class) || ! method_exists($class, $this->action)) { + throw new RuntimeException('Controller or method not found for the given url!'); + } $instance = new $class($this->container); $instance->beforeAction($this->controller, $this->action); diff --git a/sources/app/Core/Security.php b/sources/app/Core/Security.php index 0bd7c99..54207ee 100644 --- a/sources/app/Core/Security.php +++ b/sources/app/Core/Security.php @@ -1,6 +1,6 @@ $classes) { foreach ($classes as $name) { - $class = '\\'.$namespace.'\\'.$name; + $class = '\\Kanboard\\'.$namespace.'\\'.$name; $container[lcfirst($name)] = function ($c) use ($class) { return new $class($c); }; @@ -104,14 +83,11 @@ class Tool $dst_width = $resize_width; $dst_height = floor($src_height * ($resize_width / $src_width)); $dst_image = imagecreatetruecolor($dst_width, $dst_height); - } - elseif ($resize_width == 0 && $resize_height > 0) { + } elseif ($resize_width == 0 && $resize_height > 0) { $dst_width = floor($src_width * ($resize_height / $src_height)); $dst_height = $resize_height; $dst_image = imagecreatetruecolor($dst_width, $dst_height); - } - else { - + } else { $src_ratio = $src_width / $src_height; $resize_ratio = $resize_width / $resize_height; @@ -120,8 +96,7 @@ class Tool $dst_height = floor($src_height * ($resize_width / $src_width)); $dst_y = ($dst_height - $resize_height) / 2 * (-1); - } - else { + } else { $dst_width = floor($src_width * ($resize_height / $src_height)); $dst_height = $resize_height; diff --git a/sources/app/Core/Translator.php b/sources/app/Core/Translator.php index e9aa1f3..96a481f 100644 --- a/sources/app/Core/Translator.php +++ b/sources/app/Core/Translator.php @@ -1,6 +1,6 @@ projects = array(); - } - else { - + } else { $this->projects = $this->db ->table(self::TABLE) ->asc('start_date') diff --git a/sources/app/Formatter/TaskFilterAutoCompleteFormatter.php b/sources/app/Formatter/TaskFilterAutoCompleteFormatter.php index 999a894..c9af465 100644 --- a/sources/app/Formatter/TaskFilterAutoCompleteFormatter.php +++ b/sources/app/Formatter/TaskFilterAutoCompleteFormatter.php @@ -1,9 +1,9 @@ query->findAll() as $task) { - $start = new DateTime; $start->setTimestamp($task[$this->startColumn]); @@ -82,7 +81,6 @@ class TaskFilterICalendarFormatter extends TaskFilterCalendarEvent implements Fo public function addFullDayEvents() { foreach ($this->query->findAll() as $task) { - $date = new DateTime; $date->setTimestamp($task[$this->startColumn]); diff --git a/sources/app/Helper/App.php b/sources/app/Helper/App.php index 5fb89af..19801fa 100644 --- a/sources/app/Helper/App.php +++ b/sources/app/Helper/App.php @@ -1,6 +1,6 @@ '.$this->helper->e($this->session['flash_message']).''; unset($this->session['flash_message']); unset($this->session['flash_error_message']); - } - else if (isset($this->session['flash_error_message'])) { + } elseif (isset($this->session['flash_error_message'])) { $html = '
'.$this->helper->e($this->session['flash_error_message']).'
'; unset($this->session['flash_message']); unset($this->session['flash_error_message']); diff --git a/sources/app/Helper/Asset.php b/sources/app/Helper/Asset.php index fd555e0..c4178e8 100644 --- a/sources/app/Helper/Asset.php +++ b/sources/app/Helper/Asset.php @@ -1,6 +1,6 @@ '; foreach ($options as $id => $value) { - $html .= ''; } @@ -177,6 +180,23 @@ class Form extends \Core\Base return $html; } + /** + * Display file field + * + * @access public + * @param string $name + * @param array $errors + * @param boolean $multiple + * @return string + */ + public function file($name, array $errors = array(), $multiple = false) + { + $html = ''; + $html .= $this->errorList($errors, $name); + + return $html; + } + /** * Display a input field * @@ -311,7 +331,6 @@ class Form extends \Core\Base $html = ''; if (isset($errors[$name])) { - $html .= '