summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorFrederic Guillot <fred@kanboard.net>2016-01-31 17:46:19 -0500
committerFrederic Guillot <fred@kanboard.net>2016-01-31 17:46:19 -0500
commitfc21d3873e3ac63222ad4065a593c715bef4c251 (patch)
treeb1795031eda0774aeb1af9fb20606fe38611e0eb /app
parent1500ff92b2dc3d3932c8e06755bec23f9017e8a4 (diff)
When creating a new project, have the possibility to select another project to duplicate
Diffstat (limited to 'app')
-rw-r--r--app/Console/Base.php25
-rw-r--r--app/Controller/Project.php60
-rw-r--r--app/Controller/ProjectCreation.php126
-rw-r--r--app/Controller/ProjectEdit.php4
-rw-r--r--app/Controller/ProjectPermission.php56
-rw-r--r--app/Controller/Taskduplication.php2
-rw-r--r--app/Core/ExternalLink/ExternalLinkManager.php2
-rw-r--r--app/Model/ProjectDuplication.php140
-rw-r--r--app/Model/Task.php21
-rw-r--r--app/Model/TaskFinder.php17
-rw-r--r--app/ServiceProvider/AuthenticationProvider.php3
-rw-r--r--app/ServiceProvider/RouteProvider.php6
-rw-r--r--app/Template/app/layout.php6
-rw-r--r--app/Template/gantt/projects.php6
-rw-r--r--app/Template/header.php18
-rw-r--r--app/Template/project/duplicate.php6
-rw-r--r--app/Template/project/index.php4
-rw-r--r--app/Template/project/new.php24
-rw-r--r--app/Template/project_creation/create.php42
-rw-r--r--app/Template/project_edit/general.php2
-rw-r--r--app/Template/project_user/layout.php7
21 files changed, 386 insertions, 191 deletions
diff --git a/app/Console/Base.php b/app/Console/Base.php
index 4c5caf73..ac89207d 100644
--- a/app/Console/Base.php
+++ b/app/Console/Base.php
@@ -11,18 +11,19 @@ use Symfony\Component\Console\Command\Command;
* @package console
* @author Frederic Guillot
*
- * @property \Kanboard\Model\Notification $notification
- * @property \Kanboard\Model\Project $project
- * @property \Kanboard\Model\ProjectPermission $projectPermission
- * @property \Kanboard\Model\ProjectAnalytic $projectAnalytic
- * @property \Kanboard\Model\ProjectDailyColumnStats $projectDailyColumnStats
- * @property \Kanboard\Model\ProjectDailyStats $projectDailyStats
- * @property \Kanboard\Model\SubtaskExport $subtaskExport
- * @property \Kanboard\Model\OverdueNotification $overdueNotification
- * @property \Kanboard\Model\Task $task
- * @property \Kanboard\Model\TaskExport $taskExport
- * @property \Kanboard\Model\TaskFinder $taskFinder
- * @property \Kanboard\Model\Transition $transition
+ * @property \Kanboard\Model\Notification $notification
+ * @property \Kanboard\Model\Project $project
+ * @property \Kanboard\Model\ProjectPermission $projectPermission
+ * @property \Kanboard\Model\ProjectAnalytic $projectAnalytic
+ * @property \Kanboard\Model\ProjectDailyColumnStats $projectDailyColumnStats
+ * @property \Kanboard\Model\ProjectDailyStats $projectDailyStats
+ * @property \Kanboard\Model\SubtaskExport $subtaskExport
+ * @property \Kanboard\Model\OverdueNotification $overdueNotification
+ * @property \Kanboard\Model\Task $task
+ * @property \Kanboard\Model\TaskExport $taskExport
+ * @property \Kanboard\Model\TaskFinder $taskFinder
+ * @property \Kanboard\Model\Transition $transition
+ * @property \Symfony\Component\EventDispatcher\EventDispatcher $dispatcher
*/
abstract class Base extends Command
{
diff --git a/app/Controller/Project.php b/app/Controller/Project.php
index ffd62b09..661fd68b 100644
--- a/app/Controller/Project.php
+++ b/app/Controller/Project.php
@@ -171,14 +171,15 @@ class Project extends Base
$project = $this->getProject();
if ($this->request->getStringParam('duplicate') === 'yes') {
- $values = array_keys($this->request->getValues());
- if ($this->projectDuplication->duplicate($project['id'], $values) !== false) {
+ $project_id = $this->projectDuplication->duplicate($project['id'], array_keys($this->request->getValues()), $this->userSession->getId());
+
+ if ($project_id !== false) {
$this->flash->success(t('Project cloned successfully.'));
} else {
$this->flash->failure(t('Unable to clone this project.'));
}
- $this->response->redirect($this->helper->url->to('project', 'index'));
+ $this->response->redirect($this->helper->url->to('project', 'show', array('project_id' => $project_id)));
}
$this->response->html($this->projectLayout('project/duplicate', array(
@@ -240,57 +241,4 @@ class Project extends Base
'title' => t('Project activation')
)));
}
-
- /**
- * Display a form to create a new project
- *
- * @access public
- */
- public function create(array $values = array(), array $errors = array())
- {
- $is_private = isset($values['is_private']) && $values['is_private'] == 1;
-
- $this->response->html($this->template->layout('project/new', array(
- 'board_selector' => $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()),
- 'values' => $values,
- 'errors' => $errors,
- 'is_private' => $is_private,
- 'title' => $is_private ? t('New private project') : t('New project'),
- )));
- }
-
- /**
- * Display a form to create a private project
- *
- * @access public
- */
- public function createPrivate(array $values = array(), array $errors = array())
- {
- $values['is_private'] = 1;
- $this->create($values, $errors);
- }
-
- /**
- * Validate and save a new project
- *
- * @access public
- */
- public function save()
- {
- $values = $this->request->getValues();
- list($valid, $errors) = $this->projectValidator->validateCreation($values);
-
- if ($valid) {
- $project_id = $this->project->create($values, $this->userSession->getId(), true);
-
- if ($project_id > 0) {
- $this->flash->success(t('Your project have been created successfully.'));
- $this->response->redirect($this->helper->url->to('project', 'show', array('project_id' => $project_id)));
- }
-
- $this->flash->failure(t('Unable to create your project.'));
- }
-
- $this->create($values, $errors);
- }
}
diff --git a/app/Controller/ProjectCreation.php b/app/Controller/ProjectCreation.php
new file mode 100644
index 00000000..a3154034
--- /dev/null
+++ b/app/Controller/ProjectCreation.php
@@ -0,0 +1,126 @@
+<?php
+
+namespace Kanboard\Controller;
+
+/**
+ * Project Creation Controller
+ *
+ * @package controller
+ * @author Frederic Guillot
+ */
+class ProjectCreation extends Base
+{
+ /**
+ * Display a form to create a new project
+ *
+ * @access public
+ */
+ public function create(array $values = array(), array $errors = array())
+ {
+ $is_private = isset($values['is_private']) && $values['is_private'] == 1;
+ $projects_list = array(0 => t('Do not duplicate anything')) + $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId());
+
+ $this->response->html($this->template->layout('project_creation/create', array(
+ 'board_selector' => $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()),
+ 'values' => $values,
+ 'errors' => $errors,
+ 'is_private' => $is_private,
+ 'projects_list' => $projects_list,
+ 'title' => $is_private ? t('New private project') : t('New project'),
+ )));
+ }
+
+ /**
+ * Display a form to create a private project
+ *
+ * @access public
+ */
+ public function createPrivate(array $values = array(), array $errors = array())
+ {
+ $values['is_private'] = 1;
+ $this->create($values, $errors);
+ }
+
+ /**
+ * Validate and save a new project
+ *
+ * @access public
+ */
+ public function save()
+ {
+ $values = $this->request->getValues();
+ list($valid, $errors) = $this->projectValidator->validateCreation($values);
+
+ if ($valid) {
+ $project_id = $this->createOrDuplicate($values);
+
+ if ($project_id > 0) {
+ $this->flash->success(t('Your project have been created successfully.'));
+ return $this->response->redirect($this->helper->url->to('project', 'show', array('project_id' => $project_id)));
+ }
+
+ $this->flash->failure(t('Unable to create your project.'));
+ }
+
+ $this->create($values, $errors);
+ }
+
+ /**
+ * Create or duplicate a project
+ *
+ * @access private
+ * @param array $values
+ * @return boolean|integer
+ */
+ private function createOrDuplicate(array $values)
+ {
+ if ($values['src_project_id'] == 0) {
+ return $this->createNewProject($values);
+ }
+
+ return $this->duplicateNewProject($values);
+ }
+
+ /**
+ * Save a new project
+ *
+ * @access private
+ * @param array $values
+ * @return boolean|integer
+ */
+ private function createNewProject(array $values)
+ {
+ $project = array(
+ 'name' => $values['name'],
+ 'is_private' => $values['is_private'],
+ );
+
+ return $this->project->create($project, $this->userSession->getId(), true);
+ }
+
+ /**
+ * Creatte from another project
+ *
+ * @access private
+ * @param array $values
+ * @return boolean|integer
+ */
+ private function duplicateNewProject(array $values)
+ {
+ $selection = array();
+
+ foreach ($this->projectDuplication->getOptionalSelection() as $item) {
+ if (isset($values[$item]) && $values[$item] == 1) {
+ $selection[] = $item;
+ }
+ }
+
+ return $this->projectDuplication->duplicate(
+ $values['src_project_id'],
+ $selection,
+ $this->userSession->getId(),
+ $values['name'],
+ $values['is_private'] == 1
+ );
+ }
+}
diff --git a/app/Controller/ProjectEdit.php b/app/Controller/ProjectEdit.php
index 0dfc7de3..29793c47 100644
--- a/app/Controller/ProjectEdit.php
+++ b/app/Controller/ProjectEdit.php
@@ -89,11 +89,11 @@ class ProjectEdit extends Base
{
if ($redirect === 'edit') {
if (isset($values['is_private'])) {
- if (! $this->helper->user->hasProjectAccess('project', 'create', $project['id'])) {
+ if (! $this->helper->user->hasProjectAccess('ProjectCreation', 'create', $project['id'])) {
unset($values['is_private']);
}
} elseif ($project['is_private'] == 1 && ! isset($values['is_private'])) {
- if ($this->helper->user->hasProjectAccess('project', 'create', $project['id'])) {
+ if ($this->helper->user->hasProjectAccess('ProjectCreation', 'create', $project['id'])) {
$values += array('is_private' => 0);
}
}
diff --git a/app/Controller/ProjectPermission.php b/app/Controller/ProjectPermission.php
index 4434d017..e0e58240 100644
--- a/app/Controller/ProjectPermission.php
+++ b/app/Controller/ProjectPermission.php
@@ -13,6 +13,24 @@ use Kanboard\Core\Security\Role;
class ProjectPermission extends Base
{
/**
+ * Permissions are only available for team projects
+ *
+ * @access protected
+ * @param integer $project_id Default project id
+ * @return array
+ */
+ protected function getProject($project_id = 0)
+ {
+ $project = parent::getProject($project_id);
+
+ if ($project['is_private'] == 1) {
+ $this->forbidden();
+ }
+
+ return $project;
+ }
+
+ /**
* Show all permissions
*
* @access public
@@ -62,6 +80,7 @@ class ProjectPermission extends Base
*/
public function addUser()
{
+ $project = $this->getProject();
$values = $this->request->getValues();
if ($this->projectUserRole->addUser($values['project_id'], $values['user_id'], $values['role'])) {
@@ -70,7 +89,7 @@ class ProjectPermission extends Base
$this->flash->failure(t('Unable to update this project.'));
}
- $this->response->redirect($this->helper->url->to('ProjectPermission', 'index', array('project_id' => $values['project_id'])));
+ $this->response->redirect($this->helper->url->to('ProjectPermission', 'index', array('project_id' => $project['id'])));
}
/**
@@ -81,19 +100,16 @@ class ProjectPermission extends Base
public function removeUser()
{
$this->checkCSRFParam();
+ $project = $this->getProject();
+ $user_id = $this->request->getIntegerParam('user_id');
- $values = array(
- 'project_id' => $this->request->getIntegerParam('project_id'),
- 'user_id' => $this->request->getIntegerParam('user_id'),
- );
-
- if ($this->projectUserRole->removeUser($values['project_id'], $values['user_id'])) {
+ if ($this->projectUserRole->removeUser($project['id'], $user_id)) {
$this->flash->success(t('Project updated successfully.'));
} else {
$this->flash->failure(t('Unable to update this project.'));
}
- $this->response->redirect($this->helper->url->to('ProjectPermission', 'index', array('project_id' => $values['project_id'])));
+ $this->response->redirect($this->helper->url->to('ProjectPermission', 'index', array('project_id' => $project['id'])));
}
/**
@@ -103,10 +119,10 @@ class ProjectPermission extends Base
*/
public function changeUserRole()
{
- $project_id = $this->request->getIntegerParam('project_id');
+ $project = $this->getProject();
$values = $this->request->getJson();
- if (! empty($project_id) && ! empty($values) && $this->projectUserRole->changeUserRole($project_id, $values['id'], $values['role'])) {
+ if (! empty($project) && ! empty($values) && $this->projectUserRole->changeUserRole($project['id'], $values['id'], $values['role'])) {
$this->response->json(array('status' => 'ok'));
} else {
$this->response->json(array('status' => 'error'));
@@ -120,19 +136,20 @@ class ProjectPermission extends Base
*/
public function addGroup()
{
+ $project = $this->getProject();
$values = $this->request->getValues();
if (empty($values['group_id']) && ! empty($values['external_id'])) {
$values['group_id'] = $this->group->create($values['name'], $values['external_id']);
}
- if ($this->projectGroupRole->addGroup($values['project_id'], $values['group_id'], $values['role'])) {
+ if ($this->projectGroupRole->addGroup($project['id'], $values['group_id'], $values['role'])) {
$this->flash->success(t('Project updated successfully.'));
} else {
$this->flash->failure(t('Unable to update this project.'));
}
- $this->response->redirect($this->helper->url->to('ProjectPermission', 'index', array('project_id' => $values['project_id'])));
+ $this->response->redirect($this->helper->url->to('ProjectPermission', 'index', array('project_id' => $project['id'])));
}
/**
@@ -143,19 +160,16 @@ class ProjectPermission extends Base
public function removeGroup()
{
$this->checkCSRFParam();
+ $project = $this->getProject();
+ $group_id = $this->request->getIntegerParam('group_id');
- $values = array(
- 'project_id' => $this->request->getIntegerParam('project_id'),
- 'group_id' => $this->request->getIntegerParam('group_id'),
- );
-
- if ($this->projectGroupRole->removeGroup($values['project_id'], $values['group_id'])) {
+ if ($this->projectGroupRole->removeGroup($project['id'], $group_id)) {
$this->flash->success(t('Project updated successfully.'));
} else {
$this->flash->failure(t('Unable to update this project.'));
}
- $this->response->redirect($this->helper->url->to('ProjectPermission', 'index', array('project_id' => $values['project_id'])));
+ $this->response->redirect($this->helper->url->to('ProjectPermission', 'index', array('project_id' => $project['id'])));
}
/**
@@ -165,10 +179,10 @@ class ProjectPermission extends Base
*/
public function changeGroupRole()
{
- $project_id = $this->request->getIntegerParam('project_id');
+ $project = $this->getProject();
$values = $this->request->getJson();
- if (! empty($project_id) && ! empty($values) && $this->projectGroupRole->changeGroupRole($project_id, $values['id'], $values['role'])) {
+ if (! empty($project) && ! empty($values) && $this->projectGroupRole->changeGroupRole($project['id'], $values['id'], $values['role'])) {
$this->response->json(array('status' => 'ok'));
} else {
$this->response->json(array('status' => 'error'));
diff --git a/app/Controller/Taskduplication.php b/app/Controller/Taskduplication.php
index ae8bfcbc..a41183a7 100644
--- a/app/Controller/Taskduplication.php
+++ b/app/Controller/Taskduplication.php
@@ -109,7 +109,7 @@ class Taskduplication extends Base
private function chooseDestination(array $task, $template)
{
$values = array();
- $projects_list = $this->projectUserRole->getProjectsByUser($this->userSession->getId(), array(ProjectModel::ACTIVE));
+ $projects_list = $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId());
unset($projects_list[$task['project_id']]);
diff --git a/app/Core/ExternalLink/ExternalLinkManager.php b/app/Core/ExternalLink/ExternalLinkManager.php
index cd3476ca..59f36e54 100644
--- a/app/Core/ExternalLink/ExternalLinkManager.php
+++ b/app/Core/ExternalLink/ExternalLinkManager.php
@@ -120,8 +120,6 @@ class ExternalLinkManager extends Base
*/
public function find()
{
- $provider = null;
-
if ($this->userInputType === self::TYPE_AUTO) {
$provider = $this->findProvider();
} else {
diff --git a/app/Model/ProjectDuplication.php b/app/Model/ProjectDuplication.php
index f0c66834..16e4f7c2 100644
--- a/app/Model/ProjectDuplication.php
+++ b/app/Model/ProjectDuplication.php
@@ -2,6 +2,8 @@
namespace Kanboard\Model;
+use Kanboard\Core\Security\Role;
+
/**
* Project Duplication
*
@@ -12,6 +14,28 @@ namespace Kanboard\Model;
class ProjectDuplication extends Base
{
/**
+ * Get list of optional models to duplicate
+ *
+ * @access public
+ * @return array
+ */
+ public function getOptionalSelection()
+ {
+ return array('category', 'projectPermission', 'action', 'swimlane', 'task');
+ }
+
+ /**
+ * Get list of all possible models to duplicate
+ *
+ * @access public
+ * @return array
+ */
+ public function getPossibleSelection()
+ {
+ return array('board', 'category', 'projectPermission', 'action', 'swimlane', 'task');
+ }
+
+ /**
* Get a valid project name for the duplication
*
* @access public
@@ -31,78 +55,106 @@ class ProjectDuplication extends Base
}
/**
- * Create a project from another one
- *
- * @param integer $project_id Project Id
- * @return integer Cloned Project Id
- */
- public function copy($project_id)
- {
- $project = $this->project->getById($project_id);
-
- $values = array(
- 'name' => $this->getClonedProjectName($project['name']),
- 'is_active' => true,
- 'last_modified' => 0,
- 'token' => '',
- 'is_public' => 0,
- 'is_private' => empty($project['is_private']) ? 0 : 1,
- );
-
- if (! $this->db->table(Project::TABLE)->save($values)) {
- return 0;
- }
-
- return $this->db->getLastId();
- }
-
- /**
* Clone a project with all settings
*
- * @param integer $project_id Project Id
- * @param array $part_selection Selection of optional project parts to duplicate. Possible options: 'swimlane', 'action', 'category', 'task'
- * @return integer Cloned Project Id
+ * @param integer $src_project_id Project Id
+ * @param array $selection Selection of optional project parts to duplicate
+ * @param integer $owner_id Owner of the project
+ * @param string $name Name of the project
+ * @param boolean $private Force the project to be private
+ * @return integer Cloned Project Id
*/
- public function duplicate($project_id, $part_selection = array('category', 'action'))
+ public function duplicate($src_project_id, $selection = array('projectPermission', 'category', 'action'), $owner_id = 0, $name = null, $private = null)
{
$this->db->startTransaction();
// Get the cloned project Id
- $clone_project_id = $this->copy($project_id);
+ $dst_project_id = $this->copy($src_project_id, $owner_id, $name, $private);
- if (! $clone_project_id) {
+ if (! $dst_project_id) {
$this->db->cancelTransaction();
return false;
}
// Clone Columns, Categories, Permissions and Actions
- $optional_parts = array('swimlane', 'action', 'category');
- foreach (array('board', 'category', 'projectPermission', 'action', 'swimlane') as $model) {
+ foreach ($this->getPossibleSelection() as $model) {
// Skip if optional part has not been selected
- if (in_array($model, $optional_parts) && ! in_array($model, $part_selection)) {
+ if (in_array($model, $this->getOptionalSelection()) && ! in_array($model, $selection)) {
continue;
}
- if (! $this->$model->duplicate($project_id, $clone_project_id)) {
+ // Skip permissions for private projects
+ if ($private && $model === 'projectPermission') {
+ continue;
+ }
+
+ if (! $this->$model->duplicate($src_project_id, $dst_project_id)) {
$this->db->cancelTransaction();
return false;
}
}
+ if (! $this->makeOwnerManager($dst_project_id, $owner_id)) {
+ $this->db->cancelTransaction();
+ return false;
+ }
+
$this->db->closeTransaction();
- // Clone Tasks if in $part_selection
- if (in_array('task', $part_selection)) {
- $tasks = $this->taskFinder->getAll($project_id);
+ return (int) $dst_project_id;
+ }
+
+ /**
+ * Create a project from another one
+ *
+ * @access private
+ * @param integer $src_project_id
+ * @param integer $owner_id
+ * @param string $name
+ * @param boolean $private
+ * @return integer
+ */
+ private function copy($src_project_id, $owner_id = 0, $name = null, $private = null)
+ {
+ $project = $this->project->getById($src_project_id);
+ $is_private = empty($project['is_private']) ? 0 : 1;
+
+ $values = array(
+ 'name' => $name ?: $this->getClonedProjectName($project['name']),
+ 'is_active' => 1,
+ 'last_modified' => time(),
+ 'token' => '',
+ 'is_public' => 0,
+ 'is_private' => $private ? 1 : $is_private,
+ 'owner_id' => $owner_id,
+ );
+
+ if (! $this->db->table(Project::TABLE)->save($values)) {
+ return false;
+ }
+
+ return $this->db->getLastId();
+ }
+
+ /**
+ * Make sure that the creator of the duplicated project is alsp owner
+ *
+ * @access private
+ * @param integer $dst_project_id
+ * @param integer $owner_id
+ * @return boolean
+ */
+ private function makeOwnerManager($dst_project_id, $owner_id)
+ {
+ if ($owner_id > 0) {
+ $this->projectUserRole->removeUser($dst_project_id, $owner_id);
- foreach ($tasks as $task) {
- if (! $this->taskDuplication->duplicateToProject($task['id'], $clone_project_id)) {
- return false;
- }
+ if (! $this->projectUserRole->addUser($dst_project_id, $owner_id, Role::PROJECT_MANAGER)) {
+ return false;
}
}
- return (int) $clone_project_id;
+ return true;
}
}
diff --git a/app/Model/Task.php b/app/Model/Task.php
index 94b23ec2..38fdd0d5 100644
--- a/app/Model/Task.php
+++ b/app/Model/Task.php
@@ -199,4 +199,25 @@ class Task extends Base
return round(($position * 100) / count($columns), 1);
}
+
+ /**
+ * Helper method to duplicate all tasks to another project
+ *
+ * @access public
+ * @param integer $src_project_id
+ * @param integer $dst_project_id
+ * @return boolean
+ */
+ public function duplicate($src_project_id, $dst_project_id)
+ {
+ $task_ids = $this->taskFinder->getAllIds($src_project_id, array(Task::STATUS_OPEN, Task::STATUS_CLOSED));
+
+ foreach ($task_ids as $task_id) {
+ if (! $this->taskDuplication->duplicateToProject($task_id, $dst_project_id)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
}
diff --git a/app/Model/TaskFinder.php b/app/Model/TaskFinder.php
index ab290bce..4d673097 100644
--- a/app/Model/TaskFinder.php
+++ b/app/Model/TaskFinder.php
@@ -180,6 +180,23 @@ class TaskFinder extends Base
}
/**
+ * Get all tasks for a given project and status
+ *
+ * @access public
+ * @param integer $project_id
+ * @param array $status
+ * @return array
+ */
+ public function getAllIds($project_id, array $status = array(Task::STATUS_OPEN))
+ {
+ return $this->db
+ ->table(Task::TABLE)
+ ->eq(Task::TABLE.'.project_id', $project_id)
+ ->in(Task::TABLE.'.is_active', $status)
+ ->findAllByColumn('id');
+ }
+
+ /**
* Get overdue tasks query
*
* @access public
diff --git a/app/ServiceProvider/AuthenticationProvider.php b/app/ServiceProvider/AuthenticationProvider.php
index 144abb0e..aaf23083 100644
--- a/app/ServiceProvider/AuthenticationProvider.php
+++ b/app/ServiceProvider/AuthenticationProvider.php
@@ -128,8 +128,7 @@ class AuthenticationProvider implements ServiceProviderInterface
$acl->add('Gantt', array('projects', 'saveProjectDate'), Role::APP_MANAGER);
$acl->add('Group', '*', Role::APP_ADMIN);
$acl->add('Link', '*', Role::APP_ADMIN);
- $acl->add('Project', array('users', 'allowEverybody', 'allow', 'role', 'revoke', 'create'), Role::APP_MANAGER);
- $acl->add('ProjectPermission', '*', Role::APP_USER);
+ $acl->add('ProjectCreation', 'create', Role::APP_MANAGER);
$acl->add('Projectuser', '*', Role::APP_MANAGER);
$acl->add('Twofactor', 'disable', Role::APP_ADMIN);
$acl->add('UserImport', '*', Role::APP_ADMIN);
diff --git a/app/ServiceProvider/RouteProvider.php b/app/ServiceProvider/RouteProvider.php
index ebe087ae..78c46bc4 100644
--- a/app/ServiceProvider/RouteProvider.php
+++ b/app/ServiceProvider/RouteProvider.php
@@ -43,10 +43,12 @@ class RouteProvider implements ServiceProviderInterface
$container['route']->addRoute('search', 'search', 'index');
$container['route']->addRoute('search/:search', 'search', 'index');
+ // ProjectCreation routes
+ $container['route']->addRoute('project/create', 'ProjectCreation', 'create');
+ $container['route']->addRoute('project/create/private', 'ProjectCreation', 'createPrivate');
+
// Project routes
$container['route']->addRoute('projects', 'project', 'index');
- $container['route']->addRoute('project/create', 'project', 'create');
- $container['route']->addRoute('project/create/private', 'project', 'createPrivate');
$container['route']->addRoute('project/:project_id', 'project', 'show');
$container['route']->addRoute('p/:project_id', 'project', 'show');
$container['route']->addRoute('project/:project_id/customer-filter', 'customfilter', 'index');
diff --git a/app/Template/app/layout.php b/app/Template/app/layout.php
index ad1d5a9e..d5bb1d8e 100644
--- a/app/Template/app/layout.php
+++ b/app/Template/app/layout.php
@@ -1,15 +1,15 @@
<section id="main">
<div class="page-header page-header-mobile">
<ul>
- <?php if ($this->user->hasAccess('project', 'create')): ?>
+ <?php if ($this->user->hasAccess('ProjectCreation', 'create')): ?>
<li>
<i class="fa fa-plus fa-fw"></i>
- <?= $this->url->link(t('New project'), 'project', 'create') ?>
+ <?= $this->url->link(t('New project'), 'ProjectCreation', 'create') ?>
</li>
<?php endif ?>
<li>
<i class="fa fa-lock fa-fw"></i>
- <?= $this->url->link(t('New private project'), 'project', 'createPrivate') ?>
+ <?= $this->url->link(t('New private project'), 'ProjectCreation', 'createPrivate') ?>
</li>
<li>
<i class="fa fa-search fa-fw"></i>
diff --git a/app/Template/gantt/projects.php b/app/Template/gantt/projects.php
index 46d2af91..84b260bb 100644
--- a/app/Template/gantt/projects.php
+++ b/app/Template/gantt/projects.php
@@ -1,12 +1,6 @@
<section id="main">
<div class="page-header">
<ul>
- <?php if ($this->user->hasAccess('project', 'create')): ?>
- <li><i class="fa fa-plus fa-fw"></i><?= $this->url->link(t('New project'), 'project', 'create') ?></li>
- <?php endif ?>
- <li>
- <i class="fa fa-lock fa-fw"></i><?= $this->url->link(t('New private project'), 'project', 'create', array('private' => 1)) ?>
- </li>
<li>
<i class="fa fa-folder fa-fw"></i><?= $this->url->link(t('Projects list'), 'project', 'index') ?>
</li>
diff --git a/app/Template/header.php b/app/Template/header.php
index 405d07b3..074f8fd3 100644
--- a/app/Template/header.php
+++ b/app/Template/header.php
@@ -31,14 +31,26 @@
</select>
</li>
<?php endif ?>
- <li>
+ <li class="user-links">
<?php if ($this->user->hasNotifications()): ?>
<span class="notification">
<?= $this->url->link('<i class="fa fa-bell web-notification-icon"></i>', 'app', 'notifications', array('user_id' => $this->user->getId()), false, '', t('Unread notifications')) ?>
</span>
<?php endif ?>
- <span class="dropdown">
+ <div class="dropdown">
+ <a href="#" class="dropdown-menu dropdown-menu-link-icon"><i class="fa fa-plus fa-fw"></i><i class="fa fa-caret-down"></i></a>
+ <ul>
+ <?php if ($this->user->hasAccess('ProjectCreation', 'create')): ?>
+ <li><i class="fa fa-plus fa-fw"></i><?= $this->url->link(t('New project'), 'ProjectCreation', 'create') ?></li>
+ <?php endif ?>
+ <li>
+ <i class="fa fa-lock fa-fw"></i><?= $this->url->link(t('New private project'), 'ProjectCreation', 'createPrivate') ?>
+ </li>
+ </ul>
+ </div>
+
+ <div class="dropdown">
<a href="#" class="dropdown-menu dropdown-menu-link-icon"><i class="fa fa-user fa-fw"></i><i class="fa fa-caret-down"></i></a>
<ul>
<li class="no-hover"><strong><?= $this->e($this->user->getFullname()) ?></strong></li>
@@ -46,7 +58,7 @@
<li><?= $this->url->link(t('My profile'), 'user', 'show', array('user_id' => $this->user->getId())) ?></li>
<li><?= $this->url->link(t('Logout'), 'auth', 'logout') ?></li>
</ul>
- </span>
+ </div>
</li>
</ul>
</nav>
diff --git a/app/Template/project/duplicate.php b/app/Template/project/duplicate.php
index 8967c306..ca7d3302 100644
--- a/app/Template/project/duplicate.php
+++ b/app/Template/project/duplicate.php
@@ -10,13 +10,17 @@
<?= $this->form->csrf() ?>
+ <?php if ($project['is_private'] == 0): ?>
+ <?= $this->form->checkbox('projectPermission', t('Permissions'), 1, true) ?>
+ <?php endif ?>
+
<?= $this->form->checkbox('category', t('Categories'), 1, true) ?>
<?= $this->form->checkbox('action', t('Actions'), 1, true) ?>
<?= $this->form->checkbox('swimlane', t('Swimlanes'), 1, false) ?>
<?= $this->form->checkbox('task', t('Tasks'), 1, false) ?>
<div class="form-actions">
- <input type="submit" value="<?= t('Duplicate') ?>" class="btn btn-red"/>
+ <input type="submit" value="<?= t('Duplicate') ?>" class="btn btn-red">
<?= t('or') ?> <?= $this->url->link(t('cancel'), 'project', 'show', array('project_id' => $project['id'])) ?>
</div>
</form>
diff --git a/app/Template/project/index.php b/app/Template/project/index.php
index 3d2a33ea..c5dd267c 100644
--- a/app/Template/project/index.php
+++ b/app/Template/project/index.php
@@ -1,10 +1,6 @@
<section id="main">
<div class="page-header">
<ul>
- <?php if ($this->user->hasAccess('project', 'create')): ?>
- <li><i class="fa fa-plus fa-fw"></i><?= $this->url->link(t('New project'), 'project', 'create') ?></li>
- <?php endif ?>
- <li><i class="fa fa-lock fa-fw"></i><?= $this->url->link(t('New private project'), 'project', 'createPrivate') ?></li>
<?php if ($this->user->hasAccess('projectuser', 'managers')): ?>
<li><i class="fa fa-user fa-fw"></i><?= $this->url->link(t('Users overview'), 'projectuser', 'managers') ?></li>
<?php endif ?>
diff --git a/app/Template/project/new.php b/app/Template/project/new.php
deleted file mode 100644
index 8e4ccfec..00000000
--- a/app/Template/project/new.php
+++ /dev/null
@@ -1,24 +0,0 @@
-<section id="main">
- <div class="page-header">
- <ul>
- <li><i class="fa fa-folder fa-fw"></i><?= $this->url->link(t('All projects'), 'project', 'index') ?></li>
- </ul>
- </div>
- <form method="post" action="<?= $this->url->href('project', 'save') ?>" autocomplete="off">
-
- <?= $this->form->csrf() ?>
- <?= $this->form->hidden('is_private', $values) ?>
- <?= $this->form->label(t('Name'), 'name') ?>
- <?= $this->form->text('name', $values, $errors, array('autofocus', 'required', 'maxlength="50"')) ?>
-
- <div class="form-actions">
- <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
- <?= t('or') ?> <?= $this->url->link(t('cancel'), 'project', 'index') ?>
- </div>
- </form>
- <?php if (isset($is_private) && $is_private): ?>
- <div class="alert alert-info">
- <p><?= t('There is no user management for private projects.') ?></p>
- </div>
- <?php endif ?>
-</section> \ No newline at end of file
diff --git a/app/Template/project_creation/create.php b/app/Template/project_creation/create.php
new file mode 100644
index 00000000..6caa36af
--- /dev/null
+++ b/app/Template/project_creation/create.php
@@ -0,0 +1,42 @@
+<section id="main">
+ <div class="page-header">
+ <ul>
+ <li><i class="fa fa-folder fa-fw"></i><?= $this->url->link(t('All projects'), 'project', 'index') ?></li>
+ </ul>
+ </div>
+ <form class="form-popover" id="project-creation-form" method="post" action="<?= $this->url->href('ProjectCreation', 'save') ?>" autocomplete="off">
+
+ <?= $this->form->csrf() ?>
+ <?= $this->form->hidden('is_private', $values) ?>
+
+ <?= $this->form->label(t('Name'), 'name') ?>
+ <?= $this->form->text('name', $values, $errors, array('autofocus', 'required', 'maxlength="50"')) ?>
+
+ <?php if (count($projects_list) > 1): ?>
+ <?= $this->form->label(t('Create from another project'), 'src_project_id') ?>
+ <?= $this->form->select('src_project_id', $projects_list, $values) ?>
+ <?php endif ?>
+
+ <div class="project-creation-options" <?= isset($values['src_project_id']) && $values['src_project_id'] > 0 ? '' : 'style="display: none"' ?>>
+ <p class="alert"><?= t('Which parts of the project do you want to duplicate?') ?></p>
+
+ <?php if (! $is_private): ?>
+ <?= $this->form->checkbox('projectPermission', t('Permissions'), 1, true) ?>
+ <?php endif ?>
+
+ <?= $this->form->checkbox('category', t('Categories'), 1, true) ?>
+ <?= $this->form->checkbox('action', t('Actions'), 1, true) ?>
+ <?= $this->form->checkbox('swimlane', t('Swimlanes'), 1, true) ?>
+ <?= $this->form->checkbox('task', t('Tasks'), 1, false) ?>
+ </div>
+
+ <div class="form-actions">
+ <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue">
+ </div>
+ </form>
+ <?php if ($is_private): ?>
+ <div class="alert alert-info">
+ <p><?= t('There is no user management for private projects.') ?></p>
+ </div>
+ <?php endif ?>
+</section> \ No newline at end of file
diff --git a/app/Template/project_edit/general.php b/app/Template/project_edit/general.php
index 5caefa2d..28cbb66a 100644
--- a/app/Template/project_edit/general.php
+++ b/app/Template/project_edit/general.php
@@ -24,7 +24,7 @@
<?= $this->form->select('owner_id', $owners, $values, $errors) ?>
</div>
- <?php if ($this->user->hasProjectAccess('project', 'create', $project['id'])): ?>
+ <?php if ($this->user->hasProjectAccess('ProjectCreation', 'create', $project['id'])): ?>
<hr>
<?= $this->form->checkbox('is_private', t('Private project'), 1, $project['is_private'] == 1) ?>
<p class="form-help"><?= t('Private projects do not have users and groups management.') ?></p>
diff --git a/app/Template/project_user/layout.php b/app/Template/project_user/layout.php
index 3a569da4..a87efbff 100644
--- a/app/Template/project_user/layout.php
+++ b/app/Template/project_user/layout.php
@@ -1,13 +1,6 @@
<section id="main">
<div class="page-header">
<ul>
- <?php if ($this->user->hasAccess('project', 'create')): ?>
- <li><i class="fa fa-plus fa-fw"></i><?= $this->url->link(t('New project'), 'project', 'create') ?></li>
- <?php endif ?>
- <li>
- <i class="fa fa-lock fa-fw"></i>
- <?= $this->url->link(t('New private project'), 'project', 'create', array('private' => 1)) ?>
- </li>
<li>
<i class="fa fa-folder fa-fw"></i>
<?= $this->url->link(t('Projects list'), 'project', 'index') ?>