<?php
public function manageMembers($executionID)
{
    $execution = $this->getByID($executionID);
    $data      = (array)fixer::input('post')->get();

    extract($data);
    $executionID   = (int)$executionID;
    $executionType = 'execution';
    $accounts      = array_unique($accounts);
    $limited       = array_values($limited);
    $oldJoin       = $this->dao->select('`account`, `join`')->from(TABLE_TEAM)->where('root')->eq($executionID)->andWhere('type')->eq($executionType)->fetchPairs();

    foreach($accounts as $key => $account)
    {
        if(empty($account)) continue;

        if(!empty($execution->days) and (int)$days[$key] > $execution->days)
        {
            dao::$errors['message'][] = sprintf($this->lang->execution->daysGreaterProject, $execution->days);
            return false;
        }
        if((float)$hours[$key] > 24)
        {
            dao::$errors['message'][] = $this->lang->execution->errorHours;
            return false;
        }
    }

    $this->dao->delete()->from(TABLE_TEAM)->where('root')->eq($executionID)->andWhere('type')->eq($executionType)->exec();

    $executionMember = array();
    foreach($accounts as $key => $account)
    {
        if(empty($account)) continue;

        $member = new stdclass();
        $member->role      = $roles[$key];
        $member->days      = $days[$key];
        $member->hours     = $hours[$key];
        $member->limited   = $limited[$key];
        $member->teamgroup = $teamgroup[$key];

        $member->root    = $executionID;
        $member->account = $account;
        $member->join    = isset($oldJoin[$account]) ? $oldJoin[$account] : helper::today();
        $member->type    = $executionType;

        $executionMember[$account] = $member;
        $this->dao->insert(TABLE_TEAM)->data($member)->exec();
    }

    /* Only changed account update userview. */
    $oldAccounts     = array_keys($oldJoin);
    $changedAccounts = array_diff($accounts, $oldAccounts);
    $changedAccounts = array_merge($changedAccounts, array_diff($oldAccounts, $accounts));
    $changedAccounts = array_unique($changedAccounts);

    /* Add the execution team members to the project. */
    if($execution->project) $this->addProjectMembers($execution->project, $executionMember);
    if($execution->acl != 'open') $this->updateUserView($executionID, 'sprint', $changedAccounts);
}

public function linkStory($executionID, $stories = array(), $products = array(), $extra = '', $lanes = array(), $storyType = 'story', $from = '')
{
    if(empty($executionID)) return false;
    if(empty($stories)) $stories = $this->post->stories;
    if(empty($stories)) return false;
    if(empty($products)) $products = $this->post->products;

    $this->loadModel('action');
    $this->loadModel('kanban');
    $versions      = $this->loadModel('story')->getVersions($stories);
    $linkedStories = $this->dao->select('*')->from(TABLE_PROJECTSTORY)->where('project')->eq($executionID)->orderBy('order_desc')->fetchPairs('story', 'order');
    $lastOrder     = reset($linkedStories);
    $storyList     = $this->dao->select('id, status, branch')->from(TABLE_STORY)->where('id')->in(array_values($stories))->fetchAll('id');
    $execution     = $this->getById($executionID);

    $extra = str_replace(array(',', ' '), array('&', ''), $extra);
    parse_str($extra, $output);
    foreach($stories as $key => $storyID)
    {
        $notAllowedStatus = $this->app->rawMethod == 'batchcreate' ? 'closed' : 'draft,reviewing,closed';
        if(strpos($notAllowedStatus, $storyList[$storyID]->status) !== false && !$execution->PI) continue;
        if(isset($linkedStories[$storyID])) continue;

        $laneID = isset($output['laneID']) ? $output['laneID'] : 0;
        if(!empty($lanes[$storyID])) $laneID = $lanes[$storyID];

        $columnID = $this->kanban->getColumnIDByLaneID($laneID, 'backlog');
        if(empty($columnID)) $columnID = isset($output['columnID']) ? $output['columnID'] : 0;

        if(!empty($laneID) and !empty($columnID)) $this->kanban->addKanbanCell($executionID, $laneID, $columnID, 'story', $storyID);

        $data = new stdclass();
        $data->project = $executionID;
        $data->product = (int)$products[$storyID];
        $data->branch  = $storyList[$storyID]->branch;
        $data->story   = $storyID;
        $data->version = $versions[$storyID];
        $data->order   = (int)++$lastOrder;
        $this->dao->replace(TABLE_PROJECTSTORY)->data($data)->exec();

        $this->story->setStage($storyID);
        $this->linkCases($executionID, (int)$products[$storyID], $storyID);

        $action = $execution->type == 'project' ? 'linked2project' : 'linked2execution';
        if($action == 'linked2execution' and $execution->type == 'kanban') $action = 'linked2kanban';
        if($execution->multiple or $execution->type == 'project') $this->action->create('story', $storyID, $action, '', $executionID);
        if($storyType == 'requirement' and $execution->model == 'ipd') $this->dao->update(TABLE_STORY)->set('status')->eq('developing')->where('id')->eq($storyID)->exec();
    }

    /* 如果该迭代是从PI转过来的，则需要将需求关联到看板。 */
    if($from != 'pi' && $execution->PI)
    {
        $piID = $this->dao->select('pi')->from(TABLE_PIEXECUTION)->where('id')->eq($execution->PI)->fetch('pi');
        $cell = $this->dao->select('t1.*')->from(TABLE_KANBANCELL)->alias('t1')
              ->leftJoin(TABLE_KANBANCOLUMN)->alias('t2')->on('t1.column = t2.id')
              ->where('t2.execution')->eq($execution->id)
              ->fetch();

        if($cell) $this->loadModel('pi')->linkKanbanObjects($piID, $cell->lane, $cell->column, 'story', $stories, 'execution');
    }

    if(!isset($output['laneID']) or !isset($output['columnID'])) $this->kanban->updateLane($executionID, 'story');
}

public function unlinkStory($executionID, $storyID, $laneID = 0, $columnID = 0, $from = '')
{
    $execution = $this->dao->findById($executionID)->from(TABLE_EXECUTION)->fetch();
    $storyType = $this->dao->findById($storyID)->from(TABLE_STORY)->fetch('type');
    if($execution->type == 'project')
    {
        $executions       = $this->dao->select('*')->from(TABLE_EXECUTION)->where('parent')->eq($executionID)->fetchAll('id');
        $executionStories = $this->dao->select('project,story')->from(TABLE_PROJECTSTORY)->where('story')->eq($storyID)->andWhere('project')->in(array_keys($executions))->fetchAll();
        if(!empty($executionStories)) return print(js::alert($this->lang->execution->notAllowedUnlinkStory));
    }
    $this->dao->delete()->from(TABLE_PROJECTSTORY)->where('project')->eq($executionID)->andWhere('story')->eq($storyID)->limit(1)->exec();

    /* 移除需求时，同步移除PI看板中的对应列的需求。*/
    if($execution->PI and $from != 'pi')
    {
        $piID = $this->dao->select('pi')->from(TABLE_PIEXECUTION)->where('id')->eq($execution->PI)->fetch('pi');
        $cell = $this->dao->select('t1.*')->from(TABLE_KANBANCELL)->alias('t1')
            ->leftJoin(TABLE_KANBANCOLUMN)->alias('t2')->on('t1.column = t2.id')
            ->where('t2.execution')->eq($execution->id)
            ->fetch();
        $cardID = $this->dao->select('id')->from(TABLE_KANBANCARD)
             ->where('fromID')->eq($storyID)
             ->andWhere('fromType')->eq('story')
             ->andWhere('id')->in($cell->cards)
             ->fetch('id');
        if($cardID) $this->loadModel('pi')->unlinkKanbanObjects($piID, $cell->lane, $cell->column, 'story', $cardID, 'execution');
    }

    /* In ipd project, unlink stories change it's status to launched. */
    if($execution->model == 'ipd' and $storyType == 'requirement') $this->dao->update(TABLE_STORY)->set('status')->eq('launched')->where('id')->eq($storyID)->exec();

    /* Resolve TABLE_KANBANCELL's field cards. */
    if($execution->type == 'kanban')
    {
        $cell = $this->dao->select('*')->from(TABLE_KANBANCELL)
              ->where('kanban')->eq($executionID)
              ->andWhere('`column`')->eq($columnID)
              ->andWhere('lane')->eq($laneID)
              ->fetch();
        /* Resolve signal ','. */
        $cell->cards = str_replace(",$storyID,", ',', $cell->cards);
        if(strlen($cell->cards) == 1) $cell->cards = '';
        $this->dao->update(TABLE_KANBANCELL)->data($cell)
             ->where('kanban')->eq($executionID)
             ->andWhere('`column`')->eq($columnID)
             ->andWhere('lane')->eq($laneID)
             ->exec();
    }

    $order   = 1;
    $stories = $this->dao->select('*')->from(TABLE_PROJECTSTORY)->where('project')->eq($executionID)->orderBy('order')->fetchAll();
    foreach($stories as $executionstory)
    {
        if($executionstory->order != $order) $this->dao->update(TABLE_PROJECTSTORY)->set('`order`')->eq($order)->where('project')->eq($executionID)->andWhere('story')->eq($executionstory->story)->exec();
        $order++;
    }

    $this->loadModel('story')->setStage($storyID);
    $this->unlinkCases($executionID, $storyID);
    $objectType = $execution->type == 'project' ? 'unlinkedfromproject' : 'unlinkedfromexecution';
    if($execution->multiple or $execution->type == 'project') $this->loadModel('action')->create('story', $storyID, $objectType, '', $executionID);

    /* Sync unlink story in no multiple execution. */
    if(empty($execution->multiple) and $execution->type != 'project')
    {
        $this->dao->delete()->from(TABLE_PROJECTSTORY)->where('project')->eq($execution->project)->andWhere('story')->eq($storyID)->limit(1)->exec();
        $this->loadModel('action')->create('story', $storyID, 'unlinkedfromproject', '', $execution->project);
    }

    $tasks = $this->dao->select('id')->from(TABLE_TASK)->where('story')->eq($storyID)->andWhere('execution')->eq($executionID)->andWhere('status')->in('wait,doing')->fetchPairs('id');
    foreach($tasks as $taskID)
    {
        if(empty($taskID)) continue;
        $changes  = $this->loadModel('task')->cancel($taskID);
        $actionID = $this->action->create('task', $taskID, 'Canceled');
        $this->action->logHistory($actionID, $changes);
    }
}
