<?php
class testcaseTao extends testcaseModel
{
    /**
     * 获取用例的基础数据。
     * Fetch base info of a case.
     *
     * @param  int       $caseID
     * @access protected
     * @return object|false
     */
    protected function fetchBaseInfo($caseID)
    {
        return $this->dao->select('*')->from(TABLE_CASE)->where('id')->eq($caseID)->fetch();
    }

    /**
     * 查询场景名称。
     * Fetch scene name.
     *
     * @param  int       $sceneID
     * @access protected
     * @return void
     */
    protected function fetchSceneName($sceneID)
    {
        return $this->dao->findByID($sceneID)->from(TABLE_SCENE)->fetch('title');
    }

    /**
     * 通过用例的 id 列表查询步骤。
     * Fetch step by id list.
     *
     * @param  array     $caseIdList
     * @access protected
     * @return array
     */
    protected function fetchStepsByList($caseIdList)
    {
        return $this->dao->select('t2.*')->from(TABLE_CASE)->alias('t1')
            ->leftJoin(TABLE_CASESTEP)->alias('t2')->on('t1.id = t2.case')
            ->where('t1.id')->in($caseIdList)
            ->andWhere('t1.version=t2.version')
            ->orderBy('t2.id')
            ->fetchGroup('case');
    }

    /**
     * 获取某个项目下某个模块的用例列表。
     * Get project cases of a module.
     *
     * @param  int|array  $productID
     * @param  int|string $branch
     * @param  int        $moduleIdList
     * @param  string     $browseType
     * @param  string     $auto   no|unit
     * @param  string     $caseType
     * @param  string     $orderBy
     * @param  object     $pager
     * @access protected
     * @return array
     */
    protected function getModuleProjectCases($productID, $branch = 0, $moduleIdList = 0, $browseType = '', $auto = 'no', $caseType = '', $orderBy = 'id_desc', $pager = null)
    {
        return $this->dao->select('distinct t1.*, t2.*, t4.title AS storyTitle')->from(TABLE_PROJECTCASE)->alias('t1')
            ->leftJoin(TABLE_CASE)->alias('t2')->on('t1.case = t2.id')
            ->leftJoin(TABLE_PROJECTSTORY)->alias('t3')->on('t3.story = t2.story')
            ->leftJoin(TABLE_STORY)->alias('t4')->on('t3.story = t4.id')
            ->where('t1.project')->eq((int)$this->session->project)
            ->beginIF(!empty($productID))->andWhere('t2.product')->in($productID)->fi()
            ->beginIF(!empty($productID) && $branch !== 'all')->andWhere('t2.branch')->eq($branch)->fi()
            ->beginIF($moduleIdList)->andWhere('t2.module')->in($moduleIdList)->fi()
            ->beginIF($browseType == 'all')->andWhere('t2.scene')->eq(0)->fi()
            ->beginIF($browseType == 'wait')->andWhere('t2.status')->eq($browseType)->fi()
            ->beginIF($auto == 'auto' || $auto == 'unit')->andWhere('t2.auto')->eq($auto)->fi()
            ->beginIF($auto != 'auto' && $auto != 'unit')->andWhere('t2.auto')->ne('unit')->fi()
            ->beginIF($caseType)->andWhere('t2.type')->eq($caseType)->fi()
            ->andWhere('t2.deleted')->eq('0')
            ->orderBy($orderBy)
            ->page($pager, 't1.`case`')
            ->fetchAll('id');
    }

    /**
     * 获取待确认的用例列表。
     * Get need confirm case list.
     *
     * @param  int|array  $productID
     * @param  string|int $branch
     * @param  array      $modules
     * @param  string     $auto
     * @param  string     $caseType
     * @param  string     $sort
     * @param  object     $pager
     * @access protected
     * @return array
     */
    protected function getNeedConfirmList($productID, $branch, $modules, $auto, $caseType, $sort, $pager = null)
    {
        return $this->dao->select('distinct t1.*, t2.title AS storyTitle')->from(TABLE_CASE)->alias('t1')
            ->leftJoin(TABLE_STORY)->alias('t2')->on('t1.story = t2.id')
            ->leftJoin(TABLE_PROJECTCASE)->alias('t3')->on('t1.id = t3.case')
            ->where('t2.status')->eq('active')
            ->andWhere('t1.deleted')->eq('0')
            ->andWhere('t2.version > t1.storyVersion')
            ->beginIF(!empty($productID))->andWhere('t1.product')->in($productID)->fi()
            ->beginIF(!empty($productID) && $branch !== 'all')->andWhere('t1.branch')->eq($branch)->fi()
            ->beginIF($this->app->tab == 'project')->andWhere('t3.project')->eq($this->session->project)->fi()
            ->beginIF($modules)->andWhere('t1.module')->in($modules)->fi()
            ->beginIF($auto == 'auto' || $auto == 'unit')->andWhere('t1.auto')->eq($auto)->fi()
            ->beginIF($auto != 'auto' && $auto != 'unit')->andWhere('t1.auto')->ne('unit')->fi()
            ->beginIF($caseType)->andWhere('t1.type')->eq($caseType)->fi()
            ->orderBy($sort)
            ->page($pager, 't1.id')
            ->fetchAll();
    }

    /**
     * 根据套件获取用例。
     * Get cases by suite.
     *
     * @param  int         $productID
     * @param  int|string  $branch
     * @param  int         $suiteID
     * @param  array       $moduleIdList
     * @param  string      $auto    no|unit
     * @param  string      $orderBy
     * @param  object      $pager
     * @access public
     * @return array
     */
    public function getBySuite($productID, $branch = 0, $suiteID = 0, $moduleIdList = 0, $auto = 'no', $orderBy = 'id_desc', $pager = null)
    {
        return $this->dao->select('t1.*, t2.title AS storyTitle, t3.version AS version')->from(TABLE_CASE)->alias('t1')
            ->leftJoin(TABLE_STORY)->alias('t2')->on('t1.story = t2.id')
            ->leftJoin(TABLE_SUITECASE)->alias('t3')->on('t1.id = t3.case')
            ->where('t1.product')->eq($productID)
            ->beginIF($branch !== 'all')->andWhere('t1.branch')->eq($branch)->fi()
            ->beginIF($this->app->tab == 'project')->andWhere('t1.project')->eq($this->session->project)->fi()
            ->andWhere('t3.suite')->eq($suiteID)
            ->beginIF($moduleIdList)->andWhere('t1.module')->in($moduleIdList)->fi()
            ->beginIF($auto == 'auto' || $auto == 'unit')->andWhere('t1.auto')->eq($auto)->fi()
            ->beginIF($auto != 'auto' && $auto != 'unit')->andWhere('t1.auto')->ne('unit')->fi()
            ->andWhere('t1.deleted')->eq('0')
            ->orderBy($orderBy)
            ->page($pager)
            ->fetchAll('id');
    }

    /**
     * 插入用例的步骤。
     * Insert the steps of the case.
     *
     * @param  int       $caseID
     * @param  array     $steps
     * @param  array     $expects
     * @param  array     $stepTypes
     * @access protected
     * @return void
     * @param int $version
     */
    protected function insertSteps($caseID, $steps, $expects, $stepTypes, $version = 1)
    {
        $preGrade     = 0;
        $parentStepID = $grandPaStepID = 0;
        foreach($steps as $stepKey => $stepDesc)
        {
            /* 跳过步骤描述为空的步骤。 */
            /* If step desc is empty, skip it. */
            if(empty($stepDesc)) continue;

            /* 计算步骤类型和层级。 */
            /* Set step type and step grade. */
            $stepType = $stepTypes[$stepKey];
            $grade    = substr_count((string)$stepKey, '.');

            /* 如果当前步骤层级为0，父ID和祖父ID清0。 */
            /* If step grade is zero, set parent step id and grand step id to zero. */
            if($grade == 0)
            {
                $parentStepID = $grandPaStepID = 0;
            }
            /* 如果前一个步骤的层级比当前步骤的层级大，将父ID设置为祖父ID，祖父ID清0。 */
            /* If previous step grade is greater than current step grade, set parent step id to grand step id, and set grand step id to zero. */
            elseif($preGrade > $grade)
            {
                $parentStepID  = $grandPaStepID;
                $grandPaStepID = 0;
            }

            /* 构建步骤数据，插入步骤。 */
            /* Build step data, and insert it. */
            $step = new stdClass();
            $step->type    = $stepType;
            $step->parent  = $parentStepID;
            $step->case    = $caseID;
            $step->version = $version;
            $step->desc    = rtrim(htmlSpecialString($stepDesc));
            $step->expect  = $stepType == 'group' ? '' : rtrim(htmlSpecialString(zget($expects, $stepKey, '')));

            $this->dao->insert(TABLE_CASESTEP)->data($step)->autoCheck()->exec();

            /* 如果步骤类型是group，将祖父ID设置为父ID，父ID设置为当前步骤ID。 */
            /* If step type is group, set grand step id to parent step id and set parent step id to current step id. */
            if($stepType == 'group')
            {
                $grandPaStepID = $parentStepID;
                $parentStepID  = $this->dao->lastInsertID();
            }

            $preGrade = $grade;
        }
    }

    /**
     * 获取用例步骤。
     * Get case steps.
     *
     * @param  int    $caseID
     * @param  int    $version
     * @access public
     * @return array
     */
    public function getSteps($caseID, $version)
    {
        $steps = $this->dao->select('`id`,`desc`,`expect`,`type`,`parent`')->from(TABLE_CASESTEP)->where('`case`')->eq($caseID)->andWhere('version')->eq($version)->orderBy('id')->fetchAll('id');
        return $this->processSteps($steps);
    }

    /**
     * 处理用例步骤。
     * Process case steps.
     *
     * @param  array  $steps
     * @access public
     * @return array
     */
    public function processSteps($steps)
    {
        $caseSteps     = array();
        $preGrade      = 1;
        $parentSteps   = array();
        $key           = array(0, 0, 0);
        foreach($steps as $step)
        {
            $parentSteps[$step->id] = $step->parent;
            $grade = 1;
            if(isset($parentSteps[$step->parent])) $grade = isset($parentSteps[$parentSteps[$step->parent]]) ? 3 : 2;

            if($grade > $preGrade)
            {
                $key[$grade - 1] = 1;
            }
            else
            {
                if($grade < $preGrade)
                {
                    if($grade < 2) $key[1] = 0;
                    if($grade < 3) $key[2] = 0;
                }
                $key[$grade - 1] ++;
            }

            $name = implode('.', $key);
            $name = str_replace('.0', '', $name);

            $data = new stdclass();
            $data->name    = str_replace('.0', '', $name);
            $data->id      = $step->id;
            $data->step    = $step->desc;
            $data->desc    = $step->desc;
            $data->expect  = $step->expect;
            $data->type    = $step->type;
            $data->parent  = $step->parent;
            $data->grade   = $grade;

            $caseSteps[] = $data;

            $preGrade = $grade;
        }

        return $caseSteps;
    }

    /**
     * 获取相关的需求。
     * Get related stories.
     *
     * @param  array  $cases
     * @access public
     * @return array
     */
    protected function getRelatedStories($cases)
    {
        $relatedStoryIdList = array();
        foreach($cases as $case) $relatedStoryIdList[$case->story] = $case->story;

        return $this->dao->select('id, title')->from(TABLE_STORY)->where('id')->in($relatedStoryIdList)->fetchPairs();
    }

    /**
     * 获取相关的用例。
     * Get related cases.
     *
     * @param  array  $cases
     * @access public
     * @return array
     */
    protected function getRelatedCases($cases)
    {
        $relatedCaseIdList  = array();
        foreach($cases as $case)
        {
            $linkCases = explode(',', $case->linkCase);
            foreach($linkCases as $linkCaseID)
            {
                if($linkCaseID) $relatedCaseIdList[$linkCaseID] = trim($linkCaseID);
            }
        }

        return $this->dao->select('id, title')->from(TABLE_CASE)->where('id')->in($relatedCaseIdList)->fetchPairs();
    }

    /**
     * 获取相关的步骤。
     * Get related steps.
     *
     * @param  array  $caseIdList
     * @access public
     * @return array
     */
    protected function getRelatedSteps($caseIdList)
    {
        return $this->dao->select('DISTINCT t2.id, t2.parent, t2.`case`, t2.version, t2.type, t2.`desc`, t2.expect')
            ->from(TABLE_CASE)->alias('t1')
            ->leftJoin(TABLE_CASESTEP)->alias('t2')->on('t1.id = t2.`case` and t1.version = t2.version')
            ->where('t1.id')->in($caseIdList)
            ->orderBy('t2.id')
            ->fetchGroup('case', 'id');
    }

    /**
     * 获取相关的附件。
     * Get related files.
     *
     * @param  array  $caseIdList
     * @access public
     * @return array
     */
    protected function getRelatedFiles($caseIdList)
    {
        return $this->dao->select('*')->from(TABLE_FILE)->where('objectType')->eq('testcase')->andWhere('objectID')->in($caseIdList)->andWhere('extra')->ne('editor')->fetchGroup('objectID', 'id');
    }

    /**
     * 创建一个测试用例。
     * Create a test case.
     *
     * @param  object    $case
     * @access protected
     * @return bool
     */
    protected function doCreate($case)
    {
        if(empty($case->product)) $this->config->testcase->create->requiredFields = str_replace('story', '', $this->config->testcase->create->requiredFields);
        if(!empty($case->lib)) $this->config->testcase->create->requiredFields = str_replace('product', '', $this->config->testcase->create->requiredFields);

        $this->dao->insert(TABLE_CASE)->data($case, 'steps,expects,files,labels,stepType,needReview,scriptFile,scriptName')
            ->autoCheck()
            ->batchCheck($this->config->testcase->create->requiredFields, 'notempty')
            ->checkFlow()
            ->exec();
         return !dao::isError();
    }

    /**
     * 创建一个测试用例名称和前置条件。
     * Create a test case spec.
     *
     * @param  int          $caseID
     * @param  object       $case
     * @param  array|string $files
     * @access protected
     * @return void
     */
    protected function doCreateSpec($caseID, $case, $files = array())
    {
        if(empty($caseID)) return;

        $spec               = new stdclass();
        $spec->case         = $caseID;
        $spec->version      = zget($case, 'version', 1);
        $spec->title        = $case->title;
        $spec->precondition = zget($case, 'precondition', '');
        $spec->files        = is_string($files) ? $files : implode(',', array_keys($files));

        $this->dao->insert(TABLE_CASESPEC)->data($spec)->exec();
    }

    /**
     * 更新一个测试用例。
     * Update a test case.
     *
     * @param  object    $case
     * @access protected
     * @return bool
     */
    protected function doUpdate($case)
    {
        /* Remove the require field named story when the case is a lib case.*/
        $requiredFields = $this->config->testcase->edit->requiredFields;
        if(!empty($case->lib)) $requiredFields = str_replace(',story,', ',', ",$requiredFields,");

        $this->dao->update(TABLE_CASE)->data($case, 'deleteFiles,uid,stepChanged,comment,steps,expects,stepType,linkBug,renameFiles,addedFiles,files')
            ->autoCheck()
            ->batchCheck($requiredFields, 'notempty')
            ->checkFlow()
            ->where('id')->eq((int)$case->id)
            ->exec();
         return !dao::isError();
    }

    /*
     * 处理用例和项目的关系。
     * Deal with the relationship between the case and project when edit the case.
     *
     * @param  object  $oldCase
     * @param  object  $case
     * @access public
     * @return void
     */
    /**
     * @param object $oldCase
     * @param object $case
     */
    protected function updateCase2Project($oldCase, $case)
    {
        $productChanged = $oldCase->product != $case->product;
        $storyChanged   = $oldCase->story   != zget($case, 'story', $oldCase->story);

        if(!$productChanged && !$storyChanged) return true;

        if($productChanged) $this->dao->update(TABLE_PROJECTCASE)->set('product')->eq($case->product)->set('version')->eq($case->version)->where('`case`')->eq($oldCase->id)->exec();

        if($storyChanged)
        {
            /* 取消之前需求对应项目和用例的关联关系。*/
            /* If the new related story isn't linked the project, unlink the case. */
            $projects = $this->dao->select('project')->from(TABLE_PROJECTSTORY)->where('story')->eq($oldCase->story)->fetchAll('project');
            $this->dao->delete()->from(TABLE_PROJECTCASE)->where('project')->in(array_keys($projects))->andWhere('`case`')->eq($oldCase->id)->exec();

            /* 设置需求对应项目和用例的关联关系。*/
            /* If the new related story is not null, make the case link the project which link the new related story. */
            if(!empty($case->story))
            {
                $projects = $this->dao->select('project')->from(TABLE_PROJECTSTORY)->where('story')->eq($case->story)->fetchAll('project');
                if($projects)
                {
                    $projects   = array_keys($projects);
                    $lastOrders = $this->dao->select('project, MAX(`order`) AS lastOrder')->from(TABLE_PROJECTCASE)->where('project')->in($projects)->groupBy('project')->fetchPairs();

                    foreach($projects as $projectID)
                    {
                        $lastOrder = isset($lastOrders[$projectID]) ? $lastOrders[$projectID] : 0;

                        $data = new stdclass();
                        $data->project = $projectID;
                        $data->product = $case->product;
                        $data->case    = $oldCase->id;
                        $data->version = $oldCase->version;
                        $data->order   = ++ $lastOrder;

                        $this->dao->replace(TABLE_PROJECTCASE)->data($data)->autoCheck()->exec();
                    }
                }
            }
        }

        return !dao::isError();
    }

    /**
     * 更新用例步骤。
     * Update step.
     *
     * @param  object    $case
     * @param  object    $oldCase
     * @access protected
     * @return bool
     */
    protected function updateStep($case, $oldCase)
    {
        if($oldCase->lib && empty($oldCase->product))
        {
            $fromcaseVersion = $this->dao->select('fromCaseVersion')->from(TABLE_CASE)->where('fromCaseID')->eq($case->id)->fetch('fromCaseVersion');
            $fromcaseVersion = (int)$fromcaseVersion + 1;
            $this->dao->update(TABLE_CASE)->set('`fromCaseVersion`')->eq($fromcaseVersion)->where('`fromCaseID`')->eq($case->id)->exec();
        }

        if($case->steps)
        {
            $this->insertSteps($oldCase->id, $case->steps, $case->expects, (array)$case->stepType, $case->version);
        }
        else
        {
            foreach($oldCase->steps as $step)
            {
                unset($step->id);
                unset($step->name);
                unset($step->step);
                unset($step->grade);

                $step->case    = $oldCase->id;
                $step->version = $case->version;
                $this->dao->insert(TABLE_CASESTEP)->data($step)->autoCheck()->exec();
            }
        }

        return !dao::isError();
    }

    /**
     * 更新用例和 bug 的关联关系。
     * Link bugs with case.
     *
     * @param  int       $caseID
     * @param  array     $linkedBugs
     * @param  object    $case
     * @access protected
     * @return bool
     */
    protected function linkBugs($caseID, $linkedBugs, $case)
    {
        $toLinkBugs = $case->linkBug;
        $newBugs    = array_diff($toLinkBugs, $linkedBugs);
        $removeBugs = array_diff($linkedBugs, $toLinkBugs);

        foreach($newBugs as $bugID)
        {
            $this->dao->update(TABLE_BUG)->set('`case`')->eq($caseID)->set('caseVersion')->eq($case->version)->set('`story`')->eq($case->story)->set('storyVersion')->eq($case->storyVersion)->where('id')->eq($bugID)->exec();

            if($this->config->edition != 'open')
            {
                $relation = new stdClass();
                $relation->relation = 'generated';
                $relation->AID      = $bugID;
                $relation->AType    = 'bug';
                $relation->BID      = $caseID;
                $relation->BType    = 'testcase';
                $relation->product  = 0;
                $this->dao->replace(TABLE_RELATION)->data($relation)->exec();
            }
        }
        foreach($removeBugs as $bugID)
        {
            $this->dao->update(TABLE_BUG)->set('`case`')->eq(0)->set('caseVersion')->eq(0)->set('`story`')->eq(0)->set('storyVersion')->eq(0)->where('id')->eq($bugID)->exec();

            if($this->config->edition != 'open')
            {
                $this->dao->delete()->from(TABLE_RELATION)
                    ->where('relation')->eq('generated')
                    ->andWhere('AID')->eq($bugID)
                    ->andWhere('AType')->eq('bug')
                    ->andWhere('BID')->eq($caseID)
                    ->andWhere('BType')->eq('testcase')
                    ->exec();
            }
        }

        return !dao::isError();
    }

    /**
     * 解除用例和测试单的关联。
     * Unlink case from test task.
     *
     * @param  int       $caseID
     * @param  array     $testtasks
     * @access protected
     * @return bool
     * @param int $branch
     */
    protected function unlinkCaseFromTesttask($caseID, $branch, $testtasks)
    {
        $this->loadModel('action');
        foreach($testtasks as $taskID => $testtask)
        {
            if(strpos(",{$testtask->branch},", ",{$branch},") === false && $taskID)
            {
                $this->dao->delete()->from(TABLE_TESTRUN)->where('task')->eq($taskID)->andWhere('`case`')->eq($caseID)->exec();
                $this->action->create('case' ,$caseID, 'unlinkedfromtesttask', '', $taskID);
            }
        }

        return !dao::isError();
    }

    /**
     * 获取待评审的用例数量。
     * Get the amount of cases pending review.
     *
     * @access protected
     * @return int
     */
    protected function getReviewAmount()
    {
        return $this->dao->select('COUNT(id) AS count')->from(TABLE_CASE)->where('status')->eq('wait')->fetch('count');
    }

    /**
     * 导入步骤。
     * Import steps.
     *
     * @param  int    $caseID
     * @param  array  $steps
     * @access public
     * @return bool
     */
    public function importSteps($caseID, $steps)
    {
        /* 插入步骤。 */
        /* Insert steps. */
        $parentSteps = array();
        foreach($steps as $stepID => $step)
        {
            $step->case = $caseID;
            if(!empty($parentSteps[$step->parent])) $step->parent = $parentSteps[$step->parent];
            unset($step->id);

            $this->dao->insert(TABLE_CASESTEP)->data($step)->exec();

            $parentSteps[$stepID] = $this->dao->lastInsertID();
        }
        return !dao::isError();
    }

    /**
     * 导入文件。
     * Import files.
     *
     * @param  int    $caseID
     * @param  array  $files
     * @access public
     * @return bool
     */
    public function importFiles($caseID, $files)
    {
        /* 插入文件。 */
        /* Insert files. */
        foreach($files as $fileID => $file)
        {
            if(isset($file->oldpathname))
            {
                $file->pathname = str_replace('.', "copy{$caseID}.", $file->oldpathname);
                if(!empty($file->oldpathname))
                {
                    $originName = pathinfo($file->oldpathname, PATHINFO_FILENAME);
                    $datePath   = substr($file->oldpathname, 0, 6);
                    $originFile = $this->app->getAppRoot() . "www/data/upload/{$this->app->company->id}/" . "{$datePath}/" . $originName;

                    $copyName = $originName . 'copy' . $caseID;
                    $copyFile = $this->app->getAppRoot() . "www/data/upload/{$this->app->company->id}/" . "{$datePath}/" .  $copyName;
                    copy($originFile, $copyFile);
                }

                unset($file->oldpathname);
            }

            $file->objectID  = $caseID;
            $file->addedBy   = $this->app->user->account;
            $file->addedDate = helper::now();
            $file->downloads = 0;
            unset($file->id);
            $this->dao->insert(TABLE_FILE)->data($file)->exec();
        }
        return !dao::isError();
    }

    /**
     * 保存场景。
     * Save scene.
     *
     * @param  array     $sceneData
     * @param  array     $sceneList
     * @access protected
     * @return array
     */
    protected function saveScene($sceneData, $sceneList)
    {
        $scene          = new stdclass();
        $scene->title   = $sceneData['name'];
        $scene->product = $sceneData['product'];
        $scene->branch  = $sceneData['branch'];
        $scene->module  = $sceneData['module'] ?? 0;

        if(!isset($sceneData['id']))
        {
            $scene->openedBy   = $this->app->user->account;
            $scene->openedDate = helper::now();

            $this->dao->insert(TABLE_SCENE)->data($scene)->autoCheck()->exec();
            $sceneID = $this->dao->lastInsertID();

            $this->dao->update(TABLE_SCENE)->set('sort')->eq($sceneID)->where('id')->eq($sceneID)->exec();
        }
        else
        {
            $sceneID = $sceneData['id'];

            $scene->lastEditedBy   = $this->app->user->account;
            $scene->lastEditedDate = helper::now();

            $affectedRows = $this->dao->update(TABLE_SCENE)->data($scene)->where('id')->eq($sceneID)->exec();
            if(empty($affectedRows)) return array('result' => 'fail', 'message' => sprintf($this->lang->testcase->errorSceneNotExist, $sceneID));
        }

        if(dao::isError()) return array('result' => 'fail', 'message' => dao::getError());

        $tmpPId = $sceneData['tmpPId'];
        $pScene = zget($sceneList, $tmpPId, array());
        $this->fixScenePath($sceneID, $pScene);

        if(dao::isError()) return array('result' => 'fail', 'message' => dao::getError());

        return array('result' => 'success', 'message' => $this->lang->saveSuccess, 'sceneID' => $sceneID);
    }

    /**
     * 调整场景的路径。
     * Fix the scene path.
     *
     * @param  int       $sceneID
     * @param  array     $pScene
     * @access protected
     * @return bool
     */
    protected function fixScenePath($sceneID, $pScene = array())
    {
        $parent = 0;
        $grade  = 1;
        $path   = ",{$sceneID},";

        if(!empty($pScene))
        {
            $parent      = $pScene['id'];
            $parentScene = $this->dao->findById((int)$parent)->from(TABLE_SCENE)->fetch();
            $path        = $parentScene->path . "{$sceneID},";
            $grade       = $parentScene->grade + 1;
        }

        $this->dao->update(TABLE_SCENE)->set('parent')->eq($parent)->set('path')->eq($path)->set('grade')->eq($grade)->where('id')->eq($sceneID)->exec();

        return !dao::isError();
    }

    /**
     * 基于用例构建场景数据。
     * Build scene base on case.
     *
     * @param  object $scene
     * @param  array  $fieldTypes
     * @access public
     * @return object
     */
    public function buildSceneBaseOnCase($scene, $fieldTypes)
    {
        /* Set default value for the fields exist in TABLE_CASE but not in TABLE_SCENE. */
        foreach($fieldTypes as $field => $type)
        {
            if(!isset($scene->{$field})) $scene->{$field} = $type['rule'] == 'int' ? '0' : '';
        }

        $scene->caseID     = $scene->id;
        $scene->bugs       = 0;
        $scene->results    = 0;
        $scene->caseFails  = 0;
        $scene->stepNumber = 0;
        $scene->isScene    = true;

        return $scene;
    }

    /**
     * 通过搜索获取执行的用例。
     * Get execution cases by search.
     *
     * @param  int    $executionID
     * @param  int    $productID
     * @param  int    $branchID
     * @param  int    $paramID
     * @param  string $orderBy
     * @param  object $pager
     * @access public
     * @return array
     * */
    public function getExecutionCasesBySearch($executionID = 0, $productID = 0, $branchID = 0, $paramID = 0, $orderBy = 'id_desc', $pager = null)
    {
        if($paramID)
        {
            $query = $this->loadModel('search')->getQuery($paramID);
            if($query)
            {
                $this->session->set('executionCaseQuery', $query->sql);
                $this->session->set('executionCaseForm', $query->form);
            }
        }
        if($this->session->executionCaseQuery === false) $this->session->set('executionCaseQuery', ' 1 = 1');

        $caseQuery = '(' . $this->session->executionCaseQuery;

        /* 处理用例查询中的产品条件。*/
        if(strpos($this->session->executionCaseQuery, "`product` = 'all'") !== false)
        {
            $caseQuery  = str_replace("`product` = 'all'", '1 = 1', $caseQuery);
            $caseQuery .= ' AND t2.`product` ' . helper::dbIN($this->app->user->view->products);
        }
        elseif($productID)
        {
            $caseQuery .= " AND t2.`product` ='$productID'";
        }

        /* 处理用例查询中的产品分支条件。*/
        if(strpos($caseQuery, "`branch` = 'all'") !== false) $caseQuery = str_replace("`branch` = 'all'", '1 = 1', $caseQuery);
        if($branchID !== 'all' && strpos($caseQuery, '`branch` =') === false) $caseQuery .= " AND t2.`branch` in ('$branchID')";

        /* 处理用例查询中的版本条件。*/
        $caseQuery = str_replace(array('`version`', ' `product`', ' `project`'), array('t2.`version`', ' t2.`product`', ' t1.`project`'), $caseQuery);
        $caseQuery .= ')';

        return $this->dao->select('distinct t1.*, t2.*')->from(TABLE_PROJECTCASE)->alias('t1')
            ->leftJoin(TABLE_CASE)->alias('t2')->on('t1.case = t2.id')
            ->where('t1.project')->eq($executionID)
            ->andWhere('t2.deleted')->eq('0')
            ->andWhere($caseQuery)
            ->orderBy($orderBy)
            ->page($pager)
            ->fetchAll('id');
    }
}
