<?php
class mr extends control
{
    /**
     * The mr constructor.
     * @param string $moduleName
     * @param string $methodName
     */
    public function __construct($moduleName = '', $methodName = '')
    {
        parent::__construct($moduleName, $methodName);

        /* This is essential when changing tab(menu) from gitlab to repo. */
        /* Optional: common::setMenuVars('devops', $this->session->repoID); */
        $this->loadModel('ci')->setMenu();
    }

    /**
     * Browse mr.
     *
     * @param  string $mode
     * @param  string $param
     * @param  int    $objectID
     * @param  string $orderBy
     * @param  int    $recTotal
     * @param  int    $recPerPage
     * @param  int    $pageID
     * @access public
     * @return void
     */
    public function browse($mode = 'all', $param = 'all', $objectID = 0, $orderBy = 'id_desc', $recTotal = 0, $recPerPage = 20, $pageID = 1)
    {
        $this->app->loadClass('pager', $static = true);
        $pager  = new pager($recTotal, $recPerPage, $pageID);
        $MRList = $this->mr->getList($mode, $param, $orderBy, $pager);

        /* Save current URI to session. */
        $this->session->set('mrList', $this->app->getURI(true), 'repo');

        /* Sync GitLab MR to ZenTao Database. */
        $MRList = $this->mr->batchSyncMR($MRList);

        /* Check whether Mr is linked with the product. */
        $this->loadModel('gitlab');
        foreach($MRList as $MR)
        {
            $product        = $this->mr->getMRProduct($MR);
            $MR->linkButton = empty($product) ? false : true;
        }

        /* Load lang from compile module */
        $this->app->loadLang('compile');

        $this->view->title      = $this->lang->mr->common . $this->lang->colon . $this->lang->mr->browse;
        $this->view->MRList     = $MRList;
        $this->view->pager      = $pager;
        $this->view->mode       = $mode;
        $this->view->param      = $param;
        $this->view->objectID   = $objectID;
        $this->view->orderBy    = $orderBy;
        $this->display();
    }

    /**
     * Create MR function.
     *
     * @access public
     * @return void
     */
    public function create()
    {
        if($_POST)
        {
            $result = $this->mr->create();
            return $this->send($result);
        }

        $this->app->loadLang('repo'); /* Import lang in repo module. */
        $this->app->loadLang('compile');
        $this->view->title       = $this->lang->mr->create;
        $this->view->users       = $this->loadModel('user')->getPairs('noletter|noclosed');
        $this->view->jobList     = $this->loadModel('job')->getList();
        $this->view->gitlabHosts = $this->loadModel('gitlab')->getPairs();
        $this->display();
    }

    /**
     * Edit MR function.
     *
     * @access public
     * @return void
     */
    public function edit($MRID)
    {
        if($_POST)
        {
            $result = $this->mr->update($MRID);
            return $this->send($result);
        }

        $MR = $this->mr->getByID($MRID);
        if(isset($MR->gitlabID)) $rawMR = $this->mr->apiGetSingleMR($MR->gitlabID, $MR->targetProject, $MR->mriid);
        $this->view->title = $this->lang->mr->edit;
        $this->view->MR    = $MR;
        $this->view->rawMR = isset($rawMR) ? $rawMR : false;
        if(!isset($rawMR->id) or (isset($rawMR->message) and $rawMR->message == '404 Not found') or empty($rawMR)) return $this->display();

        $branchList       = $this->loadModel('gitlab')->getBranches($MR->gitlabID, $MR->targetProject);
        $targetBranchList = array();
        foreach($branchList as $branch) $targetBranchList[$branch] = $branch;

        /* Fetch user list both in Zentao and current GitLab project. */
        $bindedUsers     = $this->gitlab->getUserIdRealnamePairs($MR->gitlabID);
        $rawProjectUsers = $this->gitlab->apiGetProjectUsers($MR->gitlabID, $MR->targetProject);

        $users = array();
        foreach($rawProjectUsers as $rawProjectUser)
        {
            if(!empty($bindedUsers[$rawProjectUser->id])) $users[$rawProjectUser->id] = $bindedUsers[$rawProjectUser->id];
        }

        $gitlabUsers = $this->gitlab->getUserAccountIdPairs($MR->gitlabID);

        /* Import lang for required modules. */
        $this->loadModel('repo');
        $this->loadModel('job');
        $this->loadModel('compile');

        $repoList = array();
        $rawRepoList = $this->repo->getGitLabRepoList($MR->gitlabID, $MR->sourceProject);
        foreach($rawRepoList as $rawRepo) $repoList[$rawRepo->id] = "[$rawRepo->id] $rawRepo->name";

        $jobList = array();
        $rawJobList = $this->job->getListByRepoID($MR->repoID);
        foreach($rawJobList as $rawJob) $jobList[$rawJob->id] = "[$rawJob->id] $rawJob->name";

        $compileList = array();
        $rawCompileList = $this->compile->getListByJobID($MR->jobID);
        foreach($rawCompileList as $rawCompile) $compileList[$rawCompile->id] = "[$rawCompile->id] [{$this->lang->compile->statusList[$rawCompile->status]}] $rawCompile->name";

        $this->view->repoList         = $repoList;
        $this->view->jobList          = !empty($MR->repoID) ? $jobList : array();
        $this->view->compileList      = !empty($MR->jobID)  ? $compileList : array();

        $this->view->title            = $this->lang->mr->edit;
        $this->view->MR               = $MR;
        $this->view->targetBranchList = $targetBranchList;
        $this->view->users            = $this->loadModel('user')->getPairs('noletter|noclosed');
        $this->view->assignee         = $MR->assignee;
        $this->view->reviewer         = zget($gitlabUsers, $MR->reviewer, '');

        $this->display();
    }

    /**
     * Delete a MR.
     *
     * @param  int    $id
     * @access public
     * @return void
     */
    public function delete($id, $confirm = 'no')
    {
        if($confirm != 'yes') die(js::confirm($this->lang->mr->confirmDelete, inlink('delete', "id=$id&confirm=yes")));

        $MR = $this->mr->getByID($id);

        $this->dao->delete()->from(TABLE_MR)->where('id')->eq($id)->exec();
        $this->mr->apiDeleteMR($MR->gitlabID, $MR->targetProject, $MR->mriid);

        die(js::locate(inlink('browse'), 'parent'));
    }

    /**
     * View a MR.
     *
     * @param  int $id
     * @access public
     * @return void
     */
    public function view($id)
    {
        $MR = $this->mr->getByID($id);
        if(!$MR) die(js::error($this->lang->notFound) . js::locate($this->createLink('mr', 'browse')));
        if(isset($MR->gitlabID)) $rawMR = $this->mr->apiGetSingleMR($MR->gitlabID, $MR->targetProject, $MR->mriid);
        if(!isset($rawMR->id) or (isset($rawMR->message) and $rawMR->message == '404 Not found') or empty($rawMR)) return $this->display();

        $MR = $this->mr->apiSyncMR($MR); /* Sync MR from GitLab to ZentaoPMS. */
        $this->loadModel('gitlab');
        $sourceProject = $this->gitlab->apiGetSingleProject($MR->gitlabID, $MR->sourceProject);
        $targetProject = $this->gitlab->apiGetSingleProject($MR->gitlabID, $MR->targetProject);
        $sourceBranch  = $this->gitlab->apiGetSingleBranch($MR->gitlabID, $MR->sourceProject, $MR->sourceBranch);
        $targetBranch  = $this->gitlab->apiGetSingleBranch($MR->gitlabID, $MR->targetProject, $MR->targetBranch);

        $this->view->sourceProjectName = $sourceProject->name_with_namespace;
        $this->view->targetProjectName = $targetProject->name_with_namespace;
        $this->view->sourceProjectURL  = isset($sourceBranch->web_url) ? $sourceBranch->web_url : '';
        $this->view->targetProjectURL  = isset($targetBranch->web_url) ? $targetBranch->web_url : '';

        /* Those variables are used to render $lang->mr->commandDocument. */
        $this->view->httpRepoURL = $sourceProject->http_url_to_repo;
        $this->view->branchPath  = $sourceProject->path_with_namespace . '-' . $rawMR->source_branch;

        /* Get mr linked list. */
        $this->app->loadLang('productplan');
        $product  = $this->mr->getMRProduct($MR);

        $this->view->compile    = $this->loadModel('compile')->getById($MR->compileID);
        $this->view->compileJob = $MR->jobID ? $this->loadModel('job')->getById($MR->jobID) : false;

        $this->view->title   = $this->lang->mr->overview;
        $this->view->MR      = $MR;
        $this->view->rawMR   = isset($rawMR) ? $rawMR : false;
        $this->view->product = $product;
        $this->view->stories = $this->mr->getLinkList($MR->id, $product->id, 'story');
        $this->view->bugs    = $this->mr->getLinkList($MR->id, $product->id, 'bug');
        $this->view->tasks   = $this->mr->getLinkList($MR->id, $product->id, 'task');

        $this->display();
    }

    /**
     * Crontab sync MR from GitLab API to Zentao database, default time 5 minutes to execute once.
     *
     * @access public
     * @return void
     */
    public function syncMR()
    {
        $MRList = $this->mr->getList();
        $this->mr->batchSyncMR($MRList);

        if(dao::isError())
        {
            echo json_encode(dao::getError());
            return true;
        }

        echo 'success';
    }

    /**
     * Accept a MR.
     *
     * @param  int    $MRID
     * @access public
     * @return void
     */
    public function accept($MRID)
    {
        $MR = $this->mr->getByID($MRID);

        /* Judge that if this MR can be accepted. */
        if(isset($MR->needCI) and $MR->needCI == '1')
        {
            $compileStatus = empty($MR->compileID) ? 'fail' : $this->loadModel('compile')->getByID($MR->compileID)->status;

            if(isset($compileStatus) and $compileStatus != 'success')
            {
                return $this->send(array('result' => 'fail', 'message' => $this->lang->mr->needCI, 'locate' => helper::createLink('mr', 'view', "mr={$MRID}")));
            }
        }
        if(isset($MR->needApproved) and $MR->needApproved == '1')
        {
            if($MR->approvalStatus != 'approved')
            {
                return $this->send(array('result' => 'fail', 'message' => $this->lang->mr->needApproved, 'locate' => helper::createLink('mr', 'view', "mr={$MRID}")));
            }
        }

        /* Accept MR by using the mapped user in GitLab. */
        $sudoUser = $this->mr->getSudoUsername($MR->gitlabID, $MR->targetProject);

        if(isset($MR->gitlabID))
        {
            if(!empty($sudoUser)) $rawMR = $this->mr->apiAcceptMR($MR->gitlabID, $MR->targetProject, $MR->mriid, $sudoUser);
            if(empty($sudoUser))  $rawMR = $this->mr->apiAcceptMR($MR->gitlabID, $MR->targetProject, $MR->mriid);
        }
        if(isset($rawMR->state) and $rawMR->state == 'merged')
        {
            ///* Force reload when locate to the url. */
            //$random = uniqid();
            //return $this->send(array('result' => 'success', 'message' => $this->lang->mr->mergeSuccess, 'locate' => helper::createLink('mr', 'browse', "random={$random}")));

            $this->mr->logMergedAction($MR);

            return $this->send(array('result' => 'success', 'message' => $this->lang->mr->mergeSuccess, 'locate' => helper::createLink('mr', 'browse')));
        }

        /* The type of variable `$rawMR->message` is string. This is different with apiCreateMR. */
        if(isset($rawMR->message)) return $this->send(array('result' => 'fail', 'message' => sprintf($this->lang->mr->apiError->sudo, $rawMR->message), 'locate' => helper::createLink('mr', 'view', "mr={$MRID}")));

        return $this->send(array('result' => 'fail', 'message' => $this->lang->mr->mergeFailed, 'locate' => helper::createLink('mr', 'view', "mr={$MRID}")));
    }

    /**
     * View diff between MR source and target branches.
     *
     * @param  int    $MRID
     * @access public
     * @return void
     */
    public function diff($MRID, $encoding= '')
    {
        $this->app->loadLang('productplan');
        $this->app->loadLang('bug');
        $this->app->loadLang('task');

        $encoding = empty($encoding) ? 'utf-8' : $encoding;
        $encoding = strtolower(str_replace('_', '-', $encoding)); /* Revert $config->requestFix in $encoding. */

        $MR = $this->mr->getByID($MRID);
        if(isset($MR->gitlabID)) $rawMR = $this->mr->apiGetSingleMR($MR->gitlabID, $MR->targetProject, $MR->mriid);
        $this->view->title = $this->lang->mr->viewDiff;
        $this->view->MR    = $MR;
        $this->view->rawMR = $rawMR;
        if(!isset($rawMR->id) or (isset($rawMR->message) and $rawMR->message == '404 Not found') or empty($rawMR)) return $this->display();

        $diffs   = $this->mr->getDiffs($MR, $encoding = '');
        $arrange = $this->cookie->arrange ? $this->cookie->arrange : 'inline';

        if($this->server->request_method == 'POST')
        {
            if($this->post->arrange)
            {
                $arrange = $this->post->arrange;
                setcookie('arrange', $arrange);
            }
            if($this->post->encoding) $encoding = $this->post->encoding;
        }

        if($arrange == 'appose')
        {
            foreach($diffs as $diffFile)
            {
                if(empty($diffFile->contents)) continue;
                foreach($diffFile->contents as $content)
                {
                    $old = array();
                    $new = array();
                    foreach($content->lines as $line)
                    {
                        if($line->type != 'new') $old[$line->oldlc] = $line->line;
                        if($line->type != 'old') $new[$line->newlc] = $line->line;
                    }
                    $content->old = $old;
                    $content->new = $new;
                }
            }
        }

        $this->view->repo     = $this->loadModel('repo')->getRepoByID($MR->repoID);
        $this->view->repoID   = $MR->repoID;
        $this->view->diffs    = $diffs;
        $this->view->encoding = $encoding;
        $this->view->arrange  = $arrange;
        $this->display();
    }

    /**
     * Approval for this MR.
     *
     * @param  int    $MRID
     * @param  string $action
     * @return void
     */
    public function approval($MRID, $action = 'approve')
    {
        $MR = $this->mr->getByID($MRID);

        if($_POST)
        {
            $comment = $this->post->comment;
            $result  = $this->mr->approve($MR, $action, $comment);
            return $this->send($result);
        }

        $showCompileResult = false;
        if(!empty($MR->compileStatus))
        {
            $showCompileResult = true;
            $this->app->loadLang('compile'); /* Import lang. */
            $this->view->compileUrl = $this->createLink('job', 'view', "jobID={$MR->jobID}&compileID={$MR->compileID}");
        }
        $this->view->showCompileResult = $showCompileResult;

        $this->view->MR      = $MR;
        $this->view->action  = $action;
        $this->view->actions = $this->loadModel('action')->getList('mrapproval', $MRID);
        $this->view->users   = $this->loadModel('user')->getPairs('noletter|noclosed');
        $this->display();
    }

    /**
     * Close this MR.
     *
     * @param  int $MRID
     * @return void
     */
    public function close($MRID)
    {
        $MR = $this->mr->getByID($MRID);
        $result = $this->mr->close($MR);

        return $this->send($result);
    }

    /**
     * Reopen this MR.
     *
     * @param  int $MRID
     * @return void
     */
    public function reopen($MRID)
    {
        $MR = $this->mr->getByID($MRID);
        return $this->send($this->mr->reopen($MR));
    }

    /**
     * link MR list.
     *
     * @param  int    $MRID
     * @param  string $type
     * @param  string $orderBy
     * @param  string $link
     * @param  string $param
     * @param  int    $recTotal
     * @param  int    $recPerPage
     * @param  int    $pageID
     * @return void
     */
    public function link($MRID, $type = 'story', $orderBy = 'id_desc', $link = 'false', $param = '', $recTotal = 0, $recPerPage = 100, $pageID = 1)
    {
        $this->app->loadLang('productplan');
        $this->app->loadLang('bug');
        $this->app->loadLang('task');

        $MR       = $this->mr->getByID($MRID);
        $product  = $this->mr->getMRProduct($MR);

        /* Load pager. */
        $this->app->loadClass('pager', $static = true);
        $storyPager = new pager(0, $recPerPage, $type == 'story' ? $pageID : 1);
        $bugPager   = new pager(0, $recPerPage, $type == 'bug' ? $pageID : 1);
        $taskPager  = new pager(0, $recPerPage, $type == 'task' ? $pageID : 1);

        $stories = $this->mr->getLinkList($MRID, $product->id, 'story', $orderBy, $storyPager);
        $bugs    = $this->mr->getLinkList($MRID, $product->id, 'bug', $orderBy, $bugPager);
        $tasks   = $this->mr->getLinkList($MRID, $product->id, 'task', $orderBy, $taskPager);

        $this->view->title        = $this->lang->mr->common . $this->lang->colon . $this->lang->mr->link;
        $this->view->MR           = $MR;
        $this->view->canBeChanged = true;
        $this->view->modulePairs  = $this->loadModel('tree')->getOptionMenu($product->id, 'story');
        $this->view->users        = $this->loadModel('user')->getPairs('noletter');
        $this->view->stories      = $stories;
        $this->view->summary      = $this->loadModel('product')->summary($stories);
        $this->view->bugs         = $bugs;
        $this->view->tasks        = $tasks;
        $this->view->product      = $product;
        $this->view->storyPager   = $storyPager;
        $this->view->bugPager     = $bugPager;
        $this->view->taskPager    = $taskPager;
        $this->view->type         = $type;
        $this->view->orderBy      = $orderBy;
        $this->view->link         = $link;
        $this->view->param        = $param;
        $this->display();
    }

    /**
     * Link story to mr.
     *
     * @param int    $MRID
     * @param int    $productID
     * @param string $browseType
     * @param int    $param
     * @param string $orderBy
     * @param int    $recTotal
     * @param int    $recPerPage
     * @param int    $pageID
     * @access public
     * @return void
     */
    public function linkStory($MRID, $productID = 0, $browseType = '', $param = 0, $orderBy = 'id_desc', $recTotal = 0, $recPerPage = 100, $pageID = 1)
    {
        if(!empty($_POST['stories']))
        {
            $this->mr->link($MRID, $productID, 'story');

            if(dao::isError()) die(js::error(dao::getError()));
            die(js::locate(inlink('link', "MRID=$MRID&type=story&orderBy=$orderBy"), 'parent'));
        }

        $this->loadModel('story');
        $this->app->loadLang('productplan');

        $product = $this->loadModel('product')->getById($productID);
        $modules = $this->loadModel('tree')->getOptionMenu($productID, $viewType = 'story');

        /* Load pager. */
        $this->app->loadClass('pager', $static = true);
        $pager = new pager($recTotal, $recPerPage, $pageID);

        /* Build search form. */
        $storyStatusList = $this->lang->story->statusList;
        unset($storyStatusList['closed']);
        $queryID         = ($browseType == 'bySearch') ? (int) $param : 0;

        unset($this->config->product->search['fields']['product']);
        $this->config->product->search['actionURL']                   = $this->createLink('mr', 'link', "$MRID=$MRID&type=story&orderBy=$orderBy&link=true&param=" . helper::safe64Encode('&browseType=bySearch&queryID=myQueryID'));
        $this->config->product->search['queryID']                     = $queryID;
        $this->config->product->search['style']                       = 'simple';
        $this->config->product->search['params']['product']['values'] = array($product) + array('all' => $this->lang->product->allProductsOfProject);
        $this->config->product->search['params']['plan']['values']    = $this->loadModel('productplan')->getForProducts(array($productID => $productID));
        $this->config->product->search['params']['module']['values']  = $modules;
        $this->config->product->search['params']['status']            = array('operator' => '=', 'control' => 'select', 'values' => $storyStatusList);

        if($product->type == 'normal')
        {
            unset($this->config->product->search['fields']['branch']);
            unset($this->config->product->search['params']['branch']);
        }
        else
        {
            $this->product->setMenu($productID, 0);
            $this->config->product->search['fields']['branch']           = $this->lang->product->branch;
            $branches                                                    = array('' => '') + $this->loadModel('branch')->getPairs($productID, 'noempty');
            $this->config->product->search['params']['branch']['values'] = $branches;
        }
        $this->loadModel('search')->setSearchParams($this->config->product->search);

        $MR             = $this->mr->getByID($MRID);
        $relatedStories = $this->mr->getCommitedLink($MR->gitlabID, $MR->targetProject, $MR->mriid, 'story');

        $linkedStories = $this->mr->getLinkList($MRID, $product->id, 'story');
        if($browseType == 'bySearch')
        {
            $allStories = $this->story->getBySearch($productID, 0, $queryID, 'id', '', 'story', array_keys($linkedStories), $pager);
        }
        else
        {
            $allStories = $this->story->getProductStories($productID, 0, $moduleID   = '0', $status     = 'draft,active,changed', 'story', 'id_desc', $hasParent  = false, array_keys($linkedStories), $pager);
        }

        $this->view->modules        = $modules;
        $this->view->users          = $this->loadModel('user')->getPairs('noletter');
        $this->view->allStories     = $allStories;
        $this->view->relatedStories = $relatedStories;
        $this->view->product        = $product;
        $this->view->MRID           = $MRID;
        $this->view->browseType     = $browseType;
        $this->view->param          = $param;
        $this->view->orderBy        = $orderBy;
        $this->view->pager          = $pager;
        $this->display();
    }

    /**
     * Link bug to mr.
     *
     * @param int    $MRID
     * @param int    $productID
     * @param string $browseType
     * @param int    $param
     * @param string $orderBy
     * @param int    $recTotal
     * @param int    $recPerPage
     * @param int    $pageID
     * @access public
     * @return void
     */
    public function linkBug($MRID, $productID = 0, $browseType = '', $param = 0, $orderBy = 'id_desc', $recTotal = 0, $recPerPage = 100, $pageID = 1)
    {
        if(!empty($_POST['bugs']))
        {
            $this->mr->link($MRID, $productID, 'bug');

            if(dao::isError()) die(js::error(dao::getError()));
            die(js::locate(inlink('link', "MRID=$MRID&type=bug&orderBy=$orderBy"), 'parent'));
        }

        $this->loadModel('bug');
        $this->app->loadLang('productplan');
        $queryID = ($browseType == 'bysearch') ? (int)$param : 0;

        $product = $this->loadModel('product')->getById($productID);
        $modules = $this->loadModel('tree')->getOptionMenu($productID, $viewType = 'bug');

        /* Load pager. */
        $this->app->loadClass('pager', $static = true);
        $pager = new pager($recTotal, $recPerPage, $pageID);

        /* Build search form. */
        $this->config->bug->search['actionURL']                         = $this->createLink('mr', 'link', "$MRID=$MRID&type=bug&orderBy=$orderBy&link=true&param=" . helper::safe64Encode('&browseType=bySearch&queryID=myQueryID'));
        $this->config->bug->search['queryID']                           = $queryID;
        $this->config->bug->search['style']                             = 'simple';
        $this->config->bug->search['params']['plan']['values']          = $this->loadModel('productplan')->getForProducts(array($productID => $productID));
        $this->config->bug->search['params']['module']['values']        = $modules;
        $this->config->bug->search['params']['execution']['values']     = $this->product->getExecutionPairsByProduct($productID);
        $this->config->bug->search['params']['openedBuild']['values']   = $this->loadModel('build')->getProductBuildPairs($productID, $branch                                                         = 0, $params                                                         = '');
        $this->config->bug->search['params']['resolvedBuild']['values'] = $this->loadModel('build')->getProductBuildPairs($productID, $branch                                                         = 0, $params                                                         = '');

        unset($this->config->bug->search['fields']['product']);
        if($product->type == 'normal')
        {
            unset($this->config->bug->search['fields']['branch']);
            unset($this->config->bug->search['params']['branch']);
        }
        else
        {
            $this->product->setMenu($productID, 0);
            $this->config->bug->search['fields']['branch']           = $this->lang->product->branch;
            $branches                                                = array('' => '') + $this->loadModel('branch')->getPairs($productID, 'noempty');
            $this->config->bug->search['params']['branch']['values'] = $branches;
        }
        $this->loadModel('search')->setSearchParams($this->config->bug->search);

        $MR          = $this->mr->getByID($MRID);
        $relatedBugs = $this->mr->getCommitedLink($MR->gitlabID, $MR->targetProject, $MR->mriid, 'bug');

        $linkedBugs = $this->mr->getLinkList($MRID, $product->id, 'bug');
        if($browseType == 'bySearch')
        {
            $allBugs = $this->bug->getBySearch($productID, 0, $queryID, 'id_desc', array_keys($linkedBugs), $pager);
        }
        else
        {
            $allBugs = $this->bug->getActiveBugs($productID, 0, '0', array_keys($linkedBugs), $pager);
        }

        $this->view->modules     = $modules;
        $this->view->users       = $this->loadModel('user')->getPairs('noletter');
        $this->view->allBugs     = $allBugs;
        $this->view->relatedBugs = $relatedBugs;
        $this->view->product     = $product;
        $this->view->MRID        = $MRID;
        $this->view->browseType  = $browseType;
        $this->view->param       = $param;
        $this->view->orderBy     = $orderBy;
        $this->view->pager       = $pager;
        $this->display();
    }

    /**
     * Link task to mr.
     *
     * @param int    $MRID
     * @param int    $productID
     * @param string $browseType
     * @param int    $param
     * @param string $orderBy
     * @param int    $recTotal
     * @param int    $recPerPage
     * @param int    $pageID
     * @access public
     * @return void
     */
    public function linkTask($MRID, $productID = 0, $browseType = 'unclosed', $param = 0, $orderBy = 'id_desc', $recTotal = 0, $recPerPage = 100, $pageID = 1)
    {
        if(!empty($_POST['tasks']))
        {
            $this->mr->link($MRID, $productID, 'task');

            if(dao::isError()) die(js::error(dao::getError()));
            die(js::locate(inlink('link', "MRID=$MRID&type=task&orderBy=$orderBy"), 'parent'));
        }

        $this->loadModel('execution');
        $this->loadModel('product');
        $this->app->loadLang('task');

        /* Set browse type. */
        $browseType = strtolower($browseType);
        $queryID = ($browseType == 'bysearch') ? (int)$param : 0;

        $product = $this->loadModel('product')->getById($productID);
        $modules = $this->loadModel('tree')->getOptionMenu($productID, $viewType = 'task');

        /* Load pager. */
        $this->app->loadClass('pager', $static = true);
        $pager = new pager($recTotal, $recPerPage, $pageID);

        /* Build search form. */
        $this->config->execution->search['actionURL']                     = $this->createLink('mr', 'link', "$MRID=$MRID&type=task&orderBy=$orderBy&link=true&param=" . helper::safe64Encode('&browseType=bySearch&queryID=myQueryID'));
        $this->config->execution->search['queryID']                       = $queryID;
        $this->config->execution->search['params']['module']['values']    = $modules;
        $this->config->execution->search['params']['execution']['values'] = $this->product->getExecutionPairsByProduct($productID);
        $this->loadModel('search')->setSearchParams($this->config->execution->search);

        $MR           = $this->mr->getByID($MRID);
        $relatedTasks = $this->mr->getCommitedLink($MR->gitlabID, $MR->targetProject, $MR->mriid, 'task');
        $linkedTasks  = $this->mr->getLinkList($MRID, $product->id, 'task');

        /* Get executions by product. */
        $productExecutions   = $this->product->getExecutionPairsByProduct($productID);
        $productExecutionIDs = array_filter(array_keys($productExecutions));
        $this->config->execution->search['params']['execution']['values'] = array_filter($productExecutions);

        /* Get tasks by executions. */
        $allTasks = array();
        foreach($productExecutionIDs as $productExecutionID)
        {
            $tasks    = $this->execution->getTasks(0, $productExecutionID, array(), $browseType, $queryID, 0, $orderBy, null);
            $allTasks = array_merge($tasks, $allTasks);
        }
        /* Filter linked tasks. */
        $linkedTaskIDs = array_keys($linkedTasks);
        foreach($allTasks as $key => $task)
        {
            if(in_array($task->id, $linkedTaskIDs)) unset($allTasks[$key]);
        }

        /* Page the records. */
        $pager->setRecTotal(count($allTasks));
        $pager->setPageTotal();
        if($pager->pageID > $pager->pageTotal) $pager->setPageID($pager->pageTotal);
        $count    = 1;
        $limitMin = ($pager->pageID - 1) * $pager->recPerPage;
        $limitMax = $pager->pageID * $pager->recPerPage;
        foreach($allTasks as $key => $task)
        {
            if($count <= $limitMin or $count > $limitMax) unset($allTasks[$key]);

            $count ++;
        }

        $this->view->modules      = $modules;
        $this->view->users        = $this->loadModel('user')->getPairs('noletter');
        $this->view->allTasks     = $allTasks;
        $this->view->relatedTasks = $relatedTasks;
        $this->view->product      = $product;
        $this->view->MRID         = $MRID;
        $this->view->browseType   = $browseType;
        $this->view->param        = $param;
        $this->view->orderBy      = $orderBy;
        $this->view->pager        = $pager;
        $this->display();
    }

    /**
     * UnLink an mr link.
     *
     * @param int    $MRID
     * @param int    $productID
     * @param string $type
     * @param int    $linkID
     * @param string $confirm
     * @access public
     * @return mix
     */
    public function unlink($MRID, $productID, $type, $linkID, $confirm = 'no')
    {
        $this->app->loadLang('productplan');

        if($confirm == 'no')
        {
            die(js::confirm($this->lang->productplan->confirmUnlinkStory, $this->createLink('mr', 'unlink', "MRID=$MRID&productID=$productID&linkID=$linkID&type=$type&confirm=yes")));
        }
        else
        {
            $this->mr->unlink($MRID, $productID, $type, $linkID);

            /* if ajax request, send result. */
            if($this->server->ajax)
            {
                if(dao::isError())
                {
                    $response['result']  = 'fail';
                    $response['message'] = dao::getError();
                }
                else
                {
                    $response['result']  = 'success';
                    $response['message'] = '';
                }
                return $this->send($response);
            }
            die(js::reload('parent'));
        }
    }

    /**
     * Add a Bug for this review.
     *
     * @param  int    $repoID
     * @param  string $file
     * @param  int    $v1
     * @param  int    $v2
     * @access public
     * @return void
     */
    public function addBug($repoID, $file, $v1, $v2)
    {
        /* Handle the exception that when $repoID is empty. */
        if($repoID == "0") $this->send(array());

        $this->loadModel('repo');
        if($this->get->repoPath) $file = $this->get->repoPath;
        if(!empty($_POST))
        {
            $result = $this->mr->saveBug($repoID, $file, $v1, $v2);
            if(dao::isError()) die(json_encode($result));

            $bugID    = $result['id'];
            $repo     = $this->repo->getRepoById($repoID);
            /* Handle the exception that when $repo is empty. */
            if(empty($repo)) $this->send(array());

            $entry    = isset($repo->name) ? $repo->name . '/' . $this->repo->decodePath($file) : '';
            $location = sprintf($this->lang->repo->reviewLocation, $entry, $repo->SCM != 'Subversion' ? substr($v2, 0, 10) : $v2, $this->post->begin, $this->post->end);
            if(empty($v1))
            {
                $revision = $repo->SCM != 'Subversion' ? substr($v2, 0, 10) : $v2;
                $link = $this->repo->createLink('view', "repoID=$repoID&objectID=0&entry={$file}&revision=$v2&showBug=true") . '#L' . $this->post->begin;
            }
            else
            {
                $revision  = $repo->SCM != 'Subversion' ? substr($v1, 0, 10) : $v1;
                $revision .= ' : ';
                $revision .= $repo->SCM != 'Subversion' ? substr($v2, 0, 10) : $v2;
                $link = $this->repo->createLink('diff', "repoID=$repoID&objectID=0&entry={$file}&oldRevision=$v1&newRevision=$v2&showBug=true") . '#L' . $this->post->begin;
            }

            $actionID = $this->loadModel('action')->create('bug', $bugID, 'repoCreated', '', html::a($link, $location));
            $this->loadModel('mail')->sendmail($bugID, $actionID);

            echo json_encode($result);
        }
    }

    /**
     * AJAX: Get MR target projects.
     *
     * @param  int    $gitlabID
     * @param  int    $projectID
     * @access public
     * @return void
     */
    public function ajaxGetMRTargetProjects($gitlabID, $projectID)
    {
        $this->loadModel('gitlab');

        /* First step: get forks. Only get first level forks(not recursively). */
        $projects = $this->gitlab->apiGetForks($gitlabID, $projectID);

        /* Second step: get project itself. */
        $projects[] = $this->gitlab->apiGetSingleProject($gitlabID, $projectID);

        /* Last step: find its upstream recursively. */
        $project = $this->gitlab->apiGetUpstream($gitlabID, $projectID);
        if(!empty($project)) $projects[] = $project;

        while(!empty($project) and isset($project->id))
        {
            $project = $this->gitlab->apiGetUpstream($gitlabID, $project->id);
            if(empty($project)) break;
            $projects[] = $project;
        }

        if(!$projects) return $this->send(array('message' => array()));

        $options = "<option value=''></option>";
        foreach($projects as $project)
        {
            $options .= "<option value='{$project->id}' data-name='{$project->name}'>{$project->name_with_namespace}</option>";
        }

        $this->send($options);
    }

    /**
     * AJAX: Get repo list.
     *
     * @param  int $gitlabID
     * @param  int $projectID
     * @return void
     */
    public function ajaxGetRepoList($gitlabID, $projectID)
    {
        $this->loadModel('repo');
        $repoList = $this->repo->getGitLabRepoList($gitlabID, $projectID);

        if(!$repoList) return $this->send(array('message' => array()));
        $options = "<option value=''></option>";
        foreach($repoList as $repo) $options .= "<option value='{$repo->id}' data-name='{$repo->name}'>[{$repo->id}] {$repo->name}</option>";
        $this->send($options);
    }

    /**
     * AJAX: Get job list.
     *
     * @param  int $repoID
     * @return void
     */
    public function ajaxGetJobList($repoID)
    {
        $this->loadModel('job');
        $jobList = $this->job->getListByRepoID($repoID);

        if(!$jobList) return $this->send(array('message' => array()));
        $options = "<option value=''></option>";
        foreach($jobList as $job) $options .= "<option value='{$job->id}' data-name='{$job->name}'>[{$job->id}] {$job->name}</option>";
        $this->send($options);
   }

   /**
    * AJAX: Get compile list.
    *
    * @param  int $jobID
    * @return void
    */
   public function ajaxGetCompileList($jobID)
   {
        $this->loadModel('compile');
        $compileList = $this->compile->getListByJobID($jobID);

        if(!$compileList) return $this->send(array('message' => array()));
        $options = "<option value=''></option>";
        foreach($compileList as $compile) $options .= "<option value='{$compile->id}' data-name='{$compile->name}'>[{$compile->id}] [{$this->lang->compile->statusList[$compile->status]}] {$compile->name}</option>";
        $this->send($options);
   }
}
