<?php
/**
 * The zen file of release module of ZenTaoPMS.
 *
 * @copyright   Copyright 2009-2023 禅道软件（青岛）有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
 * @license     ZPL(https://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
 * @author      Shujie Tian<tianshujie@easycorp.ltd>
 * @package     release
 * @link        https://www.zentao.net
 */
class releaseZen extends release
{
    /**
     * 处理发布列表展示数据。
     * Process release list display data.
     *
     * @param  array     $releaseList
     * @access protected
     * @return array
     */
    protected function processReleaseListData($releaseList, $childReleases = array())
    {
        $releases = array();
        $this->loadModel('project');
        $this->loadModel('execution');
        foreach($releaseList as $release)
        {
            $buildCount = count($release->builds);

            $release->rowID   = $release->id;
            $release->rowspan = $buildCount;
            $release->actions = $this->release->buildActionList($release);

            if(!empty($release->builds))
            {
                foreach($release->builds as $build)
                {
                    $releaseInfo  = clone $release;
                    $moduleName   = $build->execution ? 'build' : 'projectbuild';
                    $canClickable = false;
                    if($moduleName == 'projectbuild' && $this->project->checkPriv((int)$build->project)) $canClickable = true;
                    if($moduleName == 'build' && $this->execution->checkPriv((int)$build->execution))    $canClickable = true;
                    $build->link = $canClickable ? $this->createLink($moduleName, 'view', "buildID={$build->id}") : '';

                    $releaseInfo->build = $build;

                    $releases[] = $releaseInfo;
                }
            }
            else
            {
                $releases[] = $release;
            }

            if(empty($release->releases)) continue;

            foreach(explode(',', $release->releases) as $childID)
            {
                if(isset($childReleases[$childID]))
                {
                    $child = clone $childReleases[$childID];
                    $child = current($this->processReleaseListData(array($child)));

                    $child->rowID  = "{$release->id}-{$childID}";
                    $child->parent = $release->id;
                    $releases[$child->rowID] = $child;
                }
            }

        }

        return $releases;
    }

    /**
     * 构造待创建的发布数据。
     * Build the release data to be create.
     *
     * @param  int          $productID
     * @param  int          $branch
     * @param  int          $projectID
     * @access protected
     * @return object|false
     */
    protected function buildReleaseForCreate($productID, $branch, $projectID = 0)
    {
        $productID = $this->post->product ? $this->post->product : $productID;
        $branch    = $this->post->branch ? $this->post->branch : $branch;
        $newSystem = $this->post->newSystem;
        if(empty($projectID))
        {
            $product = $this->loadModel('product')->getById($productID);
            if($product->shadow)
            {
                $projectID = $this->dao->select('t2.id')->from(TABLE_PROJECTPRODUCT)->alias('t1')
                    ->leftJoin(TABLE_PROJECT)->alias('t2')
                    ->on('t1.project=t2.id')
                    ->where('t1.product')->eq($productID)
                    ->andWhere('t2.type')->eq('project')
                    ->fetch('id');
            }
        }

        if(!$newSystem && !$this->post->system) $this->config->release->form->create['system']['required'] = true;
        if($newSystem  && !$this->post->systemName)
        {
            $this->config->release->form->create['systemName'] = array('type' => 'string', 'required' => true);
            $this->lang->release->systemName = $this->lang->release->system;
        }

        $release = form::data()
            ->add('product', (int)$productID)
            ->add('branch',  (int)$branch)
            ->setIF($projectID, 'project', $projectID)
            ->setIF($this->post->build === false, 'build', 0)
            ->setIF($this->post->status != 'normal', 'releasedDate', null)
            ->get();

        /* Check build if build is required. */
        if(strpos($this->config->release->create->requiredFields, 'build') !== false && empty($release->build)) dao::$errors['build'] = sprintf($this->lang->error->notempty, $this->lang->release->build);

        if(!$newSystem && $this->post->system)
        {
            $system = $this->loadModel('system')->fetchByID((int)$this->post->system);
            if(!$system) dao::$errors['system'][] = sprintf($this->lang->error->notempty, $this->lang->release->system);

            if($system->integrated == '1')
            {
                $releases = (array)$this->post->releases;

                $release->build    = '';
                $release->releases = trim(implode(',', array_filter($releases)), ',');
                if(!$release->releases) dao::$errors['releases[' . key($releases) . ']'][] = sprintf($this->lang->error->notempty, $this->lang->release->name);
            }
        }
        if(dao::isError()) return false;

        if($newSystem && $this->post->systemName)
        {
            $system = new stdclass();
            $system->name        = $this->post->systemName;
            $system->product     = $productID;
            $system->createdBy   = $this->app->user->account;
            $system->createdDate = helper::now();

            $release->system = $this->loadModel('system')->create($system);
        }

        return $release;
    }

    /**
     * 构建搜索表单字段。
     * Build search form fields.
     *
     * @param  int       $queryID
     * @param  string    $actionURL
     * @param  object    $product
     * @param  string    $branch
     * @access protected
     * @return void
     */
    protected function buildSearchForm($queryID, $actionURL, $product, $branch)
    {
        $this->config->release->search['queryID']   = $queryID;
        $this->config->release->search['actionURL'] = $actionURL;

        if($product->type != 'normal') $this->config->release->search['params']['branch']['values'] = $this->loadModel('branch')->getPairs($product->id, 'all');
        $this->config->release->search['params']['build']['values'] = $this->loadmodel('build')->getBuildPairs(array($product->id), $branch, 'notrunk|withbranch|hasproject', 0, 'execution', '', false);

        $this->loadModel('search')->setSearchParams($this->config->release->search);
    }

    /**
     * 获取发布列表的搜索条件。
     * Get the search condition of release list.
     *
     * @param  int       $queryID
     * @access protected
     * @return string
     */
    protected function getSearchQuery($queryID)
    {
        if($queryID)
        {
            $query = $this->loadModel('search')->getQuery($queryID);
            if($query)
            {
                $this->session->set('releaseQuery', $query->sql);
                $this->session->set('releaseForm', $query->form);
            }
        }

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

        /* Replace the condition of all branch to 1. */
        $allBranch = "`branch` = 'all'";
        if(strpos($releaseQuery, $allBranch) !== false) $releaseQuery = str_replace($allBranch, '1', $releaseQuery);
        $releaseQuery = preg_replace('/`(\w+)`/', 't1.`$1`', $releaseQuery);

        return $releaseQuery;
    }

    /**
     * 获取发布不可关联的需求ID列表。
     * Get the story id list that can not be linked to the release.
     *
     * @param  object    $release
     * @access protected
     * @return array
     */
    public function getExcludeStoryIdList($release)
    {
        $parentIdList = $this->dao->select('id')->from(TABLE_STORY)
            ->where('product')->eq($release->product)
            ->andWhere('type')->eq('story')
            ->andWhere('isParent')->eq('1')
            ->andWhere('status')->notIN('draft,reviewing,changing')
            ->fetchPairs();

        foreach(explode(',', $release->stories) as $storyID)
        {
            if(!$storyID) continue;
            if(!isset($parentIdList[$storyID])) $parentIdList[$storyID] = $storyID;
        }

        return $parentIdList;
    }

    /**
     * 生成的发布详情页面的需求数据。
     * Generate the story data for the release view page.
     *
     * @param  object    $release
     * @param  string    $type
     * @param  string    $link
     * @param  string    $param
     * @param  string    $orderBy
     * @param  object    $storyPager
     * @param  object    $bugPager
     * @param  object    $leftBugPager
     * @access protected
     * @return void
     */
    protected function assignVarsForView($release, $type, $link, $param, $orderBy, $storyPager, $bugPager, $leftBugPager)
    {
        $sort = common::appendOrder($orderBy);
        if(strpos($sort, 'pri_') !== false) $sort = str_replace('pri_', 'priOrder_', $sort);
        $sort .= ',buildID_asc';

        $storyIdList   = trim($release->stories, ',');
        $bugIdList     = trim($release->bugs, ',');
        $leftBugIdList = trim($release->leftBugs, ',');
        if($release->releases)
        {
            $linkedReleases = $this->release->getListByCondition(explode(',', $release->releases));
            foreach($linkedReleases as $linkedRelease)
            {
                $storyIdList   .= ',' . $linkedRelease->stories;
                $bugIdList     .= ',' . $linkedRelease->bugs;
                $leftBugIdList .= ',' . $linkedRelease->leftBugs;
            }
        }

        $stories = $this->release->getStoryList($storyIdList, $release->branch, $type == 'story' ? $sort : '', $storyPager);

        $sort = common::appendOrder($orderBy);
        $bugs = $this->release->getBugList($bugIdList, $type == 'bug' ? $sort : '', $bugPager);

        if($type == 'leftBug' && strpos($orderBy, 'severity_') !== false) $sort = str_replace('severity_', 'severityOrder_', $sort);
        $leftBugs = $this->release->getBugList($leftBugIdList, $type == 'leftBug' ? $sort : '', $leftBugPager, 'left');

        $product = $this->loadModel('product')->getByID($release->product);

        $this->view->title        = "RELEASE #$release->id $release->name/" . $product->name;
        $this->view->actions      = $this->loadModel('action')->getList('release', $release->id);
        $this->view->users        = $this->loadModel('user')->getPairs('noletter');
        $this->view->storyPager   = $storyPager;
        $this->view->stories      = $stories;
        $this->view->release      = $release;
        $this->view->orderBy      = $orderBy;
        $this->view->type         = $type;
        $this->view->link         = $link;
        $this->view->grades       = $this->loadModel('story')->getGradePairs('story', 'all');
        $this->view->showGrade    = $this->config->edition == 'ipd';
        $this->view->param        = $param;
        $this->view->storyCases   = $this->loadModel('testcase')->getStoryCaseCounts(array_keys($stories));
        $this->view->summary      = $this->product->summary($stories);
        $this->view->builds       = $this->loadModel('build')->getBuildPairs(array($release->product), 'all', 'withbranch|hasproject|hasdeleted', 0, 'execution', '', true);
        $this->view->bugs         = $bugs;
        $this->view->leftBugs     = $leftBugs;
        $this->view->bugPager     = $bugPager;
        $this->view->leftBugPager = $leftBugPager;

        if($this->app->getViewType() == 'json')
        {
            unset($this->view->storyPager);
            unset($this->view->bugPager);
            unset($this->view->leftBugPager);
        }
    }

    /**
     * 构造关联需求的搜索表单。
     * Build the search form of link story.
     *
     * @param  object    $release
     * @param  int       $queryID
     * @access protected
     * @return void
     */
    protected function buildLinkStorySearchForm($release, $queryID)
    {
        $this->app->loadLang('story');
        $this->loadModel('product');

        unset($this->config->product->search['fields']['product']);
        unset($this->config->product->search['fields']['project']);
        unset($this->config->product->search['fields']['grade']);
        unset($this->config->product->search['params']['product']);
        unset($this->config->product->search['params']['project']);
        unset($this->config->product->search['params']['grade']);

        $this->config->product->search['actionURL'] = $this->createLink($this->app->rawModule, 'view', "releaseID={$release->id}&type=story&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']['plan']['values'] = $this->loadModel('productplan')->getPairs($release->product, $release->branch, 'withMainPlan', true);
        $this->config->product->search['params']['status'] = array('operator' => '=', 'control' => 'select', 'values' => $this->lang->story->statusList);

        $searchModules = array();
        $moduleGroups  = $this->loadModel('tree')->getOptionMenu($release->product, 'story', 0, explode(',', $release->branch));
        foreach($moduleGroups as $modules) $searchModules += $modules;
        $this->config->product->search['params']['module']['values'] = $searchModules;

        if($release->productType == 'normal')
        {
            unset($this->config->product->search['fields']['branch']);
            unset($this->config->product->search['params']['branch']);
        }
        else
        {
            $branches = $this->loadModel('branch')->getPairsByIdList(explode(',', trim($release->branch, ',')));
            $this->config->product->search['fields']['branch']           = sprintf($this->lang->product->branch, $this->lang->product->branchName[$release->productType]);
            $this->config->product->search['params']['branch']['values'] = array('' => '', BRANCH_MAIN => $this->lang->branch->main) + $branches;
        }
        $this->loadModel('search')->setSearchParams($this->config->product->search);
    }

    /**
     * 构造关联Bug的搜索表单。
     * Build the search form of link bug.
     *
     * @param  object    $release
     * @param  int       $queryID
     * @param  string    $type
     * @access protected
     * @return void
     */
    protected function buildLinkBugSearchForm($release, $queryID, $type)
    {
        $this->loadModel('bug');
        unset($this->config->bug->search['fields']['product']);
        unset($this->config->bug->search['fields']['project']);
        unset($this->config->bug->search['params']['product']);
        unset($this->config->bug->search['params']['project']);

        $this->config->bug->search['actionURL'] = $this->createLink($this->app->rawModule, 'view', "releaseID={$release->id}&type={$type}&link=true&param=" . helper::safe64Encode('&browseType=bySearch&queryID=0'));
        $this->config->bug->search['queryID']   = $queryID;
        $this->config->bug->search['style']     = 'simple';

        $this->config->bug->search['params']['plan']['values']          = $this->loadModel('productplan')->getPairs($release->product, $release->branch, 'withMainPlan', true);
        $this->config->bug->search['params']['execution']['values']     = $this->loadModel('product')->getExecutionPairsByProduct($release->product, $release->branch);
        $this->config->bug->search['params']['openedBuild']['values']   = $this->loadModel('build')->getBuildPairs(array($release->product), 'all', 'releasetag');
        $this->config->bug->search['params']['resolvedBuild']['values'] = $this->config->bug->search['params']['openedBuild']['values'];

        $searchModules = array();
        $moduleGroups  = $this->loadModel('tree')->getOptionMenu($release->product, 'bug', 0, explode(',', $release->branch));
        foreach($moduleGroups as $modules) $searchModules += $modules;
        $this->config->bug->search['params']['module']['values'] = $searchModules;

        if($release->productType == 'normal')
        {
            unset($this->config->bug->search['fields']['branch']);
            unset($this->config->bug->search['params']['branch']);
        }
        else
        {
            $branches = $this->loadModel('branch')->getPairsByIdList(explode(',', trim($release->branch, ',')));
            $this->config->bug->search['fields']['branch']           = sprintf($this->lang->product->branch, $this->lang->product->branchName[$release->productType]);
            $this->config->bug->search['params']['branch']['values'] = array('' => '', BRANCH_MAIN => $this->lang->branch->main) + $branches;
        }
        $this->loadModel('search')->setSearchParams($this->config->bug->search);
    }

    /**
     * 构造导出的需求列表数据。
     * Build the story list data for export.
     *
     * @param  object    $release
     * @access protected
     * @return string
     */
    protected function buildStoryDataForExport($release)
    {
        $this->loadModel('story');

        $html    = "<h3>{$this->lang->release->stories}</h3>";
        $fields  = array('id' => $this->lang->story->id, 'title' => $this->lang->story->title);
        $stories = $this->release->getStoryList($release->stories, (int)$release->branch);
        if(empty($stories)) return $html;

        $html .= '<table><tr>';
        foreach($fields as $fieldLabel) $html .= "<th><nobr>$fieldLabel</nobr></th>\n";
        $html .= '</tr>';

        $stories = array_map(function($story){$story->title = "<a href='" . common::getSysURL() . $this->createLink('story', 'view', "storyID=$story->id") . "' target='_blank'>$story->title</a>"; return $story;}, $stories);
        foreach($stories as $row)
        {
            $html .= "<tr valign='top'>\n";
            foreach($fields as $fieldName => $fieldLabel) $html .= "<td><nobr>" . zget($row, $fieldName, '') . "</nobr></td>\n";
            $html .= "</tr>\n";
        }
        $html .= '</table>';

        return $html;
    }

    /**
     * 构造导出的解决的Bug或遗留Bug列表数据。
     * Build the resolved or generated bug list data for export.
     *
     * @param  object    $release
     * @param  string    $type        bug|leftbug
     * @access protected
     * @return string
     */
    protected function buildBugDataForExport($release, $type = 'bug')
    {
        $this->loadModel('bug');

        $title     = $type == 'bug' ? $this->lang->release->bugs : $this->lang->release->generatedBugs;
        $html      = "<h3>{$title}</h3>";
        $fields    = array('id' => $this->lang->bug->id, 'title' => $this->lang->bug->title);
        $bugIdList = $type == 'bug' ? $release->bugs : $release->leftBugs;
        $bugs      = $this->release->getBugList($bugIdList);
        if(empty($bugs)) return $html;

        $html .= '<table><tr>';
        foreach($fields as $fieldLabel) $html .= "<th><nobr>$fieldLabel</nobr></th>\n";
        $html .= '</tr>';
        $bugs = array_map(function($bug){$bug->title = "<a href='" . common::getSysURL() . $this->createLink('bug', 'view', "bugID=$bug->id") . "' target='_blank'>$bug->title</a>"; return $bug;}, $bugs);
        foreach($bugs as $row)
        {
            $html .= "<tr valign='top'>\n";
            foreach($fields as $fieldName => $fieldLabel) $html .= "<td><nobr>" . zget($row, $fieldName, '') . "</nobr></td>\n";
            $html .= "</tr>\n";
        }
        $html .= '</table>';

        return $html;
    }
}
