<?php
/**
 * The model file of build module of ZenTaoPMS.
 *
 * @copyright   Copyright 2009-2015 青岛易软天创网络科技有限公司(QingDao Nature Easy Soft Network Technology Co,LTD, www.cnezsoft.com)
 * @license     ZPL (http://zpl.pub/page/zplv12.html)
 * @author      Chunsheng Wang <chunsheng@cnezsoft.com>
 * @package     build
 * @version     $Id: model.php 4970 2013-07-02 05:58:11Z wyd621@gmail.com $
 * @link        https://www.zentao.pm
 */
?>
<?php
class buildModel extends model
{
    /**
     * Get build info.
     *
     * @param  int    $buildID
     * @param  bool   $setImgSize
     * @access public
     * @return object|bool
     */
    public function getByID($buildID, $setImgSize = false)
    {
        $build = $this->dao->select('t1.*, t2.name as executionName, t3.name as productName, t3.type as productType')
            ->from(TABLE_BUILD)->alias('t1')
            ->leftJoin(TABLE_EXECUTION)->alias('t2')->on('t1.execution = t2.id')
            ->leftJoin(TABLE_PRODUCT)->alias('t3')->on('t1.product = t3.id')
            ->where('t1.id')->eq((int)$buildID)
            ->fetch();
        if(!$build) return false;

        $build = $this->loadModel('file')->replaceImgURL($build, 'desc');
        $build->files = $this->file->getByObject('build', $buildID);
        if($setImgSize) $build->desc = $this->file->setImgSize($build->desc);
        return $build;
    }

    /**
     * Get by ID list.
     *
     * @param  array $idList
     * @access public
     * @return array
     */
    public function getByList($idList)
    {
        return $this->dao->select('*')->from(TABLE_BUILD)->where('id')->in($idList)->fetchAll('id');
    }

    /**
     * Get builds of a project.
     *
     * @param  int    $projectID
     * @param  string $type
     * @param  int    $param
     * @access public
     * @return array
     */
    public function getProjectBuilds($projectID = 0, $type = 'all', $param = 0)
    {
        return $this->dao->select('t1.*, t2.name as executionName, t2.id as executionID, t3.name as productName, t4.name as branchName')
            ->from(TABLE_BUILD)->alias('t1')
            ->leftJoin(TABLE_EXECUTION)->alias('t2')->on('t1.execution = t2.id')
            ->leftJoin(TABLE_PRODUCT)->alias('t3')->on('t1.product = t3.id')
            ->leftJoin(TABLE_BRANCH)->alias('t4')->on('t1.branch = t4.id')
            ->where('t1.project')->eq((int)$projectID)
            ->andWhere('t1.deleted')->eq(0)
            ->andWhere('t1.project')->ne(0)
            ->beginIF($type == 'product' and $param)->andWhere('t1.product')->eq($param)->fi()
            ->beginIF($type == 'bysearch')->andWhere($param)->fi()
            ->orderBy('t1.date DESC, t1.id desc')
            ->fetchAll('id');
    }

    /**
     * Get builds of a project in pairs.
     *
     * @param  int    $projectID
     * @param  int    $productID
     * @param  int    $branch
     * @param  string $params       noempty|notrunk, can be a set of them
     * @param  int    $buildID
     * @access public
     * @return array
     */
    public function getProjectBuildPairs($projectID, $productID, $branch = 0, $params = '', $buildID = 0)
    {
        $sysBuilds      = array();
        $selectedBuilds = array();
        if(strpos($params, 'noempty') === false) $sysBuilds = array('' => '');
        if(strpos($params, 'notrunk') === false) $sysBuilds = $sysBuilds + array('trunk' => $this->lang->trunk);
        if($buildID != 0) $selectedBuilds = $this->dao->select('id, name')->from(TABLE_BUILD)->where('id')->in($buildID)->fetchPairs();

        $projectBuilds = $this->dao->select('t1.id, t1.name, t1.project, t2.status as projectStatus, t3.id as releaseID, t3.status as releaseStatus, t4.name as branchName')->from(TABLE_BUILD)->alias('t1')
            ->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.project = t2.id')
            ->leftJoin(TABLE_RELEASE)->alias('t3')->on('t1.id = t3.build')
            ->leftJoin(TABLE_BRANCH)->alias('t4')->on('t1.branch = t4.id')
            ->where('t1.project')->eq((int)$projectID)
            ->beginIF($productID)->andWhere('t1.product')->eq((int)$productID)->fi()
            ->beginIF($branch)->andWhere('t1.branch')->in("0,$branch")->fi()
            ->andWhere('t1.deleted')->eq(0)
            ->orderBy('t1.date desc, t1.id desc')->fetchAll('id');

        /* Set builds and filter terminate releases. */
        $builds = array();
        foreach($projectBuilds as $buildID => $build)
        {
            if(empty($build->releaseID) and (strpos($params, 'nodone') !== false) and ($build->projectStatus === 'done')) continue;
            if((strpos($params, 'noterminate') !== false) and ($build->releaseStatus === 'terminate')) continue;
            $builds[$buildID] = $build->name;
        }
        if(!$builds) return $sysBuilds + $selectedBuilds;

        /* if the build has been released, replace build name with release name. */
        $releases = $this->dao->select('build, name')->from(TABLE_RELEASE)
            ->where('build')->in(array_keys($builds))
            ->beginIF($branch)->andWhere('branch')->in("0,$branch")->fi()
            ->andWhere('deleted')->eq(0)
            ->fetchPairs();
        foreach($releases as $buildID => $releaseName) $builds[$buildID] = $releaseName;

        return $sysBuilds + $builds + $selectedBuilds;
    }

    /**
     * Get builds of a project by search.
     *
     * @param  int    $projectID
     * @param  int    $queryID
     * @access public
     * @return array
     */
    public function getProjectBuildsBySearch($projectID, $queryID)
    {
        /* If there are saved query conditions, reset the session. */
        if((int)$queryID)
        {
            $query = $this->loadModel('search')->getQuery($queryID);
            if($query)
            {
                $this->session->set('projectBuildQuery', $query->sql);
                $this->session->set('projectBuildForm', $query->form);
            }
        }
        if($this->session->projectBuildQuery == false) $this->session->set('projectBuildQuery', ' 1 = 1');

        $buildQuery = $this->session->projectBuildQuery;

        /* Distinguish between repeated fields. */
        $fields = array('id' => '`id`', 'name' => '`name`', 'product' => '`product`', 'desc' => '`desc`', 'project' => '`project`');
        foreach($fields as $field)
        {
            if(strpos($this->session->projectBuildQuery, $field) !== false)
            {
                $buildQuery = str_replace($field, "t1." . $field, $buildQuery);
            }
        }

        return $this->getProjectBuilds($projectID, 'bysearch', $buildQuery);
    }

    /**
     * Get builds of a execution.
     *
     * @param  int        $executionID
     * @param  string     $type      all|product|bysearch
     * @param  int|string $param     productID|buildQuery
     * @access public
     * @return array
     */
    public function getExecutionBuilds($executionID, $type = '', $param = '')
    {
        return $this->dao->select('t1.*, t2.name as executionName, t3.name as productName, t4.name as branchName')
            ->from(TABLE_BUILD)->alias('t1')
            ->leftJoin(TABLE_EXECUTION)->alias('t2')->on('t1.execution = t2.id')
            ->leftJoin(TABLE_PRODUCT)->alias('t3')->on('t1.product = t3.id')
            ->leftJoin(TABLE_BRANCH)->alias('t4')->on('t1.branch = t4.id')
            ->where('t1.execution')->eq((int)$executionID)
            ->andWhere('t1.deleted')->eq(0)
            ->beginIF($type == 'product' and $param)->andWhere('t1.product')->eq($param)->fi()
            ->beginIF($type == 'bysearch')->andWhere($param)->fi()
            ->orderBy('t1.date DESC, t1.id desc')
            ->fetchAll('id');
    }

    /**
     * Get builds of a execution by search.
     *
     * @param  int    $executionID
     * @param  int    $queryID
     * @access public
     * @return array
     */
    public function getExecutionBuildsBySearch($executionID, $queryID)
    {
        /* If there are saved query conditions, reset the session. */
        if((int)$queryID)
        {
            $query = $this->loadModel('search')->getQuery($queryID);
            if($query)
            {
                $this->session->set('executionBuildQuery', $query->sql);
                $this->session->set('executionBuildForm', $query->form);
            }
        }

        if($this->session->executionBuildQuery == false) $this->session->set('executionBuildQuery', ' 1 = 1');
        $buildQuery = $this->session->executionBuildQuery;

        /* Distinguish between repeated fields. */
        $fields = array('id' => '`id`', 'name' => '`name`', 'product' => '`product`', 'desc' => '`desc`');
        foreach($fields as $field)
        {
            if(strpos($this->session->executionBuildQuery, $field) !== false)
            {
                $buildQuery = str_replace($field, "t1." . $field, $buildQuery);
            }
        }

        return $this->getExecutionBuilds($executionID, 'bysearch', $buildQuery);
    }

    /**
     * Get builds of a execution in pairs.
     *
     * @param  int    $executionID
     * @param  int    $productID
     * @param  string $params       noempty|notrunk, can be a set of them
     * @param  int    $buildID
     * @access public
     * @return array
     */
    public function getExecutionBuildPairs($executionID, $productID, $branch = 0, $params = '', $buildID = 0)
    {
        $sysBuilds      = array();
        $selectedBuilds = array();
        if(strpos($params, 'noempty') === false) $sysBuilds = array('' => '');
        if(strpos($params, 'notrunk') === false) $sysBuilds = $sysBuilds + array('trunk' => $this->lang->trunk);
        if($buildID != 0) $selectedBuilds = $this->dao->select('id, name')->from(TABLE_BUILD)->where('id')->in($buildID)->fetchPairs();

        $executionBuilds = $this->dao->select('t1.id, t1.name, t1.execution, t2.status as executionStatus, t3.id as releaseID, t3.status as releaseStatus, t4.name as branchName')->from(TABLE_BUILD)->alias('t1')
            ->leftJoin(TABLE_EXECUTION)->alias('t2')->on('t1.execution = t2.id')
            ->leftJoin(TABLE_RELEASE)->alias('t3')->on('t1.id = t3.build')
            ->leftJoin(TABLE_BRANCH)->alias('t4')->on('t1.branch = t4.id')
            ->where('t1.execution')->eq((int)$executionID)
            ->beginIF($productID)->andWhere('t1.product')->eq((int)$productID)->fi()
            ->beginIF($branch)->andWhere('t1.branch')->in("0,$branch")->fi()
            ->andWhere('t1.deleted')->eq(0)
            ->orderBy('t1.date desc, t1.id desc')->fetchAll('id');

        /* Set builds and filter terminate releases. */
        $builds = array();
        foreach($executionBuilds as $buildID => $build)
        {
            if(empty($build->releaseID) and (strpos($params, 'nodone') !== false) and ($build->executionStatus === 'done')) continue;
            if((strpos($params, 'noterminate') !== false) and ($build->releaseStatus === 'terminate')) continue;
            $builds[$buildID] = $build->name;
        }
        if(!$builds) return $sysBuilds + $selectedBuilds;

        /* if the build has been released, replace build name with release name. */
        $releases = $this->dao->select('build, name')->from(TABLE_RELEASE)
            ->where('build')->in(array_keys($builds))
            ->beginIF($branch)->andWhere('branch')->in("0,$branch")->fi()
            ->andWhere('deleted')->eq(0)
            ->fetchPairs();
        foreach($releases as $buildID => $releaseName) $builds[$buildID] = $releaseName;

        return $sysBuilds + $builds + $selectedBuilds;
    }

    /**
     * Get builds of a product in pairs.
     *
     * @param mix    $products int|array
     * @param int    $branch
     * @param string $params   noempty|notrunk, can be a set of them
     * @param bool   $replace
     *
     * @access public
     * @return array
     */
    public function getProductBuildPairs($products, $branch = 0, $params = 'noterminate, nodone', $replace = true)
    {
        $sysBuilds = array();
        if(strpos($params, 'noempty') === false) $sysBuilds = array('' => '');
        if(strpos($params, 'notrunk') === false) $sysBuilds = $sysBuilds + array('trunk' => $this->lang->trunk);

        $productBuilds = $this->dao->select('t1.id, t1.name, t1.execution, t2.status as executionStatus, t3.id as releaseID, t3.status as releaseStatus, t4.name as branchName')->from(TABLE_BUILD)->alias('t1')
            ->leftJoin(TABLE_EXECUTION)->alias('t2')->on('t1.execution = t2.id')
            ->leftJoin(TABLE_RELEASE)->alias('t3')->on('t1.id = t3.build')
            ->leftJoin(TABLE_BRANCH)->alias('t4')->on('t1.branch = t4.id')
            ->where('t1.product')->in($products)
            ->beginIF($branch)->andWhere('t1.branch')->in("0,$branch")->fi()
            ->andWhere('t1.deleted')->eq(0)
            ->orderBy('t1.date desc, t1.id desc')->fetchAll('id');

        /* Set builds and filter done executions and terminate releases. */
        $builds = array();
        foreach($productBuilds as $key => $build)
        {
            if(empty($build->releaseID) and (strpos($params, 'nodone') !== false) and ($build->executionStatus === 'done')) continue;
            if((strpos($params, 'noterminate') !== false) and ($build->releaseStatus === 'terminate')) continue;
            $builds[$key] = ((strpos($params, 'withbranch') !== false and $build->branchName) ? $build->branchName . '/' : '') . $build->name;
        }

        if(!$builds) return $sysBuilds;

        /* if the build has been released and replace is true, replace build name with release name. */
        if($replace)
        {
            $releases = $this->dao->select('build, name')->from(TABLE_RELEASE)
                ->where('build')->in(array_keys($builds))
                ->andWhere('product')->in($products)
                ->beginIF($branch)->andWhere('branch')->in("0,$branch")->fi()
                ->andWhere('deleted')->eq(0)
                ->fetchPairs();
            foreach($releases as $buildID => $releaseName) $builds[$buildID] = ((strpos($params, 'withbranch') !== false and $productBuilds[$buildID]->branchName) ? $productBuilds[$buildID]->branchName . '/' : '') . $releaseName;
        }

        return $sysBuilds + $builds;
    }

    /**
     * Get last build.
     *
     * @param  int    $executionID
     * @access public
     * @return bool | object
     */
    public function getLast($executionID)
    {
        return $this->dao->select('id, name')->from(TABLE_BUILD)
            ->where('execution')->eq((int)$executionID)
            ->orderBy('date DESC,id DESC')
            ->limit(1)
            ->fetch();
    }

    /**
     * Create a build
     *
     * @param  int    $executionID
     * @access public
     * @return void
     */
    public function create($executionID)
    {
        $build = new stdclass();
        $build->stories = '';
        $build->bugs    = '';

        $execution = $this->loadModel('execution')->getByID($executionID);

        $build = fixer::input('post')
            ->setDefault('project', $execution->project)
            ->setDefault('product', 0)
            ->setDefault('branch', 0)
            ->cleanInt('product,branch')
            ->add('execution', (int)$executionID)
            ->stripTags($this->config->build->editor->create['id'], $this->config->allowedTags)
            ->remove('resolvedBy,allchecker,files,labels,uid')
            ->get();

        $build = $this->loadModel('file')->processImgURL($build, $this->config->build->editor->create['id'], $this->post->uid);
        $this->dao->insert(TABLE_BUILD)->data($build)
            ->autoCheck()
            ->batchCheck($this->config->build->create->requiredFields, 'notempty')
            ->check('name', 'unique', "product = {$build->product} AND branch = {$build->branch} AND deleted = '0'")
            ->exec();

        if(!dao::isError())
        {
            $buildID = $this->dao->lastInsertID();
            $this->file->updateObjectID($this->post->uid, $buildID, 'build');
            $this->file->saveUpload('build', $buildID);
            $this->loadModel('score')->create('build', 'create', $buildID);
            return $buildID;
        }
    }

    /**
     * Update a build.
     *
     * @param  int    $buildID
     * @access public
     * @return void
     */
    public function update($buildID)
    {
        $buildID  = (int)$buildID;
        $oldBuild = $this->dao->select('*')->from(TABLE_BUILD)->where('id')->eq($buildID)->fetch();
        $build    = fixer::input('post')->stripTags($this->config->build->editor->edit['id'], $this->config->allowedTags)
            ->setDefault('product', $oldBuild->product)
            ->setDefault('branch', $oldBuild->branch)
            ->cleanInt('product,branch,execution')
            ->remove('allchecker,resolvedBy,files,labels,uid')
            ->get();

        $build = $this->loadModel('file')->processImgURL($build, $this->config->build->editor->edit['id'], $this->post->uid);
        $this->dao->update(TABLE_BUILD)->data($build)
            ->autoCheck()
            ->batchCheck($this->config->build->edit->requiredFields, 'notempty')
            ->where('id')->eq($buildID)
            ->check('name', 'unique', "id != $buildID AND product = {$build->product} AND branch = {$build->branch} AND deleted = '0'")
            ->exec();
        if(isset($build->branch) and $oldBuild->branch != $build->branch) $this->dao->update(TABLE_RELEASE)->set('branch')->eq($build->branch)->where('build')->eq($buildID)->exec();
        if(!dao::isError())
        {
            $this->file->updateObjectID($this->post->uid, $buildID, 'build');
            return common::createChanges($oldBuild, $build);
        }
    }

    /**
     * Update linked bug to resolved.
     *
     * @param  object    $build
     * @access public
     * @return void
     */
    public function updateLinkedBug($build)
    {
        $bugs = empty($build->bugs) ? '' : $this->dao->select('*')->from(TABLE_BUG)->where('id')->in($build->bugs)->fetchAll();
        $data = fixer::input('post')->get();
        $now  = helper::now();

        $resolvedPairs = array();
        if(isset($_POST['bugs']))
        {
            foreach($data->bugs as $key => $bugID)
            {
                if(isset($_POST['resolvedBy'][$bugID])) $resolvedPairs[$bugID] = $data->resolvedBy[$bugID];
            }
        }

        $this->loadModel('action');
        if(!$bugs) return false;
        foreach($bugs as $bug)
        {
            if($bug->status == 'resolved' or $bug->status == 'closed') continue;

            $bug->resolvedBy     = $resolvedPairs[$bug->id];
            $bug->resolvedDate   = $now;
            $bug->status         = 'resolved';
            $bug->confirmed      = 1;
            $bug->assignedDate   = $now;
            $bug->assignedTo     = $bug->openedBy;
            $bug->lastEditedBy   = $this->app->user->account;
            $bug->lastEditedDate = $now;
            $bug->resolution     = 'fixed';
            $bug->resolvedBuild  = $build->id;
            $this->dao->update(TABLE_BUG)->data($bug)->where('id')->eq($bug->id)->exec();
            $this->action->create('bug', $bug->id, 'Resolved', '', 'fixed', $bug->resolvedBy);
        }
    }

    /**
     * Link stories
     *
     * @param  int    $buildID
     * @access public
     * @return void
     */
    public function linkStory($buildID)
    {
        $build = $this->getByID($buildID);

        foreach($this->post->stories as $i => $storyID)
        {
            if(strpos(",{$build->stories},", ",{$storyID},") !== false) unset($_POST['stories'][$i]);
        }

        $build->stories .= ',' . join(',', $this->post->stories);
        $this->dao->update(TABLE_BUILD)->set('stories')->eq($build->stories)->where('id')->eq((int)$buildID)->exec();

        $this->loadModel('action');
        foreach($this->post->stories as $storyID) $this->action->create('story', $storyID, 'linked2build', '', $buildID);
    }

    /**
     * Unlink story
     *
     * @param  int    $buildID
     * @param  int    $storyID
     * @access public
     * @return void
     */
    public function unlinkStory($buildID, $storyID)
    {
        $build = $this->getByID($buildID);
        $build->stories = trim(str_replace(",$storyID,", ',', ",$build->stories,"), ',');
        if($build->stories) $build->stories = ',' . $build->stories;

        $this->dao->update(TABLE_BUILD)->set('stories')->eq($build->stories)->where('id')->eq((int)$buildID)->exec();
        $this->loadModel('action')->create('story', $storyID, 'unlinkedfrombuild', '', $buildID, '', false);
    }

    /**
     * Batch unlink story.
     *
     * @param  int    $buildID
     * @access public
     * @return void
     */
    public function batchUnlinkStory($buildID)
    {
        $storyList = $this->post->unlinkStories;
        if(empty($storyList)) return true;

        $build = $this->getByID($buildID);
        $build->stories = ",$build->stories,";
        foreach($storyList as $storyID) $build->stories = str_replace(",$storyID,", ',', $build->stories);
        $build->stories = trim($build->stories, ',');
        $this->dao->update(TABLE_BUILD)->set('stories')->eq($build->stories)->where('id')->eq((int)$buildID)->exec();

        $this->loadModel('action');
        foreach($this->post->unlinkStories as $unlinkStoryID) $this->action->create('story', $unlinkStoryID, 'unlinkedfrombuild', '', $buildID);
    }

    /**
     * Link bugs.
     *
     * @param  int    $buildID
     * @access public
     * @return void
     */
    public function linkBug($buildID)
    {
        $build = $this->getByID($buildID);

        foreach($this->post->bugs as $i => $bugID)
        {
            if(strpos(",{$build->bugs},", ",{$bugID},") !== false) unset($_POST['bugs'][$i]);
        }

        $build->bugs .= ',' . join(',', $this->post->bugs);
        $this->updateLinkedBug($build);
        $this->dao->update(TABLE_BUILD)->set('bugs')->eq($build->bugs)->where('id')->eq((int)$buildID)->exec();

        $this->loadModel('action');
        foreach($this->post->bugs as $bugID) $this->action->create('bug', $bugID, 'linked2bug', '', $buildID);
    }

    /**
     * Unlink bug.
     *
     * @param  int    $buildID
     * @param  int    $bugID
     * @access public
     * @return void
     */
    public function unlinkBug($buildID, $bugID)
    {
        $build = $this->getByID($buildID);
        $build->bugs = trim(str_replace(",$bugID,", ',', ",$build->bugs,"), ',');
        if($build->bugs) $build->bugs = ',' . $build->bugs;

        $this->dao->update(TABLE_BUILD)->set('bugs')->eq($build->bugs)->where('id')->eq((int)$buildID)->exec();
        $this->loadModel('action')->create('bug', $bugID, 'unlinkedfrombuild', '', $buildID, '', false);
    }

    /**
     * Batch unlink bug.
     *
     * @param  int    $buildID
     * @access public
     * @return void
     */
    public function batchUnlinkBug($buildID)
    {

        $bugList = $this->post->unlinkBugs;
        if(empty($bugList)) return true;

        $build = $this->getByID($buildID);
        $build->bugs = ",$build->bugs,";
        foreach($bugList as $bugID) $build->bugs = str_replace(",$bugID,", ',', $build->bugs);
        $build->bugs = trim($build->bugs, ',');
        $this->dao->update(TABLE_BUILD)->set('bugs')->eq($build->bugs)->where('id')->eq((int)$buildID)->exec();

        $this->loadModel('action');
        foreach($this->post->unlinkBugs as $unlinkBugID) $this->action->create('bug', $unlinkBugID, 'unlinkedfrombuild', '', $buildID);
    }
}
