diff --git a/api/v1/entries/doclibs.php b/api/v1/entries/doclibs.php new file mode 100644 index 0000000000..b41d8270f4 --- /dev/null +++ b/api/v1/entries/doclibs.php @@ -0,0 +1,43 @@ + + * @package entries + * @version 1 + * @link http://www.zentao.net + */ +class doclibsEntry extends Entry +{ + /** + * GET method. + * + * @access public + * @return void + */ + public function get() + { + $type = $this->param('type', 0); + $objectID = $this->param('objectID', 0); + + $libs = $this->loadModel('doc')->getLibs($type, $this->param('extra', ''), $this->param('appendLibs', ''), $objectID); + $result = array(); + foreach($libs as $libID => $libName) + { + $lib = new stdclass(); + $lib->id = $libID; + $lib->name = $libName; + $result[] = $lib; + } + krsort($result); + + $lib = new stdclass(); + $lib->id = 'files'; + $lib->name = $this->lang->doclib->files; + $result[] = $lib; + + return $this->send(200, array('libs' => array_values($result))); + } +} diff --git a/api/v1/entries/docs.php b/api/v1/entries/docs.php new file mode 100644 index 0000000000..86453efe5d --- /dev/null +++ b/api/v1/entries/docs.php @@ -0,0 +1,38 @@ + + * @package entries + * @version 1 + * @link http://www.zentao.net + */ +class docsEntry extends Entry +{ + /** + * GET method. + * + * @access public + * @return void + */ + public function get($libID = 0) + { + if(empty($libID)) $libID = $this->param('lib', 0); + if(empty($libID)) return $this->sendError(400, 'Need lib id.'); + + $docTree = $this->loadModel('doc')->getDocTree($libID); + + foreach($docTree as $i => $module) + { + if(empty($module->id)) + { + unset($docTree[$i]); + foreach($module->children as $doc) $docTree[] = $doc; + } + } + + return $this->send(200, array('docs' => array_values($docTree))); + } +} diff --git a/api/v1/entries/execution.php b/api/v1/entries/execution.php index 534b70d98f..e668ba5c39 100644 --- a/api/v1/entries/execution.php +++ b/api/v1/entries/execution.php @@ -33,10 +33,14 @@ class executionEntry extends Entry } $execution = $this->format($data->data->execution, 'openedDate:time,lastEditedDate:time,closedDate:time,canceledDate:time,begin:date,end:date,realBegan:date,realEnd:date,deleted:bool'); + + $this->loadModel('testcase'); + $execution->caseReview = ($this->config->testcase->needReview or !empty($this->config->testcase->forceReview)); + if(!$fields) $this->send(200, $execution); /* Set other fields. */ - $fields = explode(',', $fields); + $fields = explode(',', strtolower($fields)); foreach($fields as $field) { switch($field) @@ -50,6 +54,19 @@ class executionEntry extends Entry $execution->modules = $data->data->tree; } break; + case 'moduleoptionmenu': + $execution->moduleOptionMenu = $this->loadModel('tree')->getTaskOptionMenu($executionID, 0, 0, 'allModule'); + break; + case 'members': + $execution->members = $this->loadModel('user')->getTeamMemberPairs($executionID, 'execution', 'nodeleted');; + unset($execution->members['']); + break; + case 'stories': + $stories = $this->loadModel('story')->getExecutionStories($executionID); + foreach($stories as $storyID => $story) $stories[$storyID] = $this->filterFields($story, 'id,title,module,pri,status,stage,estimate'); + + $execution->stories = array_values($stories); + break; } } diff --git a/api/v1/entries/executionbugs.php b/api/v1/entries/executionbugs.php new file mode 100644 index 0000000000..786fe343ef --- /dev/null +++ b/api/v1/entries/executionbugs.php @@ -0,0 +1,66 @@ + + * @package entries + * @version 1 + * @link http://www.zentao.net + */ +class executionBugsEntry extends entry +{ + /** + * GET method. + * + * @param int $executionID + * @access public + * @return void + */ + public function get($executionID = 0) + { + if(!$executionID) $executionID = $this->param('execution', 0); + if(empty($executionID)) return $this->sendError(400, 'Need execution id.'); + + $control = $this->loadController('execution', 'bug'); + $control->bug($executionID, $this->param('product', 0), $this->param('order', 'status,id_desc'), $this->param('build', 0), $this->param('status', 'all'), 0, 0, $this->param('limit', 20), $this->param('page', 1)); + + $data = $this->getData(); + + if(isset($data->status) and $data->status == 'success') + { + $bugs = $data->data->bugs; + $pager = $data->data->pager; + $result = array(); + foreach($bugs as $bug) + { + $status = array('code' => $bug->status, 'name' => $this->lang->bug->statusList[$bug->status]); + if($bug->status == 'active' and $bug->confirmed) $status = array('code' => 'confirmed', 'name' => $this->lang->bug->labelConfirmed); + if($bug->resolution == 'postponed') $status = array('code' => 'postponed', 'name' => $this->lang->bug->labelPostponed); + if(!empty($bug->delay)) $status = array('code' => 'delay', 'name' => $this->lang->bug->overdueBugs); + $bug->status = $status; + + $result[$bug->id] = $this->format($bug, 'activatedDate:time,openedDate:time,assignedDate:time,resolvedDate:time,closedDate:time,lastEditedDate:time,deadline:date,deleted:bool'); + } + + $storyChangeds = $this->dao->select('t1.id')->from(TABLE_BUG)->alias('t1') + ->leftJoin(TABLE_STORY)->alias('t2')->on('t1.story=t2.id') + ->where('t1.id')->in(array_keys($result)) + ->andWhere('t1.story')->ne('0') + ->andWhere('t1.storyVersion != t2.version') + ->fetchPairs('id', 'id'); + foreach($storyChangeds as $bugID) + { + $status = array('code' => 'storyChanged', 'name' => $this->lang->bug->changed); + $result[$bugID]->status = $status; + } + + return $this->send(200, array('page' => $pager->pageID, 'total' => $pager->recTotal, 'limit' => $pager->recPerPage, 'bugs' => array_values($result))); + } + + if(isset($data->status) and $data->status == 'fail') return $this->sendError(400, $data->message); + + return $this->sendError(400, 'error'); + } +} diff --git a/api/v1/entries/executioncases.php b/api/v1/entries/executioncases.php new file mode 100644 index 0000000000..f0431fb2a6 --- /dev/null +++ b/api/v1/entries/executioncases.php @@ -0,0 +1,49 @@ + + * @package entries + * @version 1 + * @link http://www.zentao.net + */ +class executionCasesEntry extends entry +{ + /** + * GET method. + * + * @param int $executionID + * @access public + * @return void + */ + public function get($executionID = 0) + { + if(!$executionID) $executionID = $this->param('execution', 0); + if(empty($executionID)) return $this->sendError(400, 'Need execution id.'); + + $control = $this->loadController('execution', 'testcase'); + $control->testcase($executionID, $this->param('status', 'all'), $this->param('order', 'id_desc'), 0, $this->param('limit', 20), $this->param('page', 1)); + + $data = $this->getData(); + + if(isset($data->status) and $data->status == 'success') + { + $cases = $data->data->cases; + $pager = $data->data->pager; + $result = array(); + foreach($cases as $case) + { + $case->status = array('code' => $case->status, 'name' => $this->lang->testcase->statusList[$case->status]); + $result[] = $this->format($case, 'openedDate:time,reviewedDate:date,lastEditedDate:time,lastRunDate:time'); + } + + return $this->send(200, array('page' => $pager->pageID, 'total' => $pager->recTotal, 'limit' => $pager->recPerPage, 'cases' => $result)); + } + + if(isset($data->status) and $data->status == 'fail') return $this->sendError(400, $data->message); + + return $this->sendError(400, 'error'); + } +} diff --git a/api/v1/entries/executions.php b/api/v1/entries/executions.php index 41de1524eb..507076dabb 100644 --- a/api/v1/entries/executions.php +++ b/api/v1/entries/executions.php @@ -20,23 +20,55 @@ class executionsEntry extends entry */ public function get($projectID = 0) { - $control = $this->loadController('execution', 'all'); - $control->all($this->param('status', 'all'), $this->param('project', $projectID), $this->param('order', 'id_desc'), 0, $this->param('total', 0), $this->param('limit', 20), $this->param('page', 1)); - $data = $this->getData(); + $appendFields = $this->param('fields', ''); + $withProject = $this->param('withProject', ''); + if(strpos(strtolower(",{$appendFields},"), ',dropmenu,') !== false) return $this->getDropMenu(); - if(isset($data->status) and $data->status == 'success') + if($projectID) { - $pager = $data->data->pager; - $result = array(); - foreach($data->data->executionStats as $execution) - { - $result[] = $this->format($execution, 'openedDate:time,lastEditedDate:time,closedDate:time,canceledDate:time,begin:date,end:date,realBegan:date,realEnd:date,deleted:bool'); - } - return $this->send(200, array('page' => $pager->pageID, 'total' => $pager->recTotal, 'limit' => $pager->recPerPage, 'executions' => $result)); - } - if(isset($data->status) and $data->status == 'fail') return $this->sendError(400, $data->message); + $control = $this->loadController('project', 'execution'); + $control->execution($this->param('status', 'undone'), $projectID, $this->param('order', 'id_desc'), $this->param('product', 0), 0, $this->param('limit', 20), $this->param('page', 1)); - return $this->sendError(400, 'error'); + /* Response */ + $data = $this->getData(); + if(!$data or !isset($data->status)) return $this->sendError(400, 'error'); + if(isset($data->status) and $data->status == 'fail') return $this->sendError(400, $data->message); + + $executions = $data->data->executionStats; + $pager = $data->data->pager; + $projects = $data->data->projects; + } + else + { + $control = $this->loadController('execution', 'all'); + $control->all($this->param('status', 'all'), $this->param('project', $projectID), $this->param('order', 'id_desc'), 0, 0, $this->param('limit', 20), $this->param('page', 1)); + $data = $this->getData(); + + if(!$data or !isset($data->status)) return $this->sendError(400, 'error'); + if(isset($data->status) and $data->status == 'fail') return $this->sendError(400, $data->message); + + $executions = $data->data->executionStats; + $pager = $data->data->pager; + $projects = $data->data->projects; + } + + $result = array(); + foreach($data->data->executionStats as $execution) + { + foreach($execution->hours as $field => $value) $execution->$field = $value; + + $execution = $this->filterFields($execution, 'id,name,project,code,type,parent,begin,end,status,openedBy,openedDate,delay,progress,' . $appendFields); + $result[] = $this->format($execution, 'openedDate:time,lastEditedDate:time,closedDate:time,canceledDate:time,begin:date,end:date,realBegan:date,realEnd:date,deleted:bool'); + } + + $data = array(); + $data['page'] = $pager->pageID; + $data['total'] = $pager->recTotal; + $data['limit'] = $pager->recPerPage; + $data['executions'] = $result; + if(!empty($withProject)) $data['projects'] = $projects; + + return $this->send(200, $data); } /** @@ -70,4 +102,50 @@ class executionsEntry extends entry $this->send(201, $this->format($execution, 'openedDate:time,lastEditedDate:time,closedDate:time,canceledDate:time')); } + + /** + * Get drop menu. + * + * @access public + * @return void + */ + public function getDropMenu() + { + $control = $this->loadController('execution', 'ajaxGetDropMenu'); + $control->ajaxGetDropMenu($this->request('executionID', 0), $this->request('module', 'execution'), $this->request('method', 'task'), $this->request('extra', '')); + + $data = $this->getData(); + if(isset($data->result) and $data->result == 'fail') return $this->sendError(400, $data->message); + + $account = $this->app->user->account; + $projects = $data->data->projects; + $dropMenu = array('involved' => array(), 'other' => array(), 'closed' => array()); + foreach($data->data->executions as $projectID => $projectExecutions) + { + foreach($projectExecutions as $execution) + { + if(helper::diffDate(date('Y-m-d'), $execution->end) > 0) $execution->delay = true; + $teams = $execution->teams; + $execution = $this->filterFields($execution, 'id,project,model,type,name,code,status,PM,delay'); + + $projectName = zget($projects, $execution->project, ''); + if($projectName) $execution->name = $projectName . '/' . $execution->name; + + if($execution->status == 'closed') + { + $dropMenu['closed'][] = $execution; + } + elseif($execution->status != 'done' and $execution->status != 'closed' and ($execution->PM == $account or isset($teams->$account))) + { + $dropMenu['involved'][] = $execution; + } + else + { + $dropMenu['other'][] = $execution; + } + } + } + + $this->send(200, $dropMenu); + } } diff --git a/api/v1/entries/executionStories.php b/api/v1/entries/executionstories.php similarity index 72% rename from api/v1/entries/executionStories.php rename to api/v1/entries/executionstories.php index cf6ba73b58..54a1f971e4 100644 --- a/api/v1/entries/executionStories.php +++ b/api/v1/entries/executionstories.php @@ -9,7 +9,7 @@ * @version 1 * @link http://www.zentao.net */ -class executionStoriesEntry extends entry +class executionStoriesEntry extends entry { /** * GET method. @@ -20,8 +20,11 @@ class executionStoriesEntry extends entry */ public function get($executionID) { + if(empty($executionID)) $this->param('execution', 0); + if(empty($executionID)) return $this->sendError(400, 'Need execution id.'); + $control = $this->loadController('execution', 'story'); - $control->story($executionID, $this->param('order', ''), $this->param('type', 'all'), 0, $this->param('total', 0), $this->param('limit', 20), $this->param('page', 1)); + $control->story($executionID, $this->param('order', 'id_desc'), $this->param('status', 'all'), 0, 0, $this->param('limit', 20), $this->param('page', 1)); $data = $this->getData(); if(isset($data->status) and $data->status == 'success') @@ -33,13 +36,10 @@ class executionStoriesEntry extends entry { $result[] = $this->format($story, 'openedDate:time,assignedDate:time,reviewedDate:time,lastEditedDate:time,closedDate:time'); } - return $this->send(200, array('page' => $pager->pageID, 'total' => $pager->recTotal, 'limit' => $pager->recPerPage, 'executionStories' => $result)); + return $this->send(200, array('page' => $pager->pageID, 'total' => $pager->recTotal, 'limit' => $pager->recPerPage, 'stories' => $result)); } - if(isset($data->status) and $data->status == 'fail') - { - return $this->sendError(400, $data->message); - } + if(isset($data->status) and $data->status == 'fail') return $this->sendError(400, $data->message); return $this->sendError(400, 'error'); } diff --git a/api/v1/entries/file.php b/api/v1/entries/file.php new file mode 100644 index 0000000000..b69ed0539d --- /dev/null +++ b/api/v1/entries/file.php @@ -0,0 +1,28 @@ + + * @package entries + * @version 1 + * @link http://www.zentao.net + */ +class fileEntry extends Entry +{ + /** + * PUT method. + * + * @access public + * @return void + */ + public function put($fileID) + { + $uid = $this->param('uid', ''); + $action = $this->param('action', ''); + if($action == 'remove') unset($_SESSION['album']['used'][$uid][$fileID]); + + $this->send(200, array('id' => $fileID)); + } +} diff --git a/api/v1/entries/files.php b/api/v1/entries/files.php new file mode 100644 index 0000000000..17faf9f28f --- /dev/null +++ b/api/v1/entries/files.php @@ -0,0 +1,37 @@ + + * @package entries + * @version 1 + * @link http://www.zentao.net + */ +class filesEntry extends Entry +{ + /** + * POST method. + * + * @access public + * @return void + */ + public function post() + { + $uid = $this->param('uid', ''); + + $control = $this->loadController('file', 'ajaxUpload'); + $control->ajaxUpload($uid); + + $data = $this->getData(); + + if(!$data or !isset($data->status)) return $this->send400('error'); + if(isset($data->status) and $data->status == 'error') + { + return isset($data->code) and $data->code == 404 ? $this->send404() : $this->sendError(400, $data->message); + } + + $this->send(200, array('id' => $data->id)); + } +} diff --git a/api/v1/entries/langs.php b/api/v1/entries/langs.php new file mode 100644 index 0000000000..a29ee1b0f8 --- /dev/null +++ b/api/v1/entries/langs.php @@ -0,0 +1,51 @@ + + * @package entries + * @version 1 + * @link http://www.zentao.net + */ +class langsEntry extends entry +{ + /** + * GET method. + * + * @access public + * @return void + */ + public function get() + { + $modules = $this->param('modules', ''); + $language = $this->param('lang', ''); + + if($language and !isset($this->config->langs[$language])) return $this->sendError(400, 'Error lang parameter'); + if(empty($modules)) return $this->sendError(400, 'Need modules'); + + if(empty($language)) $language = 'zh-cn'; + $this->app->setClientLang($language); + + $modules = explode(',', $modules); + foreach($modules as $module) + { + if($module == 'all') + { + foreach(glob($this->app->getModuleRoot() . '*') as $modulePath) + { + if(!is_dir($modulePath)) continue; + + $moduleName = basename($modulePath); + $this->app->loadLang($moduleName); + } + break; + } + + $this->app->loadLang($module); + } + + return $this->send(200, $this->lang); + } +} diff --git a/api/v1/entries/product.php b/api/v1/entries/product.php index ca5af67f04..e22c2d34ba 100644 --- a/api/v1/entries/product.php +++ b/api/v1/entries/product.php @@ -33,10 +33,17 @@ class productEntry extends Entry } $product = $this->format($data->data->product, 'createdDate:time'); + + $users = $data->data->users; + $product->PO = $this->formatUser($product->PO, $users); + $product->QD = $this->formatUser($product->QD, $users); + $product->RD = $this->formatUser($product->RD, $users); + $product->createdBy = $this->formatUser($product->createdBy, $users); + if(isset($product->feedback)) $product->feedback = $this->formatUser($product->feedback, $users); if(!$fields) return $this->send(200, $product); /* Set other fields. */ - $fields = explode(',', $fields); + $fields = explode(',', strtolower($fields)); foreach($fields as $field) { switch($field) @@ -50,6 +57,29 @@ class productEntry extends Entry $product->modules = $data->data->tree; } break; + case 'actions': + $product->addComment = common::hasPriv('action', 'comment') ? true : false; + + $actions = $data->data->actions; + $product->actions = $this->loadModel('action')->processActionForAPI($actions, $users, $this->lang->product); + break; + case 'lastexecution': + $execution = $this->dao->select('t2.id,t2.name')->from(TABLE_PROJECTPRODUCT)->alias('t1') + ->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.project = t2.id') + ->where('t2.deleted')->eq(0) + ->andWhere('t1.product')->eq($productID) + ->andWhere('t2.type')->in('sprint,stage') + ->orderBy('t2.id desc') + ->limit(1) + ->fetch(); + if($execution) + { + $workhour = $this->loadModel('project')->computerProgress(array($execution->id => $execution)); + if(isset($workhour[$execution->id])) $execution->progress = $workhour[$execution->id]->progress; + } + + $product->lastExecution = $execution; + break; } } diff --git a/api/v1/entries/productplans.php b/api/v1/entries/productplans.php index 665d2f5c92..ea7ddbdbfd 100644 --- a/api/v1/entries/productplans.php +++ b/api/v1/entries/productplans.php @@ -32,9 +32,10 @@ class productplansEntry extends entry { $result = array(); $plans = $data->data->plans; + $pager = $data->data->pager; foreach($plans as $plan) $result[] = $plan; - return $this->send(200, array('plans' => $result)); + return $this->send(200, array('page' => $pager->pageID, 'total' => $pager->recTotal, 'limit' => $pager->recPerPage, 'plans' => array_values($result))); } if(isset($data->status) and $data->status == 'fail') return $this->sendError(400, $data->message); diff --git a/api/v1/entries/products.php b/api/v1/entries/products.php index 11698865cf..aad54b2939 100644 --- a/api/v1/entries/products.php +++ b/api/v1/entries/products.php @@ -20,23 +20,24 @@ class productsEntry extends entry */ public function get($programID = 0) { + $fields = $this->param('fields', ''); + if(strpos(strtolower(",{$fields},"), ',dropmenu,') !== false) return $this->getDropMenu(); + if(!$programID) $programID = $this->param('program', 0); + $mergeChildren = $this->param('mergeChildren', ''); if($programID) { $control = $this->loadController('program', 'product'); - $control->product($programID, $this->param('status', 'all'), $this->param('order', 'order_asc'), 0, 10000); + $control->product($programID, $this->param('status', 'all'), $this->param('order', 'order_asc'), 0, $this->param('limit', '20'), $this->param('page', '1')); /* Response */ $data = $this->getData(); - if(isset($data->status) and $data->status == 'success') - { - $result = array(); - $products = $data->data->products; - foreach($products as $product) $result[] = $this->format($product, 'createdDate:time'); + if(!$data or !isset($data->status)) return $this->sendError(400, 'error'); + if(isset($data->status) and $data->status == 'fail') return $this->sendError(400, $data->message); + + $products = $data->data->products; - return $this->send(200, array('products' => $result)); - } } else { @@ -45,19 +46,51 @@ class productsEntry extends entry /* Response */ $data = $this->getData(); - if(isset($data->status) and $data->status == 'success') - { - $result = array(); - $products = $data->data->productStats; - foreach($products as $product) $result[] = $this->format($product, 'createdDate:time'); + if(!$data or !isset($data->status)) return $this->sendError(400, 'error'); + if(isset($data->status) and $data->status == 'fail') return $this->sendError(400, $data->message); - return $this->send(200, array('products' => $result)); - } + $products = $data->data->productStats; + if($mergeChildren) $products = $data->data->productStructure; } - if(isset($data->status) and $data->status == 'fail') return $this->sendError(400, $data->message); + $result = array(); + if($mergeChildren) + { + $programs = $this->mergeChildren($products); + return $this->send(200, $programs); + } + else + { + $accounts = array(); + foreach($products as $product) + { + $accounts[$product->PO] = $product->PO; + $accounts[$product->QD] = $product->QD; + $accounts[$product->RD] = $product->RD; + $accounts[$product->createdBy] = $product->createdBy; + if(isset($product->feedback)) $accounts[$product->feedback] = $product->feedback; + if(!empty($product->mailto)) + { + foreach(explode(',', $product->mailto) as $account) + { + $account = trim($account); + if(empty($account)) continue; + $accounts[$account] = $account; + } + } - return $this->sendError(400, 'error'); + $result[] = $this->format($product, 'createdDate:time'); + } + + $data = array(); + $data['total'] = count($result); + $data['products'] = $result; + + $withUser = $this->param('withUser', ''); + if(!empty($withUser)) $data['users'] = $this->loadModel('user')->getListByAccounts($accounts, 'account'); + + return $this->send(200, $data); + } } /** @@ -88,4 +121,115 @@ class productsEntry extends entry $this->send(200, $product); } + + /** + * Get dropmenu. + * + * @access public + * @return void + */ + public function getDropMenu() + { + $control = $this->loadController('product', 'ajaxGetDropMenu'); + $control->ajaxGetDropMenu($this->request('productID', 0), $this->request('module', 'product'), $this->request('method', 'browse'), $this->request('extra', ''), $this->request('from', '')); + + $data = $this->getData(); + if(isset($data->result) and $data->result == 'fail') return $this->sendError(400, $data->message); + + $dropMenu = array('owner' => array(), 'other' => array(), 'closed' => array()); + foreach($data->data->products as $programID => $products) + { + foreach($products as $product) + { + $product = $this->filterFields($product, 'id,program,name,code,status,PO'); + + if($product->status == 'closed') + { + $dropMenu['closed'][] = $product; + } + elseif($product->PO == $this->app->user->account) + { + $dropMenu['owner'][] = $product; + } + else + { + $dropMenu['other'][] = $product; + } + } + } + $this->send(200, $dropMenu); + } + + /** + * Merge children products. + * + * @param array $products + * @access public + * @return void + */ + public function mergeChildren($products) + { + $programs = array(); + foreach($products as $programID => $program) + { + $programs[$programID] = new stdclass(); + if(!empty($programID)) + { + $programs[$programID]->id = $programID; + $programs[$programID]->name = $program->programName; + $programs[$programID]->type = 'program'; + } + + $unclosedTotal = 0; + foreach($program as $field => $value) + { + if(!isset($programs[$programID]->children)) $programs[$programID]->children = array(); + if(isset($value->products)) + { + $lineID = $field; + if(empty($lineID)) + { + foreach($value->products as $product) + { + unset($product->desc); + $programs[$programID]->children[$product->id] = $product; + if($product->status != 'closed') $unclosedTotal += 1; + } + } + else + { + $line = new stdclass(); + $line->id = $lineID; + $line->name = $value->lineName; + $line->type = 'line'; + + $line->children = array(); + foreach($value->products as $product) + { + unset($product->desc); + $line->children[$product->id] = $product; + if($product->status != 'closed') $unclosedTotal += 1; + } + if(isset($line->children)) $line->children = array_values($line->children); + + $programs[$programID]->children[$lineID] = $line; + } + if(isset($programs[$programID]->children)) $programs[$programID]->children = array_values($programs[$programID]->children); + $programs[$programID]->unclosedTotal = $unclosedTotal; + } + } + } + + $topProducts = array(); + if(isset($programs[0])) + { + $topProducts = $programs[0]->children; + unset($programs[0]); + } + + $programs = array_values($programs); + foreach($topProducts as $product) $programs[] = $product; + + return $programs; + } } diff --git a/api/v1/entries/program.php b/api/v1/entries/program.php index f423487ef4..17a023d90b 100644 --- a/api/v1/entries/program.php +++ b/api/v1/entries/program.php @@ -9,6 +9,6 @@ * @version 1 * @link http://www.zentao.net */ -class ProgramEntry extends Entry +class programEntry extends Entry { } diff --git a/api/v1/entries/programs.php b/api/v1/entries/programs.php index 1b36ddafea..0333e849b5 100644 --- a/api/v1/entries/programs.php +++ b/api/v1/entries/programs.php @@ -9,7 +9,7 @@ * @version 1 * @link http://www.zentao.net */ -class ProgramsEntry extends Entry +class programsEntry extends Entry { /** * GET method. @@ -19,25 +19,90 @@ class ProgramsEntry extends Entry */ public function get() { + $_COOKIE['showClosed'] = $this->param('showClosed', 0); + $mergeChildren = $this->param('mergeChildren', 0); + + $fields = $this->param('fields', ''); + if(stripos(strtolower(",{$fields},"), ",dropmenu,") !== false) return $this->getDropMenu(); + $program = $this->loadController('program', 'browse'); $program->browse($this->param('status', 'all'), $this->param('order', 'order_asc')); $data = $this->getData(); - if(isset($data->status) and $data->status == 'success') + if(!$data or !isset($data->status)) return $this->sendError(400, 'error'); + if(isset($data->status) and $data->status == 'fail') return $this->sendError(400, $data->message); + + $programs = (array)$data->data->programs; + $progressList = (array)$data->data->progressList; + $users = $data->data->users; + $result = array(); + foreach($programs as $program) { - $programs = $data->data->programs; - $result = array(); - foreach($programs as $program) + if(isset($progressList[$program->id])) $program->progress = $progressList[$program->id]; + $param = $this->format($program, 'begin:date,end:date,realBegan:date,realEnd:date,openedDate:time,lastEditedDate:time,closedDate:time,canceledDate:time,deleted:bool'); + + if($mergeChildren) { - $result[] = $this->format($program, 'begin:date,end:date,realBegan:date,realEnd:date,openedDate:time,lastEditedDate:time,closedDate:time,canceledDate:time,deleted:bool'); + unset($program->desc); + $program->openedBy = zget($users, $program->openedBy); + $program->closedBy = zget($users, $program->closedBy); + $program->canceledBy = zget($users, $program->canceledBy); + $program->PO = zget($users, $program->PO); + $program->PM = zget($users, $program->PM); + $program->QD = zget($users, $program->QD); + $program->RD = zget($users, $program->RD); + $program->end = $program->end == LONG_TIME ? $this->lang->program->longTime : $program->end; + + $programBudget = in_array($this->app->getClientLang(), array('zh-cn','zh-tw')) ? round((float)$program->budget / 10000, 2) . $this->lang->project->tenThousand : round((float)$program->budget, 2); + $program->labelBudget = $program->budget != 0 ? zget($this->lang->project->currencySymbol, $program->budgetUnit) . ' ' . $programBudget : $this->lang->project->future; + + if(empty($program->parent)) $result[$program->id] = $program; + if(isset($programs[$program->parent])) + { + $parentProgram = $programs[$program->parent]; + if(!isset($parentProgram->children)) $parentProgram->children = array(); + $parentProgram->children[] = $program; + } + } + else + { + $result[] = $program; } - return $this->send(200, array('programs' => $result)); } - if(isset($data->status) and $data->status == 'fail') + return $this->send(200, array('programs' => array_values($result))); + } + + /** + * Get drop menu. + * + * @access public + * @return void + */ + public function getDropMenu() + { + + $programs = $this->dao->select('id,name,parent,path,grade,`order`')->from(TABLE_PROJECT) + ->where('deleted')->eq('0') + ->andWhere('type')->eq('program') + ->andWhere('id')->in($this->app->user->view->programs) + ->beginIF(empty($_COOKIE['showClosed']))->andWhere('status')->ne('closed')->fi() + ->orderBy('grade desc, `order`') + ->fetchAll('id'); + + $dropMenu = array(); + foreach($programs as $programID => $program) { - return $this->sendError(400, $data->message); + if(empty($program->parent)) + { + $dropMenu[] = $program; + } + elseif(isset($programs[$program->parent])) + { + if(!isset($programs[$program->parent]->children)) $programs[$program->parent]->children = array(); + $programs[$program->parent]->children[] = $program; + } } - return $this->sendError(400, 'error'); + $this->send(200, $dropMenu); } } diff --git a/api/v1/entries/project.php b/api/v1/entries/project.php index b4fb6cd374..63aff2576c 100644 --- a/api/v1/entries/project.php +++ b/api/v1/entries/project.php @@ -20,20 +20,64 @@ class projectEntry extends entry */ public function get($projectID) { + $fields = strtolower($this->param('fields')); + $control = $this->loadController('project', 'view'); $control->view($projectID); $data = $this->getData(); - if(!$data or !isset($data->status)) return $this->sendError(400, 'error'); - if(isset($data->status) and $data->status == 'success') return $this->send(200, $this->format($data->data->project, 'begin:date,end:date,realBegan:date,realEnd:date,openedDate:time,lastEditedDate:time,closedDate:time,canceledDate:time,deleted:bool')); - if(isset($data->status) and $data->status == 'fail') + if(!$data or !isset($data->status)) return $this->sendError(400, 'error'); + if(isset($data->status) and $data->status == 'fail') return $this->sendError(400, $data->message); + + $project = $this->format($data->data->project, 'begin:date,end:date,realBegan:date,realEnd:date,openedDate:time,lastEditedDate:time,closedDate:time,canceledDate:time,deleted:bool'); + if(empty($fields)) return $this->send(200, $project); + + /* Set other fields. */ + $fields = explode(',', $fields); + foreach($fields as $field) { - if(isset($data->code) and $data->code == 404) $this->send404(); - return $this->sendError(400, $data->message); + switch($field) + { + case 'team': + $teams = array(); + $accounts = array(); + foreach($data->data->teamMembers as $account => $team) + { + $team = $this->filterFields($team, "account,role,join,realname"); + + $teams[$account] = $team; + $accounts[$account] = $account; + } + $users = $this->loadModel('user')->getListByAccounts($accounts, 'account'); + foreach($teams as $account => $team) + { + $user = zget($users, $account, ''); + $team->avatar = $user->avatar; + } + + $project->teams = $teams; + break; + case "stat": + $project->stat = $data->data->statData; + break; + case "workhour": + $workhour = $data->data->workhour; + $workhour->progress = ($workhour->totalConsumed + $workhour->totalLeft) ? floor($workhour->totalConsumed / ($workhour->totalConsumed + $workhour->totalLeft) * 1000) / 1000 * 100 : 0; + $project->workhour = $workhour; + break; + case "actions": + $actions = $data->data->actions; + $project->actions = $this->loadModel('action')->processActionForAPI($actions, (array)$data->data->users, $this->lang->project); + break; + case "dynamics": + $dynamics = $data->data->dynamics; + $project->dynamics = $this->loadModel('action')->processDynamicForAPI($dynamics); + break; + } } - $this->sendError(400, 'error'); + return $this->send(200, $project); } /** diff --git a/api/v1/entries/projectbugs.php b/api/v1/entries/projectbugs.php new file mode 100644 index 0000000000..df5c2de092 --- /dev/null +++ b/api/v1/entries/projectbugs.php @@ -0,0 +1,66 @@ + + * @package entries + * @version 1 + * @link http://www.zentao.net + */ +class projectBugsEntry extends entry +{ + /** + * GET method. + * + * @param int $projectID + * @access public + * @return void + */ + public function get($projectID = 0) + { + if(!$projectID) $projectID = $this->param('project', 0); + if(empty($projectID)) return $this->sendError(400, 'Need project id.'); + + $control = $this->loadController('project', 'bug'); + $control->bug($projectID, $this->param('product', 0), $this->param('order', 'status,id_desc'), $this->param('build', 0), $this->param('status', 'all'), 0, 0, $this->param('limit', 20), $this->param('page', 1)); + + $data = $this->getData(); + + if(isset($data->status) and $data->status == 'success') + { + $bugs = $data->data->bugs; + $pager = $data->data->pager; + $result = array(); + foreach($bugs as $bug) + { + $status = array('code' => $bug->status, 'name' => $this->lang->bug->statusList[$bug->status]); + if($bug->status == 'active' and $bug->confirmed) $status = array('code' => 'confirmed', 'name' => $this->lang->bug->labelConfirmed); + if($bug->resolution == 'postponed') $status = array('code' => 'postponed', 'name' => $this->lang->bug->labelPostponed); + if(!empty($bug->delay)) $status = array('code' => 'delay', 'name' => $this->lang->bug->overdueBugs); + $bug->status = $status; + + $result[$bug->id] = $this->format($bug, 'activatedDate:time,openedDate:time,assignedDate:time,resolvedDate:time,closedDate:time,lastEditedDate:time,deadline:date,deleted:bool'); + } + + $storyChangeds = $this->dao->select('t1.id')->from(TABLE_BUG)->alias('t1') + ->leftJoin(TABLE_STORY)->alias('t2')->on('t1.story=t2.id') + ->where('t1.id')->in(array_keys($result)) + ->andWhere('t1.story')->ne('0') + ->andWhere('t1.storyVersion != t2.version') + ->fetchPairs('id', 'id'); + foreach($storyChangeds as $bugID) + { + $status = array('code' => 'storyChanged', 'name' => $this->lang->bug->changed); + $result[$bugID]->status = $status; + } + + return $this->send(200, array('page' => $pager->pageID, 'total' => $pager->recTotal, 'limit' => $pager->recPerPage, 'bugs' => array_values($result))); + } + + if(isset($data->status) and $data->status == 'fail') return $this->sendError(400, $data->message); + + return $this->sendError(400, 'error'); + } +} diff --git a/api/v1/entries/projects.php b/api/v1/entries/projects.php index 77f9c45f28..ae68b4319d 100644 --- a/api/v1/entries/projects.php +++ b/api/v1/entries/projects.php @@ -21,26 +21,50 @@ class projectsEntry extends entry public function get($programID = 0) { if(!$programID) $programID = $this->param('program', 0); + $appendFields = $this->param('fields', ''); + if(stripos(strtolower(",{$appendFields},"), ',dropmenu,') !== false) return $this->getDropMenu(); - $control = $this->loadController('project', 'browse'); - $control->browse($programID, $this->param('status', 'all'), 0, $this->param('order', 'order_asc'), 0, $this->param('limit', 20), $this->param('page', 1)); - $data = $this->getData(); + $_COOKIE['involved'] = $this->param('involved', 0); + + if($programID) + { + $control = $this->loadController('program', 'project'); + $control->project($programID, $this->param('status', 'all'), $this->param('order', 'order_asc'), 0, $this->param('limit', 20), $this->param('page', 1)); + $data = $this->getData(); + } + else + { + $control = $this->loadController('project', 'browse'); + $control->browse($programID, $this->param('status', 'all'), 0, $this->param('order', 'order_asc'), 0, $this->param('limit', 20), $this->param('page', 1)); + $data = $this->getData(); + } if(isset($data->status) and $data->status == 'success') { $pager = $data->data->pager; + $users = $data->data->users; $result = array(); foreach($data->data->projectStats as $project) { + foreach($project->hours as $field => $value) $project->$field = $value; + + $project = $this->filterFields($project, 'id,name,code,model,type,budget,budgetUnit,parent,begin,end,status,openedBy,openedDate,PM,delay,progress,' . $appendFields); $result[] = $this->format($project, 'openedDate:time,lastEditedDate:time,closedDate:time,canceledDate:time'); } - return $this->send(200, array('page' => $pager->pageID, 'total' => $pager->recTotal, 'limit' => (int)$pager->recPerPage, 'projects' => $result)); + + $data = array(); + $data['page'] = $pager->pageID; + $data['total'] = $pager->recTotal; + $data['limit'] = (int)$pager->recPerPage; + $data['projects'] = $result; + + $withUser = $this->param('withUser', ''); + if(!empty($withUser)) $data['users'] = $users; + + return $this->send(200, $data); } - if(isset($data->status) and $data->status == 'fail') - { - return $this->sendError(400, $data->message); - } + if(isset($data->status) and $data->status == 'fail') return $this->sendError(400, $data->message); // TODO There is no handle for 401. return $this->sendError(400, 'error'); @@ -57,6 +81,7 @@ class projectsEntry extends entry $fields = 'name,begin,end,products'; $this->batchSetPost($fields); + $this->setPost('code', $this->request('code', '')); $this->setPost('acl', $this->request('acl', 'private')); $this->setPost('parent', $this->request('program', 0)); $this->setPost('whitelist', $this->request('whitelist', array())); @@ -64,7 +89,7 @@ class projectsEntry extends entry $this->setPost('model', $this->request('model', 'scrum')); $control = $this->loadController('project', 'create'); - $this->requireFields('name,begin,end,products'); + $this->requireFields('name,code,begin,end,products'); $control->create($this->request('model', 'scrum')); @@ -76,4 +101,43 @@ class projectsEntry extends entry $this->send(201, $this->format($project, 'openedDate:time,lastEditedDate:time,closedDate:time,canceledDate:time')); } + + /** + * Get drop menu. + * + * @access public + * @return void + */ + public function getDropMenu() + { + $control = $this->loadController('project', 'ajaxGetDropMenu'); + $control->ajaxGetDropMenu($this->request('projectID', 0), $this->request('module', 'project'), $this->request('method', 'browse')); + + $data = $this->getData(); + if(isset($data->result) and $data->result == 'fail') return $this->sendError(400, $data->message); + + $dropMenu = array('owner' => array(), 'other' => array(), 'closed' => array()); + foreach($data->data->projects as $programID => $projects) + { + foreach($projects as $project) + { + if(helper::diffDate(date('Y-m-d'), $project->end) > 0) $project->delay = true; + $project = $this->filterFields($project, 'id,model,type,name,code,parent,status,PM,delay'); + + if($project->status == 'closed') + { + $dropMenu['closed'][] = $project; + } + elseif($project->PM == $this->app->user->account) + { + $dropMenu['owner'][] = $project; + } + else + { + $dropMenu['other'][] = $project; + } + } + } + $this->send(200, $dropMenu); + } } diff --git a/api/v1/entries/projectstories.php b/api/v1/entries/projectstories.php new file mode 100644 index 0000000000..b8be4683ab --- /dev/null +++ b/api/v1/entries/projectstories.php @@ -0,0 +1,46 @@ + + * @package project + * @version 1 + * @link http://www.zentao.net + */ +class projectStoriesEntry extends entry +{ + /** + * GET method. + * + * @param int $projectID + * @access public + * @return void + */ + public function get($projectID) + { + if(!$projectID) $projectID = $this->param('project'); + if(!$projectID) return $this->sendError(400, 'Need product id.'); + + $control = $this->loadController('projectstory', 'story'); + $control->story($projectID, $this->param('product', 0), $this->param('branch', ''), $this->param('status', 'unclosed'), 0, 'story', $this->param('order', 'id_desc'), 0, $this->param('limit', 20), $this->param('page', 1)); + $data = $this->getData(); + + if(isset($data->status) and $data->status == 'success') + { + $stories = $data->data->stories; + $pager = $data->data->pager; + $result = array(); + foreach($stories as $story) + { + $result[] = $this->format($story, 'openedDate:time,assignedDate:time,reviewedDate:time,lastEditedDate:time,closedDate:time'); + } + return $this->send(200, array('page' => $pager->pageID, 'total' => $pager->recTotal, 'limit' => $pager->recPerPage, 'stories' => $result)); + } + + if(isset($data->status) and $data->status == 'fail') return $this->sendError(400, $data->message); + + return $this->sendError(400, 'error'); + } +} diff --git a/api/v1/entries/reports.php b/api/v1/entries/reports.php new file mode 100644 index 0000000000..d55f98932b --- /dev/null +++ b/api/v1/entries/reports.php @@ -0,0 +1,375 @@ + + * @package entries + * @version 1 + * @link http://www.zentao.net + */ +class reportsEntry extends entry +{ + /** + * GET method. + * + * @access public + * @return void + */ + public function get() + { + $fields = $this->param('fields', ''); + $dept = $this->param('dept', 0); + $account = $this->param('account', ''); + $year = $this->param('year', date('Y')); + if(empty($fields)) return $this->send(400, 'Need fields param for report.'); + + $accounts = array(); + if($account) $accounts = array($account => $account); + if(empty($accounts) and $dept) $accounts = array_keys($this->loadModel('dept')->getDeptUserPairs($dept)); + if(empty($accounts) and empty($dept)) $accounts = array_keys($this->loadModel('user')->getPairs('noclosed')); + + $fields = explode(',', strtolower($fields)); + $report = array(); + foreach($fields as $field) + { + $field = trim($field); + if(empty($field)) continue; + + if($field == 'projectoverview') + { + $report['projectOverview'] = $this->projectOverview($accounts); + } + elseif($field == 'radar') + { + $report['radar'] = $this->radar($accounts, $year); + } + elseif($field == 'projectprogress') + { + $report['projectProgress'] = $this->projectProgress(); + } + elseif($field == 'executionprogress') + { + $report['executionProgress'] = $this->executionProgress(); + } + elseif($field == 'productprogress') + { + $report['productProgress'] = $this->productProgress(); + } + elseif($field == 'bugprogress') + { + $report['bugProgress'] = $this->bugProgress(); + } + elseif($field == 'bugprogress') + { + $report['bugProgress'] = $this->bugProgress(); + } + elseif($field == 'output') + { + $report['output'] = $this->loadModel('report')->getOutput4API($accounts, $year); + } + } + + return $this->send(200, $report); + } + + /** + * Get project overview by status. + * + * @param array $accounts + * @access public + * @return array + */ + public function projectOverview($accounts) + { + $statusOverview = $this->loadModel('report')->getProjectStatusOverview($accounts); + + $this->app->loadLang('project'); + $total = 0; + $overview = array(); + foreach($statusOverview as $status => $count) + { + $total += $count; + $statusName = zget($this->lang->project->statusList, $status); + + $overview[$status] = array(); + $overview[$status]['code'] = $status; + $overview[$status]['name'] = $statusName; + $overview[$status]['total'] = $count; + } + + $projectOverview = array(); + $projectOverview['total'] = $total; + $projectOverview['overview'] = array_values($overview); + + return $projectOverview; + } + + /** + * Get radar data. include product, execution, qa, devel and other. + * + * @param array $accounts + * @param string $year + * @access public + * @return array + */ + public function radar($accounts, $year) + { + $contributions = $this->loadModel('report')->getUserYearContributions($accounts, $year); + $annualDataConfig = $this->config->report->annualData; + + $radarData = array('product' => 0, 'execution' => 0, 'devel' => 0, 'qa' => 0, 'other' => 0); + foreach($contributions as $objectType => $objectContributions) + { + foreach($objectContributions as $actionName => $count) + { + $radarTypes = isset($annualDataConfig['radar'][$objectType][$actionName]) ? $annualDataConfig['radar'][$objectType][$actionName] : array('other'); + foreach($radarTypes as $radarType) $radarData[$radarType] += $count; + } + } + + $radar = array(); + foreach($radarData as $radarType => $total) + { + $radar[$radarType]['code'] = $radarType; + $radar[$radarType]['name'] = $this->lang->report->annualData->radarItems[$radarType]; + $radar[$radarType]['total'] = $total; + } + + return array_values($radar); + } + + /** + * Get project progress. + * + * @access public + * @return array + */ + public function projectProgress() + { + $projects = $this->loadModel('program')->getProjectStats(0, 'all'); + $this->app->loadLang('project'); + + $processedProjects = array(); + $statusList['all']['total'] = 0; + $statusList['doing']['total'] = 0; + $statusList['wait']['total'] = 0; + $statusList['closed']['total'] = 0; + foreach($projects as $project) + { + $newProject = new stdclass(); + $newProject->id = $project->id; + $newProject->name = $project->name; + $newProject->status = $project->status; + $newProject->progress = round($project->hours->progress, 1); + $newProject->totalConsumed = round($project->hours->totalConsumed, 1); + $newProject->totalLeft = round($project->hours->totalLeft, 1); + if(isset($project->delay)) $newProject->delay = $project->delay; + + $statusList['all']['total'] += 1; + if(isset($statusList[$project->status])) $statusList[$project->status]['total'] += 1; + + $processedProjects[$project->id] = $newProject; + } + + foreach(array_keys($statusList) as $status) + { + $statusName = zget($this->lang->project->statusList, $status); + if($status == 'all') $statusName = $this->lang->project->featureBar['all']; + + $statusList[$status]['code'] = $status; + $statusList[$status]['name'] = $statusName; + } + + return array('statusList' => $statusList, 'projects' => array_values($processedProjects)); + } + + /** + * Get execution progress. + * + * @access public + * @return array + */ + public function executionProgress() + { + $executions = $this->loadModel('project')->getStats(0, 'all', 0, 0, 30, 'id_desc'); + $this->app->loadLang('execution'); + + $processedExecutions = array(); + $statusList['all']['total'] = 0; + $statusList['doing']['total'] = 0; + $statusList['wait']['total'] = 0; + $statusList['closed']['total'] = 0; + foreach($executions as $execution) + { + $newExecution = new stdclass(); + $newExecution->id = $execution->id; + $newExecution->name = $execution->name; + $newExecution->status = $execution->status; + $newExecution->progress = round($execution->hours->progress, 1); + $newExecution->totalConsumed = round($execution->hours->totalConsumed, 1); + $newExecution->totalLeft = round($execution->hours->totalLeft, 1); + if(isset($execution->delay)) $newExecution->delay = $execution->delay; + + $statusList['all']['total'] += 1; + if(isset($statusList[$execution->status])) $statusList[$execution->status]['total'] += 1; + + $processedExecutions[$execution->id] = $newExecution; + } + + foreach(array_keys($statusList) as $status) + { + $statusName = zget($this->lang->execution->statusList, $status); + if($status == 'all') $statusName = $this->lang->execution->allTasks; + + $statusList[$status]['code'] = $status; + $statusList[$status]['name'] = $statusName; + } + + return array('statusList' => $statusList, 'executions' => array_values($processedExecutions)); + } + + /** + * Get product progress with story. + * + * @access public + * @return array + */ + public function productProgress() + { + $this->app->loadLang('product'); + $this->app->loadLang('story'); + + $storyStatusStat = $this->dao->select('t1.product,t2.name,t2.status,t1.status as storyStatus,count(*) as storyCount')->from(TABLE_STORY)->alias('t1') + ->leftJoin(TABLE_PRODUCT)->alias('t2')->on('t1.product=t2.id') + ->where('t2.deleted')->eq(0) + ->andWhere('t1.deleted')->eq(0) + ->beginIF(!$this->app->user->admin)->andWhere('t2.id')->in($this->app->user->view->products)->fi() + ->groupBy('t1.product,t1.status') + ->orderBy('t1.product_desc,t1.status') + ->fetchAll(); + + $productStatusList['all']['total'] = 0; + $productStatusList['normal']['total'] = 0; + $productStatusList['closed']['total'] = 0; + + $processedProducts = array(); + $productStoryStat = array(); + foreach($storyStatusStat as $product) + { + $productStoryStat[$product->product][$product->storyStatus] = $product->storyCount; + + if(isset($processedProducts[$product->product])) continue; + + $newProduct = new stdclass(); + $newProduct->id = $product->product; + $newProduct->name = $product->name; + $newProduct->status = $product->status; + + $processedProducts[$product->product] = $newProduct; + $productStatusList['all']['total'] += 1; + if(isset($productStatusList[$product->status])) $productStatusList[$product->status]['total'] += 1; + } + + /* Set story status statistics integrate into product. */ + $storyStatusList = array('draft' => array(), 'active' => array(), 'changed' => array(), 'closed' => array()); + foreach($processedProducts as $productID => $product) + { + $product->storyStat = array(); + foreach(array_keys($storyStatusList) as $storyStatus) $product->storyStat[$storyStatus] = isset($productStoryStat[$productID][$storyStatus]) ? $productStoryStat[$productID][$storyStatus] : 0; + $product->progress = $product->storyStat['closed'] == 0 ? 0 : round($product->storyStat['closed'] / array_sum($product->storyStat) * 100, 1); + } + + foreach(array_keys($productStatusList) as $status) + { + $statusName = zget($this->lang->product->statusList, $status); + if($status == 'all') $statusName = $this->lang->product->allStory; + if($status == 'normal') $statusName = $this->lang->product->unclosed; + + $productStatusList[$status]['code'] = $status; + $productStatusList[$status]['name'] = $statusName; + } + + foreach(array_keys($storyStatusList) as $status) + { + $statusName = zget($this->lang->story->statusList, $status); + + $storyStatusList[$status]['code'] = $status; + $storyStatusList[$status]['name'] = $statusName; + } + + return array('productStatusList' => $productStatusList, 'products' => array_values($processedProducts), 'storyStatusList' => $storyStatusList); + } + + /** + * Get bug progress by product. + * + * @access public + * @return array + */ + public function bugProgress() + { + $this->app->loadLang('product'); + $this->app->loadLang('bug'); + + $bugStatusStat = $this->dao->select('t1.product,t2.name,t2.status,t1.status as bugStatus,count(*) as bugCount')->from(TABLE_BUG)->alias('t1') + ->leftJoin(TABLE_PRODUCT)->alias('t2')->on('t1.product=t2.id') + ->where('t2.deleted')->eq(0) + ->andWhere('t1.deleted')->eq(0) + ->beginIF(!$this->app->user->admin)->andWhere('t2.id')->in($this->app->user->view->products)->fi() + ->groupBy('t1.product,t1.status') + ->orderBy('t1.product_desc,t1.status') + ->fetchAll(); + + $productStatusList['all']['total'] = 0; + $productStatusList['normal']['total'] = 0; + $productStatusList['closed']['total'] = 0; + + $processedProducts = array(); + $productBugStat = array(); + foreach($bugStatusStat as $product) + { + $productBugStat[$product->product][$product->bugStatus] = $product->bugCount; + + if(isset($processedProducts[$product->product])) continue; + + $newProduct = new stdclass(); + $newProduct->id = $product->product; + $newProduct->name = $product->name; + $newProduct->status = $product->status; + + $processedProducts[$product->product] = $newProduct; + $productStatusList['all']['total'] += 1; + if(isset($productStatusList[$product->status])) $productStatusList[$product->status]['total'] += 1; + } + + /* Set bug status statistics integrate into product. */ + $bugStatusList = array('active' => array(), 'resolved' => array(), 'closed' => array()); + foreach($processedProducts as $productID => $product) + { + $product->bugStat = array(); + foreach(array_keys($bugStatusList) as $bugStatus) $product->bugStat[$bugStatus] = isset($productBugStat[$productID][$bugStatus]) ? $productBugStat[$productID][$bugStatus] : 0; + } + + foreach(array_keys($productStatusList) as $status) + { + $statusName = zget($this->lang->product->statusList, $status); + if($status == 'all') $statusName = $this->lang->product->allStory; + if($status == 'normal') $statusName = $this->lang->product->unclosed; + + $productStatusList[$status]['code'] = $status; + $productStatusList[$status]['name'] = $statusName; + } + + foreach(array_keys($bugStatusList) as $status) + { + $statusName = zget($this->lang->bug->statusList, $status); + + $bugStatusList[$status]['code'] = $status; + $bugStatusList[$status]['name'] = $statusName; + } + + return array('productStatusList' => $productStatusList, 'bugs' => array_values($processedProducts), 'bugStatusList' => $bugStatusList); + } +} diff --git a/api/v1/entries/stakeholders.php b/api/v1/entries/stakeholders.php new file mode 100644 index 0000000000..e67a3a2d30 --- /dev/null +++ b/api/v1/entries/stakeholders.php @@ -0,0 +1,134 @@ + + * @package entries + * @version 1 + * @link http://www.zentao.net + */ +class stakeholdersEntry extends entry +{ + /** + * GET method. + * + * @param int $programID + * @access public + * @return void + */ + public function get($programID = 0) + { + if(!$programID) $programID = $this->param('program', 0); + + if($programID) + { + $control = $this->loadController('program', 'stakeholder'); + $control->stakeholder($programID, $this->param('order', 't1.id_desc'), 0, $this->param('limit', 20), $this->param('page', 1)); + $data = $this->getData(); + } + + if(isset($data->status) and $data->status == 'success') + { + $this->app->loadLang('stakeholder'); + $pager = $data->data->pager; + $users = $data->data->users; + $result = array(); + foreach($data->data->stakeholders as $stakeholder) + { + $stakeholder->roleName = zget($this->lang->user->roleList, $stakeholder->role, ''); + $stakeholder->typeName = zget($this->lang->stakeholder->fromList, $stakeholder->from, ''); + + $result[] = $stakeholder; + } + + $data = array(); + $data['page'] = $pager->pageID; + $data['total'] = $pager->recTotal; + $data['limit'] = (int)$pager->recPerPage; + $data['stakeholders'] = $result; + + $withUser = $this->param('withUser', ''); + if(!empty($withUser)) $data['users'] = $users; + + return $this->send(200, $data); + } + + if(isset($data->status) and $data->status == 'fail') return $this->sendError(400, $data->message); + + // TODO There is no handle for 401. + return $this->sendError(400, 'error'); + } + + /** + * POST method. + * + * @access public + * @return void + */ + public function post() + { + $fields = 'name,begin,end,products'; + $this->batchSetPost($fields); + + $this->setPost('code', $this->request('code', '')); + $this->setPost('acl', $this->request('acl', 'private')); + $this->setPost('parent', $this->request('program', 0)); + $this->setPost('whitelist', $this->request('whitelist', array())); + $this->setPost('PM', $this->request('PM', '')); + $this->setPost('model', $this->request('model', 'scrum')); + + $control = $this->loadController('project', 'create'); + $this->requireFields('name,code,begin,end,products'); + + $control->create($this->request('model', 'scrum')); + + $data = $this->getData(); + if(isset($data->result) and $data->result == 'fail') return $this->sendError(400, $data->message); + if(!isset($data->result)) return $this->sendError(400, 'error'); + + $project = $this->loadModel('project')->getByID($data->id); + + $this->send(201, $this->format($project, 'openedDate:time,lastEditedDate:time,closedDate:time,canceledDate:time')); + } + + /** + * Get drop menu. + * + * @access public + * @return void + */ + public function getDropMenu() + { + $control = $this->loadController('project', 'ajaxGetDropMenu'); + $control->ajaxGetDropMenu($this->request('projectID', 0), $this->request('module', 'project'), $this->request('method', 'browse')); + + $data = $this->getData(); + if(isset($data->result) and $data->result == 'fail') return $this->sendError(400, $data->message); + + $dropMenu = array('owner' => array(), 'other' => array(), 'closed' => array()); + foreach($data->data->projects as $programID => $projects) + { + foreach($projects as $project) + { + if(helper::diffDate(date('Y-m-d'), $project->end) > 0) $project->delay = true; + $project = $this->filterFields($project, 'id,model,type,name,code,parent,status,PM,delay'); + + if($project->status == 'closed') + { + $dropMenu['closed'][] = $project; + } + elseif($project->PM == $this->app->user->account) + { + $dropMenu['owner'][] = $project; + } + else + { + $dropMenu['other'][] = $project; + } + } + } + $this->send(200, $dropMenu); + } +} diff --git a/api/v1/entries/stories.php b/api/v1/entries/stories.php index 0f14613b7d..09e8182702 100644 --- a/api/v1/entries/stories.php +++ b/api/v1/entries/stories.php @@ -9,53 +9,36 @@ * @version 1 * @link http://www.zentao.net */ -class storiesEntry extends entry +class storiesEntry extends entry { /** * GET method. * * @param int $productID - * @param int $projectID * @access public * @return void */ - public function get($productID = 0, $projectID = 0) + public function get($productID = 0) { if(!$productID) $productID = $this->param('product'); - if(!$projectID) $projectID = $this->param('project'); - if(!$productID and !$projectID) return $this->sendError(400, 'Need product or project id.'); + if(!$productID) return $this->sendError(400, 'Need product id.'); - if($projectID) - { - $control = $this->loadController('projectstory', 'story'); - $control->story($projectID, $productID, $this->param('branch', 0), $this->param('type', ''), 0, 'story', $this->param('order', ''), $this->param('total', 0), $this->param('limit', 20), $this->param('page', 1)); - $data = $this->getData(); - } - else - { - $control = $this->loadController('product', 'browse'); - $control->browse($productID, $this->param('branch', 0), $this->param('type', ''), 0, 'story', $this->param('order', ''), $this->param('total', 0), $this->param('limit', 20), $this->param('page', 1)); - $data = $this->getData(); - } + $control = $this->loadController('product', 'browse'); + $control->browse($productID, $this->param('branch', ''), $this->param('status', 'unclosed'), 0, $this->param('type', 'story'), $this->param('order', 'id_desc'), 0, $this->param('limit', 20), $this->param('page', 1)); - if(isset($data->status) and $data->status == 'success') - { - $stories = $data->data->stories; - $pager = $data->data->pager; - $result = array(); - foreach($stories as $story) - { - $result[] = $this->format($story, 'openedDate:time,assignedDate:time,reviewedDate:time,lastEditedDate:time,closedDate:time,deleted:bool'); - } - return $this->send(200, array('page' => $pager->pageID, 'total' => $pager->recTotal, 'limit' => $pager->recPerPage, 'stories' => $result)); - } + $data = $this->getData(); + if(!$data or !isset($data->status)) return $this->sendError(400, 'error'); + if(isset($data->status) and $data->status == 'fail') return $this->sendError(400, $data->message); - if(isset($data->status) and $data->status == 'fail') + $stories = $data->data->stories; + $pager = $data->data->pager; + $result = array(); + foreach($stories as $story) { - return $this->sendError(400, $data->message); + if(isset($story->children)) $story->children = array_values((array)$story->children); + $result[] = $this->format($story, 'openedDate:time,assignedDate:time,reviewedDate:time,lastEditedDate:time,closedDate:time,deleted:bool'); } - - return $this->sendError(400, 'error'); + return $this->send(200, array('page' => $pager->pageID, 'total' => $pager->recTotal, 'limit' => $pager->recPerPage, 'stories' => $result)); } /** @@ -83,7 +66,7 @@ class storiesEntry extends entry $this->requireFields('title,spec,pri,category'); $control->create($productID); - + $data = $this->getData(); if(isset($data->result) and $data->result == 'fail') return $this->sendError(400, $data->message); if(isset($data->result) and !isset($data->id)) return $this->sendError(400, $data->message); diff --git a/api/v1/entries/tabs.php b/api/v1/entries/tabs.php new file mode 100644 index 0000000000..caf7fa7ae5 --- /dev/null +++ b/api/v1/entries/tabs.php @@ -0,0 +1,77 @@ + + * @package entries + * @version 1 + * @link http://www.zentao.net + */ +class tabsEntry extends baseEntry +{ + /** + * Get tabs. + * + * @param string $moduleName work| + * @access public + * @return void + */ + public function get($moduleName) + { + $menus = array(); + if($moduleName == 'work') + { + $this->app->loadLang('my'); + $tabs = array('calendar', 'task', 'bug', 'story', 'issue', 'risk', 'myMeeting'); + + foreach($tabs as $menuKey) + { + if(!common::hasPriv('my', $menuKey)) continue; + $label = $this->lang->my->$menuKey; + if($menuKey == 'calendar') $label = $this->lang->my->calendarAction; + + $menu = new stdclass(); + $menu->code = $menuKey; + $menu->name = $label; + + $menus[] = $menu; + } + } + elseif($moduleName == 'product') + { + $this->app->loadLang('product'); + $tabs = array('story', 'plan', 'project', 'release', 'requirement', 'doc', 'view'); + + foreach($tabs as $menuKey) + { + if($menuKey == 'requirement' and empty($this->config->URAndSR)) continue; + if(isset($this->lang->product->menu->$menuKey)) + { + list($label, $module, $method) = explode('|', $this->lang->product->menu->$menuKey['link']); + if(!common::hasPriv($module, $method)) continue; + } + else + { + if(!common::hasPriv('product', $menuKey)) continue; + } + + $label = zget($this->lang->product, $menuKey, ''); + if($menuKey == 'view') $label = $this->lang->overview; + if($menuKey == 'doc') $label = $this->lang->doc->common; + if($menuKey == 'project') $label = $this->lang->project->common; + if($menuKey == 'story') $label = $this->lang->createObjects['story']; + if($menuKey == 'requirement') $label = $this->lang->URCommon; + + $menu = new stdclass(); + $menu->code = $menuKey; + $menu->name = $label; + + $menus[] = $menu; + } + } + + $this->send(200, array('tabs' => $menus)); + } +} diff --git a/api/v1/entries/tasks.php b/api/v1/entries/tasks.php index 0a6b31ac07..7d657f275a 100644 --- a/api/v1/entries/tasks.php +++ b/api/v1/entries/tasks.php @@ -9,7 +9,7 @@ * @version 1 * @link http://www.zentao.net */ -class tasksEntry extends entry +class tasksEntry extends entry { /** * GET method. @@ -31,7 +31,7 @@ class tasksEntry extends entry { /* Get tasks by execution. */ $control = $this->loadController('execution', 'task'); - $control->task($executionID, $this->param('status', 'all'), 0, $this->param('order', ''), $this->param('total', 0), $this->param('limit', 100), $this->param('page', 1)); + $control->task($executionID, $this->param('status', 'all'), 0, $this->param('order', 'id_desc'), 0, $this->param('limit', 100), $this->param('page', 1)); $data = $this->getData(); } @@ -42,6 +42,7 @@ class tasksEntry extends entry $result = array(); foreach($tasks as $task) { + if(isset($task->children)) $task->children = array_values((array)$task->children); $result[] = $this->format($task, 'openedDate:time,assignedDate:time,realStarted:time,finishedDate:time,canceledDate:time,closedDate:time,lastEditedDate:time,deleted:bool'); } return $this->send(200, array('page' => $pager->pageID, 'total' => $pager->recTotal, 'limit' => $pager->recPerPage, 'tasks' => $result)); @@ -61,7 +62,7 @@ class tasksEntry extends entry */ public function post($executionID) { - $fields = 'name,type,assignedTo,estimate,story,parent,execution,module,pri,desc,estStarted,deadline,mailto'; + $fields = 'name,type,assignedTo,estimate,story,execution,project,module,pri,desc,estStarted,deadline,mailto,team,teamEstimate,multiple,uid'; $this->batchSetPost($fields); $assignedTo = $this->request('assignedTo'); @@ -71,7 +72,7 @@ class tasksEntry extends entry $this->requireFields('name,assignedTo,type,estStarted,deadline'); $control->create($executionID, $this->request('storyID', 0), $this->request('moduleID', 0), $this->request('copyTaskID', 0), $this->request('copyTodoID', 0)); - + $data = $this->getData(); if(!isset($data->id)) return $this->sendError(400, $data->message); diff --git a/api/v1/entries/todoactivate.php b/api/v1/entries/todoactivate.php new file mode 100644 index 0000000000..fe26a2ef2a --- /dev/null +++ b/api/v1/entries/todoactivate.php @@ -0,0 +1,32 @@ + + * @package entries + * @version 1 + * @link http://www.zentao.net + */ +class todoActivateEntry extends Entry +{ + /** + * GET method. + * + * @param int $taskID + * @access public + * @return void + */ + public function get($todoID) + { + $control = $this->loadController('todo', 'activate'); + $control->activate($todoID); + + $data = $this->getData(); + if($data->status == 'fail') return $this->sendError(400, $data->message); + + $todo = $this->loadModel('todo')->getByID($todoID); + $this->send(200, $this->format($todo, 'assignedDate:time,finishedDate:time,closedDate:time')); + } +} diff --git a/api/v1/entries/todofinish.php b/api/v1/entries/todofinish.php new file mode 100644 index 0000000000..6d6f97339d --- /dev/null +++ b/api/v1/entries/todofinish.php @@ -0,0 +1,32 @@ + + * @package entries + * @version 1 + * @link http://www.zentao.net + */ +class todoFinishEntry extends Entry +{ + /** + * GET method. + * + * @param int $taskID + * @access public + * @return void + */ + public function get($todoID) + { + $control = $this->loadController('todo', 'finish'); + $control->finish($todoID); + + $data = $this->getData(); + if($data->status == 'fail') return $this->sendError(400, $data->message); + + $todo = $this->loadModel('todo')->getByID($todoID); + $this->send(200, $this->format($todo, 'assignedDate:time,finishedDate:time,closedDate:time')); + } +} diff --git a/api/v1/entries/todos.php b/api/v1/entries/todos.php index e03e26d90d..81e612c152 100644 --- a/api/v1/entries/todos.php +++ b/api/v1/entries/todos.php @@ -20,7 +20,7 @@ class todosEntry extends entry public function get() { $control = $this->loadController('my', 'todo'); - $control->todo($this->param('date', 'all'), $this->param('user', ''), $this->param('status', 'all'), $this->param('order', 'date_desc'), 0, $this->param('total', 0), $this->param('limit', 20), $this->param('page', 1)); + $control->todo($this->param('type', 'all'), $this->param('userID', ''), $this->param('status', 'all'), $this->param('order', 'date_desc,status,begin'), $this->param('total', 0), $this->param('limit', 100), $this->param('page', 1)); $data = $this->getData(); if(!isset($data->status)) return $this->sendError(400, 'error'); @@ -63,7 +63,7 @@ class todosEntry extends entry if(isset($data->result) and !isset($data->id)) return $this->sendError(400, $data->message); $todo = $this->loadModel('todo')->getByID($data->id); - + $this->send(201, $this->format($todo, 'assignedDate:time,finishedDate:time,closedDate:time')); } } diff --git a/api/v1/entries/user.php b/api/v1/entries/user.php index cc96d1349e..dc3e23579a 100644 --- a/api/v1/entries/user.php +++ b/api/v1/entries/user.php @@ -52,12 +52,13 @@ class userEntry extends Entry unset($profile->password); $info->profile = $this->format($profile, 'last:time,locked:time,birthday:date,join:date'); - $info->profile->role = array('code' => $info->profile->role, 'name' => $this->lang->user->roleList[$info->profile->role]); + $info->profile->role = array('code' => $info->profile->role, 'name' => $this->lang->user->roleList[$info->profile->role]); + $info->profile->admin = strpos($this->app->company->admins, ",{$profile->account},") !== false; if(!$fields) return $this->send(200, $info); /* Set other fields. */ - $fields = explode(',', $fields); + $fields = explode(',', strtolower($fields)); $this->loadModel('my'); foreach($fields as $field) @@ -67,13 +68,23 @@ class userEntry extends Entry case 'product': $info->product = array('total' => 0, 'products' => array()); - $products = $this->my->getProducts(); + $products = $this->my->getProducts('ownbyme'); if($products) { - $info->product['total'] = $products->allCount; + $info->product['total'] = $products->unclosedCount; $info->product['products'] = $products->products; } break; + case 'undoneproduct': + $info->undoneProduct = array('total' => 0, 'products' => array()); + + $products = $this->my->getProducts('undone'); + if($products) + { + $info->undoneProduct['total'] = $products->allCount; + $info->undoneProduct['products'] = $products->products; + } + break; case 'project': $info->project = array('total' => 0, 'projects' => array()); @@ -84,11 +95,96 @@ class userEntry extends Entry $info->project['projects'] = $projects->projects; } break; + case 'lastproject': + $info->lastProject = array('total' => 0, 'projects' => array()); + + $control = $this->loadController('project', 'ajaxGetDropMenu'); + $control->ajaxGetDropMenu(0, 'project', 'index'); + $data = $this->getData(); + + if($data->status == 'success') + { + $myProjects['owner'] = array(); + $myProjects['other'] = array(); + foreach($data->data->projects as $programID => $programProjects) + { + foreach($programProjects as $project) + { + if($project->status == 'closed') continue; + + $project = $this->filterFields($project, 'id,model,type,name,code,parent,status,PM'); + if($project->PM == $this->app->user->account) + { + $myProjects['owner'][] = $project; + } + else + { + $myProjects['other'][] = $project; + } + } + } + $lastProjects = array_merge($myProjects['owner'], $myProjects['other']); + $lastProjects = array_slice($lastProjects, 0, 3); + + $info->lastProject['total'] = count($lastProjects); + $info->lastProject['projects'] = $lastProjects; + } + break; + case 'execution': + $info->execution = array('total' => 0, 'executions' => array()); + if(!common::hasPriv('my', 'execution')) break; + + $control = $this->loadController('my', 'execution'); + $control->execution($this->param('type', 'undone'), $this->param('order', 'id_desc'), $this->param('total', 0), $this->param('limit', 5), $this->param('page', 1)); + $data = $this->getData(); + + if($data->status == 'success') + { + $info->execution['total'] = $data->data->pager->recTotal; + $info->execution['executions'] = array_values((array)$data->data->executions); + } + break; + case 'lastexecution': + $info->lastExecution = array('total' => 0, 'executions' => array()); + + $control = $this->loadController('execution', 'ajaxGetDropMenu'); + $control->ajaxGetDropMenu(0, 'execution', 'browse', ''); + $data = $this->getData(); + + $account = $this->app->user->account; + if($data->status == 'success') + { + $myExecutions['owner'] = array(); + $myExecutions['other'] = array(); + foreach($data->data->executions as $projectID => $projectExecutions) + { + foreach($projectExecutions as $execution) + { + if($execution->status == 'done' or $execution->status == 'closed') continue; + + if($execution->PM == $account or isset($execution->teams->$account)) + { + $myExecutions['owner'][] = $this->filterFields($execution, 'id,model,type,name,code,parent,status,PM'); + } + else + { + $myExecutions['other'][] = $this->filterFields($execution, 'id,model,type,name,code,parent,status,PM'); + } + } + } + $lastExecutions = array_merge($myExecutions['owner'], $myExecutions['other']); + $lastExecutions = array_slice($lastExecutions, 0, 3); + + $info->lastExecution['total'] = count($lastExecutions); + $info->lastExecution['executions'] = $lastExecutions; + } + break; case 'actions': $info->actions = $this->my->getActions(); break; case 'task': $info->task = array('total' => 0, 'tasks' => array()); + if(!common::hasPriv('my', 'task')) break; $control = $this->loadController('my', 'task'); $control->task($this->param('type', 'assignedTo'), $this->param('order', 'id_desc'), $this->param('total', 0), $this->param('limit', 5), $this->param('page', 1)); @@ -97,12 +193,13 @@ class userEntry extends Entry if($data->status == 'success') { $info->task['total'] = $data->data->pager->recTotal; - $info->task['tasks'] = $data->data->tasks; + $info->task['tasks'] = array_values((array)$data->data->tasks); } break; case 'bug': $info->bug = array('total' => 0, 'bugs' => array()); + if(!common::hasPriv('my', 'bug')) break; $control = $this->loadController('my', 'bug'); $control->bug($this->param('type', 'assignedTo'), $this->param('order', 'id_desc'), $this->param('total', 0), $this->param('limit', 5), $this->param('page', 1)); @@ -110,54 +207,120 @@ class userEntry extends Entry if($data->status == 'success') { + $bugs = array(); + foreach($data->data->bugs as $bug) + { + $status = array('code' => $bug->status, 'name' => $this->lang->bug->statusList[$bug->status]); + if($bug->status == 'active' and $bug->confirmed) $status = array('code' => 'confirmed', 'name' => $this->lang->bug->labelConfirmed); + if($bug->resolution == 'postponed') $status = array('code' => 'postponed', 'name' => $this->lang->bug->labelPostponed); + if(!empty($bug->delay)) $status = array('code' => 'delay', 'name' => $this->lang->bug->overdueBugs); + $bug->status = $status; + + $bugs[$bug->id] = $bug; + } + + $storyChangeds = $this->dao->select('t1.id')->from(TABLE_BUG)->alias('t1') + ->leftJoin(TABLE_STORY)->alias('t2')->on('t1.story=t2.id') + ->where('t1.id')->in(array_keys($bugs)) + ->andWhere('t1.story')->ne('0') + ->andWhere('t1.storyVersion != t2.version') + ->fetchPairs('id', 'id'); + foreach($storyChangeds as $bugID) + { + $status = array('code' => 'storyChanged', 'name' => $this->lang->bug->changed); + $bugs[$bugID]->status = $status; + } + $info->bug['total'] = $data->data->pager->recTotal; - $info->bug['bugs'] = $data->data->bugs; + $info->bug['bugs'] = array_values($bugs); } break; case 'todo': $info->todo = array('total' => 0, 'todos' => array()); + if(!common::hasPriv('my', 'todo')) break; $control = $this->loadController('my', 'todo'); - $control->todo($this->param('date', 'all'), '', 'all', 'date_desc', 0, 0, $this->param('limit', 10), 1); + $control->todo($this->param('date', 'all'), '', 'all', 'date_desc', 0, 0, $this->param('limit', 5), 1); $data = $this->getData(); if($data->status == 'success') { $info->todo['total'] = $data->data->pager->recTotal; - $info->todo['todos'] = $data->data->todos; + $info->todo['todos'] = array_values((array)$data->data->todos); + } + + break; + case 'story': + $info->story = array('total' => 0, 'stories' => array()); + if(!common::hasPriv('my', 'story')) break; + + $control = $this->loadController('my', 'story'); + $control->story($this->param('type', 'assignedTo'), $this->param('order', 'id_desc'), $this->param('total', 0), $this->param('limit', 5), $this->param('page', 1)); + $data = $this->getData(); + + if($data->status == 'success') + { + $stories = array(); + foreach($data->data->stories as $story) + { + $story->status = array('code' => $story->status, 'name' => $this->lang->story->statusList[$story->status]); + $stories[$story->id] = $story; + } + + $info->story['total'] = $data->data->pager->recTotal; + $info->story['stories'] = array_values($stories); } break; case 'issue': + $info->issue = array('total' => 0, 'issues' => array()); + if(!common::hasPriv('my', 'issue')) break; + if(!empty($this->config->maxVersion)) { - $info->issue = array('total' => 0, 'issues' => array()); - $control = $this->loadController('my', 'issue'); - $control->issue('createdBy', 'id_desc', 0, $this->param('limit', 10), 1); + $control->issue('createdBy', 'id_desc', 0, $this->param('limit', 5), 1); $data = $this->getData(); if($data->status == 'success') { $info->issue['total'] = $data->data->pager->recTotal; - $info->issue['issues'] = $data->data->issues; + $info->issue['issues'] = array_values((array)$data->data->issues); } } break; case 'risk': + $info->risk = array('total' => 0, 'risks' => array()); + if(!common::hasPriv('my', 'risk')) break; + if(!empty($this->config->maxVersion)) { - $info->risk = array('total' => 0, 'risks' => array()); - $control = $this->loadController('my', 'risk'); - $control->risk('createdBy', 'id_desc', 0, $this->param('limit', 10), 1); + $control->risk('createdBy', 'id_desc', 0, $this->param('limit', 5), 1); $data = $this->getData(); if($data->status == 'success') { $info->risk['total'] = $data->data->pager->recTotal; - $info->risk['risks'] = $data->data->risks; + $info->risk['risks'] = array_values((array)$data->data->risks); + } + } + break; + case 'meeting': + $info->meeting = array('total' => 0, 'meetings' => array()); + if(!common::hasPriv('my', 'myMeeting')) break; + + if(!empty($this->config->maxVersion)) + { + $control = $this->loadController('my', 'myMeeting'); + $control->myMeeting('all', 'id_desc', 0, $this->param('limit', 5), 1); + $data = $this->getData(); + + if($data->status == 'success') + { + $info->meeting['total'] = $data->data->pager->recTotal; + $info->meeting['meetings'] = array_values((array)$data->data->meetings); } } break; @@ -167,6 +330,16 @@ class userEntry extends Entry case 'contribute': $info->contribute = $this->my->getContribute(); break; + case 'rights': + $inAdminGroup = $this->dao->select('t1.*')->from(TABLE_USERGROUP)->alias('t1') + ->leftJoin(TABLE_GROUP)->alias('t2')->on('t1.group=t2.id') + ->where('t1.account')->eq($info->profile->account) + ->andWhere('t2.role')->eq('admin') + ->fetch(); + + $info->rights = array(); + $info->rights['admin'] = (!empty($inAdminGroup) or $this->app->user->admin); + $info->rights['rights'] = $this->app->user->rights['rights']; } } diff --git a/api/v1/entries/users.php b/api/v1/entries/users.php index 58dea59897..aa68973ca1 100644 --- a/api/v1/entries/users.php +++ b/api/v1/entries/users.php @@ -9,7 +9,7 @@ * @version 1 * @link http://www.zentao.net */ -class usersEntry extends entry +class usersEntry extends entry { /** * GET method. @@ -19,22 +19,32 @@ class usersEntry extends entry */ public function get() { - $control = $this->loadController('company', 'browse'); - $control->browse('inside', 0, $this->param('type', 'bydept'), $this->param('order', 'id_desc'), $this->param('total', 0), $this->param('limit', 20), $this->param('page', 1)); - $data = $this->getData(); + if(!common::hasPriv('company', 'browse')) return $this->sendError(400, 'error: no company-browse priv.'); - if(isset($data->status) and $data->status == 'success') + $appendFields = $this->param('fileds', ''); + $type = $this->param('type', 'bydept'); + $limit = (int)$this->param('limit', 20); + + $pager = null; + if($limit) { - $users = $data->data->users; - $pager = $data->data->pager; - $result = array(); - foreach($users as $user) $result[] = $this->format($user, 'locked:time'); - return $this->send(200, array('page' => $pager->pageID, 'total' => $pager->recTotal, 'limit' => $pager->recPerPage, 'users' => $result)); + $this->app->loadClass('pager', $static = true); + $pager = pager::init(0, $limit, $this->param('page', 1)); } - if(isset($data->status) and $data->status == 'fail') return $this->sendError(400, $data->message); + $users = $this->loadModel('company')->getUsers($this->param('browse', 'inside'), $type, 0, 0, $this->param('order', 'id_desc'), $pager); + $result = array(); + foreach($users as $user) + { + $user = $this->filterFields($user, 'id,dept,account,realname,role,pinyin,email,' . $appendFields); + $result[] = $this->format($user, 'locked:time'); + } - return $this->sendError(400, 'error'); + $pageID = $pager ? $pager->pageID : 1; + $total = $pager ? $pager->recTotal : count($result); + $limit = $pager ? $pager->recPerPage : $total; + + return $this->send(200, array('page' => $pageID, 'total' => $total, 'limit' => $limit, 'users' => $result)); } /** diff --git a/config/routes.php b/config/routes.php index c55fa34bb2..0a3d96dd15 100644 --- a/config/routes.php +++ b/config/routes.php @@ -4,7 +4,14 @@ */ $routes = array(); -$routes['/tokens'] = 'tokens'; +$routes['/tokens'] = 'tokens'; +$routes['/langs'] = 'langs'; +$routes['/comments'] = 'comments'; + +$routes['/tabs/:module'] = 'tabs'; + +$routes['/files'] = 'files'; +$routes['/files/:id'] = 'file'; $routes['/configurations'] = 'configs'; $routes['/configurations/:name'] = 'config'; @@ -27,13 +34,16 @@ $routes['/releases/:id'] = 'release'; $routes['/stories'] = 'stories'; $routes['/products/:id/stories'] = 'stories'; +$routes['/projects/:id/stories'] = 'projectStories'; $routes['/executions/:id/stories'] = 'executionStories'; $routes['/stories/:id'] = 'story'; $routes['/stories/:id/change'] = 'storyChange'; -$routes['/products/:id/bugs'] = 'bugs'; -$routes['/bugs'] = 'bugs'; -$routes['/bugs/:id'] = 'bug'; +$routes['/products/:id/bugs'] = 'bugs'; +$routes['/projects/:id/bugs'] = 'projectBugs'; +$routes['/executions/:id/bugs'] = 'executionBugs'; +$routes['/bugs'] = 'bugs'; +$routes['/bugs/:id'] = 'bug'; $routes['/programs/:id/projects'] = 'projects'; $routes['/projects'] = 'projects'; @@ -57,21 +67,26 @@ $routes['/user'] = 'user'; $routes['/programs'] = 'programs'; $routes['/programs/:id'] = 'program'; +$routes['/programs/:id/stakeholders'] = 'stakeholders'; + $routes['/products/:productID/issues'] = 'productIssues'; $routes['/projects/:projectID/issues'] = 'issues'; $routes['/issues'] = 'issues'; $routes['/issues/:issueID'] = 'issue'; -$routes['/todos'] = 'todos'; -$routes['/todos/:id'] = 'todo'; +$routes['/todos'] = 'todos'; +$routes['/todos/:id'] = 'todo'; +$routes['/todos/:id/finish'] = 'todoFinish'; +$routes['/todos/:id/activate'] = 'todoActivate'; $routes['/projects/:projectID/builds'] = 'builds'; $routes['/builds'] = 'builds'; $routes['/builds/:id'] = 'build'; -$routes['/products/:id/testcases'] = 'testcases'; -$routes['/testcases'] = 'testcases'; -$routes['/testcases/:id'] = 'testcase'; +$routes['/products/:id/testcases'] = 'testcases'; +$routes['/executions/:id/testcases'] = 'executioncases'; +$routes['/testcases'] = 'testcases'; +$routes['/testcases/:id'] = 'testcase'; $routes['/projects/:projectID/testtasks'] = 'testtasks'; $routes['/testtasks'] = 'testtasks'; @@ -84,6 +99,13 @@ $routes['/risks/:id'] = 'risk'; $routes['/departments'] = 'departments'; $routes['/departments/:id'] = 'department'; +$routes['/doclibs'] = 'doclibs'; +$routes['/doclibs/:id'] = 'docs'; +$routes['/docs'] = 'docs'; +$routes['/docs/:id'] = 'doc'; + +$routes['/reports'] = 'reports'; + $routes['/z/folders'] = 'zfolders'; $routes['/z/folders/:id'] = 'zfolder'; $routes['/z/files/:id'] = 'zfile'; diff --git a/framework/api/entry.class.php b/framework/api/entry.class.php index ce80564fcc..a68bd080e0 100644 --- a/framework/api/entry.class.php +++ b/framework/api/entry.class.php @@ -486,6 +486,8 @@ class baseEntry $type = $field[1]; $isArray = false; + if(!isset($object->$key)) continue; + $pos = strpos($type, ']'); if($pos !== FALSE) { @@ -519,6 +521,48 @@ class baseEntry } } + /** + * Filter fields. + * + * @param object $object + * @param array $filters + * @access public + * @return object + */ + public function filterFields($object, $allowable = '') + { + if(empty($allowable)) return $object; + if(is_string($allowable)) $allowable = explode(',', $allowable); + + $filtered = new stdclass(); + foreach($allowable as $field) + { + $field = trim($field); + if(empty($field)) continue; + if(!isset($object->$field)) continue; + $filtered->$field = $object->$field; + } + + return $filtered; + } + + /** + * Format user. + * + * @param string $account + * @param array $users + * @access public + * @return array + */ + public function formatUser($account, $users) + { + $user = array(); + $user['account'] = $account; + $user['realname'] = zget($users, $account); + + return $user; + } + /** * 类型转换. * Typecasting. @@ -589,7 +633,7 @@ class baseEntry { $module = $this->app->getModuleName(); $method = $this->app->getMethodName(); - if($module and $method and !commonModel::hasPriv($module, $method)) + if($module and $method and !$this->loadModel('common')->isOpenMethod($module, $method) and !commonModel::hasPriv($module, $method)) { $this->send(403, array('error' => 'Access not allowed')); } diff --git a/module/action/control.php b/module/action/control.php index 2525547440..61d51ee2d6 100755 --- a/module/action/control.php +++ b/module/action/control.php @@ -134,7 +134,7 @@ class action extends control $actionID = $this->action->create($objectType, $objectID, 'Commented', $this->post->comment); if(defined('RUN_MODE') && RUN_MODE == 'api') { - die(array('status' => 'success', 'data' => $actionID)); + return $this->send(array('status' => 'success', 'data' => $actionID)); } else { diff --git a/module/action/model.php b/module/action/model.php index 912b312719..98a4eca807 100755 --- a/module/action/model.php +++ b/module/action/model.php @@ -69,7 +69,7 @@ class actionModel extends model $this->dao->insert(TABLE_ACTION)->data($action)->autoCheck()->exec(); - $actionID = $this->dbh->lastInsertID(); + $actionID = $this->dao->lastInsertID(); if($this->post->uid) $this->file->updateObjectID($this->post->uid, $objectID, $objectType); @@ -983,7 +983,7 @@ class actionModel extends model $action->date = date(DT_MONTHTIME2, strtotime($action->date)); $action->actionLabel = isset($this->lang->$objectType->$actionType) ? $this->lang->$objectType->$actionType : $action->action; $action->actionLabel = isset($this->lang->action->label->$actionType) ? $this->lang->action->label->$actionType : $action->actionLabel; - $action->objectLabel = $this->getObjectLabel($objectType, $action->objectID, $requirements); + $action->objectLabel = $this->getObjectLabel($objectType, $action->objectID, $actionType, $requirements); /* If action type is login or logout, needn't link. */ if($actionType == 'svncommited' or $actionType == 'gitcommited') $action->actor = zget($commiters, $action->actor); @@ -1110,11 +1110,12 @@ class actionModel extends model * * @param string $objectType * @param int $objectID + * @param string $actionType * @param array $requirements * @access public * @return string */ - public function getObjectLabel($objectType, $objectID, $requirements) + public function getObjectLabel($objectType, $objectID, $actionType, $requirements) { $actionObjectLabel = $objectType; if(isset($this->lang->action->label->$objectType)) @@ -1579,4 +1580,83 @@ class actionModel extends model echo $actionType; } } + + /** + * Process action for API. + * + * @param array $actions + * @param array $users + * @param array $objectLang + * @access public + * @return array + */ + public function processActionForAPI($actions, $users = array(), $objectLang = array()) + { + $actions = (array)$actions; + foreach($actions as $action) + { + $action->actor = zget($users, $action->actor); + if($action->action == 'assigned') $action->extra = zget($users, $action->extra); + if(strpos($action->actor, ':') !== false) $action->actor = substr($action->actor, strpos($action->actor, ':') + 1); + + ob_start(); + $this->printAction($action); + $action->desc = ob_get_contents(); + ob_end_clean(); + + if($action->history) + { + foreach($action->history as $i => $history) + { + $history->fieldName = zget($objectLang, $history->field); + $action->history[$i] = $history; + } + } + } + return array_values($actions); + } + + /** + * Process dynamic for API. + * + * @param array $dynamics + * @access public + * @return array + */ + public function processDynamicForAPI($dynamics) + { + $users = $this->loadModel('user')->getList(); + $simplifyUsers = array(); + foreach($users as $user) + { + $simplifyUser = new stdclass(); + $simplifyUser->id = $user->id; + $simplifyUser->account = $user->account; + $simplifyUser->realname = $user->realname; + $simplifyUser->avatar = $user->avatar; + $simplifyUsers[$user->account] = $simplifyUser; + } + + $actions = array(); + foreach($dynamics as $key => $dynamic) + { + if($dynamic->objectType == 'user') continue; + + $simplifyUser = zget($simplifyUsers, $dynamic->actor, ''); + $actor = $simplifyUser; + if(empty($simplifyUser)) + { + $actor = new stdclass(); + $actor->id = 0; + $actor->account = $dynamic->actor; + $actor->realname = $dynamic->actor; + $actor->avatar = ''; + } + + $dynamic->actor = $actor; + $actions[] = $dynamic; + } + + return $actions; + } } diff --git a/module/block/lang/zh-cn.php b/module/block/lang/zh-cn.php index dbd8e2ed9f..7ce129a602 100644 --- a/module/block/lang/zh-cn.php +++ b/module/block/lang/zh-cn.php @@ -585,3 +585,20 @@ $lang->block->flowchart['project'] = array('项目经理', '创建' . $lang->exe if($config->systemMode == 'new') $lang->block->flowchart['project'] = array('项目经理', '创建项目、' . $lang->executionCommon, '维护团队', "关联需求", '分解任务', '跟踪进度'); $lang->block->flowchart['dev'] = array('研发人员', '领取任务和Bug', '设计实现方案', '更新状态', '完成任务和Bug', '提交代码'); $lang->block->flowchart['tester'] = array('测试人员', '撰写用例', '执行用例', '提交Bug', '验证Bug', '关闭Bug'); + +$lang->block->zentaoapp = new stdclass(); +$lang->block->zentaoapp->thisYearInvestment = '今年投入'; +$lang->block->zentaoapp->sinceTotalInvestment = '从使用至今,总投入'; +$lang->block->zentaoapp->myStory = '我的需求'; +$lang->block->zentaoapp->allStorySum = '需求总数'; +$lang->block->zentaoapp->storyCompleteRate = '需求完成率'; +$lang->block->zentaoapp->latestExecution = '近期执行'; +$lang->block->zentaoapp->involvedExecution = '我参与的执行'; +$lang->block->zentaoapp->mangedProduct = '负责产品'; +$lang->block->zentaoapp->involvedProject = '参与项目'; +$lang->block->zentaoapp->customIndexCard = '定制首页卡片'; +$lang->block->zentaoapp->createStory = '提需求'; +$lang->block->zentaoapp->createEffort = '记日志'; +$lang->block->zentaoapp->createDoc = '建文档'; +$lang->block->zentaoapp->createTodo = '建待办'; +$lang->block->zentaoapp->workbench = '工作台'; diff --git a/module/bug/lang/zh-cn.php b/module/bug/lang/zh-cn.php index 0aa5536766..12dc4b85fb 100644 --- a/module/bug/lang/zh-cn.php +++ b/module/bug/lang/zh-cn.php @@ -151,12 +151,16 @@ $lang->bug->assignToMeAB = '指派给我'; $lang->bug->openedByMeAB = '由我创建'; $lang->bug->resolvedByMeAB = '由我解决'; -$lang->bug->ditto = '同上'; -$lang->bug->dittoNotice = '该bug与上一bug不属于同一产品!'; -$lang->bug->noAssigned = '未指派'; -$lang->bug->noBug = '暂时没有Bug。'; -$lang->bug->noModule = '
|
- 不能创建临时目录,请确认目录%s是否存在并有操作权限。
+ 不能创建临时目录,请确认目录%s是否存在并有操作权限。
Can't create tmp directory, make sure the directory %s exists and has permission to operate.
|