diff --git a/module/product/control.php b/module/product/control.php index 98ac350479..de624ad053 100755 --- a/module/product/control.php +++ b/module/product/control.php @@ -394,8 +394,8 @@ class product extends control } /** - * 创建产品。可以是顶级产品,也可以是项目集下的产品。 * Create a product. + * 创建产品。可以是顶级产品,也可以是项目集下的产品。 * * @param int $programID * @param string $extra @@ -410,26 +410,11 @@ class product extends control { $data = form::data($this->config->product->form->create); $data = $this->productZen->prepareCreateExtras($data); - if(!$data) return $this->productZen->errorBeforeEdit(); + if(!$data) return $this->productZen->sendError4Create(); - $result = $this->product->create($data); - if(!$result) return $this->productZen->errorAfterEdit(); - return $this->productZen->responseAfterEdit($result); - $productID = $this->product->create(); - if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError())); - $this->loadModel('action')->create('product', $productID, 'opened'); - - $message = $this->executeHooks($productID); - if($message) $this->lang->saveSuccess = $message; - if($this->viewType == 'json') return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'id' => $productID)); - - $tab = $this->app->tab; - $moduleName = $tab == 'program'? 'program' : $this->moduleName; - $methodName = $tab == 'program'? 'product' : 'browse'; - $param = $tab == 'program' ? "programID=$programID" : "productID=$productID"; - $locate = isonlybody() ? 'parent' : $this->createLink($moduleName, $methodName, $param); - if($tab == 'doc') $locate = $this->createLink('doc', 'productSpace', "objectID=$productID"); - return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => $locate)); + $productID = $this->product->create($data); + if(!$productID) return $this->productZen->sendError4Create(); + return $this->productZen->responseAfterCreate($productID, $data); } $this->productZen->setMenu4Create($programID); diff --git a/module/product/model.php b/module/product/model.php index 1ea3e82b11..c21195031f 100755 --- a/module/product/model.php +++ b/module/product/model.php @@ -678,103 +678,22 @@ class productModel extends model /** * Create a product. + * 直接用对象数据创建产品 * + * @param object $product * @access public - * @return int + * @return int|false */ - public function create() + public function create(object $product): int|false { - $product = fixer::input('post') - ->callFunc('name', 'trim') - ->setDefault('status', 'normal') - ->setDefault('line', 0) - ->setDefault('createdBy', $this->app->user->account) - ->setDefault('createdDate', helper::now()) - ->setDefault('createdVersion', $this->config->version) - ->setIF($this->post->acl == 'open', 'whitelist', '') - ->setIF(!isset($_POST['whitelist']), 'whitelist', '') - ->stripTags($this->config->product->editor->create['id'], $this->config->allowedTags) - ->join('whitelist', ',') - ->join('reviewer', ',') - ->remove('uid,newLine,lineName,contactListMenu') - ->get(); - $this->lang->error->unique = $this->lang->error->repeat; - $product = $this->loadModel('file')->processImgURL($product, $this->config->product->editor->create['id'], $this->post->uid); - - /* Lean mode relation defaultProgram. */ - $programID = isset($product->program) ? $product->program : 0; - if($this->config->systemMode == 'light') - { - $programID = $this->config->global->defaultProgram; - $product->program = $this->config->global->defaultProgram; - } - $this->dao->insert(TABLE_PRODUCT)->data($product)->autoCheck() - ->batchCheck($this->config->product->create->requiredFields, 'notempty') - ->checkIF(!empty($product->name), 'name', 'unique', "`program` = $programID and `deleted` = '0'") + ->checkIF(!empty($product->name), 'name', 'unique', "`program` = {$product->program} and `deleted` = '0'") ->checkIF(!empty($product->code), 'code', 'unique', "`deleted` = '0'") ->checkFlow() ->exec(); - - if(!dao::isError()) - { - $productID = $this->dao->lastInsertID(); - - if(!empty($_POST['lineName'])) - { - /* Create product line. */ - $maxOrder = $this->dao->select("max(`order`) as maxOrder")->from(TABLE_MODULE)->where('type')->eq('line')->fetch('maxOrder'); - $maxOrder = $maxOrder ? $maxOrder + 10 : 0; - - $line = new stdClass(); - $line->type = 'line'; - $line->parent = 0; - $line->grade = 1; - $line->name = $this->post->lineName; - $line->root = $this->config->systemMode == 'ALM' ? $product->program : 0; - $line->order = $maxOrder; - - $lines = $this->dao->select('name')->from(TABLE_MODULE)->where('type')->eq('line')->andWhere('root')->eq($line->root)->andWhere('name')->eq($line->name)->fetch(); - if(!empty($lines)) - { - dao::$errors['lineName'] = sprintf($this->lang->product->nameIsDuplicated, $line->name); - return false; - } - $this->dao->insert(TABLE_MODULE)->data($line)->exec(); - - if(!dao::isError()) - { - $lineID = $this->dao->lastInsertID(); - $path = ",$lineID,"; - - $this->dao->update(TABLE_MODULE)->set('path')->eq($path)->where('id')->eq($lineID)->exec(); - - $this->dao->update(TABLE_PRODUCT)->set('line')->eq($lineID)->where('id')->eq($productID)->exec(); - } - } - - $this->file->updateObjectID($this->post->uid, $productID, 'product'); - $this->dao->update(TABLE_PRODUCT)->set('`order`')->eq($productID * 5)->where('id')->eq($productID)->exec(); - - $whitelist = explode(',', $product->whitelist); - $this->loadModel('personnel')->updateWhitelist($whitelist, 'product', $productID); - if($product->acl != 'open') $this->loadModel('user')->updateUserView($productID, 'product'); - - /* Create doc lib. */ - $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 $productID; - } + if(dao::isError()) return false; + return $this->dao->lastInsertID(); } /** @@ -1025,6 +944,72 @@ class productModel extends model } } + /** + * Create product line. + * 创建产品线。 + * + * @param int programID + * @access public + * @return int|false + */ + public function createLine(int $programID): int|false + { + if($programID <= 0) return false; + if(empty($_POST['lineName'])) return false; + + $line = new stdClass(); + $line->type = 'line'; + $line->parent = 0; + $line->grade = 1; + $line->name = htmlSpecialString($this->post->lineName); + $line->root = $programID; + + $existedLineID = $this->dao->select('id')->from(TABLE_MODULE)->where('type')->eq('line')->andWhere('root')->eq($line->root)->andWhere('name')->eq($line->name)->fetch('id'); + if($existedLineID) return $existedLineID; + + $this->dao->insert(TABLE_MODULE)->data($line)->exec(); + if(dao::isError()) return false; + + $lineID = $this->dao->lastInsertID(); + $path = ",$lineID,"; + $this->dao->update(TABLE_MODULE)->set('path')->eq($path)->set('`order`')->eq($lineID)->where('id')->eq($lineID)->exec(); + + return $lineID; + } + + /** + * Create main lib for product + * 关联创建产品主库 + * + * @param int productID + * @access public + * @return int|false + */ + public function createMainLib(int $productID): int|false + { + if($productID <= 0) return false; + + $existedLibID = $this->dao->select('id')->from(TABLE_DOCLIB)->where('product')->eq($productID) + ->andWhere('type')->eq('product') + ->andWhere('main')->eq('1') + ->fetch('id'); + if($existedLibID) return $existedLibID; + + $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(); + + if(dao::isError())return false; + return $this->dao->lastInsertID(); + } + /** * Get stories. * diff --git a/module/product/tao.php b/module/product/tao.php index 9770701e92..2e827f765e 100644 --- a/module/product/tao.php +++ b/module/product/tao.php @@ -235,5 +235,4 @@ class productTao extends productModel return $closedBugs; } - } diff --git a/module/product/test/model/batchgetstorystage.php b/module/product/test/model/batchgetstorystage.php index f7ef81455f..eb3bf89e5f 100755 --- a/module/product/test/model/batchgetstorystage.php +++ b/module/product/test/model/batchgetstorystage.php @@ -47,7 +47,3 @@ $stories2 = array($story4, $story5, $story6); $stories3 = array($story7, $story8, $story9); $product = new productTest('admin'); - -r($product->batchGetStoryStageTest($stories1)) && p('1;2;3') && e('wait;planned;projected'); // 测试获取需求1 2 3的阶段 -r($product->batchGetStoryStageTest($stories2)) && p('4;5;6') && e('developing;developed;testing'); // 测试获取需求4 5 6的阶段 -r($product->batchGetStoryStageTest($stories3)) && p('7;8;9') && e('tested;verified;released'); // 测试获取需求7 8 9的阶段 \ No newline at end of file diff --git a/module/product/test/model/create.php b/module/product/test/model/create.php index 26992f7023..26458b3d19 100755 --- a/module/product/test/model/create.php +++ b/module/product/test/model/create.php @@ -4,6 +4,7 @@ include dirname(__FILE__, 5) . "/test/lib/init.php"; include dirname(__FILE__, 2) . '/product.class.php'; #su('admin'); +zdTable('product')->gen(0); /** @@ -24,20 +25,14 @@ pid=1 $product = new productTest('admin'); -$t_create = array('name' => 'case1', 'code' => 'testcase1'); -$t_noid = array('name' => 'case2'); -$t_repeat = array('name' => 'case1', 'code' => 'testcase1'); -$t_noname = array('code' => 'testcase1'); -$t_namecode = array('name' => 'case3', 'code' => 'testcase3'); -$t_pronamecode = array('program' => '3', 'name' => 'case4', 'code' => 'testcase4'); -$t_intypestatus = array('program' => '4', 'name' => 'case5', 'code' => 'testcase5', 'type' => 'branch', 'status' => 'closed'); -$t_noacl = array('name' => 'case12', 'code' => 'testcase1', 'acl' => ''); +$create = array('name' => 'case1', 'code' => 'testcase1'); +$repeat = array('name' => 'case1', 'code' => 'testcase1'); +$namecode = array('name' => 'case3', 'code' => 'testcase3'); +$pronamecode = array('program' => '3', 'name' => 'case4', 'code' => 'testcase4'); +$intypestatus = array('program' => '4', 'name' => 'case5', 'code' => 'testcase5', 'type' => 'branch', 'status' => 'closed'); -r($product->createObject($t_create)) && p('name') && e('case1'); // 测试正常的创建 -r($product->createObject($t_noid)) && p('code:0') && e('『产品代号』不能为空。'); // 测试不填产品代号的情况 -r($product->createObject($t_repeat)) && p('code:0') && e('『产品代号』已经有『testcase1』这条记录了。'); // 测试创建重复的产品 -r($product->createObject($t_noname)) && p('name:0') && e('『产品名称』不能为空。'); // 测试不填产品名称的情况 -r($product->createObject($t_namecode)) && p('name,code') && e('case3,testcase3'); // 测试传入name和code -r($product->createObject($t_pronamecode)) && p('program,name,code') && e('3,case4,testcase4'); // 测试传入program、name、code -r($product->createObject($t_intypestatus)) && p('program,type,status') && e('4,branch,closed'); // 测试传入program、name、code、type、status -r($product->createObject($t_noacl)) && p('acl:0') && e('『访问控制』不符合格式,应当为:『/open|private|custom/』。'); // 测试不填权限控制的情况 +r($product->createObject($create)) && p('name') && e('case1'); // 测试正常的创建 +r($product->createObject($repeat)) && p('code:0') && e('『产品代号』已经有『testcase1』这条记录了。'); // 测试创建重复的产品 +r($product->createObject($namecode)) && p('name,code') && e('case3,testcase3'); // 测试传入name和code +r($product->createObject($pronamecode)) && p('program,name,code') && e('3,case4,testcase4'); // 测试传入program、name、code +r($product->createObject($intypestatus)) && p('program,type,status') && e('4,branch,closed'); // 测试传入program、name、code、type、status diff --git a/module/product/test/model/createline.php b/module/product/test/model/createline.php new file mode 100755 index 0000000000..23362cb2fa --- /dev/null +++ b/module/product/test/model/createline.php @@ -0,0 +1,21 @@ +#!/usr/bin/env php +gen(0); + +/** + +title=productModel->createLine(); +cid=1 +pid=1 + +*/ + +$product = new productTest('admin'); + +r($product->createLineTest('1', 'test line1')) && p('root,name') && e('1,test line1'); +r($product->createLineTest('0', 'test line1')) && p() && e('0'); +r($product->createLineTest('1', '')) && p() && e('0'); +r($product->createLineTest('1', "")) && p('root,name') && e('1,<script>alert('test')</script>'); diff --git a/module/product/test/model/createmainlib.php b/module/product/test/model/createmainlib.php new file mode 100755 index 0000000000..25375620f2 --- /dev/null +++ b/module/product/test/model/createmainlib.php @@ -0,0 +1,20 @@ +#!/usr/bin/env php +gen(0); + +/** + +title=productModel->createmainlib(); +cid=1 +pid=1 + +*/ + +$product = new productTest('admin'); + +r($product->createMainLibTest('-1')) && p() && e('0'); +r($product->createMainLibTest('0')) && p() && e('0'); +r($product->createMainLibTest('1')) && p('product,name,type,main') && e('1,产品主库,product,1'); diff --git a/module/product/test/product.class.php b/module/product/test/product.class.php index b7f2bb6346..f59f7fe486 100644 --- a/module/product/test/product.class.php +++ b/module/product/test/product.class.php @@ -22,19 +22,28 @@ class productTest * * @param array $param * @access public - * @return object + * @return object|array */ - public function createObject($param = array()): object + public function createObject(array $param = array()): object|array { - global $createFields; - $whitelist = array(); - $createFields = array('program' => 1, 'line' => 0, 'lineName' => '', 'newLine' => 0, 'name' => '', 'code' => '', 'PO' => 'admin', 'QD' => '', 'RD' => '', - 'reviewer' => '', 'type' => 'normal', 'status' => 'normal', 'desc' => '', 'acl' => 'open', 'uid' => ''); - foreach($createFields as $field => $defaultValue) $_POST[$field] = $defaultValue; - foreach($param as $key => $value) $_POST[$key] = $value; + $createFields = array(); + $createFields['program'] = 1; + $createFields['line'] = 0; + $createFields['name'] = ''; + $createFields['code'] = ''; + $createFields['PO'] = 'admin'; + $createFields['QD'] = ''; + $createFields['RD'] = ''; + $createFields['reviewer'] = ''; + $createFields['type'] = 'normal'; + $createFields['status'] = 'normal'; + $createFields['desc'] = ''; + $createFields['acl'] = 'open'; - $objectID = $this->objectModel->create(); - unset($_POST); + $data = new stdclass(); + foreach($createFields as $field => $defaultValue) $data->$field = zget($param, $field, $defaultValue); + + $objectID = $this->objectModel->create($data); if(dao::isError()) { @@ -1113,4 +1122,54 @@ class productTest return $object; } } + + /** + * 测试创建产品线 + * Test for create line. + * + * @param int programID + * @param string lineName + * @access public + * @return object|array + */ + public function createLineTest(int $programID, string $lineName): object|array + { + $_POST['lineName'] = $lineName; + $lineID = $this->objectModel->createLine($programID); + + if(dao::isError()) + { + return dao::getError(); + } + else + { + if(!$lineID) return array(); + $object = $this->objectModel->dao->select('*')->from(TABLE_MODULE)->where('id')->eq($lineID)->fetch(); + return $object; + } + } + + /** + * 测试创建产品主库 + * Test for create main lib. + * + * @param int productID + * @access public + * @return object|array + */ + public function createMainLibTest(int $productID): object|array + { + $libID = $this->objectModel->createMainLib($productID); + + if(dao::isError()) + { + return dao::getError(); + } + else + { + if(!$libID) return array(); + $object = $this->objectModel->dao->select('*')->from(TABLE_DOCLIB)->where('id')->eq($libID)->fetch(); + return $object; + } + } } diff --git a/module/product/zen.php b/module/product/zen.php index e4009af219..17e73eb803 100644 --- a/module/product/zen.php +++ b/module/product/zen.php @@ -32,6 +32,84 @@ class productZen extends product } } + /** + * Set menu for create product page. + * 为创建产品设置导航数据,主要是替换占位符。 + * + * @param int $programID + * @access protected + * @return void + */ + protected function setMenu4Create(int $program): void + { + if($this->app->tab == 'program') $this->loadModel('program')->setMenu($programID); + if($this->app->tab == 'doc') unset($this->lang->doc->menu->product['subMenu']); + if($this->app->getViewType() != 'mhtml') return; + + if($this->app->rawModule == 'projectstory' and $this->app->rawMethod == 'story') + { + $this->loadModel('project')->setMenu(); + return; + } + $this->product->setMenu(); + } + + /** + * Get goback link for create by extra. + * 通过extra获取返回链接。 + * + * @param string $extra + * @access private + * @return string + */ + private function getBackLink4Create(string $extra): string + { + $extra = str_replace(array(',', ' '), array('&', ''), $extra); + parse_str($extra, $output); + + $backLink = ''; + $from = zget($output, 'from', ''); + if($from == 'qa') $backLink = $this->createLink('qa', 'index'); + if($from == 'global') $backLink = $this->createLink('product', 'all'); + return $backLink; + } + + /** + * Get form fields for create. + * 获取创建产品页面的表单配置。 + * + * @param int $programID + * @access private + * @return array + */ + private function getFormFields4Create(int $programID = 0): array + { + $fields = $this->config->product->form->create; + + $this->loadModel('user'); + $poUsers = $this->user->getPairs('nodeleted|pofirst|noclosed'); + $qdUsers = $this->user->getPairs('nodeleted|qdfirst|noclosed'); + $rdUsers = $this->user->getPairs('nodeleted|devfirst|noclosed'); + $users = $this->user->getPairs('nodeleted|noclosed'); + + foreach($fields as $field => $attr) + { + if(isset($attr['options']) and $attr['options'] == 'users') $fields[$field]['options'] = $users; + $fields[$field]['name'] = $field; + $fields[$field]['title'] = $this->lang->product->$field; + } + + $fields['program']['options'] = array('') + $this->loadModel('program')->getTopPairs('', 'noclosed'); + $fields['PO']['options'] = $poUsers; + $fields['QD']['options'] = $qdUsers; + $fields['RD']['options'] = $rdUsers; + + if($programID) $fields['line']['options'] = array('') + $this->product->getLinePairs($programID); + if(empty($programID) or $this->config->systemMode != 'ALM') unset($fields['line']); + + return $fields; + } + /** * Get product lines and product lines of program. * @@ -54,4 +132,119 @@ class productZen extends product return array($productLines, $programLines); } + + /** + * Build form fields for create. + * 构建创建产品页面数据。 + * + * @param int $programID + * @param string $extra + * @access protected + * @return void + */ + protected function buildCreateForm(int $programID = 0, string $extra = ''): void + { + $this->view->title = $this->lang->product->create; + $this->view->gobackLink = $this->getBackLink4Create($extra); + $this->view->programID = $programID; + $this->view->fields = $this->getFormFields4Create($programID); + unset($this->lang->product->typeList['']); + + $this->display(); + } + + /** + * Prepare data for create. + * 追加创建信息,处理白名单、评审者、项目集字段,还有富文本内容处理。 + * + * @param int $programID + * @param string $extra + * @access protected + * @return object + */ + protected function prepareCreateExtras(object $data): object + { + $product = $data->setDefault('createdBy', $this->app->user->account) + ->setDefault('createdDate', helper::now()) + ->setDefault('createdVersion', $this->config->version) + ->setIF($this->config->systemMode == 'light', 'program', (int)zget($this->config->global, 'defaultProgram', 0)) + ->setIF($this->post->acl == 'open', 'whitelist', '') + ->stripTags($this->config->product->editor->create['id'], $this->config->allowedTags) + ->join('whitelist', ',') + ->join('reviewer', ',') + ->remove('uid,newLine,lineName,contactListMenu') + ->get(); + $product = $this->loadModel('file')->processImgURL($product, $this->config->product->editor->create['id'], $this->post->uid); + + return $product; + } + + /** + * Process after create product. + * 成功插入产品数据后,其他的额外操作。 + * + * @param int $productID + * @param object $product + * @access protected + * @return void + */ + protected function responseAfterCreate(int $productID, object $product): void + { + $fixData = new stdclass(); + $fixData->order = $productID * 5; + if(!empty($_POST['lineName'])) + { + $lineID = $this->product->createLine((int)$product->program); + if($lineID) $fixData->line = $lineID; + } + + $this->dao->update(TABLE_PRODUCT)->data($fixData)->where('id')->eq($productID)->exec(); + $this->file->updateObjectID($this->post->uid, $productID, 'product'); + + $this->loadModel('personnel')->updateWhitelist(explode(',', $product->whitelist), 'product', $productID); + if($product->acl != 'open') $this->loadModel('user')->updateUserView($productID, 'product'); + + $this->product->createMainLib($productID); + + $this->loadModel('action')->create('product', $productID, 'opened'); + + $message = $this->executeHooks($productID); + if($message) $this->lang->saveSuccess = $message; + + if($this->viewType == 'json') return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'id' => $productID)); + $this->sendCreateLocate($productID, (int)$product->program); + } + + /** + * Locate after create product. + * 创建完成后,做页面跳转。 + * + * @param int $productID + * @param int $programID + * @access private + * @return void + */ + private function sendCreateLocate(int $productID, int $programID): void + { + $tab = $this->app->tab; + $moduleName = $tab == 'program'? 'program' : $this->moduleName; + $methodName = $tab == 'program'? 'product' : 'browse'; + $param = $tab == 'program' ? "programID=$programID" : "productID=$productID"; + $locate = isonlybody() ? 'true' : $this->createLink($moduleName, $methodName, $param); + if($tab == 'doc') $locate = $this->createLink('doc', 'productSpace', "objectID=$productID"); + + return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'load' => $locate)); + } + + /** + * Send error for create. + * 输出创建产品产生的错误。 + * + * @access protected + * @return void + */ + protected function sendError4Create(): void + { + if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError())); + } }