diff options
Diffstat (limited to 'app')
44 files changed, 518 insertions, 341 deletions
diff --git a/app/Api/File.php b/app/Api/File.php index 269803e1..e4204e6d 100644 --- a/app/Api/File.php +++ b/app/Api/File.php @@ -2,6 +2,7 @@ namespace Kanboard\Api; +use Kanboard\Core\Base; use Kanboard\Core\ObjectStorage\ObjectStorageException; /** @@ -10,22 +11,22 @@ use Kanboard\Core\ObjectStorage\ObjectStorageException; * @package api * @author Frederic Guillot */ -class File extends \Kanboard\Core\Base +class File extends Base { - public function getFile($file_id) + public function getTaskFile($file_id) { - return $this->file->getById($file_id); + return $this->taskFile->getById($file_id); } - public function getAllFiles($task_id) + public function getAllTaskFiles($task_id) { - return $this->file->getAll($task_id); + return $this->taskFile->getAll($task_id); } - public function downloadFile($file_id) + public function downloadTaskFile($file_id) { try { - $file = $this->file->getById($file_id); + $file = $this->taskFile->getById($file_id); if (! empty($file)) { return base64_encode($this->objectStorage->get($file['path'])); @@ -36,23 +37,55 @@ class File extends \Kanboard\Core\Base } } - public function createFile($project_id, $task_id, $filename, $blob) + public function createTaskFile($project_id, $task_id, $filename, $blob) { try { - return $this->file->uploadContent($project_id, $task_id, $filename, $blob); + return $this->taskFile->uploadContent($task_id, $filename, $blob); } catch (ObjectStorageException $e) { $this->logger->error($e->getMessage()); return false; } } + public function removeTaskFile($file_id) + { + return $this->taskFile->remove($file_id); + } + + public function removeAllTaskFiles($task_id) + { + return $this->taskFile->removeAll($task_id); + } + + // Deprecated procedures + + public function getFile($file_id) + { + return $this->getTaskFile($file_id); + } + + public function getAllFiles($task_id) + { + return $this->getAllTaskFiles($task_id); + } + + public function downloadFile($file_id) + { + return $this->downloadTaskFile($file_id); + } + + public function createFile($project_id, $task_id, $filename, $blob) + { + return $this->createTaskFile($project_id, $task_id, $filename, $blob); + } + public function removeFile($file_id) { - return $this->file->remove($file_id); + return $this->removeTaskFile($file_id); } public function removeAllFiles($task_id) { - return $this->file->removeAll($task_id); + return $this->removeAllTaskFiles($task_id); } } diff --git a/app/Controller/BoardPopover.php b/app/Controller/BoardPopover.php index f2b39d8d..965669ff 100644 --- a/app/Controller/BoardPopover.php +++ b/app/Controller/BoardPopover.php @@ -93,7 +93,7 @@ class BoardPopover extends Base { $task = $this->getTask(); - $this->response->html($this->template->render('file/screenshot', array( + $this->response->html($this->template->render('task_file/screenshot', array( 'task' => $task, ))); } diff --git a/app/Controller/BoardTooltip.php b/app/Controller/BoardTooltip.php index da07ec4e..bc07ce09 100644 --- a/app/Controller/BoardTooltip.php +++ b/app/Controller/BoardTooltip.php @@ -62,7 +62,7 @@ class BoardTooltip extends Base $task = $this->getTask(); $this->response->html($this->template->render('board/tooltip_files', array( - 'files' => $this->file->getAll($task['id']), + 'files' => $this->taskFile->getAll($task['id']), 'task' => $task, ))); } diff --git a/app/Controller/Listing.php b/app/Controller/Listing.php index c7d3d9a8..c784dd50 100644 --- a/app/Controller/Listing.php +++ b/app/Controller/Listing.php @@ -32,6 +32,9 @@ class Listing extends Base $this->response->html($this->helper->layout->app('listing/show', $params + array( 'paginator' => $paginator, + 'categories_list' => $this->category->getList($params['project']['id'], false), + 'users_list' => $this->projectUserRole->getAssignableUsersList($params['project']['id'], false), + 'custom_filters_list' => $this->customFilter->getAll($params['project']['id'], $this->userSession->getId()), ))); } } diff --git a/app/Controller/Task.php b/app/Controller/Task.php index 4db8f86e..98b7a041 100644 --- a/app/Controller/Task.php +++ b/app/Controller/Task.php @@ -66,8 +66,8 @@ class Task extends Base $this->response->html($this->helper->layout->task('task/show', array( 'project' => $this->project->getById($task['project_id']), - 'files' => $this->file->getAllDocuments($task['id']), - 'images' => $this->file->getAllImages($task['id']), + 'files' => $this->taskFile->getAllDocuments($task['id']), + 'images' => $this->taskFile->getAllImages($task['id']), 'comments' => $this->comment->getAll($task['id'], $this->userSession->getCommentSorting()), 'subtasks' => $subtasks, 'links' => $this->taskLink->getAllGroupedByLabel($task['id']), diff --git a/app/Controller/File.php b/app/Controller/TaskFile.php index 4ac45fbd..102fdc5c 100644 --- a/app/Controller/File.php +++ b/app/Controller/TaskFile.php @@ -5,12 +5,12 @@ namespace Kanboard\Controller; use Kanboard\Core\ObjectStorage\ObjectStorageException; /** - * File controller + * File File Controller * * @package controller * @author Frederic Guillot */ -class File extends Base +class TaskFile extends Base { /** * Screenshot @@ -21,12 +21,12 @@ class File extends Base { $task = $this->getTask(); - if ($this->request->isPost() && $this->file->uploadScreenshot($task['project_id'], $task['id'], $this->request->getValue('screenshot')) !== false) { + if ($this->request->isPost() && $this->taskFile->uploadScreenshot($task['id'], $this->request->getValue('screenshot')) !== false) { $this->flash->success(t('Screenshot uploaded successfully.')); return $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])), true); } - $this->response->html($this->helper->layout->task('file/screenshot', array( + $this->response->html($this->helper->layout->task('task_file/screenshot', array( 'task' => $task, ))); } @@ -40,7 +40,7 @@ class File extends Base { $task = $this->getTask(); - $this->response->html($this->helper->layout->task('file/new', array( + $this->response->html($this->helper->layout->task('task_file/new', array( 'task' => $task, 'max_size' => $this->helper->text->phpToBytes(ini_get('upload_max_filesize')), ))); @@ -55,7 +55,7 @@ class File extends Base { $task = $this->getTask(); - if (! $this->file->uploadFiles($task['project_id'], $task['id'], 'files')) { + if (! $this->taskFile->uploadFiles($task['id'], $this->request->getFileInfo('files'))) { $this->flash->failure(t('Unable to upload the file.')); } @@ -71,7 +71,7 @@ class File extends Base { try { $task = $this->getTask(); - $file = $this->file->getById($this->request->getIntegerParam('file_id')); + $file = $this->taskFile->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']))); @@ -92,10 +92,10 @@ class File extends Base public function open() { $task = $this->getTask(); - $file = $this->file->getById($this->request->getIntegerParam('file_id')); + $file = $this->taskFile->getById($this->request->getIntegerParam('file_id')); if ($file['task_id'] == $task['id']) { - $this->response->html($this->template->render('file/open', array( + $this->response->html($this->template->render('task_file/open', array( 'file' => $file, 'task' => $task, ))); @@ -111,10 +111,10 @@ class File extends Base { try { $task = $this->getTask(); - $file = $this->file->getById($this->request->getIntegerParam('file_id')); + $file = $this->taskFile->getById($this->request->getIntegerParam('file_id')); if ($file['task_id'] == $task['id']) { - $this->response->contentType($this->file->getImageMimeType($file['name'])); + $this->response->contentType($this->taskFile->getImageMimeType($file['name'])); $this->objectStorage->output($file['path']); } } catch (ObjectStorageException $e) { @@ -133,18 +133,18 @@ class File extends Base try { $task = $this->getTask(); - $file = $this->file->getById($this->request->getIntegerParam('file_id')); + $file = $this->taskFile->getById($this->request->getIntegerParam('file_id')); if ($file['task_id'] == $task['id']) { - $this->objectStorage->output($this->file->getThumbnailPath($file['path'])); + $this->objectStorage->output($this->taskFile->getThumbnailPath($file['path'])); } } 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'])); + $this->taskFile->generateThumbnailFromData($file['path'], $data); + $this->objectStorage->output($this->taskFile->getThumbnailPath($file['path'])); } } @@ -157,9 +157,9 @@ class File extends Base { $this->checkCSRFParam(); $task = $this->getTask(); - $file = $this->file->getById($this->request->getIntegerParam('file_id')); + $file = $this->taskFile->getById($this->request->getIntegerParam('file_id')); - if ($file['task_id'] == $task['id'] && $this->file->remove($file['id'])) { + if ($file['task_id'] == $task['id'] && $this->taskFile->remove($file['id'])) { $this->flash->success(t('File removed successfully.')); } else { $this->flash->failure(t('Unable to remove this file.')); @@ -176,9 +176,9 @@ class File extends Base public function confirm() { $task = $this->getTask(); - $file = $this->file->getById($this->request->getIntegerParam('file_id')); + $file = $this->taskFile->getById($this->request->getIntegerParam('file_id')); - $this->response->html($this->helper->layout->task('file/remove', array( + $this->response->html($this->helper->layout->task('task_file/remove', array( 'task' => $task, 'file' => $file, ))); diff --git a/app/Core/Base.php b/app/Core/Base.php index ab99fcea..31c07355 100644 --- a/app/Core/Base.php +++ b/app/Core/Base.php @@ -67,7 +67,8 @@ use Pimple\Container; * @property \Kanboard\Model\Config $config * @property \Kanboard\Model\Currency $currency * @property \Kanboard\Model\CustomFilter $customFilter - * @property \Kanboard\Model\File $file + * @property \Kanboard\Model\TaskFile $taskFile + * @property \Kanboard\Model\ProjectFile $projectFile * @property \Kanboard\Model\Group $group * @property \Kanboard\Model\GroupMember $groupMember * @property \Kanboard\Model\LastLogin $lastLogin diff --git a/app/Model/File.php b/app/Model/File.php index 46fc4bb9..e17ecb2b 100644 --- a/app/Model/File.php +++ b/app/Model/File.php @@ -2,31 +2,44 @@ namespace Kanboard\Model; +use Exception; use Kanboard\Event\FileEvent; use Kanboard\Core\Tool; use Kanboard\Core\ObjectStorage\ObjectStorageException; /** - * File model + * Base File Model * * @package model * @author Frederic Guillot */ -class File extends Base +abstract class File extends Base { /** - * SQL table name - * - * @var string - */ - const TABLE = 'files'; - - /** - * Events + * Get PicoDb query to get all files * - * @var string + * @access protected + * @return \PicoDb\Table */ - const EVENT_CREATE = 'file.create'; + protected function getQuery() + { + return $this->db + ->table(static::TABLE) + ->columns( + static::TABLE.'.id', + static::TABLE.'.name', + static::TABLE.'.path', + static::TABLE.'.is_image', + static::TABLE.'.'.static::FOREIGN_KEY, + static::TABLE.'.date', + static::TABLE.'.user_id', + static::TABLE.'.size', + User::TABLE.'.username', + User::TABLE.'.name as user_name' + ) + ->join(User::TABLE, 'id', 'user_id') + ->asc(static::TABLE.'.name'); + } /** * Get a file by the id @@ -37,146 +50,120 @@ class File extends Base */ public function getById($file_id) { - return $this->db->table(self::TABLE)->eq('id', $file_id)->findOne(); + return $this->db->table(static::TABLE)->eq('id', $file_id)->findOne(); } /** - * Remove a file + * Get all files * * @access public - * @param integer $file_id File id - * @return bool + * @param integer $id + * @return array */ - public function remove($file_id) + public function getAll($id) { - try { - $file = $this->getbyId($file_id); - $this->objectStorage->remove($file['path']); - - if ($file['is_image'] == 1) { - $this->objectStorage->remove($this->getThumbnailPath($file['path'])); - } - - return $this->db->table(self::TABLE)->eq('id', $file['id'])->remove(); - } catch (ObjectStorageException $e) { - $this->logger->error($e->getMessage()); - return false; - } + return $this->getQuery()->eq(static::FOREIGN_KEY, $id)->findAll(); } /** - * Remove all files for a given task + * Get all images * * @access public - * @param integer $task_id Task id - * @return bool + * @param integer $id + * @return array */ - public function removeAll($task_id) + public function getAllImages($id) { - $file_ids = $this->db->table(self::TABLE)->eq('task_id', $task_id)->asc('id')->findAllByColumn('id'); - $results = array(); - - foreach ($file_ids as $file_id) { - $results[] = $this->remove($file_id); - } + return $this->getQuery()->eq(static::FOREIGN_KEY, $id)->eq('is_image', 1)->findAll(); + } - return ! in_array(false, $results, true); + /** + * Get all files without images + * + * @access public + * @param integer $id + * @return array + */ + public function getAllDocuments($id) + { + return $this->getQuery()->eq(static::FOREIGN_KEY, $id)->eq('is_image', 0)->findAll(); } /** * Create a file entry in the database * * @access public - * @param integer $task_id Task id + * @param integer $id Foreign key * @param string $name Filename * @param string $path Path on the disk * @param integer $size File size * @return bool|integer */ - public function create($task_id, $name, $path, $size) + public function create($id, $name, $path, $size) { - $result = $this->db->table(self::TABLE)->save(array( - 'task_id' => $task_id, + $values = array( + static::FOREIGN_KEY => $id, 'name' => substr($name, 0, 255), 'path' => $path, 'is_image' => $this->isImage($name) ? 1 : 0, 'size' => $size, 'user_id' => $this->userSession->getId() ?: 0, 'date' => time(), - )); + ); - if ($result) { - $this->container['dispatcher']->dispatch( - self::EVENT_CREATE, - new FileEvent(array('task_id' => $task_id, 'name' => $name)) - ); + $result = $this->db->table(static::TABLE)->insert($values); - return (int) $this->db->getLastId(); + if ($result) { + $file_id = (int) $this->db->getLastId(); + $event = new FileEvent($values + array('file_id' => $file_id)); + $this->dispatcher->dispatch(static::EVENT_CREATE, $event); + return $file_id; } return false; } /** - * Get PicoDb query to get all files + * Remove all files * * @access public - * @return \PicoDb\Table + * @param integer $id + * @return bool */ - public function getQuery() + public function removeAll($id) { - return $this->db - ->table(self::TABLE) - ->columns( - self::TABLE.'.id', - self::TABLE.'.name', - self::TABLE.'.path', - self::TABLE.'.is_image', - self::TABLE.'.task_id', - self::TABLE.'.date', - self::TABLE.'.user_id', - self::TABLE.'.size', - User::TABLE.'.username', - User::TABLE.'.name as user_name' - ) - ->join(User::TABLE, 'id', 'user_id') - ->asc(self::TABLE.'.name'); - } + $file_ids = $this->db->table(static::TABLE)->eq(static::FOREIGN_KEY, $id)->asc('id')->findAllByColumn('id'); + $results = array(); - /** - * Get all files for a given task - * - * @access public - * @param integer $task_id Task id - * @return array - */ - public function getAll($task_id) - { - return $this->getQuery()->eq('task_id', $task_id)->findAll(); - } + foreach ($file_ids as $file_id) { + $results[] = $this->remove($file_id); + } - /** - * Get all images for a given task - * - * @access public - * @param integer $task_id Task id - * @return array - */ - public function getAllImages($task_id) - { - return $this->getQuery()->eq('task_id', $task_id)->eq('is_image', 1)->findAll(); + return ! in_array(false, $results, true); } /** - * Get all files without images for a given task + * Remove a file * * @access public - * @param integer $task_id Task id - * @return array + * @param integer $file_id File id + * @return bool */ - public function getAllDocuments($task_id) + public function remove($file_id) { - return $this->getQuery()->eq('task_id', $task_id)->eq('is_image', 0)->findAll(); + try { + $file = $this->getById($file_id); + $this->objectStorage->remove($file['path']); + + if ($file['is_image'] == 1) { + $this->objectStorage->remove($this->getThumbnailPath($file['path'])); + } + + return $this->db->table(static::TABLE)->eq('id', $file['id'])->remove(); + } catch (ObjectStorageException $e) { + $this->logger->error($e->getMessage()); + return false; + } } /** @@ -226,103 +213,96 @@ class File extends Base } /** - * Generate the path for a new filename + * Generate the path for a thumbnails * * @access public - * @param integer $project_id Project id - * @param integer $task_id Task id - * @param string $filename Filename + * @param string $key Storage key * @return string */ - public function generatePath($project_id, $task_id, $filename) + public function getThumbnailPath($key) { - return $project_id.DIRECTORY_SEPARATOR.$task_id.DIRECTORY_SEPARATOR.hash('sha1', $filename.time()); + return 'thumbnails'.DIRECTORY_SEPARATOR.$key; } /** - * Generate the path for a thumbnails + * Generate the path for a new filename * * @access public - * @param string $key Storage key + * @param integer $id Foreign key + * @param string $filename Filename * @return string */ - public function getThumbnailPath($key) + public function generatePath($id, $filename) { - return 'thumbnails'.DIRECTORY_SEPARATOR.$key; + return static::PATH_PREFIX.DIRECTORY_SEPARATOR.$id.DIRECTORY_SEPARATOR.hash('sha1', $filename.time()); } /** - * Handle file upload + * Upload multiple files * * @access public - * @param integer $project_id Project id - * @param integer $task_id Task id - * @param string $form_name File form name + * @param integer $id + * @param array $files * @return bool */ - public function uploadFiles($project_id, $task_id, $form_name) + public function uploadFiles($id, array $files) { try { - $file = $this->request->getFileInfo($form_name); - - if (empty($file)) { + if (empty($files)) { return false; } - foreach ($file['error'] as $key => $error) { - if ($error == UPLOAD_ERR_OK && $file['size'][$key] > 0) { - $original_filename = $file['name'][$key]; - $uploaded_filename = $file['tmp_name'][$key]; - $destination_filename = $this->generatePath($project_id, $task_id, $original_filename); - - if ($this->isImage($original_filename)) { - $this->generateThumbnailFromFile($uploaded_filename, $destination_filename); - } - - $this->objectStorage->moveUploadedFile($uploaded_filename, $destination_filename); - - $this->create( - $task_id, - $original_filename, - $destination_filename, - $file['size'][$key] - ); - } + foreach (array_keys($files['error']) as $key) { + $file = array( + 'name' => $files['name'][$key], + 'tmp_name' => $files['tmp_name'][$key], + 'size' => $files['size'][$key], + 'error' => $files['error'][$key], + ); + + $this->uploadFile($id, $file); } return true; - } catch (ObjectStorageException $e) { + } catch (Exception $e) { $this->logger->error($e->getMessage()); return false; } } /** - * Handle screenshot upload + * Upload a file * * @access public - * @param integer $project_id Project id - * @param integer $task_id Task id - * @param string $blob Base64 encoded image - * @return bool|integer + * @param integer $id + * @param array $file */ - public function uploadScreenshot($project_id, $task_id, $blob) + public function uploadFile($id, array $file) { - $original_filename = e('Screenshot taken %s', $this->helper->dt->datetime(time())).'.png'; - return $this->uploadContent($project_id, $task_id, $original_filename, $blob); + if ($file['error'] == UPLOAD_ERR_OK && $file['size'] > 0) { + $destination_filename = $this->generatePath($id, $file['name']); + + if ($this->isImage($file['name'])) { + $this->generateThumbnailFromFile($file['tmp_name'], $destination_filename); + } + + $this->objectStorage->moveUploadedFile($file['tmp_name'], $destination_filename); + $this->create($id, $file['name'], $destination_filename, $file['size']); + } else { + throw new Exception('File not uploaded: '.var_export($file['error'], true)); + } } /** * Handle file upload (base64 encoded content) * * @access public - * @param integer $project_id Project id - * @param integer $task_id Task id - * @param string $original_filename Filename - * @param string $blob Base64 encoded file + * @param integer $id + * @param string $original_filename + * @param string $blob * @return bool|integer */ - public function uploadContent($project_id, $task_id, $original_filename, $blob) + public function uploadContent($id, $original_filename, $blob) { try { $data = base64_decode($blob); @@ -331,7 +311,7 @@ class File extends Base return false; } - $destination_filename = $this->generatePath($project_id, $task_id, $original_filename); + $destination_filename = $this->generatePath($id, $original_filename); $this->objectStorage->put($destination_filename, $data); if ($this->isImage($original_filename)) { @@ -339,7 +319,7 @@ class File extends Base } return $this->create( - $task_id, + $id, $original_filename, $destination_filename, strlen($data) diff --git a/app/Model/Notification.php b/app/Model/Notification.php index 87c1a796..c252aa31 100644 --- a/app/Model/Notification.php +++ b/app/Model/Notification.php @@ -72,7 +72,7 @@ class Notification extends Base return e('%s updated a comment on the task #%d', $event_author, $event_data['task']['id']); case Comment::EVENT_CREATE: return e('%s commented on the task #%d', $event_author, $event_data['task']['id']); - case File::EVENT_CREATE: + case TaskFile::EVENT_CREATE: return e('%s attached a file to the task #%d', $event_author, $event_data['task']['id']); case Task::EVENT_USER_MENTION: return e('%s mentioned you in the task #%d', $event_author, $event_data['task']['id']); @@ -94,7 +94,7 @@ class Notification extends Base public function getTitleWithoutAuthor($event_name, array $event_data) { switch ($event_name) { - case File::EVENT_CREATE: + case TaskFile::EVENT_CREATE: return e('New attachment on task #%d: %s', $event_data['file']['task_id'], $event_data['file']['name']); case Comment::EVENT_CREATE: return e('New comment on task #%d', $event_data['comment']['task_id']); diff --git a/app/Model/ProjectFile.php b/app/Model/ProjectFile.php new file mode 100644 index 00000000..aa9bf15b --- /dev/null +++ b/app/Model/ProjectFile.php @@ -0,0 +1,40 @@ +<?php + +namespace Kanboard\Model; + +/** + * Project File Model + * + * @package model + * @author Frederic Guillot + */ +class ProjectFile extends File +{ + /** + * SQL table name + * + * @var string + */ + const TABLE = 'project_has_files'; + + /** + * SQL foreign key + * + * @var string + */ + const FOREIGN_KEY = 'project_id'; + + /** + * Path prefix + * + * @var string + */ + const PATH_PREFIX = 'projects'; + + /** + * Events + * + * @var string + */ + const EVENT_CREATE = 'project.file.create'; +} diff --git a/app/Model/Task.php b/app/Model/Task.php index 38fdd0d5..f8b41b9f 100644 --- a/app/Model/Task.php +++ b/app/Model/Task.php @@ -92,7 +92,7 @@ class Task extends Base return false; } - $this->file->removeAll($task_id); + $this->taskFile->removeAll($task_id); return $this->db->table(self::TABLE)->eq('id', $task_id)->remove(); } diff --git a/app/Model/TaskFile.php b/app/Model/TaskFile.php new file mode 100644 index 00000000..45a3b97f --- /dev/null +++ b/app/Model/TaskFile.php @@ -0,0 +1,54 @@ +<?php + +namespace Kanboard\Model; + +/** + * Task File Model + * + * @package model + * @author Frederic Guillot + */ +class TaskFile extends File +{ + /** + * SQL table name + * + * @var string + */ + const TABLE = 'task_has_files'; + + /** + * SQL foreign key + * + * @var string + */ + const FOREIGN_KEY = 'task_id'; + + /** + * Path prefix + * + * @var string + */ + const PATH_PREFIX = 'tasks'; + + /** + * Events + * + * @var string + */ + const EVENT_CREATE = 'task.file.create'; + + /** + * Handle screenshot upload + * + * @access public + * @param integer $task_id Task id + * @param string $blob Base64 encoded image + * @return bool|integer + */ + public function uploadScreenshot($task_id, $blob) + { + $original_filename = e('Screenshot taken %s', $this->helper->dt->datetime(time())).'.png'; + return $this->uploadContent($task_id, $original_filename, $blob); + } +} diff --git a/app/Model/TaskFinder.php b/app/Model/TaskFinder.php index 4d673097..95ddc12f 100644 --- a/app/Model/TaskFinder.php +++ b/app/Model/TaskFinder.php @@ -89,7 +89,7 @@ class TaskFinder extends Base ->table(Task::TABLE) ->columns( '(SELECT COUNT(*) FROM '.Comment::TABLE.' WHERE task_id=tasks.id) AS nb_comments', - '(SELECT COUNT(*) FROM '.File::TABLE.' WHERE task_id=tasks.id) AS nb_files', + '(SELECT COUNT(*) FROM '.TaskFile::TABLE.' WHERE task_id=tasks.id) AS nb_files', '(SELECT COUNT(*) FROM '.Subtask::TABLE.' WHERE '.Subtask::TABLE.'.task_id=tasks.id) AS nb_subtasks', '(SELECT COUNT(*) FROM '.Subtask::TABLE.' WHERE '.Subtask::TABLE.'.task_id=tasks.id AND status=2) AS nb_completed_subtasks', '(SELECT COUNT(*) FROM '.TaskLink::TABLE.' WHERE '.TaskLink::TABLE.'.task_id = tasks.id) AS nb_links', diff --git a/app/Notification/Mail.php b/app/Notification/Mail.php index d05dbdf2..c924fb50 100644 --- a/app/Notification/Mail.php +++ b/app/Notification/Mail.php @@ -4,7 +4,7 @@ namespace Kanboard\Notification; use Kanboard\Core\Base; use Kanboard\Model\Task; -use Kanboard\Model\File; +use Kanboard\Model\TaskFile; use Kanboard\Model\Comment; use Kanboard\Model\Subtask; @@ -82,7 +82,7 @@ class Mail extends Base implements NotificationInterface public function getMailSubject($event_name, array $event_data) { switch ($event_name) { - case File::EVENT_CREATE: + case TaskFile::EVENT_CREATE: $subject = $this->getStandardMailSubject(e('New attachment'), $event_data); break; case Comment::EVENT_CREATE: diff --git a/app/Schema/Mysql.php b/app/Schema/Mysql.php index 1cebbd22..c85dde6f 100644 --- a/app/Schema/Mysql.php +++ b/app/Schema/Mysql.php @@ -6,7 +6,27 @@ use PDO; use Kanboard\Core\Security\Token; use Kanboard\Core\Security\Role; -const VERSION = 105; +const VERSION = 106; + +function version_106(PDO $pdo) +{ + $pdo->exec('RENAME TABLE files TO task_has_files'); + + $pdo->exec(" + CREATE TABLE project_has_files ( + `id` INT NOT NULL AUTO_INCREMENT, + `project_id` INT NOT NULL, + `name` VARCHAR(255) NOT NULL, + `path` VARCHAR(255) NOT NULL, + `is_image` TINYINT(1) DEFAULT 0, + `size` INT DEFAULT 0 NOT NULL, + `user_id` INT DEFAULT 0 NOT NULL, + `date` INT DEFAULT 0 NOT NULL, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + PRIMARY KEY(id) + ) ENGINE=InnoDB CHARSET=utf8" + ); +} function version_105(PDO $pdo) { diff --git a/app/Schema/Postgres.php b/app/Schema/Postgres.php index b0b89a7c..dc8de510 100644 --- a/app/Schema/Postgres.php +++ b/app/Schema/Postgres.php @@ -6,7 +6,26 @@ use PDO; use Kanboard\Core\Security\Token; use Kanboard\Core\Security\Role; -const VERSION = 85; +const VERSION = 86; + +function version_86(PDO $pdo) +{ + $pdo->exec('ALTER TABLE files RENAME TO task_has_files'); + + $pdo->exec(" + CREATE TABLE project_has_files ( + id SERIAL PRIMARY KEY, + project_id INTEGER NOT NULL, + name VARCHAR(255) NOT NULL, + path VARCHAR(255) NOT NULL, + is_image BOOLEAN DEFAULT '0', + size INTEGER DEFAULT 0 NOT NULL, + user_id INTEGER DEFAULT 0 NOT NULL, + date INTEGER DEFAULT 0 NOT NULL, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE + )" + ); +} function version_85(PDO $pdo) { @@ -17,7 +36,7 @@ function version_84(PDO $pdo) { $pdo->exec(" CREATE TABLE task_has_external_links ( - id SERIAL, + id SERIAL PRIMARY KEY, link_type VARCHAR(100) NOT NULL, dependency VARCHAR(100) NOT NULL, title VARCHAR(255) NOT NULL, diff --git a/app/Schema/Sqlite.php b/app/Schema/Sqlite.php index aa10e58b..e88f621f 100644 --- a/app/Schema/Sqlite.php +++ b/app/Schema/Sqlite.php @@ -6,7 +6,26 @@ use Kanboard\Core\Security\Token; use Kanboard\Core\Security\Role; use PDO; -const VERSION = 97; +const VERSION = 98; + +function version_98(PDO $pdo) +{ + $pdo->exec('ALTER TABLE files RENAME TO task_has_files'); + + $pdo->exec(" + CREATE TABLE project_has_files ( + id INTEGER PRIMARY KEY, + project_id INTEGER NOT NULL, + name TEXT COLLATE NOCASE NOT NULL, + path TEXT NOT NULL, + is_image INTEGER DEFAULT 0, + size INTEGER DEFAULT 0 NOT NULL, + user_id INTEGER DEFAULT 0 NOT NULL, + date INTEGER DEFAULT 0 NOT NULL, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE + )" + ); +} function version_97(PDO $pdo) { diff --git a/app/ServiceProvider/AuthenticationProvider.php b/app/ServiceProvider/AuthenticationProvider.php index c2f7a5c4..cc7b9302 100644 --- a/app/ServiceProvider/AuthenticationProvider.php +++ b/app/ServiceProvider/AuthenticationProvider.php @@ -76,7 +76,7 @@ class AuthenticationProvider implements ServiceProviderInterface $acl->add('Comment', '*', Role::PROJECT_MEMBER); $acl->add('Customfilter', '*', Role::PROJECT_MEMBER); $acl->add('Export', '*', Role::PROJECT_MANAGER); - $acl->add('File', array('screenshot', 'create', 'save', 'remove', 'confirm'), Role::PROJECT_MEMBER); + $acl->add('TaskFile', array('screenshot', 'create', 'save', 'remove', 'confirm'), Role::PROJECT_MEMBER); $acl->add('Gantt', '*', Role::PROJECT_MANAGER); $acl->add('Project', array('share', 'integrations', 'notifications', 'duplicate', 'disable', 'enable', 'remove'), Role::PROJECT_MANAGER); $acl->add('ProjectPermission', '*', Role::PROJECT_MANAGER); diff --git a/app/ServiceProvider/ClassProvider.php b/app/ServiceProvider/ClassProvider.php index 61a4c512..a4fa1ff2 100644 --- a/app/ServiceProvider/ClassProvider.php +++ b/app/ServiceProvider/ClassProvider.php @@ -31,7 +31,6 @@ class ClassProvider implements ServiceProviderInterface 'Config', 'Currency', 'CustomFilter', - 'File', 'Group', 'GroupMember', 'LastLogin', @@ -40,6 +39,7 @@ class ClassProvider implements ServiceProviderInterface 'OverdueNotification', 'PasswordReset', 'Project', + 'ProjectFile', 'ProjectActivity', 'ProjectDuplication', 'ProjectDailyColumnStats', @@ -63,6 +63,7 @@ class ClassProvider implements ServiceProviderInterface 'TaskExport', 'TaskExternalLink', 'TaskFinder', + 'TaskFile', 'TaskFilter', 'TaskLink', 'TaskModification', diff --git a/app/ServiceProvider/RouteProvider.php b/app/ServiceProvider/RouteProvider.php index 5003eb88..c723140e 100644 --- a/app/ServiceProvider/RouteProvider.php +++ b/app/ServiceProvider/RouteProvider.php @@ -94,8 +94,6 @@ class RouteProvider implements ServiceProviderInterface $container['route']->addRoute('public/task/:task_id/:token', 'task', 'readonly'); $container['route']->addRoute('project/:project_id/task/:task_id/activity', 'activity', 'task'); - $container['route']->addRoute('project/:project_id/task/:task_id/screenshot', 'file', 'screenshot'); - $container['route']->addRoute('project/:project_id/task/:task_id/upload', 'file', 'create'); $container['route']->addRoute('project/:project_id/task/:task_id/transitions', 'task', 'transitions'); $container['route']->addRoute('project/:project_id/task/:task_id/analytics', 'task', 'analytics'); $container['route']->addRoute('project/:project_id/task/:task_id/subtasks', 'subtask', 'show'); diff --git a/app/Subscriber/NotificationSubscriber.php b/app/Subscriber/NotificationSubscriber.php index 07660050..651b8a96 100644 --- a/app/Subscriber/NotificationSubscriber.php +++ b/app/Subscriber/NotificationSubscriber.php @@ -6,7 +6,7 @@ use Kanboard\Event\GenericEvent; use Kanboard\Model\Task; use Kanboard\Model\Comment; use Kanboard\Model\Subtask; -use Kanboard\Model\File; +use Kanboard\Model\TaskFile; use Symfony\Component\EventDispatcher\EventSubscriberInterface; class NotificationSubscriber extends BaseSubscriber implements EventSubscriberInterface @@ -28,7 +28,7 @@ class NotificationSubscriber extends BaseSubscriber implements EventSubscriberIn Comment::EVENT_CREATE => 'handleEvent', Comment::EVENT_UPDATE => 'handleEvent', Comment::EVENT_USER_MENTION => 'handleEvent', - File::EVENT_CREATE => 'handleEvent', + TaskFile::EVENT_CREATE => 'handleEvent', ); } diff --git a/app/Template/app/filters_helper.php b/app/Template/app/filters_helper.php index e4cbb942..c16c2251 100644 --- a/app/Template/app/filters_helper.php +++ b/app/Template/app/filters_helper.php @@ -1,6 +1,6 @@ <?= $this->hook->render('template:app:filters-helper:before', isset($project) ? array('project' => $project) : array()) ?> -<div class="dropdown filters"> - <i class="fa fa-caret-down"></i> <a href="#" class="dropdown-menu"><?= t('Filters') ?></a> +<div class="dropdown"> + <a href="#" class="dropdown-menu dropdown-menu-link-icon" title="<?= t('Default filters') ?>"><i class="fa fa-filter fa-fw"></i><i class="fa fa-caret-down"></i></a> <ul> <li><a href="#" class="filter-helper filter-reset" data-filter="<?= isset($reset) ? $reset : '' ?>" title="<?= t('Keyboard shortcut: "%s"', 'r') ?>"><?= t('Reset filters') ?></a></li> <li><a href="#" class="filter-helper" data-filter="status:open assignee:me"><?= t('My tasks') ?></a></li> diff --git a/app/Template/app/overview.php b/app/Template/app/overview.php index ebb3b412..0b354791 100644 --- a/app/Template/app/overview.php +++ b/app/Template/app/overview.php @@ -1,11 +1,10 @@ -<div class="search"> +<div class="filter-box"> <form method="get" action="<?= $this->url->dir() ?>" class="search"> <?= $this->form->hidden('controller', array('controller' => 'search')) ?> <?= $this->form->hidden('action', array('action' => 'index')) ?> <?= $this->form->text('search', array(), array(), array('placeholder="'.t('Search').'"'), 'form-input-large') ?> + <?= $this->render('app/filters_helper') ?> </form> - - <?= $this->render('app/filters_helper') ?> </div> <?= $this->render('app/projects', array('paginator' => $project_paginator, 'user' => $user)) ?> diff --git a/app/Template/board/tooltip_files.php b/app/Template/board/tooltip_files.php index 407309b3..4fa14b57 100644 --- a/app/Template/board/tooltip_files.php +++ b/app/Template/board/tooltip_files.php @@ -8,9 +8,9 @@ </tr> <tr> <td> - <i class="fa fa-download fa-fw"></i><?= $this->url->link(t('download'), 'file', 'download', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'file_id' => $file['id'])) ?> + <i class="fa fa-download fa-fw"></i><?= $this->url->link(t('download'), 'TaskFile', 'download', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'file_id' => $file['id'])) ?> <?php if ($file['is_image'] == 1): ?> - <i class="fa fa-eye"></i> <?= $this->url->link(t('open file'), 'file', 'open', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'file_id' => $file['id']), false, 'popover') ?> + <i class="fa fa-eye"></i> <?= $this->url->link(t('open file'), 'TaskFile', 'open', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'file_id' => $file['id']), false, 'popover') ?> <?php endif ?> </td> </tr> diff --git a/app/Template/board/view_private.php b/app/Template/board/view_private.php index 63d261f6..b5e38c66 100644 --- a/app/Template/board/view_private.php +++ b/app/Template/board/view_private.php @@ -1,6 +1,6 @@ <section id="main"> - <?= $this->render('project/filters', array( + <?= $this->render('project_header/header', array( 'project' => $project, 'filters' => $filters, 'categories_list' => $categories_list, diff --git a/app/Template/calendar/show.php b/app/Template/calendar/show.php index d74e945e..7085b51e 100644 --- a/app/Template/calendar/show.php +++ b/app/Template/calendar/show.php @@ -1,5 +1,5 @@ <section id="main"> - <?= $this->render('project/filters', array( + <?= $this->render('project_header/header', array( 'project' => $project, 'filters' => $filters, )) ?> diff --git a/app/Template/event/file_create.php b/app/Template/event/task_file_create.php index 1a36bc8f..1a36bc8f 100644 --- a/app/Template/event/file_create.php +++ b/app/Template/event/task_file_create.php diff --git a/app/Template/file/open.php b/app/Template/file/open.php deleted file mode 100644 index 3df012b6..00000000 --- a/app/Template/file/open.php +++ /dev/null @@ -1,6 +0,0 @@ -<div class="page-header"> - <h2><?= $this->e($file['name']) ?></h2> - <div class="task-file-viewer"> - <img src="<?= $this->url->href('file', 'image', array('file_id' => $file['id'], 'project_id' => $task['project_id'], 'task_id' => $file['task_id'])) ?>" alt="<?= $this->e($file['name']) ?>"/> - </div> -</div>
\ No newline at end of file diff --git a/app/Template/gantt/project.php b/app/Template/gantt/project.php index 1face3b8..fe193c2b 100644 --- a/app/Template/gantt/project.php +++ b/app/Template/gantt/project.php @@ -1,5 +1,5 @@ <section id="main"> - <?= $this->render('project/filters', array( + <?= $this->render('project_header/header', array( 'project' => $project, 'filters' => $filters, 'users_list' => $users_list, diff --git a/app/Template/listing/show.php b/app/Template/listing/show.php index e7aa5947..9a5992e3 100644 --- a/app/Template/listing/show.php +++ b/app/Template/listing/show.php @@ -1,7 +1,10 @@ <section id="main"> - <?= $this->render('project/filters', array( + <?= $this->render('project_header/header', array( 'project' => $project, 'filters' => $filters, + 'custom_filters_list' => $custom_filters_list, + 'users_list' => $users_list, + 'categories_list' => $categories_list, )) ?> <?php if ($paginator->isEmpty()): ?> diff --git a/app/Template/notification/file_create.php b/app/Template/notification/task_file_create.php index 63f7d1b8..63f7d1b8 100644 --- a/app/Template/notification/file_create.php +++ b/app/Template/notification/task_file_create.php diff --git a/app/Template/project/filters.php b/app/Template/project/filters.php deleted file mode 100644 index 78794e1c..00000000 --- a/app/Template/project/filters.php +++ /dev/null @@ -1,106 +0,0 @@ -<div class="page-header"> - <?= $this->hook->render('template:project:header:before', array('project' => $project)) ?> - - <div class="dropdown"> - <i class="fa fa-caret-down"></i> <a href="#" class="dropdown-menu"><?= t('Actions') ?></a> - <ul> - <?php if (isset($is_board)): ?> - <li> - <span class="filter-display-mode" <?= $this->board->isCollapsed($project['id']) ? '' : 'style="display: none;"' ?>> - <i class="fa fa-expand fa-fw"></i> - <?= $this->url->link(t('Expand tasks'), 'board', 'expand', array('project_id' => $project['id']), false, 'board-display-mode', t('Keyboard shortcut: "%s"', 's')) ?> - </span> - <span class="filter-display-mode" <?= $this->board->isCollapsed($project['id']) ? 'style="display: none;"' : '' ?>> - <i class="fa fa-compress fa-fw"></i> - <?= $this->url->link(t('Collapse tasks'), 'board', 'collapse', array('project_id' => $project['id']), false, 'board-display-mode', t('Keyboard shortcut: "%s"', 's')) ?> - </span> - </li> - <li> - <span class="filter-compact"> - <i class="fa fa-th fa-fw"></i> <a href="#" class="filter-toggle-scrolling" title="<?= t('Keyboard shortcut: "%s"', 'c') ?>"><?= t('Compact view') ?></a> - </span> - <span class="filter-wide" style="display: none"> - <i class="fa fa-arrows-h fa-fw"></i> <a href="#" class="filter-toggle-scrolling" title="<?= t('Keyboard shortcut: "%s"', 'c') ?>"><?= t('Horizontal scrolling') ?></a> - </span> - </li> - <li> - <span class="filter-max-height" style="display: none"> - <i class="fa fa-arrows-v fa-fw"></i> <a href="#" class="filter-toggle-height"><?= t('Set maximum column height') ?></a> - </span> - <span class="filter-min-height"> - <i class="fa fa-arrows-v fa-fw"></i> <a href="#" class="filter-toggle-height"><?= t('Remove maximum column height') ?></a> - </span> - </li> - <?php endif ?> - <?= $this->render('project/dropdown', array('project' => $project)) ?> - </ul> - </div> - <ul class="views"> - <li <?= $filters['controller'] === 'board' ? 'class="active"' : '' ?>> - <i class="fa fa-th fa-fw"></i> - <?= $this->url->link(t('Board'), 'board', 'show', array('project_id' => $project['id'], 'search' => $filters['search']), false, 'view-board', t('Keyboard shortcut: "%s"', 'v b')) ?> - </li> - <li <?= $filters['controller'] === 'calendar' ? 'class="active"' : '' ?>> - <i class="fa fa-calendar fa-fw"></i> - <?= $this->url->link(t('Calendar'), 'calendar', 'show', array('project_id' => $project['id'], 'search' => $filters['search']), false, 'view-calendar', t('Keyboard shortcut: "%s"', 'v c')) ?> - </li> - <li <?= $filters['controller'] === 'listing' ? 'class="active"' : '' ?>> - <i class="fa fa-list fa-fw"></i> - <?= $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> - <?php if ($this->user->hasProjectAccess('gantt', 'project', $project['id'])): ?> - <li <?= $filters['controller'] === 'gantt' ? 'class="active"' : '' ?>> - <i class="fa fa-sliders fa-fw"></i> - <?= $this->url->link(t('Gantt'), 'gantt', 'project', array('project_id' => $project['id'], 'search' => $filters['search']), false, 'view-gantt', t('Keyboard shortcut: "%s"', 'v g')) ?> - </li> - <?php endif ?> - </ul> - <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> - - <div class="filter-dropdowns"> - - <?= $this->render('app/filters_helper', array('reset' => 'status:open', 'project' => $project)) ?> - - <?php if (isset($custom_filters_list) && ! empty($custom_filters_list)): ?> - <div class="dropdown filters"> - <i class="fa fa-caret-down"></i> <a href="#" class="dropdown-menu"><?= t('My filters') ?></a> - <ul> - <?php foreach ($custom_filters_list as $filter): ?> - <li><a href="#" class="filter-helper" data-<?php if ($filter['append']): ?><?= 'append-' ?><?php endif ?>filter='<?= $this->e($filter['filter']) ?>'><?= $this->e($filter['name']) ?></a></li> - <?php endforeach ?> - </ul> - </div> - <?php endif ?> - - <?php if (isset($users_list)): ?> - <div class="dropdown filters"> - <i class="fa fa-caret-down"></i> <a href="#" class="dropdown-menu"><?= t('Users') ?></a> - <ul> - <li><a href="#" class="filter-helper" data-append-filter="assignee:nobody"><?= t('Not assigned') ?></a></li> - <?php foreach ($users_list as $user): ?> - <li><a href="#" class="filter-helper" data-append-filter='assignee:"<?= $this->e($user) ?>"'><?= $this->e($user) ?></a></li> - <?php endforeach ?> - </ul> - </div> - <?php endif ?> - - <?php if (isset($categories_list) && ! empty($categories_list)): ?> - <div class="dropdown filters"> - <i class="fa fa-caret-down"></i> <a href="#" class="dropdown-menu"><?= t('Categories') ?></a> - <ul> - <li><a href="#" class="filter-helper" data-append-filter="category:none"><?= t('No category') ?></a></li> - <?php foreach ($categories_list as $category): ?> - <li><a href="#" class="filter-helper" data-append-filter='category:"<?= $this->e($category) ?>"'><?= $this->e($category) ?></a></li> - <?php endforeach ?> - </ul> - </div> - <?php endif ?> - </div> - - <?= $this->hook->render('template:project:header:after', array('project' => $project)) ?> -</div>
\ No newline at end of file diff --git a/app/Template/project_header/dropdown.php b/app/Template/project_header/dropdown.php new file mode 100644 index 00000000..bbc033bf --- /dev/null +++ b/app/Template/project_header/dropdown.php @@ -0,0 +1,34 @@ +<div class="dropdown"> + <i class="fa fa-caret-down"></i> <a href="#" class="dropdown-menu"><?= t('Actions') ?></a> + <ul> + <?php if ($is_board): ?> + <li> + <span class="filter-display-mode" <?= $this->board->isCollapsed($project['id']) ? '' : 'style="display: none;"' ?>> + <i class="fa fa-expand fa-fw"></i> + <?= $this->url->link(t('Expand tasks'), 'board', 'expand', array('project_id' => $project['id']), false, 'board-display-mode', t('Keyboard shortcut: "%s"', 's')) ?> + </span> + <span class="filter-display-mode" <?= $this->board->isCollapsed($project['id']) ? 'style="display: none;"' : '' ?>> + <i class="fa fa-compress fa-fw"></i> + <?= $this->url->link(t('Collapse tasks'), 'board', 'collapse', array('project_id' => $project['id']), false, 'board-display-mode', t('Keyboard shortcut: "%s"', 's')) ?> + </span> + </li> + <li> + <span class="filter-compact"> + <i class="fa fa-th fa-fw"></i> <a href="#" class="filter-toggle-scrolling" title="<?= t('Keyboard shortcut: "%s"', 'c') ?>"><?= t('Compact view') ?></a> + </span> + <span class="filter-wide" style="display: none"> + <i class="fa fa-arrows-h fa-fw"></i> <a href="#" class="filter-toggle-scrolling" title="<?= t('Keyboard shortcut: "%s"', 'c') ?>"><?= t('Horizontal scrolling') ?></a> + </span> + </li> + <li> + <span class="filter-max-height" style="display: none"> + <i class="fa fa-arrows-v fa-fw"></i> <a href="#" class="filter-toggle-height"><?= t('Set maximum column height') ?></a> + </span> + <span class="filter-min-height"> + <i class="fa fa-arrows-v fa-fw"></i> <a href="#" class="filter-toggle-height"><?= t('Remove maximum column height') ?></a> + </span> + </li> + <?php endif ?> + <?= $this->render('project/dropdown', array('project' => $project)) ?> + </ul> +</div>
\ No newline at end of file diff --git a/app/Template/project_header/header.php b/app/Template/project_header/header.php new file mode 100644 index 00000000..f6e5af9e --- /dev/null +++ b/app/Template/project_header/header.php @@ -0,0 +1,15 @@ +<div class="project-header"> + <?= $this->hook->render('template:project:header:before', array('project' => $project)) ?> + + <?= $this->render('project_header/dropdown', array('project' => $project, 'is_board' => isset($is_board))) ?> + <?= $this->render('project_header/views', array('project' => $project, 'filters' => $filters)) ?> + <?= $this->render('project_header/search', array( + 'project' => $project, + 'filters' => $filters, + 'custom_filters_list' => isset($custom_filters_list) ? $custom_filters_list : array(), + 'users_list' => isset($users_list) ? $users_list : array(), + 'categories_list' => isset($categories_list) ? $categories_list : array(), + )) ?> + + <?= $this->hook->render('template:project:header:after', array('project' => $project)) ?> +</div>
\ No newline at end of file diff --git a/app/Template/project_header/search.php b/app/Template/project_header/search.php new file mode 100644 index 00000000..2b2a2c39 --- /dev/null +++ b/app/Template/project_header/search.php @@ -0,0 +1,45 @@ +<div class="filter-box"> + <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').'"')) ?> + + <?= $this->render('app/filters_helper', array('reset' => 'status:open', 'project' => $project)) ?> + + <?php if (isset($custom_filters_list) && ! empty($custom_filters_list)): ?> + <div class="dropdown"> + <a href="#" class="dropdown-menu dropdown-menu-link-icon" title="<?= t('Custom filters') ?>"><i class="fa fa-bookmark fa-fw"></i><i class="fa fa-caret-down"></i></a> + <ul> + <?php foreach ($custom_filters_list as $filter): ?> + <li><a href="#" class="filter-helper" data-<?php if ($filter['append']): ?><?= 'append-' ?><?php endif ?>filter='<?= $this->e($filter['filter']) ?>'><?= $this->e($filter['name']) ?></a></li> + <?php endforeach ?> + </ul> + </div> + <?php endif ?> + + <?php if (isset($users_list)): ?> + <div class="dropdown"> + <a href="#" class="dropdown-menu dropdown-menu-link-icon" title="<?= t('User filters') ?>"><i class="fa fa-users fa-fw"></i> <i class="fa fa-caret-down"></i></a> + <ul> + <li><a href="#" class="filter-helper" data-append-filter="assignee:nobody"><?= t('Not assigned') ?></a></li> + <?php foreach ($users_list as $user): ?> + <li><a href="#" class="filter-helper" data-append-filter='assignee:"<?= $this->e($user) ?>"'><?= $this->e($user) ?></a></li> + <?php endforeach ?> + </ul> + </div> + <?php endif ?> + + <?php if (isset($categories_list) && ! empty($categories_list)): ?> + <div class="dropdown"> + <a href="#" class="dropdown-menu dropdown-menu-link-icon" title="<?= t('Category filters') ?>"><i class="fa fa-tags fa-fw"></i><i class="fa fa-caret-down"></i></a> + <ul> + <li><a href="#" class="filter-helper" data-append-filter="category:none"><?= t('No category') ?></a></li> + <?php foreach ($categories_list as $category): ?> + <li><a href="#" class="filter-helper" data-append-filter='category:"<?= $this->e($category) ?>"'><?= $this->e($category) ?></a></li> + <?php endforeach ?> + </ul> + </div> + <?php endif ?> + </form> +</div> diff --git a/app/Template/project_header/views.php b/app/Template/project_header/views.php new file mode 100644 index 00000000..1219a213 --- /dev/null +++ b/app/Template/project_header/views.php @@ -0,0 +1,20 @@ +<ul class="views"> + <li <?= $filters['controller'] === 'board' ? 'class="active"' : '' ?>> + <i class="fa fa-th fa-fw"></i> + <?= $this->url->link(t('Board'), 'board', 'show', array('project_id' => $project['id'], 'search' => $filters['search']), false, 'view-board', t('Keyboard shortcut: "%s"', 'v b')) ?> + </li> + <li <?= $filters['controller'] === 'calendar' ? 'class="active"' : '' ?>> + <i class="fa fa-calendar fa-fw"></i> + <?= $this->url->link(t('Calendar'), 'calendar', 'show', array('project_id' => $project['id'], 'search' => $filters['search']), false, 'view-calendar', t('Keyboard shortcut: "%s"', 'v c')) ?> + </li> + <li <?= $filters['controller'] === 'listing' ? 'class="active"' : '' ?>> + <i class="fa fa-list fa-fw"></i> + <?= $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> + <?php if ($this->user->hasProjectAccess('gantt', 'project', $project['id'])): ?> + <li <?= $filters['controller'] === 'gantt' ? 'class="active"' : '' ?>> + <i class="fa fa-sliders fa-fw"></i> + <?= $this->url->link(t('Gantt'), 'gantt', 'project', array('project_id' => $project['id'], 'search' => $filters['search']), false, 'view-gantt', t('Keyboard shortcut: "%s"', 'v g')) ?> + </li> + <?php endif ?> +</ul>
\ No newline at end of file diff --git a/app/Template/search/index.php b/app/Template/search/index.php index 329c072a..9231a6f3 100644 --- a/app/Template/search/index.php +++ b/app/Template/search/index.php @@ -8,14 +8,13 @@ </ul> </div> - <div class="search"> + <div class="filter-box"> <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') ?> + <?= $this->render('app/filters_helper') ?> </form> - - <?= $this->render('app/filters_helper') ?> </div> <?php if (empty($values['search'])): ?> diff --git a/app/Template/task/menu.php b/app/Template/task/menu.php index e997a6ad..cddd930a 100644 --- a/app/Template/task/menu.php +++ b/app/Template/task/menu.php @@ -38,11 +38,11 @@ </li> <li> <i class="fa fa-file fa-fw"></i> - <?= $this->url->link(t('Attach a document'), 'file', 'create', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'popover') ?> + <?= $this->url->link(t('Attach a document'), 'TaskFile', 'create', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'popover') ?> </li> <li> <i class="fa fa-camera fa-fw"></i> - <?= $this->url->link(t('Add a screenshot'), 'file', 'screenshot', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'popover') ?> + <?= $this->url->link(t('Add a screenshot'), 'TaskFile', 'screenshot', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'popover') ?> </li> <li> <i class="fa fa-files-o fa-fw"></i> diff --git a/app/Template/task/show.php b/app/Template/task/show.php index 43906a81..a32232ae 100644 --- a/app/Template/task/show.php +++ b/app/Template/task/show.php @@ -25,7 +25,7 @@ <?= $this->render('task/time_tracking_summary', array('task' => $task)) ?> -<?= $this->render('file/show', array( +<?= $this->render('task_file/show', array( 'task' => $task, 'files' => $files, 'images' => $images diff --git a/app/Template/file/new.php b/app/Template/task_file/new.php index e84ef839..f03ce8dc 100644 --- a/app/Template/file/new.php +++ b/app/Template/task_file/new.php @@ -18,7 +18,7 @@ <div id="file-dropzone" data-max-size="<?= $max_size ?>" - data-url="<?= $this->url->href('file', 'save', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>"> + data-url="<?= $this->url->href('TaskFile', 'save', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>"> <div id="file-dropzone-inner"> <?= t('Drag and drop your files here') ?> <?= t('or') ?> <a href="#" id="file-browser"><?= t('choose files') ?></a> </div> diff --git a/app/Template/task_file/open.php b/app/Template/task_file/open.php new file mode 100644 index 00000000..e3721b59 --- /dev/null +++ b/app/Template/task_file/open.php @@ -0,0 +1,6 @@ +<div class="page-header"> + <h2><?= $this->e($file['name']) ?></h2> + <div class="task-file-viewer"> + <img src="<?= $this->url->href('TaskFile', 'image', array('file_id' => $file['id'], 'project_id' => $task['project_id'], 'task_id' => $file['task_id'])) ?>" alt="<?= $this->e($file['name']) ?>"/> + </div> +</div>
\ No newline at end of file diff --git a/app/Template/file/remove.php b/app/Template/task_file/remove.php index 24844835..5e6c83f2 100644 --- a/app/Template/file/remove.php +++ b/app/Template/task_file/remove.php @@ -8,7 +8,7 @@ </p> <div class="form-actions"> - <?= $this->url->link(t('Yes'), 'file', 'remove', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'file_id' => $file['id']), true, 'btn btn-red') ?> + <?= $this->url->link(t('Yes'), 'TaskFile', 'remove', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'file_id' => $file['id']), true, 'btn btn-red') ?> <?= t('or') ?> <?= $this->url->link(t('cancel'), 'task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'close-popover') ?> </div> diff --git a/app/Template/file/screenshot.php b/app/Template/task_file/screenshot.php index 58b93ac3..72214362 100644 --- a/app/Template/file/screenshot.php +++ b/app/Template/task_file/screenshot.php @@ -6,7 +6,7 @@ <p id="screenshot-inner"><?= t('Take a screenshot and press CTRL+V or ⌘+V to paste here.') ?></p> </div> -<form class="popover-form" action="<?= $this->url->href('file', 'screenshot', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>" method="post"> +<form class="popover-form" action="<?= $this->url->href('TaskFile', 'screenshot', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>" method="post"> <input type="hidden" name="screenshot"/> <?= $this->form->csrf() ?> <div class="form-actions"> diff --git a/app/Template/file/show.php b/app/Template/task_file/show.php index ed006299..98f26c33 100644 --- a/app/Template/file/show.php +++ b/app/Template/task_file/show.php @@ -8,21 +8,21 @@ <div class="file-thumbnails"> <?php foreach ($images as $file): ?> <div class="file-thumbnail"> - <a href="<?= $this->url->href('file', 'open', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'file_id' => $file['id'])) ?>" class="popover"><img src="<?= $this->url->href('file', 'thumbnail', array('file_id' => $file['id'], 'project_id' => $task['project_id'], 'task_id' => $file['task_id'])) ?>" title="<?= $this->e($file['name']) ?>" alt="<?= $this->e($file['name']) ?>"></a> + <a href="<?= $this->url->href('TaskFile', 'open', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'file_id' => $file['id'])) ?>" class="popover"><img src="<?= $this->url->href('TaskFile', 'thumbnail', array('file_id' => $file['id'], 'project_id' => $task['project_id'], 'task_id' => $file['task_id'])) ?>" title="<?= $this->e($file['name']) ?>" alt="<?= $this->e($file['name']) ?>"></a> <div class="file-thumbnail-content"> <div class="file-thumbnail-title"> <div class="dropdown"> <a href="#" class="dropdown-menu dropdown-menu-link-text"><?= $this->e($file['name']) ?> <i class="fa fa-caret-down"></i></a> <ul> - <?php if ($this->user->hasProjectAccess('file', 'remove', $task['project_id'])): ?> + <?php if ($this->user->hasProjectAccess('TaskFile', 'remove', $task['project_id'])): ?> <li> <i class="fa fa-trash fa-fw"></i> - <?= $this->url->link(t('Remove'), 'file', 'confirm', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'file_id' => $file['id']), false, 'popover') ?> + <?= $this->url->link(t('Remove'), 'TaskFile', 'confirm', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'file_id' => $file['id']), false, 'popover') ?> </li> <?php endif ?> <li> <i class="fa fa-download fa-fw"></i> - <?= $this->url->link(t('Download'), 'file', 'download', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'file_id' => $file['id'])) ?> + <?= $this->url->link(t('Download'), 'TaskFile', 'download', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'file_id' => $file['id'])) ?> </li> </ul> </div> @@ -54,15 +54,15 @@ <div class="dropdown"> <a href="#" class="dropdown-menu dropdown-menu-link-text"><?= $this->e($file['name']) ?> <i class="fa fa-caret-down"></i></a> <ul> - <?php if ($this->user->hasProjectAccess('file', 'remove', $task['project_id'])): ?> + <?php if ($this->user->hasProjectAccess('TaskFile', 'remove', $task['project_id'])): ?> <li> <i class="fa fa-trash fa-fw"></i> - <?= $this->url->link(t('Remove'), 'file', 'confirm', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'file_id' => $file['id']), false, 'popover') ?> + <?= $this->url->link(t('Remove'), 'TaskFile', 'confirm', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'file_id' => $file['id']), false, 'popover') ?> </li> <?php endif ?> <li> <i class="fa fa-download fa-fw"></i> - <?= $this->url->link(t('Download'), 'file', 'download', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'file_id' => $file['id'])) ?> + <?= $this->url->link(t('Download'), 'TaskFile', 'download', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'file_id' => $file['id'])) ?> </li> </ul> </div> |