From aa6fdd3544b64eeab3d577c58425e128c809a135 Mon Sep 17 00:00:00 2001
From: Frederic Guillot <fred@kanboard.net>
Date: Sat, 20 Jun 2015 14:34:47 -0400
Subject: Improve automatic action duplication with unit tests and improve
 database schema

---
 app/Model/Action.php   | 123 +++++++++++++++++++++++++++++++++----------------
 app/Model/Board.php    |  13 ++++++
 app/Model/Category.php |   2 +-
 3 files changed, 97 insertions(+), 41 deletions(-)

(limited to 'app/Model')

diff --git a/app/Model/Action.php b/app/Model/Action.php
index 7547e37e..35872bdd 100644
--- a/app/Model/Action.php
+++ b/app/Model/Action.php
@@ -53,7 +53,7 @@ class Action extends Base
             'TaskAssignCategoryColor' => t('Assign automatically a category based on a color'),
             'CommentCreation' => t('Create a comment from an external provider'),
             'TaskCreation' => t('Create a task from an external provider'),
-            'TaskLogMoveAnotherColumn' => t('Add a comment logging moving the task between columns'),
+            'TaskLogMoveAnotherColumn' => t('Add a comment log when moving the task between columns'),
             'TaskAssignUser' => t('Change the assignee based on an external username'),
             'TaskAssignCategoryLabel' => t('Change the category based on an external label'),
             'TaskUpdateStartDate' => t('Automatically update the start date'),
@@ -176,7 +176,6 @@ class Action extends Base
         $params = array();
 
         foreach ($this->getAll() as $action) {
-
             $action = $this->load($action['action_name'], $action['project_id'], $action['event_name']);
             $params += $action->getActionRequiredParameters();
         }
@@ -194,7 +193,10 @@ class Action extends Base
     public function getById($action_id)
     {
         $action = $this->db->table(self::TABLE)->eq('id', $action_id)->findOne();
-        $action['params'] = $this->db->table(self::TABLE_PARAMS)->eq('action_id', $action_id)->findAll();
+
+        if (! empty($action)) {
+            $action['params'] = $this->db->table(self::TABLE_PARAMS)->eq('action_id', $action_id)->findAll();
+        }
 
         return $action;
     }
@@ -286,7 +288,7 @@ class Action extends Base
      * @param  string           $name         Action class name
      * @param  integer          $project_id   Project id
      * @param  string           $event        Event name
-     * @return \Core\Listener                 Action instance
+     * @return \Action\Base
      */
     public function load($name, $project_id, $event)
     {
@@ -295,70 +297,111 @@ class Action extends Base
     }
 
     /**
-     * Copy Actions and related Actions Parameters from a project to another one
+     * Copy actions from a project to another one (skip actions that cannot resolve parameters)
      *
      * @author Antonio Rabelo
-     * @param  integer    $project_from      Project Template
-     * @return integer    $project_to        Project that receives the copy
+     * @param  integer    $src_project_id      Source project id
+     * @return integer    $dst_project_id      Destination project id
      * @return boolean
      */
-    public function duplicate($project_from, $project_to)
+    public function duplicate($src_project_id, $dst_project_id)
     {
-        $actionTemplate = $this->action->getAllByProject($project_from);
+        $actions = $this->action->getAllByProject($src_project_id);
 
-        foreach ($actionTemplate as $action) {
+        foreach ($actions as $action) {
 
-            unset($action['id']);
-            $action['project_id'] = $project_to;
-            $actionParams = $action['params'];
-            unset($action['params']);
+            $this->db->startTransaction();
 
-            if (! $this->db->table(self::TABLE)->save($action)) {
-                return false;
-            }
+            $values = array(
+                'project_id' => $dst_project_id,
+                'event_name' => $action['event_name'],
+                'action_name' => $action['action_name'],
+            );
 
-            $action_clone_id = $this->db->getConnection()->getLastId();
+            if (! $this->db->table(self::TABLE)->insert($values)) {
+                $this->container['logger']->debug('Action::duplicate => unable to create '.$action['action_name']);
+                $this->db->cancelTransaction();
+                continue;
+            }
 
-            foreach ($actionParams as $param) {
-                unset($param['id']);
-                $param['value'] = $this->resolveDuplicatedParameters($param, $project_to);
-                $param['action_id'] = $action_clone_id;
+            $action_id = $this->db->getConnection()->getLastId();
 
-                if (! $this->db->table(self::TABLE_PARAMS)->save($param)) {
-                    return false;
-                }
+            if (! $this->duplicateParameters($dst_project_id, $action_id, $action['params'])) {
+                $this->container['logger']->debug('Action::duplicate => unable to copy parameters for '.$action['action_name']);
+                $this->db->cancelTransaction();
+                continue;
             }
+
+            $this->db->closeTransaction();
         }
 
-        // $this->container['fileCache']->remove('proxy_action_getAll');
+        return true;
+    }
+
+    /**
+     * Duplicate action parameters
+     *
+     * @access public
+     * @param  integer  $project_id
+     * @param  integer  $action_id
+     * @param  array    $params
+     * @return boolean
+     */
+    public function duplicateParameters($project_id, $action_id, array $params)
+    {
+        foreach ($params as $param) {
+
+            $value = $this->resolveParameters($param, $project_id);
+
+            if ($value === false) {
+                $this->container['logger']->debug('Action::duplicateParameters => unable to resolve '.$param['name'].'='.$param['value']);
+                return false;
+            }
+
+            $values = array(
+                'action_id' => $action_id,
+                'name' => $param['name'],
+                'value' => $value,
+            );
+
+            if (! $this->db->table(self::TABLE_PARAMS)->insert($values)) {
+                return false;
+            }
+        }
 
         return true;
     }
 
     /**
-     * Resolve type of action value from a project to the respective value in another project
+     * Resolve action parameter values according to another project
      *
      * @author Antonio Rabelo
-     * @param  integer    $param             An action parameter
-     * @return integer    $project_to        Project to find the corresponding values
-     * @return mixed                         The corresponding values from $project_to
+     * @access public
+     * @param  array      $param             Action parameter
+     * @param  integer    $project_id        Project to find the corresponding values
+     * @return mixed
      */
-    private function resolveDuplicatedParameters($param, $project_to)
+    public function resolveParameters(array $param, $project_id)
     {
-        switch($param['name']) {
+        switch ($param['name']) {
             case 'project_id':
-                return $project_to;
+                return $project_id;
             case 'category_id':
-                $categoryTemplate = $this->category->getById($param['value']);
-                $categoryFromNewProject = $this->db->table(Category::TABLE)->eq('project_id', $project_to)->eq('name', $categoryTemplate['name'])->findOne();
-                return $categoryFromNewProject['id'];
+                return $this->category->getIdByName($project_id, $this->category->getNameById($param['value'])) ?: false;
             case 'src_column_id':
             case 'dest_column_id':
+            case 'dst_column_id':
             case 'column_id':
-                $boardTemplate = $this->board->getColumn($param['value']);
-                $boardFromNewProject = $this->db->table(Board::TABLE)->eq('project_id', $project_to)->eq('title', $boardTemplate['title'])->findOne();
-                return $boardFromNewProject['id'];
-            // TODO: Add user_id
+                $column = $this->board->getColumn($param['value']);
+
+                if (empty($column)) {
+                    return false;
+                }
+
+                return $this->board->getColumnIdByTitle($project_id, $column['title']) ?: false;
+            case 'user_id':
+            case 'owner_id':
+                return $this->projectPermission->isMember($project_id, $param['value']) ? $param['value'] : false;
             default:
                 return $param['value'];
         }
diff --git a/app/Model/Board.php b/app/Model/Board.php
index eecbc91c..f6f968f4 100644
--- a/app/Model/Board.php
+++ b/app/Model/Board.php
@@ -376,6 +376,19 @@ class Board extends Base
         return $this->db->table(self::TABLE)->eq('id', $column_id)->findOne();
     }
 
+    /**
+     * Get a column id by the name
+     *
+     * @access public
+     * @param  integer  $project_id
+     * @param  string   $title
+     * @return integer
+     */
+    public function getColumnIdByTitle($project_id, $title)
+    {
+        return (int) $this->db->table(self::TABLE)->eq('project_id', $project_id)->eq('title', $title)->findOneColumn('id');
+    }
+
     /**
      * Get the position of the last column for a given project
      *
diff --git a/app/Model/Category.php b/app/Model/Category.php
index c8ba7251..9f93e7be 100644
--- a/app/Model/Category.php
+++ b/app/Model/Category.php
@@ -58,7 +58,7 @@ class Category extends Base
     }
 
     /**
-     * Get a category id by the project and the name
+     * Get a category id by the category name and project id
      *
      * @access public
      * @param  integer   $project_id      Project id
-- 
cgit v1.2.3