* @link https://www.zentao.net */ class projectTao extends projectModel { /** * Update project table when start a project. * * @param int $projectID * @param object $project * @access protected * @return bool */ protected function doStart($projectID, $project) { $this->dao->update(TABLE_PROJECT)->data($project, 'comment') ->autoCheck() ->check($this->config->project->start->requiredFields, 'notempty') ->checkIF($project->realBegan != '', 'realBegan', 'le', helper::today()) ->checkFlow() ->where('id')->eq($projectID) ->exec(); return !dao::isError(); } /** * Update project table when suspend a project. * * @param int $projectID * @param object $project * * @access protected * @return bool */ protected function doSuspend($projectID, $project) { $this->dao->update(TABLE_PROJECT)->data($project) ->autoCheck() ->checkFlow() ->where('id')->eq($projectID) ->exec(); return !dao::isError(); } /** * Update project table when close a project. * * @param int $projectID * @param object $project * @param object $oldProject * * @access protected * @return bool */ protected function doClosed($projectID, $project, $oldProject) { $this->lang->error->ge = $this->lang->project->ge; $this->dao->update(TABLE_PROJECT)->data($project) ->autoCheck() ->check($this->config->project->close->requiredFields, 'notempty') ->checkIF($project->realEnd != '', 'realEnd', 'le', helper::today()) ->checkIF($project->realEnd != '', 'realEnd', 'ge', $oldProject->realBegan) ->checkFlow() ->where('id')->eq($projectID) ->exec(); return !dao::isError(); } /** * 激活项目时更新项目的信息。 * Update project table when activate a project. * * @param int $projectID * @param object $project * @access protected * @return bool */ protected function doActivate($projectID ,$project) { $this->dao->update(TABLE_PROJECT)->data($project , 'readjustTime, readjustTask, comment') ->autoCheck() ->checkFlow() ->where('id')->eq((int)$projectID) ->exec(); return !dao::isError(); } /** * 更新项目主表。 * Update project table when edit a project. * * @param int $projectID * @param object $project * @access protected * @return bool */ public function doUpdate($projectID, $project) { $project->id = $projectID; $this->dao->update(TABLE_PROJECT)->data($project) ->autoCheck('begin,end') ->check('end', 'gt', $project->begin) ->checkIF(!empty($project->name), 'name', 'unique', "id != $projectID and `type` = 'project' and `parent` = '$project->parent' and `model` = " . $this->dao->sqlobj->quote($project->model) . " and `deleted` = '0'") ->checkIF(!empty($project->code), 'code', 'unique', "id != $projectID and `type` = 'project' and `parent` = '$project->parent' and `deleted` = '0'") ->checkFlow() ->where('id')->eq($projectID) ->exec(); return !dao::isError(); } /** * 新增项目。 * Insert a project to project table. * * @param object $project * @access protected * @return bool */ protected function doCreate($project) { $this->lang->error->unique = $this->lang->error->repeat; $this->dao->insert(TABLE_PROJECT)->data($project) ->autoCheck() ->batchCheck($this->config->project->create->requiredFields, 'notempty') ->checkIF(!empty($project->name), 'name', 'unique', "`type`='project' and `parent` = " . $this->dao->sqlobj->quote($project->parent) . " and `model` = " . $this->dao->sqlobj->quote($project->model) . " and `deleted` = '0'") ->checkIF(!empty($project->code), 'code', 'unique', "`type`='project' and `parent` = " . $this->dao->sqlobj->quote($project->parent) . " and `model` = " . $this->dao->sqlobj->quote($project->model) . " and `deleted` = '0'") ->checkIF($project->end != '', 'end', 'gt', $project->begin) ->checkFlow() ->exec(); return !dao::isError(); } /** * 获取未完成的任务。 * Fetch undone tasks. * * @param int $projectID * @access protected * @return object[] */ protected function fetchUndoneTasks($projectID) { return $this->dao->select('id,estStarted,deadline,status')->from(TABLE_TASK) ->where('deadline')->notZeroDate() ->andWhere('status')->in('wait,doing') ->andWhere('project')->eq($projectID) ->fetchAll(); } /** * 更新任务的起止日期。 * Update start and end date of tasks. * * @param array $tasks * @param object $oldProject * @param object $project * @access protected * @return bool */ protected function updateTasksStartAndEndDate($tasks, $oldProject, $project) { $beginTimeStamp = strtotime($project->begin); foreach($tasks as $task) { if($task->status == 'wait' && !helper::isZeroDate($task->estStarted)) { $taskDays = helper::diffDate($task->deadline, $task->estStarted); $taskOffset = helper::diffDate($task->estStarted, $oldProject->begin); $estStartedTimeStamp = $beginTimeStamp + $taskOffset * 24 * 3600; $estStarted = date('Y-m-d', $estStartedTimeStamp); $deadline = date('Y-m-d', $estStartedTimeStamp + $taskDays * 24 * 3600); if($estStarted > $project->end) $estStarted = $project->end; if($deadline > $project->end) $deadline = $project->end; $this->dao->update(TABLE_TASK) ->set('estStarted')->eq($estStarted) ->set('deadline')->eq($deadline) ->where('id')->eq($task->id) ->exec(); if(dao::isError()) return false; } else { $taskOffset = helper::diffDate($task->deadline, $oldProject->begin); $deadline = date('Y-m-d', $beginTimeStamp + $taskOffset * 24 * 3600); if($deadline > $project->end) $deadline = $project->end; $this->dao->update(TABLE_TASK)->set('deadline')->eq($deadline)->where('id')->eq($task->id)->exec(); if(dao::isError()) return false; } } return true; } /** * 插入项目团队成员。 * Insert project members. * * @param array $members * @param int $projectID * @param array $oldJoin * @access protected * @return array */ protected function insertMember($members, $projectID, $oldJoin) { $accounts = array(); foreach($members as $member) { if(empty($member->account)) continue; $account = $member->account; $accounts[] = $account; $data = new stdclass(); $data->role = $member->role; $data->days = $member->days; $data->hours = $member->hours; $data->limited = isset($member->limited) ? $member->limited : 'no'; $data->root = $projectID; $data->account = $account; $data->join = isset($oldJoin[$account]) ? $oldJoin[$account] : helper::today(); $data->type = 'project'; $this->dao->insert(TABLE_TEAM)->data($data)->exec(); } return $accounts; } /** * 更新项目团队成员的视图权限 * Update member view when project member changed. * * @param int $projectID * @param array $accounts * @param array $oldJoin * @access protected * @return void */ protected function updateMemberView($projectID, $accounts, $oldJoin) { /* Only changed account update userview. */ $oldAccounts = array_keys($oldJoin); $removedAccounts = array_diff($oldAccounts, $accounts); $addedAccounts = array_diff($accounts, $oldAccounts); $changedAccounts = array_merge($removedAccounts, $addedAccounts); $changedAccounts = array_unique($changedAccounts); $childSprints = $this->dao->select('id')->from(TABLE_PROJECT) ->where('project')->eq($projectID) ->andWhere('type')->in('stage,sprint,kanban') ->andWhere('deleted')->eq('0') ->fetchPairs(); $linkedProducts = $this->dao->select("t2.id")->from(TABLE_PROJECTPRODUCT)->alias('t1') ->leftJoin(TABLE_PRODUCT)->alias('t2')->on('t1.product = t2.id') ->where('t2.deleted')->eq(0) ->andWhere('t1.project')->eq($projectID) ->andWhere('t2.vision')->eq($this->config->vision) ->fetchPairs(); $this->loadModel('user')->updateUserView(array($projectID), 'project', $changedAccounts); if(!empty($childSprints)) $this->user->updateUserView($childSprints, 'sprint', $changedAccounts); if(!empty($linkedProducts)) $this->user->updateUserView(array_keys($linkedProducts), 'product', $changedAccounts); if(empty($addedAccounts) && empty($removedAccounts)) return; /* Log history. */ $actionID = $this->loadModel('action')->create('project', $projectID, 'ManagedTeam'); $users = $this->loadModel('user')->getPairs('noletter'); $addedAccountList = array_map(function($account) use ($users) { return zget($users, $account); }, $addedAccounts); $removedAccountList = array_map(function($account) use ($users) { return zget($users, $account); }, $removedAccounts); if(!empty($addedAccountList)) $changes[] = array('field' => 'addDiff', 'old' => '', 'new' => '', 'diff' => join(',', $addedAccountList)); if(!empty($removedAccountList)) $changes[] = array('field' => 'removeDiff', 'old' => '', 'new' => '', 'diff' => join(',', $removedAccountList)); if(!empty($changes)) $this->action->logHistory($actionID, $changes); /* Remove execution members. */ if($this->post->removeExecution == 'yes' and !empty($childSprints) and !empty($removedAccountList)) { $childTeams = $this->dao->select('root,account')->from(TABLE_TEAM) ->where('root')->in($childSprints) ->andWhere('type')->eq('execution') ->andWhere('account')->in($removedAccounts) ->fetchGroup('root', 'account'); $this->dao->delete()->from(TABLE_TEAM) ->where('root')->in($childSprints) ->andWhere('type')->eq('execution') ->andWhere('account')->in($removedAccounts) ->exec(); foreach($childTeams as $sprintID => $childTeam) { $childAccounts = array_keys($childTeam); $removedAccountList = array_intersect($childAccounts, $removedAccounts); if(empty($removedAccountList)) continue; $removedAccountList = array_map(function($account) use ($users) { return zget($users, $account); }, $removedAccountList); $changes = array(array('field' => 'removeDiff', 'old' => '', 'new' => '', 'diff' => join(',', $removedAccountList))); $actionID = $this->action->create('execution', $sprintID, 'syncProjectTeam'); $this->action->logHistory($actionID, $changes); } } } /** * 获取项目的详情,包含project表的所有内容。 * Get project details, including all contents of the project. * * @param int $projectID * @param string $type * @access protected * @return object|false */ protected function fetchProjectInfo($projectID, $type = '') { $project = $this->dao->select('*') ->from(TABLE_PROJECT) ->where('id')->eq($projectID) ->beginIF(!empty($type))->andWhere('`type`')->in($type)->fi() ->fetch(); /* Filter the date is empty or 1970. */ if($project && helper::isZeroDate($project->end)) $project->end = ''; return $project; } /** * 创建项目后,创建默认的项目主库。 * Create doclib after create a project. * * @param int $projectID * @param object $project * @param object $program * @access protected * @return bool */ protected function createDocLib($projectID, $project, $program) { /* Create doc lib. */ $this->app->loadLang('doc'); $authorizedUsers = array(); if($project->parent && $project->acl == 'program') { $stakeHolders = $this->loadModel('stakeholder')->getStakeHolderPairs($project->parent); $authorizedUsers = array_keys($stakeHolders); foreach(explode(',', $project->whitelist) as $user) { if(empty($user)) continue; $authorizedUsers[$user] = $user; } $authorizedUsers[$project->PM] = $project->PM; $authorizedUsers[$project->openedBy] = $project->openedBy; $authorizedUsers[$program->PM] = $program->PM; $authorizedUsers[$program->openedBy] = $program->openedBy; } $lib = new stdclass(); $lib->project = $projectID; $lib->name = $this->lang->doclib->main['project']; $lib->type = 'project'; $lib->main = '1'; $lib->acl = 'default'; $lib->users = ',' . implode(',', array_filter($authorizedUsers)) . ','; $lib->vision = zget($project, 'vision', 'rnd'); $lib->addedBy = $this->app->user->account; $lib->addedDate = helper::now(); $this->dao->insert(TABLE_DOCLIB)->data($lib)->exec(); return !dao::isError(); } /** * 创建项目时,如果直接输入了产品名,则创建产品并与项目关联。 * Create doclib after create a project. * * @param int $projectID * @param object $project * @param object $postData * @param object $program * @access protected * @return bool */ protected function createProduct($projectID, $project, $postData, $program) { /* If parent not empty, link products or create products. */ $product = new stdclass(); $product->name = $project->hasProduct && !empty($postData->rawdata->productName) ? $postData->rawdata->productName : zget($project, 'name', ''); $product->shadow = zget($project, 'vision', 'rnd') == 'rnd' ? (int)empty($project->hasProduct) : 1; $product->bind = $postData->rawdata->parent ? 0 : 1; $product->program = $project->parent ? current(array_filter(explode(',', zget($program, 'path', '')))) : 0; $product->acl = $project->acl == 'open' ? 'open' : 'private'; $product->PO = $project->PM; $product->QD = ''; $product->RD = ''; $product->whitelist = ''; $product->createdBy = $this->app->user->account; $product->createdDate = helper::now(); $product->status = 'normal'; $product->line = 0; $product->desc = ''; $product->createdVersion = $this->config->version; $product->vision = zget($project, 'vision', 'rnd'); if($this->config->edition != 'open') $product->workflowGroup = $this->dao->select('id')->from(TABLE_WORKFLOWGROUP)->where('code')->eq('productproject')->fetch('id'); $this->app->loadLang('product'); $this->dao->insert(TABLE_PRODUCT)->data($product) ->check('name', 'notempty') ->checkIF(!empty($product->name), 'name', 'unique', "`program` = {$product->program} and `deleted` = '0'") ->exec(); if(dao::isError()) return false; $productID = $this->dao->lastInsertId(); /* Update whitelist and user view. */ if(!$project->hasProduct) $this->loadModel('personnel')->updateWhitelist(explode(',', $project->whitelist), 'product', $productID); $this->loadModel('action')->create('product', $productID, 'opened'); $this->dao->update(TABLE_PRODUCT)->set('`order`')->eq($productID * 5)->where('id')->eq($productID)->exec(); if($product->acl != 'open') $this->loadModel('user')->updateUserView(array($productID), 'product'); $productSettingList = isset($this->config->global->productSettingList) ? json_decode($this->config->global->productSettingList, true) : array(); if($this->config->edition != 'open' && $productSettingList) { $productSettingList[] = $productID; $this->loadModel('setting')->setItem('system.common.global.productSettingList', json_encode($productSettingList)); } /* Link product. */ $projectProduct = new stdclass(); $projectProduct->project = $projectID; $projectProduct->product = $productID; $projectProduct->branch = 0; $projectProduct->plan = 0; $this->dao->insert(TABLE_PROJECTPRODUCT)->data($projectProduct)->exec(); if(dao::isError()) return false; /* Create doc lib. */ if($project->hasProduct) $this->createProductDocLib($productID); return !dao::isError(); } /** * 创建产品后,创建默认的产品主库。 * Create doclib after create a product. * * @param int $productID * @access protected * @return bool */ protected function createProductDocLib($productID) { $this->app->loadLang('doc'); $lib = new stdclass(); $lib->product = $productID; $lib->name = $this->lang->doclib->main['product']; $lib->type = 'product'; $lib->main = '1'; $lib->acl = 'default'; $lib->addedBy = $this->app->user->account; $lib->addedDate = helper::now(); $this->dao->insert(TABLE_DOCLIB)->data($lib)->exec(); return !dao::isError(); } /** * 获取创建项目时选择的产品数量。 * Get products count from post. * * @param object $project * @param object $rawdata * @access protected * @return int */ protected function getLinkedProductsCount($project, $rawdata) { if(!isset($project) || !empty($project->newProject)) return 0; $linkedProductsCount = 0; if($project->hasProduct && isset($rawdata->products)) { foreach($rawdata->products as $product) { if(empty($product)) continue; $linkedProductsCount ++; } } return $linkedProductsCount; } /** * 创建项目后,将项目创建者加到项目管理员分组。 * Create project admin after create a project. * * @param int $projectID * @access protected * @return bool */ protected function addProjectAdmin($projectID) { $projectAdmin = $this->dao->select('*')->from(TABLE_PROJECTADMIN)->where('account')->eq($this->app->user->account)->fetch(); if(!empty($projectAdmin) && $projectAdmin->projects != 'all') { $newProject = $projectAdmin->projects . ",$projectID"; $this->dao->update(TABLE_PROJECTADMIN)->set('projects')->eq($newProject)->where('account')->eq($projectAdmin->account)->andWhere('`group`')->eq($projectAdmin->group)->exec(); } else { $maxGroupID = $this->dao->select('max(`group`) as maxGroupID')->from(TABLE_PROJECTADMIN)->fetch('maxGroupID'); $projectAdmin = new stdclass(); $projectAdmin->account = $this->app->user->account; $projectAdmin->group = (int)$maxGroupID + 1; $projectAdmin->projects = $projectID; $this->dao->replace(TABLE_PROJECTADMIN)->data($projectAdmin)->exec(); } return !dao::isError(); } /** * 生成project模块的项目下拉框跳转链接. * Build project link in project page. * * @param string $method * @access protected * @return string */ protected function buildLinkForProject($method) { if($method == 'execution') return helper::createLink($module, $method, "status=all&projectID=%s"); if($method == 'managePriv') return helper::createLink($module, 'group', "projectID=%s"); if($method == 'showerrornone') return helper::createLink('projectstory', 'story', "projectID=%s"); $methods = ',bug,testcase,testtask,testreport,build,dynamic,view,manageproducts,team,managemembers,whitelist,addwhitelist,group,'; if(strpos($methods, ',' . $method . ',') !== false) return helper::createLink($module, $method, "projectID=%s"); } /** * 生成bug模块的项目下拉框跳转链接. * Build project link in bug page. * * @param string $method * @access protected * @return string */ protected function buildLinkForBug($method) { if($method == 'create') return helper::createLink($module, $method, "productID=0&branch=0&extras=projectID=%s"); if($method == 'edit') return helper::createLink('project', 'bug', "projectID=%s"); } /** * 生成story模块的项目下拉框跳转链接. * Build project link in story page. * * @param string $method * @access protected * @return string */ protected function buildLinkForStory($method) { if($method == 'change' || $method == 'create') return helper::createLink('projectstory', 'story', "projectID=%s"); if($method == 'zerocase') return helper::createLink('project', 'testcase', "projectID=%s"); } /** * 构造批量更新项目的数据。 * Build bathc update project data. * * @param array $data * @param array $oldProjects * @access public * @return array */ public function buildBatchUpdateProjects($data, $oldProjects) { if(empty($data)) return array(); $extendFields = $this->getFlowExtendFields(); $projects = array(); foreach($data as $projectID => $project) { $projectID = (int)$projectID; $projects[$projectID] = new stdClass(); $projects[$projectID]->id = $projectID; $projects[$projectID]->name = $project->name; $projects[$projectID]->model = $oldProjects[$projectID]->model; $projects[$projectID]->PM = $project->PM; $projects[$projectID]->begin = $project->begin; $projects[$projectID]->end = $project->end == $this->lang->project->longTime ? LONG_TIME : $project->end; $projects[$projectID]->acl = $project->acl; $projects[$projectID]->days = $project->days; $projects[$projectID]->lastEditedBy = $this->app->user->account; $projects[$projectID]->lastEditedDate = helper::now(); if(isset($project->parent)) $projects[$projectID]->parent = $project->parent; if(isset($project->code)) $projects[$projectID]->code = $project->code; if($project->end == $this->lang->project->longTime) $projects[$projectID]->days = 0; foreach($extendFields as $extendField) { $projects[$projectID]->{$extendField->field} = $project->{$extendField->field}; if(is_array($projects[$projectID]->{$extendField->field})) $projects[$projectID]->{$extendField->field} = implode(',', $projects[$projectID]->{$extendField->field}); $projects[$projectID]->{$extendField->field} = htmlSpecialString($projects[$projectID]->{$extendField->field}); } } return $projects; } /** * 删除项目团队成员。 * Delete project team member. * * @param array|int $projectIdList * @param string $type * @param string $account * @param string $realname * @param array $changes * @access protected * @return bool */ protected function unlinkTeamMember($projectIdList, $type, $account, $realname, $changes) { $accountProjects = $this->dao->select('root')->from(TABLE_TEAM) ->where('root')->in($projectIdList) ->andWhere('type')->eq($type) ->andWhere('account')->eq($account) ->fetchPairs(); $this->dao->delete()->from(TABLE_TEAM) ->where('root')->in($projectIdList) ->andWhere('type')->eq($type) ->andWhere('account')->eq($account) ->exec(); $this->loadModel('action'); foreach($accountProjects as $projectID) { $actionID = $this->action->create($type, $projectID, $type == 'project' ? 'managedTeam' : 'syncProjectTeam'); $this->action->logHistory($actionID, $changes); } return !dao::isError(); } /** * 对项目按照我的、其它的和关闭的进行分类。 * Classify projects according to my, other and closed. * * @param array $projects * @access protected * @return array */ protected function classifyProjects($projects) { $classifiedProjects = array('myProjects' => array(), 'otherProjects' => array(), 'closedProjects' => array()); foreach($projects as $project) { if(strpos('wait,doing,closed', $project->status) === false) continue; /* Convert predefined HTML entities to characters. */ $project->name = htmlspecialchars_decode($project->name, ENT_QUOTES); $projectPath = explode(',', trim($project->path, ',')); $topProgram = !empty($project->parent) ? $projectPath[0] : $project->parent; if(!helper::hasFeature('program')) $topProgram = 0; if($project->PM == $this->app->user->account) { if($project->status != 'closed') { $classifiedProjects['myProjects'][$topProgram][$project->status][] = $project; } else { $classifiedProjects['closedProjects']['my'][$topProgram][$project->closedDate] = $project; } } else { if($project->status != 'closed') { $classifiedProjects['otherProjects'][$topProgram][$project->status][] = $project; } else { $classifiedProjects['closedProjects']['other'][$topProgram][$project->closedDate] = $project; } } } return $classifiedProjects; } /** * 对groups列表进行排序和缩减。 * Sort and reduce projects. * * @param array $projectsStats * @param int $retainNum * @access protected * @return array */ protected function sortAndReduceClosedProjects($projectsStats, $retainNum = 2) { $sortedAndReducedProjects = array('my' => $projectsStats['myProjects'], 'other' => $projectsStats['otherProjects']); $closedProjects = $projectsStats['closedProjects']; foreach($closedProjects as $group => $groupedProjects) { foreach($groupedProjects as $topProgram => $projects) { krsort($projects); if($retainNum > 0) { $sortedAndReducedProjects[$group][$topProgram]['closed'] = array_slice($projects, 0, $retainNum); } } } return $sortedAndReducedProjects; } /** * 根据项目集ID查询所有项目集的层级。 * Get all program level of a program. * * @param int $program * @param string $path * @param int $grade * @access protected * @return string */ protected function getParentProgram($path, $grade) { $programList = $this->dao->select('id,name')->from(TABLE_PROGRAM) ->where('id')->in(trim($path, ',')) ->andWhere('grade')->lt($grade) ->orderBy('grade asc') ->fetchPairs(); return implode('/', $programList); } /** * 根据状态和和我参与的查询项目列表。 * Get project list by status and with my participation. * * @param string $status * @param string $orderBy * @param bool $involved * @param object $pager * @access protected * @return array */ protected function fetchProjectList($status, $orderBy, $involved, $pager) { $query = ''; if($status == 'bysearch' && $this->session->projectQuery !== false) $query = $this->session->projectQuery; $query = str_replace('`id`','t1.id', $query); return $this->dao->select('DISTINCT t1.*')->from(TABLE_PROJECT)->alias('t1') ->leftJoin(TABLE_TEAM)->alias('t2')->on('t1.id=t2.root') ->leftJoin(TABLE_STAKEHOLDER)->alias('t3')->on('t1.id=t3.objectID') ->where('t1.deleted')->eq('0') ->andWhere('t1.vision')->eq($this->config->vision) ->andWhere('t1.type')->eq('project') ->beginIF(!in_array($status, array('all', 'undone', 'review', 'unclosed', 'delayed', 'bysearch'), true))->andWhere('t1.status')->eq($status)->fi() ->beginIF(in_array($status, array('undone', 'unclosed')))->andWhere('t1.status')->in('wait,doing')->fi() ->beginIF($status == 'delayed')->andWhere('t1.status')->notIn('done,closed,suspend')->andWhere('t1.end')->lt(helper::today())->fi() ->beginIF($status == 'review') ->andWhere("FIND_IN_SET('{$this->app->user->account}', t1.reviewers)") ->andWhere('t1.reviewStatus')->eq('doing') ->fi() ->beginIF($this->cookie->involved || $involved) ->andWhere('t2.type')->eq('project') ->andWhere('t1.openedBy', true)->eq($this->app->user->account) ->orWhere('t1.PM')->eq($this->app->user->account) ->orWhere('t2.account')->eq($this->app->user->account) ->orWhere('(t3.user')->eq($this->app->user->account) ->andWhere('t3.deleted')->eq(0) ->markRight(1) ->orWhere("CONCAT(',', t1.whitelist, ',')")->like("%,{$this->app->user->account},%") ->markRight(1) ->fi() ->beginIF($status == 'bysearch' && $query)->andWhere($query)->fi() ->orderBy($orderBy) ->page($pager, 't1.id') ->fetchAll('id'); } /** * 通过条件查询和数量限制查询项目列表。 * Get project list by query and with limit. * * @param string $status * @param int $projectID * @param string $orderBy * @param int $limit * @param string $excludedModel * @access protected * @return array */ protected function fetchProjectListByQuery($status = 'all', $projectID = 0, $orderBy = 'order_asc', $limit = 0, $excludedModel = '') { return $this->dao->select('*')->from(TABLE_PROJECT) ->where('type')->eq('project') ->andWhere('vision')->eq($this->config->vision) ->andWhere('deleted')->eq(0) ->beginIF($projectID)->andWhere('hasProduct')->eq('1')->fi() ->beginIF($projectID)->andWhere('multiple')->eq('1')->fi() ->beginIF($excludedModel)->andWhere('model')->ne($excludedModel)->fi() ->beginIF(!$this->app->user->admin)->andWhere('id')->in($this->app->user->view->projects)->fi() ->beginIF($status == 'undone')->andWhere('status')->notIN('done,closed')->fi() ->beginIF($status == 'unclosed')->andWhere('status')->ne('closed')->fi() ->beginIF($status && !in_array($status, array('all', 'undone', 'unclosed', 'involved')))->andWhere('status')->eq($status)->fi() ->beginIF($status == 'involved') ->andWhere('openedBy', true)->eq($this->app->user->account) ->orWhere('PM')->eq($this->app->user->account) ->orWhere("CONCAT(',', whitelist, ',')")->like("%,{$this->app->user->account},%") ->markRight(1) ->fi() ->beginIF($projectID)->orWhere('id')->eq($projectID)->fi() ->orderBy($orderBy) ->beginIF($limit)->limit($limit)->fi() ->fetchAll('id'); } /** * 根据项目ID列表查询团队成员数量。 * Get project team member count by project id list. * * @param array $projectIdList * @access protected * @return array */ protected function fetchMemberCountByIdList($projectIdList) { return $this->dao->select('t1.root, count(t1.id) as count')->from(TABLE_TEAM)->alias('t1') ->leftJoin(TABLE_USER)->alias('t2')->on('t1.account=t2.account') ->where('t1.root')->in($projectIdList) ->andWhere('t2.deleted')->eq(0) ->groupBy('t1.root') ->fetchPairs(); } /** * 根据项目ID列表查询任务的总预计工时。 * Get task all estimate by project id list. * * @param array $projectIdList * @param string $fields * @access protected * @return array */ protected function fetchTaskEstimateByIdList($projectIdList, $fields = 'estimate') { $fields = explode(',', $fields); $selectFields = 't2.project'; foreach($fields as $field) $selectFields .= ", ROUND(SUM(t1.{$field}), 1) AS {$field}"; return $this->dao->select($selectFields)->from(TABLE_TASK)->alias('t1') ->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.execution = t2.id') ->where('t1.parent')->lt(1) ->andWhere('t2.project')->in($projectIdList) ->andWhere('t1.deleted')->eq(0) ->andWhere('t2.deleted')->eq(0) ->groupBy('t2.project') ->fetchAll('project'); } /** * 通过项目ID列表查询需求的数量。 * Get the number of stories associated with the project. * * @param array $projectIdList * @access protected * @return array */ protected function getTotalStoriesByProject($projectIdList) { return $this->dao->select("t1.project, count(t2.id) as `allStories`, count(if(t2.status = 'active' or t2.status = 'changing', 1, null)) as `leftStories`, count(if(t2.status = 'closed' and t2.`closedReason` = 'done', 1, null)) as `doneStories`")->from(TABLE_PROJECTSTORY)->alias('t1') ->leftJoin(TABLE_STORY)->alias('t2')->on('t1.story=t2.id') ->where('t1.project')->in($projectIdList) ->andWhere('deleted')->eq('0') ->groupBy('project') ->fetchAll('project'); } /** * 删除团队成员。 * Delete Members. * * @param int $projectID * @param string $openedBy * @param array $deleteMembers * @access protected * @return bool */ protected function deleteMembers($projectID, $openedBy, $deleteMembers) { $this->dao->delete()->from(TABLE_TEAM) ->where('root')->eq($projectID) ->andWhere('type')->eq('project') ->andWhere('account')->in($deleteMembers) ->andWhere('account')->ne($openedBy) ->exec(); return !dao::isError(); } /** * 通过项目ID获取任务数量统计。 * Get the number of tasks associated with the project. * * @param array $projectIdList * @access protected * @return array */ protected function getTotalTaskByProject($projectIdList) { $executionIdList = $this->dao->select('id')->from(TABLE_EXECUTION)->where('project')->in($projectIdList)->andWhere('deleted')->eq(0)->fetchPairs(); return $this->dao->select("project, count(id) as allTasks, count(if(status = 'wait', 1, null)) as waitTasks, count(if(status = 'doing', 1, null)) as doingTasks, count(if(status = 'done', 1, null)) as doneTasks, count(if(status = 'wait' or status = 'pause' or status = 'cancel', 1, null)) as leftTasks, count(if(status = 'done' or status = 'closed', 1, null)) as litedoneTasks")->from(TABLE_TASK) ->where('execution')->in($executionIdList) ->andWhere('deleted')->eq(0) ->groupBy('project') ->fetchAll('project'); } /** * 返回请求被拒绝的跳转信息。 * return accessDenied response. * * @access protected * @return string */ protected function accessDenied() { $this->session->set('project', '0'); return $this->app->control->sendError($this->lang->project->accessDenied, helper::createLink('project', 'browse')); } /** * 修改无迭代项目下执行的状态。 * Modify the execution status when changing the status of no execution project. * * @param int $projectID * @param string $status * @access protected * @return array|false|int */ protected function changeExecutionStatus($projectID, $status) { if(!in_array($status, array('start', 'suspend', 'activate', 'close'))) return false; $execution = $this->dao->select('*')->from(TABLE_EXECUTION)->where('project')->eq($projectID)->andWhere('multiple')->eq('0')->fetch(); if(!$execution) return false; $project = $this->dao->select('*')->from(TABLE_PROJECT)->where('id')->eq($projectID)->fetch(); $postData = new stdclass(); $postData->status = $status; $postData->begin = $execution->begin; $postData->end = $execution->end; $postData->uid = ''; $postData->comment = ''; if($status == 'close') $postData->realEnd = $project->realEnd; return $this->loadModel('execution')->$status($execution->id, $postData); } /** * 通过项目ID获取Bug数量统计。 * Get the number of bugs associated with the project. * * @param array $projectIdList * @access protected * @return array */ protected function getTotalBugByProject($projectIdList) { return $this->dao->select("project, count(id) AS allBugs, count(if(status = 'active', 1, null)) AS leftBugs, count(if(status = 'resolved', 1, null)) AS doneBugs")->from(TABLE_BUG) ->where('project')->in($projectIdList) ->andWhere('deleted')->eq(0) ->groupBy('project') ->fetchAll('project'); } /** * 根据项目模式设置菜单。 * Set menu by project model. * * @param string $projectModel * @access protected * @return bool */ protected function setMenuByModel($projectModel) { global $lang, $config, $app; $model = 'scrum'; if(in_array($projectModel, $this->config->project->waterfallList)) { $model = 'waterfall'; $lang->project->createExecution = str_replace($lang->executionCommon, $lang->project->stage, $lang->project->createExecution); $lang->project->lastIteration = str_replace($lang->executionCommon, $lang->project->stage, $lang->project->lastIteration); $this->loadModel('execution'); $executionCommonLang = $lang->executionCommon; $lang->executionCommon = $lang->project->stage; include $this->app->getModulePath('', 'execution') . 'lang/' . $this->app->getClientLang() . '.php'; $lang->execution->typeList['sprint'] = $executionCommonLang; if($app->rawModule == 'project' && $app->rawMethod == 'executionreport') $lang->waterfall->menu->execution['subModule'] .= ',project'; } elseif($projectModel == 'kanban') { $model = 'kanbanProject'; $lang->executionCommon = $lang->project->kanban; } if(isset($lang->$model)) { $key = 'project-' . $model; if(isset($config->customMenu->$key)) { $lang->{$model}->menuOrder = array(); $lang->{$model}->dividerMenu = ''; $items = json_decode($config->customMenu->$key); $prevItem = ''; foreach($items as $item) { $lang->{$model}->menuOrder[$item->order] = $item->name; if($prevItem == 'divider') $lang->{$model}->dividerMenu .= $item->name . ','; $prevItem = $item->name; } if($lang->{$model}->dividerMenu) $lang->{$model}->dividerMenu = ',' . trim($lang->{$model}->dividerMenu, ',') . ','; } if($this->config->inCompose) { $repoServers = $this->loadModel('pipeline')->getPairs($this->config->pipeline->checkRepoServers); if(empty($repoServers)) unset($lang->{$model}->menu->devops); } $lang->project->menu = $lang->{$model}->menu; $lang->project->menuOrder = $lang->{$model}->menuOrder; $lang->project->dividerMenu = $lang->{$model}->dividerMenu; } return true; } /** * 根据是否产品型项目设置菜单。 * Set menu by whether product project. * * @param int $projectID * @param int $hasProduct * @param string $model * @access protected * @return bool */ protected function setMenuByProduct($projectID, $hasProduct, $model) { global $lang; if($hasProduct) { unset($lang->project->menu->projectplan); unset($lang->project->menu->settings['subMenu']->module); } $projectProduct = (int)$this->dao->select('product')->from(TABLE_PROJECTPRODUCT)->where('project')->eq($projectID)->fetch('product'); if(isset($lang->project->menu->settings['subMenu']->module['link'])) $lang->project->menu->settings['subMenu']->module['link'] = sprintf($lang->project->menu->settings['subMenu']->module['link'], $projectProduct); if(!$hasProduct && in_array($model, $this->config->project->scrumList) && isset($lang->project->menu->projectplan)) $lang->project->menu->projectplan['link'] = sprintf($lang->project->menu->projectplan['link'], $projectProduct); if(!$hasProduct) unset($lang->project->menu->settings['subMenu']->products); if(!in_array($model, $this->config->project->scrumList)) unset($lang->project->menu->projectplan); return true; } /** * 根据访问模块的导航组设置菜单。 * Set menu lang for module. * * @param string $navGroup * @param int $executionID * @param object $project * @access protected * @return bool */ protected function setNavGroupMenu($navGroup, $executionID, $project) { global $lang; foreach($lang->$navGroup->menu as $label => $menu) { $objectID = 0; if(strpos($this->config->project->multiple['project'], ",{$label},") !== false) $objectID = $project->id; if(strpos($this->config->project->multiple['execution'], ",{$label},") !== false) { $objectID = $executionID; $lang->$navGroup->menu->{$label}['subModule'] = 'project'; } $lang->$navGroup->menu->$label = commonModel::setMenuVarsEx($menu, $objectID); if(isset($menu['subMenu'])) { foreach($menu['subMenu'] as $key1 => $subMenu) $lang->$navGroup->menu->{$label}['subMenu']->$key1 = common::setMenuVarsEx($subMenu, $objectID); } if(!isset($menu['dropMenu'])) continue; foreach($menu['dropMenu'] as $key2 => $dropMenu) { if(strpos($this->config->project->multiple['project'], ",{$label},") !== false) $objectID = $project->id; if(strpos($this->config->project->multiple['execution'], ",{$key2},") !== false) { $objectID = $executionID; $lang->$navGroup->menu->{$label}['subModule'] = 'project'; } $lang->$navGroup->menu->{$label}['dropMenu']->$key2 = common::setMenuVarsEx($dropMenu, $objectID); if(!isset($dropMenu['subMenu'])) continue; foreach($dropMenu['subMenu'] as $key3 => $subMenu) $lang->$navGroup->menu->{$label}['dropMenu']->$key3 = common::setMenuVarsEx($subMenu, $objectID); } } return true; } /** * 创建里程碑报告。 * * @param int $projectID * @access protected * @return bool */ protected function createMilestoneReport($projectID) { $reportID = $this->dao->select('id')->from(TABLE_DOC)->where('project')->eq($projectID)->andWhere('reportModule')->eq('milestone')->andWhere('type')->eq('projectReport')->fetch('id'); if(!empty($reportID)) return true; $report = new stdclass(); $report->title = $this->lang->project->milestoneReport; $report->project = $projectID; $report->reportModule = 'milestone'; $report->templateType = 'projectReport'; $report->addedBy = 'system'; $report->addedDate = helper::now(); $this->dao->insert(TABLE_DOC)->data($report)->exec(); return !dao::isError(); } }