diff --git a/module/measrecord/control.php b/module/measrecord/control.php
new file mode 100644
index 0000000000..302aa4ec3b
--- /dev/null
+++ b/module/measrecord/control.php
@@ -0,0 +1,40 @@
+
+ * @package measrecord
+ * @version $Id$
+ * @link http://www.zentao.net
+ */
+class measrecord extends control
+{
+ public function browse($program = 0, $measID = 0, $orderBy = 'id_desc', $recTotal = 0, $recPerPage = 10, $pageID = 1)
+ {
+ $this->app->loadLang('measurement');
+ $this->app->loadClass('pager', $static=true);
+ $pager = new pager($recTotal, $recPerPage, $pageID);
+
+ $this->lang->navGroup->measrecord = 'project';
+
+ $this->loadModel('project')->setMenu($program);
+
+ $this->view->records = $this->measrecord->getListByProgram($program, $measID, $orderBy, $pager);
+ $this->view->measList = $this->measrecord->getSavedMeas($program);
+ $this->view->title = $this->lang->measrecord->browse;
+ $this->view->program = $program;
+ $this->view->programs = $this->loadModel('program')->getPairs();
+ $this->view->products = $this->loadModel('product')->getPairs();
+ $this->view->projects = $this->loadModel('project')->getPairsByProgram();
+
+ if($measID)
+ {
+ $measurement = $this->loadModel('measurement')->getByID($measID);
+ $measurement->params = json_decode($measurement->params);
+ $this->view->measurement = $measurement;
+ }
+ $this->display();
+ }
+}
diff --git a/module/measrecord/css/browse.css b/module/measrecord/css/browse.css
new file mode 100644
index 0000000000..cb0846f9f4
--- /dev/null
+++ b/module/measrecord/css/browse.css
@@ -0,0 +1,34 @@
+.side-col .detail-content{padding-left: 10px; margin-top: 0px}
+.tree li.has-list.open:before {border-left: none;}
+#measTree li {padding: 0 0 0 8px;}
+#measTree li.has-list {padding-left: 20px;}
+#measTree li > a {display: block; padding: 5px 0; border-radius: 2px; padding-left: 6px; height: 30px;}
+#measTree li > a > span {display: inline-block;}
+#measTree li > a > span + span {margin-left: 8px;}
+#measTree li.selected > a {background-color: #E8F3FC;}
+#measTree li.selected > a > span.title {color: #006AF1;}
+#measTree .label-id {border-color: #cbd0db; color: #7d8599}
+#measTree .label.label-type {background: #fff; border: 1px solid #7d8599; color:#7d8599}
+
+#measTree li > a > span.title {color: #3C4353; white-space: nowrap; max-width: 60%; overflow: hidden; text-overflow: ellipsis; vertical-align: middle;}
+#measTree li > a > span.user {color: #838a9d;}
+#measTree li > a > span.user > .icon-person {font-size: 14px; position: relative; top: -1px; color: #a6aab8}
+
+#measTree li > .list-toggle {transform: rotate(0deg); width: 16px; height: 16px; border: 4px solid #a6aab8; border-radius: 2px; top: 7px;}
+#measTree li > .list-toggle:before {content: ' '; display: block; position: absolute; border: 1px solid #a6aab8; top: 2px; left: -3px; right: -3px; bottom: 2px; min-width: 0; transition: all .2s;}
+#measTree li > .list-toggle:hover:before,
+#measTree li > .list-toggle:hover {border-color: #006AF1;}
+#measTree li.open > .list-toggle {width: 12px; height: 12px; top: 9px; background-color: #a6aab8; border-width: 3px; left: 3px;}
+#measTree li.open > .list-toggle:before {border: none; height: 2px; width: 6px; left: 0; top: 2px; background: #fff;}
+#measTree li.open > .list-toggle:hover {background: #006AF1;}
+
+#measTree ul > li:after {display: block; position: absolute; content: ' '; border-top: 2px solid #cbd0db; top: 14px; left: -12px; z-index: 1; width: 10px;}
+#measTree ul > li:before,
+#measTree ul > li.has-list:before {background: none; content: ' '; display: block; position: absolute; width: auto; height: auto; border: none; border-left: 2px solid #cbd0db; top: -13px; bottom: 12px; left: -12px;}
+#measTree ul > li:last-child:before {bottom: auto; height: 29px;}
+#measTree ul > li:first-child:before {top: -9px;}
+#measTree ul > li.has-list:first-child:before {top: -13px;}
+#measTree ul > li.tree-single-item:before {height: 23px;}
+#measTree ul > li.has-list:after {width: 14px;}
+#measTree ul > li.item-meas a.selected{color: #0c64eb;}
+.side-col{padding-right: 5px; width: 25%}
diff --git a/module/measrecord/js/browse.js b/module/measrecord/js/browse.js
new file mode 100644
index 0000000000..ff645ecc8e
--- /dev/null
+++ b/module/measrecord/js/browse.js
@@ -0,0 +1,8 @@
+$(function()
+{
+ var $taskTree = $('#measTree').tree(
+ {
+ name: 'measTree',
+ initialState: 'preserve'
+ });
+});
diff --git a/module/measrecord/lang/de.php b/module/measrecord/lang/de.php
new file mode 100644
index 0000000000..e84c7a4536
--- /dev/null
+++ b/module/measrecord/lang/de.php
@@ -0,0 +1,8 @@
+measrecord->common = 'Project Measurement';
+$lang->measrecord->browse = 'History';
+$lang->measrecord->list = 'Measurements';
+$lang->measrecord->params = 'Measurement Parameters';
+
+$lang->measrecord->value = 'Metrics';
+$lang->measrecord->date = 'Created';
diff --git a/module/measrecord/lang/en.php b/module/measrecord/lang/en.php
new file mode 100644
index 0000000000..e84c7a4536
--- /dev/null
+++ b/module/measrecord/lang/en.php
@@ -0,0 +1,8 @@
+measrecord->common = 'Project Measurement';
+$lang->measrecord->browse = 'History';
+$lang->measrecord->list = 'Measurements';
+$lang->measrecord->params = 'Measurement Parameters';
+
+$lang->measrecord->value = 'Metrics';
+$lang->measrecord->date = 'Created';
diff --git a/module/measrecord/lang/fr.php b/module/measrecord/lang/fr.php
new file mode 100644
index 0000000000..e84c7a4536
--- /dev/null
+++ b/module/measrecord/lang/fr.php
@@ -0,0 +1,8 @@
+measrecord->common = 'Project Measurement';
+$lang->measrecord->browse = 'History';
+$lang->measrecord->list = 'Measurements';
+$lang->measrecord->params = 'Measurement Parameters';
+
+$lang->measrecord->value = 'Metrics';
+$lang->measrecord->date = 'Created';
diff --git a/module/measrecord/lang/zh-cn.php b/module/measrecord/lang/zh-cn.php
new file mode 100644
index 0000000000..ad3a24dfb1
--- /dev/null
+++ b/module/measrecord/lang/zh-cn.php
@@ -0,0 +1,8 @@
+measrecord->common = '项目度量';
+$lang->measrecord->browse = '度量记录';
+$lang->measrecord->list = '度量列表';
+$lang->measrecord->params = '度量参数';
+
+$lang->measrecord->value = '度量值';
+$lang->measrecord->date = '创建时间';
diff --git a/module/measrecord/model.php b/module/measrecord/model.php
new file mode 100644
index 0000000000..f14b9d66e6
--- /dev/null
+++ b/module/measrecord/model.php
@@ -0,0 +1,74 @@
+
+ * @package measrecord
+ * @version $Id$
+ * @link http://www.zentao.net
+ */
+class measrecordModel extends model
+{
+ /**
+ * Get list by program
+ *
+ * @param int $program
+ * @param int $measurementID
+ * @param string $orderBy
+ * @param object $pager
+ * @access public
+ * @return array
+ */
+ public function getListByProgram($program, $measurementID, $orderBy = 'id_desc', $pager = null)
+ {
+ return $this->dao->select('*')->from(TABLE_MEASRECORDS)
+ ->where('project')->eq($program)
+ ->andWhere('mid')->eq($measurementID)
+ ->orderBy($orderBy)
+ ->page($pager)
+ ->fetchAll();
+ }
+
+ public function getSavedMeas($program)
+ {
+ return $this->dao->select('DISTINCT r.mid, m.name, m.purpose')->from(TABLE_BASICMEAS)->alias('m')
+ ->leftJoin(TABLE_MEASRECORDS)->alias('r')
+ ->on('m.id=r.mid')
+ ->where('project')->eq($program)
+ ->orderBy('m.purpose, m.order desc')
+ ->fetchGroup('purpose');
+ }
+
+ /**
+ * Save meas record.
+ *
+ * @param object $measurement
+ * @param array $params
+ * @param mix $queryResult
+ * @access public
+ * @return bool
+ */
+ public function save($measurement, $params, $value, $type = '')
+ {
+ $record = new stdclass();
+ $record->type = $type;
+ $record->mid = $measurement->id;
+ $record->measCode = $measurement->code;
+ $record->date = helper::today();
+ $record->year = date('Y');
+ $record->month = date('Ym');
+ $record->week = date('W');
+ $record->day = date('Ymd');
+ $record->params = json_encode($params);
+ $record->value = $value;
+
+ if(isset($params['$program'])) $record->program = $params['$program'];
+ if(isset($params['$product'])) $record->product = $params['$product'];
+ if(isset($params['$project'])) $record->project = $params['$project'];
+
+ $this->dao->insert(TABLE_MEASRECORDS)->data($record)->exec();
+ return !dao::isError();
+ }
+}
diff --git a/module/measrecord/view/browse.html.php b/module/measrecord/view/browse.html.php
new file mode 100644
index 0000000000..3399ef262a
--- /dev/null
+++ b/module/measrecord/view/browse.html.php
@@ -0,0 +1,86 @@
+getModuleRoot() . 'common/view/header.html.php';?>
+
+
+
+
+
+
+
+ $meases)
+ {
+ $class = '';
+ $purposeName = zget($lang->measurement->purposeList, $purpose, $purpose);
+ echo "- " . "" . $purposeName. '';
+ if(!empty($meases))
+ {
+ echo "
";
+ foreach($meases as $meas)
+ {
+ $class = '';
+ if(isset($measurement) and zget($measurement, 'id') == $meas->mid) $class = 'selected';
+ echo '- ' . html::a($this->createLink('measrecord', 'browse', "program={$program}&measurement={$meas->mid}"), ' ' . $meas->name, '', "class='$class' title='$meas->name'") . "
";
+ }
+ echo "
";
+ }
+ echo ' ';
+ }
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | idAB;?> |
+ measurement->name;?> |
+ params as $param):?>
+ showName;?> |
+
+
+ measrecord->value;?>
+ (unit?>)
+ |
+ measrecord->date;?> |
+
+
+
+
+ params = json_decode($record->params);?>
+
+ | id;?> |
+ name;?> |
+ params as $param):?>
+
+ varName, 'program') !== false) $value = zget($programs, zget($record->params, $param->varName, $this->session->program), '')?>
+ varName, 'product') !== false) $value = zget($products, zget($record->params, $param->varName, $this->session->product), '')?>
+ varName, 'project') !== false) $value = zget($projects, zget($record->params, $param->varName, $this->session->project), '')?>
+
+
+ |
+
+ value;?> |
+ date;?> |
+
+
+
+
+
+
+
+
+
+
+getModuleRoot() . 'common/view/footer.html.php';?>
diff --git a/module/measurement/config.php b/module/measurement/config.php
new file mode 100644
index 0000000000..388ee8c2ea
--- /dev/null
+++ b/module/measurement/config.php
@@ -0,0 +1,56 @@
+measurement = new stdclass();
+$config->measurement->editor = new stdclass();
+$config->measurement->editor->createtemplate = array('id' => 'content', 'tools' => 'measurementTools');
+$config->measurement->editor->edittemplate = array('id' => 'content', 'tools' => 'measurementTools');
+
+$config->measurement->createbasic = new stdclass();
+$config->measurement->editbasic = new stdclass();
+$config->measurement->createderivation = new stdclass();
+$config->measurement->editderivation = new stdclass();
+
+$config->measurement->createbasic->requiredFields = 'name,code,unit,definition';
+$config->measurement->editbasic->requiredFields = 'name,code,unit,definition';
+$config->measurement->createderivation->requiredFields = 'name,purpose';
+$config->measurement->editderivation->requiredFields = 'name,purpose';
+
+$config->measurement->createtemplate = new stdclass();
+$config->measurement->createtemplate->requiredFields = 'name';
+
+$config->measurement->edittemplate = new stdclass();
+$config->measurement->edittemplate->requiredFields = $config->measurement->createtemplate->requiredFields;
+
+$config->measurement->sqlBlackList = "create,drop,backup,alter,insert,replace,update,delete,rename,do,truncate,load,handler,lock,unlock,grant,outfile,infile";
+$config->measurement->sqlBlackFunc = "current_user,load_file,session_user,database,user,system_user";
+
+$config->measurement->triggers = array();
+$config->measurement->triggers[] = 'project.close';
+
+$config->measurement->scopeParams = new stdclass();
+$config->measurement->scopeParams->program = '$program int';
+$config->measurement->scopeParams->product = '$program int, $product int';
+$config->measurement->scopeParams->project = '$program int, $product int, $stage int';
+
+global $lang;
+$config->measurement->search['module'] = 'measurement';
+$config->measurement->search['fields']['name'] = $lang->measurement->name;
+$config->measurement->search['fields']['id'] = $lang->measurement->id;
+$config->measurement->search['fields']['purpose'] = $lang->measurement->purpose;
+$config->measurement->search['fields']['scope'] = $lang->measurement->scope;
+$config->measurement->search['fields']['object'] = $lang->measurement->object;
+$config->measurement->search['fields']['code'] = $lang->measurement->code;
+$config->measurement->search['fields']['unit'] = $lang->measurement->unit;
+$config->measurement->search['fields']['definition'] = $lang->measurement->definition;
+$config->measurement->search['fields']['collectType'] = $lang->measurement->collectType;
+$config->measurement->search['fields']['createdBy'] = $lang->measurement->createdBy;
+
+$config->measurement->search['params']['name'] = array('operator' => 'include', 'control' => 'input', 'values' => '');
+$config->measurement->search['params']['id'] = array('operator' => '=', 'control' => 'input', 'values' => '');
+$config->measurement->search['params']['purpose'] = array('operator' => '=', 'control' => 'select', 'values' => $lang->measurement->purposeList);
+$config->measurement->search['params']['scope'] = array('operator' => '=', 'control' => 'select', 'values' => $lang->measurement->scopeList);
+$config->measurement->search['params']['object'] = array('operator' => '=', 'control' => 'select', 'values' => $lang->measurement->objectList);
+$config->measurement->search['params']['code'] = array('operator' => 'include', 'control' => 'input', 'values' => '');
+$config->measurement->search['params']['unit'] = array('operator' => 'include', 'control' => 'input', 'values' => '');
+$config->measurement->search['params']['definition'] = array('operator' => 'include', 'control' => 'input', 'values' => '');
+$config->measurement->search['params']['collectType'] = array('operator' => '=', 'control' => 'select', 'values' => $lang->measurement->collectTypeList);
+$config->measurement->search['params']['createdBy'] = array('operator' => '=', 'control' => 'select', 'values' => 'users');
diff --git a/module/measurement/control.php b/module/measurement/control.php
new file mode 100644
index 0000000000..0367cb44a7
--- /dev/null
+++ b/module/measurement/control.php
@@ -0,0 +1,684 @@
+
+ * @package measurement
+ * @link https://www.zentao.net
+ */
+class measurement extends control
+{
+ /**
+ * Construct.
+ *▫
+ * @access public
+ * @return void
+ */
+ public function __construct()
+ {
+ parent::__construct();
+
+ /* Set measurement menu group. */
+ $this->projectID = isset($_GET['PRJ']) ? $_GET['PRJ'] : 0;
+ if(!$this->projectID)
+ {
+ $this->lang->navGroup->measurement = 'system';
+ $this->lang->navGroup->report = 'system';
+ }
+ }
+
+ /**
+ * Browse measurements.
+ *
+ * @param string $browseType
+ * @param int $param
+ * @param string $orderBy
+ * @param int $recTotal
+ * @param int $recPerPage
+ * @param int $pageID
+ * @access public
+ * @return void
+ */
+ public function browse(string $browseType = 'all', int $param = 0, string $orderBy = 'purpose,order_desc', int $recTotal = 0, int $recPerPage = 10, int $pageID = 1)
+ {
+ $this->loadModel('datatable');
+ $this->session->set('measList', $this->app->getURI(true));
+
+ $this->app->loadClass('pager', true);
+ $pager = new pager($recTotal, $recPerPage, $pageID);
+
+ /* Build the search form. */
+ $queryID = ($browseType == 'bysearch') ? (int)$param : 0;
+ $actionURL = $this->createLink('measurement', 'browse', "browseType=bysearch&queryID=myQueryID");
+ $this->measurement->buildSearchForm($actionURL, $queryID);
+
+ $this->view->title = $this->lang->measurement->browse;
+ $this->view->position[] = $this->lang->measurement->browse;
+
+ $this->view->pager = $pager;
+ $this->view->recTotal = $recTotal;
+ $this->view->recPerPage = $recPerPage;
+ $this->view->pageID = $pageID;
+ $this->view->orderBy = $orderBy;
+ $this->view->browseType = $browseType;
+ $this->view->param = $param;
+ $this->view->measurementList = $this->measurement->getList($browseType, $queryID, $orderBy, $pager);
+ $this->display();
+ }
+
+ /**
+ * Create basic measurement.
+ *
+ * @access public
+ * @return void
+ */
+ public function createBasic()
+ {
+ if($_POST)
+ {
+ $measurementID = $this->measurement->createBasic();
+
+ if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
+
+ $this->loadModel('action')->create('measurement', $measurementID, 'createBasic');
+
+ $response = array();
+ $response['result'] = 'success';
+ $response['message'] = $this->lang->measurement->saveSuccess;
+ $response['locate'] = $this->session->measList ? $this->session->measList : $this->createLink('measurement', 'browse', "basic");
+
+ return $this->send($response);
+ }
+
+ $this->view->title = $this->lang->measurement->create;
+ $this->view->position[] = $this->lang->measurement->create;
+
+ $this->view->moduleList = $this->measurement->getModulePairs();
+ $this->view->triggerOptions = $this->measurement->getTriggerOptions();
+ $this->view->users = $this->loadModel('user')->getPairs('noclosed|noletter');
+ $this->display();
+ }
+
+ /**
+ * Delete measurement.
+ *
+ * @param int $measurementID
+ * @param string $confirm
+ * @access public
+ * @return void
+ */
+ public function delete(int $measurementID, string $confirm = 'no')
+ {
+ if($confirm == 'no')
+ {
+ return print(js::confirm($this->lang->measurement->confirmDelete, $this->createLink('measurement', 'delete', "measurementID=$measurementID&confirm=yes"), ''));
+ }
+ else
+ {
+ $this->measurement->delete(TABLE_BASICMEAS, $measurementID);
+
+ /* if ajax request, send result. */
+ if($this->server->ajax)
+ {
+ if(dao::isError())
+ {
+ $response['result'] = 'fail';
+ $response['message'] = dao::getError();
+ }
+ else
+ {
+ $response['result'] = 'success';
+ $response['message'] = '';
+ }
+ return $this->send($response);
+ }
+
+ return print(js::locate(inlink('browse'), 'parent'));
+ }
+ }
+
+ /**
+ * Edit basic measurement.
+ *
+ * @param int $measurementID
+ * @access public
+ * @return void
+ */
+ public function editBasic(int $measurementID)
+ {
+ $measurement = $this->measurement->getByID($measurementID);
+
+ if($_POST)
+ {
+ $measurementID = $this->measurement->editBasic($measurementID);
+ if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
+ $this->loadModel('action')->create('measurement',$measurementID,'editBasic');
+
+ $response = array();
+ $response['result'] = 'success';
+ $response['message'] = $this->lang->measurement->saveSuccess;
+ $response['locate'] = $this->session->measList ? $this->session->measList : $this->createLink('measurement', 'browse', "basic");
+ return $this->send($response);
+ }
+
+ $this->view->title = $this->lang->measurement->editBasic;
+ $this->view->position[] = $this->lang->measurement->editBasic;
+ $this->view->measurement = $measurement;
+ $this->view->users = $this->loadModel('user')->getPairs('noclosed|noletter');
+ $this->view->triggerOptions = $this->measurement->getTriggerOptions();
+ $this->display();
+ }
+
+ /**
+ * Search measurements.
+ *
+ * @param string $types
+ * @param int $measurementID
+ * @access public
+ * @return void
+ */
+ public function searchMeas(string $types, int $measurementID)
+ {
+ if($types == 'derivemeas')
+ {
+ $basicMeasurement = $this->measurement->getPairs();
+ $derivation = $this->measurement->getByID('derivation', $measurementID);
+ $definition = json_decode($derivation->definition,true);
+
+ $definitionItem = array();
+ $definitionForm = array();
+ foreach($definition as $key => $value)
+ {
+ if(preg_match('/\{|\}/', $value))
+ {
+ $basicmeasID = preg_replace('/\{|\}/', '', $value);
+ $basicmeasName = $basicMeasurement[$basicmeasID];
+ $definitionItem[] = $basicmeasName;
+ $definitionForm[] = '通过接口获';
+ }
+ else
+ {
+ $methodName = $value;
+ if($methodName == '*') $methodName = 'x';
+ if($methodName == '/') $methodName = '÷';
+
+ $definitionItem[] = $methodName;
+ $definitionForm[] = $value;
+ }
+ }
+
+ $returnData = array();
+ $returnData['definitionItem'] = $definitionItem;
+ $returnData['definitionForm'] = $definitionForm;
+ }
+ }
+
+ /**
+ * Browse template.
+ *
+ * @param string $type
+ * @param int $recTotal
+ * @param int $recPerPage
+ * @param int $pageID
+ * @access public
+ * @return void
+ */
+ public function template(string $type = 'complex', int $recTotal = 0, int $recPerPage = 20, int $pageID = 1)
+ {
+ $this->app->loadClass('pager', $static = true);
+ $pager = pager::init($recTotal, $recPerPage, $pageID);
+
+ if($type == 'complex') $templates = $this->measurement->getTemplates($pager);
+ if($type == 'single')
+ {
+ $this->app->loadLang('report');
+ $templates = $this->measurement->getReports('list', $pager);
+ }
+
+ $this->view->title = $this->lang->measurement->template;
+ $this->view->position[] = $this->lang->measurement->template;
+ $this->view->type = $type;
+ $this->view->templates = $templates;
+ $this->view->pager = $pager;
+ $this->display();
+ }
+
+ /**
+ * Create report template.
+ *
+ * @access public
+ * @return void
+ */
+ public function createTemplate()
+ {
+ if($_POST)
+ {
+ $this->measurement->saveTemplate();
+ if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
+
+ $locate = $this->createLink('measurement', 'template');
+ return $this->send(array('result' => 'success', 'message' => $this->lang->measurement->saveSuccess, 'locate' => $locate));
+ }
+
+ $this->view->title = $this->lang->measurement->createTemplate;
+ $this->view->position[] = $this->lang->measurement->createTemplate;
+ $this->view->basicMeases = $this->measurement->getPairs();
+ $this->view->reports = $this->measurement->getReports('pairs');
+ $this->display();
+ }
+
+ /**
+ * Edit report template.
+ *
+ * @param int $templateID
+ * @access public
+ * @return void
+ */
+ public function editTemplate(int $templateID = 0)
+ {
+ if($_POST)
+ {
+ $changes = $this->measurement->editTemplate($templateID);
+ if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
+
+ $locate = $this->createLink('measurement', 'template');
+ return $this->send(array('result' => 'success', 'message' => $this->lang->measurement->saveSuccess, 'locate' => $locate));
+ }
+
+ $this->view->title = $this->lang->measurement->editTemplate;
+ $this->view->position[] = $this->lang->measurement->editTemplate;
+ $this->view->template = $this->measurement->getTemplateByID($templateID);
+ $this->view->basicMeases = $this->measurement->getPairs('basic');
+ $this->view->deriveMeases = $this->measurement->getPairs('derivation');
+ $this->view->reports = $this->measurement->getReports('pairs');
+ $this->display();
+ }
+
+ /**
+ * View report template.
+ *
+ * @param int $templateID
+ * @param int $projectID
+ * @access public
+ * @return void
+ */
+ public function viewTemplate(int $templateID = 0, int $projectID = 0)
+ {
+ $template = $this->measurement->getTemplateByID($templateID);
+ $this->view->template = $template;
+
+ if($_POST)
+ {
+ $_GET['onlybody'] = 'yes';
+ $this->view->content = $this->measurement->parseTemplateContent($template->content);
+ return $this->display('measurement', 'report');
+ }
+
+ $this->view->title = $this->lang->measurement->viewTemplate;
+ $this->view->position[] = $this->lang->measurement->viewTemplate;
+ $this->view->projectID = $projectID;
+ $this->view->basicMeases = $this->measurement->getPairs('basic');
+ $this->view->deriveMeases = $this->measurement->getPairs('derivation');
+ $this->view->projectPairs = $this->loadModel('project')->getPairsByModel($template->model);
+ $this->view->components = $this->measurement->getTemplateComponents($template->content);
+ $this->display();
+ }
+
+ /**
+ * Save report template.
+ *
+ * @access public
+ * @return void
+ */
+ public function saveReport()
+ {
+ $report = fixer::input('post')
+ ->add('createdBy', $this->app->user->account)
+ ->add('createdDate', helper::today())
+ ->remove('parseContent, saveReport')
+ ->get();
+
+ $this->dao->insert(TABLE_PROGRAMREPORT)->data($report)->check('name', 'notempty')->autocheck()->exec();
+
+ if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
+ return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess));
+ }
+
+ /**
+ * Set SQL function.
+ *
+ * @param int $measurementID
+ * @access public
+ * @return void
+ */
+ public function setSQL(int $measurementID)
+ {
+ $measurement = $this->measurement->getByID($measurementID);
+ if($_POST)
+ {
+ $result = $this->measurement->createSqlFunction($this->post->sql, $measurement);
+ if($result['result'] != 'success') return $this->send($result);
+
+ foreach($this->post->varName as $i => $varName)
+ {
+ if(empty($varName)) return $this->send(array('result' => 'fail', 'errors' => $this->lang->measurement->tips->noticeVarName));
+ $params[$varName]['showName'] = zget($this->post->showName, $i, '');
+ $errors = array();
+ if($params[$varName]['showName'] == '') $errors[] = sprintf($this->lang->measurement->tips->showNameMissed, $varName);
+ if(empty($this->post->queryValue[$i])) $errors[] = sprintf($this->lang->measurement->tips->noticeQueryValue, $varName);
+ if(!empty($errors)) return $this->send(array('result' => 'fail', 'errors' => join("
", $errors)));
+
+ $params[$varName]['varName'] = $varName;
+ $params[$varName]['varType'] = zget($this->post->varType, $i, 'input');
+ $params[$varName]['showName'] = zget($this->post->showName, $i, '');
+ $params[$varName]['options'] = $this->post->options[$i];
+ $params[$varName]['defaultValue'] = zget($this->post->defaultValue, $i, '');
+ }
+
+ $this->dao->update(TABLE_BASICMEAS)
+ ->set('configure')->eq($this->post->sql)
+ ->set('params')->eq(json_encode($params))
+ ->where('id')->eq($measurementID)
+ ->exec();
+
+ $params = $this->measurement->processPostParams();
+ $measFunction = $this->measurement->getSqlFunctionName($measurement);
+ $queryResult = $this->measurement->execSqlMeasurement($measurement, $params, $this->post->action);
+ if($queryResult === false) return $this->send(array('result' => 'fail', 'message' => $this->measurement->errorInfo));
+
+ if($this->post->action == 'save') return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => inlink('browse')));
+ return $this->send(array('result' => 'success', 'queryResult' => sprintf($this->lang->measurement->saveSqlMeasSuccess, $queryResult)));
+ }
+
+ $defaultParamString = zget($this->config->measurement->scopeParams, $measurement->scope);
+ $defaultSQL = sprintf($this->lang->measurement->sqlTemplate, $this->measurement->getSqlFunctionName($measurement), $defaultParamString);
+ $params = json_decode($measurement->params, true);
+
+ $this->view->title = $this->lang->measurement->design . $this->lang->colon . $measurement->name;
+ $this->view->position[] = $this->lang->measurement->design;
+ $this->view->sql = !empty($measurement->configure) ? $measurement->configure : $defaultSQL;
+ $this->view->params = empty($params) ? array() : json_decode($measurement->params, true);
+ $this->view->measurement = $measurement;
+ $this->view->measurementID = $measurementID;
+ $this->view->programPairs = $this->loadModel('project')->getPairsByProgram();
+ $this->display();
+ }
+
+ /**
+ * Get sql params from sql.
+ *
+ * @access public
+ * @return void
+ */
+ public function getSqlParams()
+ {
+ $measurementID = $this->post->measurementID;
+ $measurement = $this->measurement->getByID($measurementID);
+ if(!$this->post->sql) return print('');
+ if($this->post->sql == $measurement->configure) $this->locate($this->server->http_referer);
+
+ $measFunction = $this->measurement->getSqlFunctionName($measurement);
+ $postFunction = $this->measurement->parseSqlFunction($this->post->sql);
+ if(!$postFunction) return print("sql error."); //@todo fix error message.
+ $this->post->sql = str_replace($postFunction, $measFunction, $this->post->sql);
+
+ $this->view->params = $this->measurement->parseSqlParams($this->post->sql);
+ $this->view->measurement = $measurement;
+ $this->view->measurementID = $measurementID;
+ $this->display();
+ }
+
+ /**
+ * Init crontab queue.
+ *
+ * @access public
+ * @return void
+ */
+ public function initCrontabQueue()
+ {
+ $this->measurement->initCrontabQueue();
+ return print('success');
+ }
+
+ /**
+ * Exec crontab queue.
+ *
+ * @access public
+ * @return void
+ */
+ public function execCrontabQueue()
+ {
+ $queues = $this->measurement->getUnexecutedQueues();
+ foreach($queues as $queue)
+ {
+ if($queue->execTime and date('H:i') < $queue->execTime) continue;
+ $this->measurement->execQueue($queue);
+ }
+ return print('success');
+ }
+
+ /**
+ * Set tips.
+ *
+ * @param string $type
+ * @param string $object
+ * @access public
+ * @return void
+ */
+ public function setTips(string $type = 'progress', string $object = 'SPI')
+ {
+ $this->loadModel('custom');
+
+ if(strtolower($this->server->request_method) == "post")
+ {
+ $data = (array)fixer::input('post')->get();
+ extract($data);
+
+ $tipsConfig = array();
+ foreach($mins as $key => $min)
+ {
+ $max = zget($maxs, $key, '');
+ $tip = zget($tips, $key, '');
+ $range = zget($ranges, $key, '');
+ if(empty($min) and empty($max) and empty($tip)) continue;
+ if($object == 'SPI' || $object == 'CPI')
+ {
+ if($min < 0 || $max < 0) return $this->send(array('result' => 'fail', 'message' => $this->lang->custom->numberError));
+ }
+ if(!preg_match("/^(\-|\+?)\d+(\.\d+)?$/", $min)) return $this->send(array('result' => 'fail', 'message' => $this->lang->custom->regionMustNumber));
+ if(!preg_match("/^(\-|\+?)\d+(\.\d+)?$/", $max)) return $this->send(array('result' => 'fail', 'message' => $this->lang->custom->regionMustNumber));
+ if(empty($tip)) return $this->send(array('result' => 'fail', 'message' => $this->lang->custom->tipNotEmpty));
+
+ $tipCofig = new stdclass();
+ $tipCofig->min = $min;
+ $tipCofig->max = $max;
+ $tipCofig->tip = $tip;
+ $tipCofig->type = $object;
+ $tipCofig->range = $range;
+
+ $tipsConfig[$key] = $tipCofig;
+ }
+
+ if($type == 'progress') $this->loadModel('setting')->setItem("system.custom.$object.progressTip", json_encode($tipsConfig));
+ if($type == 'cost') $this->loadModel('setting')->setItem("system.custom.$object.costTip", json_encode($tipsConfig));
+
+ if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
+ return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => $this->createLink('measurement', 'setTips', "type=$type&object=$object")));
+ }
+
+ $tipType = $type . 'Tip';
+ $tipsConfig = isset($this->config->custom->$object->$tipType) ? $this->config->custom->$object->$tipType : '';
+
+ $this->view->title = $this->lang->custom->common . $this->lang->colon . $this->lang->custom->setTips;
+ $this->view->position[] = $this->lang->custom->common;
+ $this->view->position[] = $this->lang->custom->setTips;
+ $this->view->tipsConfig = json_decode($tipsConfig);
+ $this->view->type = $type;
+ $this->view->object = $object;
+ $this->display();
+ }
+
+ /**
+ * Ajax set params.
+ *
+ * @param int $measurementID
+ * @access public
+ * @return void
+ */
+ public function ajaxSetParams(int $measurementID = 0)
+ {
+ $measurement = $this->measurement->getByID($measurementID);
+
+ $data = fixer::input('post')->get();
+ $params = array();
+ foreach($data->varName as $i => $varName)
+ {
+ if(empty($varName)) return $this->send(array('result' => 'fail', 'message' => $this->lang->measurement->tips->noticeVarName));
+ if(empty($data->showName[$i])) return $this->send(array('result' => 'fail', 'message' => sprintf($this->lang->measurement->tips->noticeShowName, $data->showName[$i])));
+ if(empty($data->varType[$i])) return $this->send(array('result' => 'fail', 'message' => sprintf($this->lang->measurement->tips->noticeVarType, $data->varName[$i])));
+
+ $params[$varName]['varName'] = $varName;
+ $params[$varName]['varType'] = $data->varType[$i];
+ $params[$varName]['showName'] = $data->showName[$i];
+ $params[$varName]['options'] = $data->options[$i];
+ if(isset($data->defaultValue)) $params[$varName]['defaultValue'] = zget($data->defaultValue, $i, '');
+ if(isset($data->queryValue)) $params[$varName]['queryValue'] = zget($data->queryValue, $i, '');
+ }
+
+ $this->session->set('params', json_encode($params));
+ if($measurement->method == 'php') $this->session->set('queryParams', $this->post->queryValue);
+ return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'callback' => "hideParamForm()"));
+ }
+
+ /**
+ * Ajax build SQL.
+ *
+ * @access public
+ * @return void
+ */
+ public function ajaxBuildSQL()
+ {
+ return print($this->fetch('sqlbuilder', 'create'));
+ }
+
+ /**
+ * Ajax get param control.
+ *
+ * @param string $controlType
+ * @param string $optionType
+ * @param string $value
+ * @access public
+ * @return void
+ */
+ public function ajaxGetParamControl(string $controlType, string $optionType = '', string $value = '')
+ {
+ $value = base64_decode($value);
+ $html = '';
+ $html .= "
" . $this->measurement->buildParamControl('defaultValue', $controlType, $value, $optionType) . " | ";
+ $html .= "
" . $this->measurement->buildParamControl('queryValue', $controlType, '', $optionType) . " | ";
+
+ return print($html);
+ }
+
+ /**
+ * Ajax check element whether need params.
+ *
+ * @param string $holderValue
+ * @access public
+ * @return void
+ */
+ public function ajaxCheckElementNeedParams(string $holderValue)
+ {
+ $holderValue = base64_Decode($holderValue);
+ $sysData = $this->measurement->parseHolderValue($holderValue);
+ $params = $this->measurement->getParams($sysData);
+ $needParams = empty($params) ? 'no' : 'yes';
+ return print($needParams);
+ }
+
+ /**
+ * Ajax build param form.
+ *
+ * @param string $holderValue
+ * @access public
+ * @return void
+ */
+ public function ajaxBuildParamForm(string $holderValue)
+ {
+ $holderValue = base64_Decode($holderValue);
+ $sysData = $this->measurement->parseHolderValue($holderValue);
+
+ $this->view->controlID = $sysData->controlID;
+ $this->view->params = $this->measurement->getParams($sysData);
+ $this->view->sysData = $sysData;
+ $this->display();
+ }
+
+ /**
+ * Ajax get module actions.
+ *
+ * @param string $module
+ * @access public
+ * @return void
+ */
+ public function ajaxGetModuleActions(string $module)
+ {
+ $actions = $this->measurement->getModuleActions($module);
+ return print(html::select('action', $actions, '', "class='form-control chosen'"));
+ }
+
+ /**
+ * Update sort.
+ *
+ * @access public
+ * @return void
+ */
+ public function updateOrder()
+ {
+ $idList = explode(',', trim($this->post->meas, ','));
+ $orderBy = $this->post->orderBy;
+ foreach($idList as $id => $value)
+ {
+ if($value == 'undefined') unset($idList[$id]);
+ }
+
+ if(strpos($orderBy, 'order') === false) return false;
+ $data = $this->dao->select('id, `order`')->from(TABLE_BASICMEAS)->where('id')->in($idList)->orderBy($orderBy)->fetchPairs('order', 'id');
+
+ foreach($data as $order => $id)
+ {
+ $newID = array_shift($idList);
+ if($id == $newID) continue;
+ $this->dao->update(TABLE_BASICMEAS)->set('`order`')->eq($order)->where('id')->eq($newID)->exec();
+ }
+ }
+
+ /**
+ * Batch edit measurement.
+ *
+ * @access public
+ * @return void
+ */
+ public function batchEdit()
+ {
+ $measurementID = $this->post->measurement;
+ if(!empty($measurementID))
+ {
+ $this->view->measurements = $this->dao->select('*')->from(TABLE_BASICMEAS)->where('id')->in($this->post->measurement)->andWhere('deleted')->eq('0')->orderBy('order_desc')->fetchAll();
+ }
+ elseif($_POST)
+ {
+ $this->measurement->batchEdit();
+ if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
+ return print(js::locate($this->createLink('measurement', 'browse'), 'parent'));
+ }
+
+ $this->view->title = $this->lang->measurement->common . $this->lang->colon . $this->lang->measurement->batchEdit;
+ $this->view->position[] = $this->lang->measurement->batchEdit;
+ $this->view->orderBy = 'order_desc';
+ $this->display();
+ }
+}
diff --git a/module/measurement/css/common.css b/module/measurement/css/common.css
new file mode 100644
index 0000000000..fd17b44bb3
--- /dev/null
+++ b/module/measurement/css/common.css
@@ -0,0 +1,5 @@
+.cycleConfig .checkbox-primary{float:left; width:100px;}
+.cycleConfig .tab-pane{padding:8px 0px;}
+fieldset legend{font-size:16px;}
+.tips span{padding: 0 5px;}
+.table td.nowrap{overflow: hidden; white-space: nowrap; text-overflow: ellipsis;}
diff --git a/module/measurement/css/setsql.css b/module/measurement/css/setsql.css
new file mode 100644
index 0000000000..573a700efa
--- /dev/null
+++ b/module/measurement/css/setsql.css
@@ -0,0 +1,2 @@
+textarea.form-control{ font-size:14px;}
+#responseBox{padding-left:20px; font-size:14px;}
diff --git a/module/measurement/css/viewtemplate.css b/module/measurement/css/viewtemplate.css
new file mode 100644
index 0000000000..7042cdfdba
--- /dev/null
+++ b/module/measurement/css/viewtemplate.css
@@ -0,0 +1 @@
+.form-group > div span{line-height:20px;}
diff --git a/module/measurement/js/batchedit.js b/module/measurement/js/batchedit.js
new file mode 100644
index 0000000000..a8b6655190
--- /dev/null
+++ b/module/measurement/js/batchedit.js
@@ -0,0 +1,12 @@
+$(function()
+{
+ $('#measurementList').on('sort.sortable', function(e, data)
+ {
+ var list = '';
+ for(i = 0; i < data.list.length; i++)
+ {
+ list += $(data.list[i].item).attr('data-id') + ',';
+ }
+ $.post(createLink('measurement', 'updateOrder'), {'meas' : list, 'orderBy' : orderBy});
+ });
+});
diff --git a/module/measurement/js/browse.js b/module/measurement/js/browse.js
new file mode 100644
index 0000000000..a8b6655190
--- /dev/null
+++ b/module/measurement/js/browse.js
@@ -0,0 +1,12 @@
+$(function()
+{
+ $('#measurementList').on('sort.sortable', function(e, data)
+ {
+ var list = '';
+ for(i = 0; i < data.list.length; i++)
+ {
+ list += $(data.list[i].item).attr('data-id') + ',';
+ }
+ $.post(createLink('measurement', 'updateOrder'), {'meas' : list, 'orderBy' : orderBy});
+ });
+});
diff --git a/module/measurement/js/createtemplate.js b/module/measurement/js/createtemplate.js
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/module/measurement/js/design.js b/module/measurement/js/design.js
new file mode 100644
index 0000000000..e7c5cbd3ae
--- /dev/null
+++ b/module/measurement/js/design.js
@@ -0,0 +1,97 @@
+$(document).on('click', '#reDesignButton', function()
+{
+ location.href = createLink('measurement', 'design', "measurementID=" + measurementID + "&step=1&type=reDesign");
+});
+
+$(document).on('change', "select[name^='options']", function()
+{
+ var $tr = $(this).closest('tr');
+ var optionType = $(this).val();
+ updateParamControl($tr, 'select', optionType);
+})
+
+function setParams(data)
+{
+ if(data)
+ {
+ var tbodyHtml = $('#setParams table tbody').html();
+ $('#tmpDataBox').append(tbodyHtml);
+ $('#setParams table tbody').empty();
+
+ var html = $('#templateBox table tbody tr.template').html();
+ for(i in data)
+ {
+ if(data[i].indexOf('|'))
+ {
+ params = data[i].split('|');
+ paramName = params[0];
+ defaultValue = params[1];
+ }
+ else
+ {
+ paramName = data[i];
+ defaultValue = '';
+ }
+ if($('#tmpDataBox').find('#' + paramName).length)
+ {
+ $('#setParams table tbody').append($('#tmpDataBox').find('#' + paramName).prop('outerHTML'));
+ }
+ else
+ {
+ $('#setParams table tbody').append('
' + paramName + html + '
');
+ $('#setParams table tbody tr:last td:first').find('span:first').html(paramName);
+ $('#setParams table tbody tr:last td:first').find('input:hidden').val(paramName);
+ }
+
+ $('#setParams table tbody tr:last input[name^="defaultValue"]').val(defaultValue);
+
+ var index = $('#setParams table tbody tr:last').index();
+ $('#setParams table tbody tr:last td').eq(2).find('input:radio').attr('name', 'varType[' + index + ']');
+ }
+ $('#submit').prop('disabled', false);
+ $('#setParams').modal('show');
+ }
+}
+
+function hideParamForm()
+{
+ $('#setParams').modal('hide');
+ location.href = createLink('measurement', 'design', "measurementID=" + measurementID + "&step=3");
+}
+
+function toggleSelectList(obj)
+{
+ controlType = $(obj).val();
+ if(controlType == 'select')
+ {
+ $(obj).parents('td').find('select').removeClass('hidden');
+ }
+ else
+ {
+ $(obj).parents('td').find('select').addClass('hidden');
+ }
+
+ var optionType = $(obj).parents('td').find('select').val();
+ var $tr = $(obj).closest('tr');
+ updateParamControl($tr, controlType, optionType);
+}
+
+function updateParamControl($tr, controlType, optionType)
+{
+ var defaultValue = $tr.find('[name^=defaultValue]').val();
+ defaultValue = window.btoa(defaultValue);
+ $.get(createLink('measurement', 'ajaxGetParamControl', "controlType=" + controlType + "&optionType=" + optionType + '&defaultValue=' + defaultValue), function(data)
+ {
+ $tr.find("td:last").remove();
+ $tr.find("td:last").remove();
+ $tr.append(data);
+ $tr.find("td input").each(function()
+ {
+ if($(this).hasClass('form-date')) $(this).datepicker();
+ });
+ $tr.find("td select").each(function()
+ {
+ if($(this).hasClass('chosen')) $(this).chosen();
+ });
+ });
+}
diff --git a/module/measurement/js/setsql.js b/module/measurement/js/setsql.js
new file mode 100644
index 0000000000..f16e0569b7
--- /dev/null
+++ b/module/measurement/js/setsql.js
@@ -0,0 +1,95 @@
+$().ready(function()
+{
+ $.setAjaxForm('#sqlForm', function(response)
+ {
+ if(response.result == 'success')
+ {
+ $('#responseBox').removeClass('text-danger').html(response.queryResult);
+ $('#submit').prop('disabled', false).addClass('btn-primary');
+ if(typeof(response.locate) != 'undefined') location.href = response.locate;
+ }
+ else
+ {
+ $('#responseBox').addClass('text-danger').html(response.errors);
+ }
+ return false;
+ });
+
+ $(document).on('click', '#testBtn', function()
+ {
+ $('#action').val('test');
+ $('#sqlForm').submit();
+ });
+
+ $(document).on('click', '#submit', function()
+ {
+ $('#action').val('save');
+ $('#sqlForm').submit();
+ });
+
+
+ $('#sql').change(function()
+ {
+ $('#submit').prop('disabled', true).removeClass('btn-primary');
+ $.post(createLink('measurement', 'getsqlparams'),
+ {
+ sql: $('#sql').val(), measurementID:measurementID},
+ function(response)
+ {
+ $('#paramBox').html(response);
+ }
+ );
+ });
+});
+
+$('[name^=varType]').change();
+
+function togglParamList(obj)
+{
+ var varType = $(obj).parents('td').find('select[name*=varType]');
+ toggleSelectList(varType);
+}
+
+function toggleSelectList(obj)
+{
+ isSelect = $(obj).val() == 'select';
+ if(isSelect)
+ {
+ $(obj).parents('td').find('select[name*=options]').removeClass('hidden');
+ }
+ else
+ {
+ $(obj).parents('td').find('select[name*=options]').addClass('hidden');
+ }
+
+ var optionType = $(obj).parents('td').find('select[name*=options]').val();
+ var tr = $(obj).closest('tr');
+ controlType = $(obj).val();
+ updateParamControl(tr, controlType, optionType);
+}
+
+function updateParamControl(tr, controlType, optionType)
+{
+ var defaultValue = tr.find('[name^=defaultValue]').val();
+ defaultValue = window.btoa(defaultValue);
+ $.get(createLink('measurement', 'ajaxGetParamControl', "controlType=" + controlType + "&optionType=" + optionType + '&defaultValue=' + defaultValue), function(data)
+ {
+ tr.find("td:last").remove();
+ tr.find("td:last").remove();
+ tr.append(data);
+ tr.find("td input").each(function()
+ {
+ if($(this).hasClass('form-date')) $(this).datepicker();
+ });
+ tr.find("td select").each(function()
+ {
+ if($(this).hasClass('chosen')) $(this).chosen();
+ });
+ });
+}
+
+function submitForm(value)
+{
+ $('#action').val(value);
+ $('#sqlForm').submit();
+}
diff --git a/module/measurement/js/setsqlparams.js b/module/measurement/js/setsqlparams.js
new file mode 100644
index 0000000000..58bc4e1c7c
--- /dev/null
+++ b/module/measurement/js/setsqlparams.js
@@ -0,0 +1,38 @@
+
+function toggleSelectList(obj)
+{
+ isSelect = $(obj).prop('checked');
+ if(isSelect)
+ {
+ $(obj).parents('td').find('select').removeClass('hidden');
+ }
+ else
+ {
+ $(obj).parents('td').find('select').addClass('hidden');
+ }
+
+ var optionType = $(obj).parents('td').find('select').val();
+ var $tr = $(obj).closest('tr');
+ controlType = isSelect ? 'select' : 'input';
+ updateParamControl($tr, controlType, optionType);
+}
+
+function updateParamControl($tr, controlType, optionType)
+{
+ var defaultValue = $tr.find('#defaultValue').val();
+ defaultValue = window.btoa(defaultValue);
+ $.get(createLink('measurement', 'ajaxGetParamControl', "controlType=" + controlType + "&optionType=" + optionType + '&defaultValue=' + defaultValue), function(data)
+ {
+ $tr.find("td:last").remove();
+ $tr.find("td:last").remove();
+ $tr.append(data);
+ $tr.find("td input").each(function()
+ {
+ if($(this).hasClass('form-date')) $(this).datepicker();
+ });
+ $tr.find("td select").each(function()
+ {
+ if($(this).hasClass('chosen')) $(this).chosen();
+ });
+ });
+}
diff --git a/module/measurement/js/viewtemplate.js b/module/measurement/js/viewtemplate.js
new file mode 100644
index 0000000000..00260f013d
--- /dev/null
+++ b/module/measurement/js/viewtemplate.js
@@ -0,0 +1,12 @@
+$(document).ready(function()
+{
+ $('#submit').click(function()
+ {
+ $('.m-measurement-viewtemplate .col-md-9').show();
+ $('#reportForm').submit();
+ });
+
+ $('fieldset [name^=program]').prop('readonly', true);
+
+ $('.m-measurement-viewtemplate .col-md-9').hide();
+});
diff --git a/module/measurement/lang/de.php b/module/measurement/lang/de.php
new file mode 100644
index 0000000000..2d3b61b28b
--- /dev/null
+++ b/module/measurement/lang/de.php
@@ -0,0 +1,234 @@
+measurement->common = 'Measurement';
+$lang->measurement->setTips = 'Interval Reminder';
+$lang->measurement->scope = 'Scope';
+$lang->measurement->object = 'Object';
+$lang->measurement->purpose = 'Purpose';
+$lang->measurement->code = 'Code';
+$lang->measurement->order = 'Order';
+$lang->measurement->returns = 'Return Type';
+$lang->measurement->definition = 'Measurement Definition';
+$lang->measurement->name = 'Measurement Name';
+$lang->measurement->type = 'Measurement Type';
+$lang->measurement->params = 'Parameter Settings';
+$lang->measurement->basicMeas = 'Basic Measurement';
+$lang->measurement->deriveMeas = 'Derived Measurement';
+$lang->measurement->measList = 'Measurement List';
+$lang->measurement->reportList = 'Report List';
+$lang->measurement->saveReport = 'Save Report';
+$lang->measurement->saveReportAB = 'Save Report';
+$lang->measurement->test = 'Test Measurement';
+$lang->measurement->batchEdit = 'Batch Edit';
+$lang->measurement->sqlBuilder = 'SQL Builder';
+$lang->measurement->template = 'Report template';
+$lang->measurement->model = 'Project Model';
+
+$lang->measurement->modelList['waterfall'] = 'Waterfall';
+$lang->measurement->modelList['scrum'] = 'Scrum';
+
+$lang->measurement->report = new stdclass;
+$lang->measurement->report->name = 'Report name';
+$lang->measurement->report->program = 'project';
+$lang->measurement->report->product = $lang->productCommon;
+$lang->measurement->report->project = 'stage';
+$lang->measurement->report->createdBy = 'CreatedBy';
+$lang->measurement->report->createdDate = 'CreatedDate';
+
+$lang->measurement->searchMeas = 'Search Measurement';
+$lang->measurement->designPHP = 'Design PHP';
+$lang->measurement->designSQL = 'Design SQL';
+$lang->measurement->initCrontabQueue = 'Initialize Crontab queue';
+$lang->measurement->execCrontabQueue = 'Execute Crontab queue';
+$lang->measurement->saveSqlMeasSuccess = 'Query successful, test result: %s';
+
+$lang->measurement->actionConfig = "Action Config";
+$lang->measurement->moduleName = 'Module Name';
+$lang->measurement->actionName = 'Action Name';
+$lang->measurement->cycleConfig = "Cycle Config";
+$lang->measurement->execTime = "Execution Time";
+$lang->measurement->cycleDay = 'Day';
+$lang->measurement->cycleWeek = 'Week';
+$lang->measurement->cycleMonth = 'Month';
+$lang->measurement->every = 'Interval';
+$lang->measurement->dayNames = array(1 => 'Monday', 2 => 'Tuesday', 3 => 'Wednesday', 4 => 'Thursday', 5 => 'Friday', 6 => 'Saturday', 0 => 'Saturday');
+
+$lang->measurement->cycleType['day'] = 'Every day %s';
+$lang->measurement->cycleType['week'] = 'Weekly %s';
+$lang->measurement->cycleType['month'] = 'Monthly %s';
+
+$lang->measurement->scopeList[''] = '';
+$lang->measurement->scopeList['project'] = 'project';
+$lang->measurement->scopeList['product'] = $lang->productCommon;
+$lang->measurement->scopeList['sprint'] = 'stage';
+
+$lang->measurement->purposeList[''] = '';
+$lang->measurement->purposeList['scale'] = 'Scale';
+$lang->measurement->purposeList['duration'] = 'Duration';
+$lang->measurement->purposeList['workload'] = 'Workload';
+$lang->measurement->purposeList['cost'] = 'Cost';
+$lang->measurement->purposeList['quality'] = 'Quality';
+
+$lang->measurement->objectList[''] = '';
+$lang->measurement->objectList['staff'] = 'Staff';
+$lang->measurement->objectList['finance'] = 'Task';
+$lang->measurement->objectList['case'] = 'Case';
+$lang->measurement->objectList['bug'] = 'Bug';
+$lang->measurement->objectList['review'] = 'Review';
+$lang->measurement->objectList['stage'] = 'Stage';
+$lang->measurement->objectList['program'] = 'Project';
+$lang->measurement->objectList['softRequest'] = 'Story';
+$lang->measurement->objectList['userRequest'] = 'Requirements';
+
+$lang->measurement->typeList['basic'] = $lang->measurement->basicMeas;
+
+$lang->measurement->sysData = $lang->measurement->typeList;
+$lang->measurement->sysData['report'] = 'A single report';
+
+$lang->measurement->collectTypeList['crontab'] = 'Crontab';
+$lang->measurement->collectTypeList['action'] = 'Action';
+
+$lang->measurement->buildinParams = new stdclass;
+$lang->measurement->buildinParams->program = 'Project';
+$lang->measurement->buildinParams->day = 'Day';
+
+$lang->measurement->codeExistence = 'Measurement code: %s already exists.';
+$lang->measurement->codeEmpty = 'The measurement code for ID: %s cannot be empty.';
+$lang->measurement->nameEmpty = 'The measurement name for ID: %s cannot be empty.';
+$lang->measurement->unitEmpty = 'The measurement unit for ID: %s cannot be empty.';
+$lang->measurement->definitionEmpty = 'The measurement definition for ID: %s cannot be empty.';
+
+$lang->measurement->noticeScope = 'Notice Scope';
+$lang->measurement->design = 'Design';
+$lang->measurement->browse = 'Measurements';
+$lang->measurement->browseBasic = 'Basic Measurement';
+$lang->measurement->browseDerivation = 'Derived Measurement';
+$lang->measurement->create = 'Create Measurement';
+$lang->measurement->createBasic = $lang->measurement->create;
+$lang->measurement->editBasic = 'Edit Basic Measurement';
+$lang->measurement->editDerivation = 'Edit Derived Measurement';
+$lang->measurement->delete = 'Delete';
+$lang->measurement->deleted = 'Deleted';
+$lang->measurement->collectType = 'Collect Type';
+$lang->measurement->collectConf = 'Collect Config';
+$lang->measurement->collectedBy = 'CollectedBy';
+$lang->measurement->unit = 'Unit';
+$lang->measurement->save = 'Save';
+$lang->measurement->saveSuccess = 'SaveSuccess';
+$lang->measurement->reDesign = 'The redesign';
+$lang->measurement->confirmDelete = 'Do you want to delete?';
+$lang->measurement->options = 'Options';
+$lang->measurement->id = 'ID';
+$lang->measurement->createTemplate = 'Creating a composite template';
+$lang->measurement->createSingle = 'Creating a single template';
+$lang->measurement->editTemplate = 'Edit Template';
+$lang->measurement->viewTemplate = 'View Template';
+$lang->measurement->content = 'Content';
+$lang->measurement->addMeas = 'Add Measurement';
+$lang->measurement->dataSource = 'Data Source';
+$lang->measurement->dataName = 'Data Name';
+$lang->measurement->setSQL = 'Set SQL';
+$lang->measurement->setPHP = 'Set PHP';
+$lang->measurement->callSqlBuilder = 'Call Sql Builder';
+$lang->measurement->query = 'Query';
+$lang->measurement->byQuery = 'Search';
+$lang->measurement->call = 'Call';
+$lang->measurement->queryResult = 'Query Result:';
+$lang->measurement->setParams = 'Set Params';
+$lang->measurement->createdBy = 'CreatedBy';
+$lang->measurement->createdDate = 'CreatedDate';
+$lang->measurement->purpose = 'Purpose';
+$lang->measurement->aim = 'Aim';
+$lang->measurement->analyst = 'Analyst';
+$lang->measurement->analysisMethod = 'Analysis Method';
+
+$lang->measurement->placeholder = new stdclass();
+$lang->measurement->placeholder->sql = 'Please complete the MySQL custom function statement.';
+$lang->measurement->placeholder->php = 'Please follow the code required by the system. The class name cannot be changed and must include the GET method.';
+$lang->measurement->codeTemplate = <<
+EOT;
+
+$lang->measurement->sqlTemplate = <<measurement->param = new stdclass();
+$lang->measurement->param->varName = 'Variable Name';
+$lang->measurement->param->showName = 'Show Name';
+$lang->measurement->param->varType = 'Type';
+$lang->measurement->param->defaultValue = 'Default Value';
+$lang->measurement->param->queryValue = 'Query Value';
+
+$lang->measurement->param->typeList['input'] = 'Input';
+$lang->measurement->param->typeList['date'] = 'Date';
+$lang->measurement->param->typeList['select'] = 'Select';
+
+$lang->measurement->param->options['project'] = 'Project List';
+$lang->measurement->param->options['product'] = $lang->productCommon . 'List';
+$lang->measurement->param->options['sprint'] = $lang->executionCommon . 'List';
+
+$lang->measurement->tips = new stdclass();
+$lang->measurement->tips->nameError = 'Error in MySQL function name, please check function name.';
+$lang->measurement->tips->createError = 'Failed to create a custom function for MySQL. Error message:
%s';
+$lang->measurement->tips->noticeSelect = 'SQL statements can only be query statements';
+$lang->measurement->tips->noticeBlack = 'SQL contains the disable SQL keyword %s';
+$lang->measurement->tips->noticeVarName = 'The variable name is not set';
+$lang->measurement->tips->noticeVarType = 'The type of the variable %s is not set';
+$lang->measurement->tips->noticeShowName = 'The show name of the variable %s is not set';
+$lang->measurement->tips->noticeQueryValue = 'The query value of the variable %s is not set';
+$lang->measurement->tips->showNameMissed = 'The show name of the variable %s is not set';
+$lang->measurement->tips->errorSql = 'SQL statement error! Error:';
+$lang->measurement->tips->click2SetParams = 'Please click on the red variable block to set parameters, and then';
+$lang->measurement->tips->view = 'View';
+$lang->measurement->tips->click2InsertData = "Click to insert a metric or report";
+
+$lang->basicmeas = new stdclass();
+$lang->basicmeas->name = $lang->measurement->name;
+$lang->basicmeas->code = $lang->measurement->code;
+$lang->basicmeas->unit = $lang->measurement->unit;
+$lang->basicmeas->definition = $lang->measurement->definition;
+
+$lang->derivemeas = new stdclass();
+$lang->derivemeas->name = $lang->measurement->name;
+$lang->derivemeas->purpose = $lang->measurement->purpose;
+
+$lang->meastemplate = new stdclass();
+$lang->meastemplate->id = 'ID';
+$lang->meastemplate->single = 'A Single Template';
+$lang->meastemplate->complex = 'Composite Template';
+$lang->meastemplate->name = 'Template Name';
+$lang->meastemplate->desc = 'Description';
+$lang->meastemplate->content = 'Content';
+$lang->meastemplate->createdBy = 'CreatedBy';
+$lang->meastemplate->createdDate = 'CreatedDate';
+$lang->meastemplate->addedBy = 'AddedBy';
+$lang->meastemplate->addedDate = 'AddedDate';
+
+$lang->meastemplate->actions = array();
+$lang->measurement->actions[] = 'Before unit testing';
+$lang->measurement->actions[] = 'After the test is complete';
+$lang->measurement->actions[] = 'Test report review completed';
+$lang->measurement->actions[] = 'Test Plan Review';
+$lang->measurement->actions[] = 'Milestone report after review';
+$lang->measurement->actions[] = 'After Milestone Review';
+$lang->measurement->actions[] = 'Quantified project monitoring process';
+$lang->measurement->actions[] = 'Upon completion of stories';
+$lang->measurement->actions[] = 'After requirements stories';
+$lang->measurement->actions[] = 'After compiling the stories specification';
+$lang->measurement->actions[] = 'Stories specification review';
+$lang->measurement->actions[] = 'After the stories milestone';
+$lang->measurement->actions[] = 'After the project plan review';
+$lang->measurement->actions[] = 'The project plan review is completed';
+
+$lang->measurement->actions['project.close'] = 'End of the project';
diff --git a/module/measurement/lang/en.php b/module/measurement/lang/en.php
new file mode 100644
index 0000000000..2d3b61b28b
--- /dev/null
+++ b/module/measurement/lang/en.php
@@ -0,0 +1,234 @@
+measurement->common = 'Measurement';
+$lang->measurement->setTips = 'Interval Reminder';
+$lang->measurement->scope = 'Scope';
+$lang->measurement->object = 'Object';
+$lang->measurement->purpose = 'Purpose';
+$lang->measurement->code = 'Code';
+$lang->measurement->order = 'Order';
+$lang->measurement->returns = 'Return Type';
+$lang->measurement->definition = 'Measurement Definition';
+$lang->measurement->name = 'Measurement Name';
+$lang->measurement->type = 'Measurement Type';
+$lang->measurement->params = 'Parameter Settings';
+$lang->measurement->basicMeas = 'Basic Measurement';
+$lang->measurement->deriveMeas = 'Derived Measurement';
+$lang->measurement->measList = 'Measurement List';
+$lang->measurement->reportList = 'Report List';
+$lang->measurement->saveReport = 'Save Report';
+$lang->measurement->saveReportAB = 'Save Report';
+$lang->measurement->test = 'Test Measurement';
+$lang->measurement->batchEdit = 'Batch Edit';
+$lang->measurement->sqlBuilder = 'SQL Builder';
+$lang->measurement->template = 'Report template';
+$lang->measurement->model = 'Project Model';
+
+$lang->measurement->modelList['waterfall'] = 'Waterfall';
+$lang->measurement->modelList['scrum'] = 'Scrum';
+
+$lang->measurement->report = new stdclass;
+$lang->measurement->report->name = 'Report name';
+$lang->measurement->report->program = 'project';
+$lang->measurement->report->product = $lang->productCommon;
+$lang->measurement->report->project = 'stage';
+$lang->measurement->report->createdBy = 'CreatedBy';
+$lang->measurement->report->createdDate = 'CreatedDate';
+
+$lang->measurement->searchMeas = 'Search Measurement';
+$lang->measurement->designPHP = 'Design PHP';
+$lang->measurement->designSQL = 'Design SQL';
+$lang->measurement->initCrontabQueue = 'Initialize Crontab queue';
+$lang->measurement->execCrontabQueue = 'Execute Crontab queue';
+$lang->measurement->saveSqlMeasSuccess = 'Query successful, test result: %s';
+
+$lang->measurement->actionConfig = "Action Config";
+$lang->measurement->moduleName = 'Module Name';
+$lang->measurement->actionName = 'Action Name';
+$lang->measurement->cycleConfig = "Cycle Config";
+$lang->measurement->execTime = "Execution Time";
+$lang->measurement->cycleDay = 'Day';
+$lang->measurement->cycleWeek = 'Week';
+$lang->measurement->cycleMonth = 'Month';
+$lang->measurement->every = 'Interval';
+$lang->measurement->dayNames = array(1 => 'Monday', 2 => 'Tuesday', 3 => 'Wednesday', 4 => 'Thursday', 5 => 'Friday', 6 => 'Saturday', 0 => 'Saturday');
+
+$lang->measurement->cycleType['day'] = 'Every day %s';
+$lang->measurement->cycleType['week'] = 'Weekly %s';
+$lang->measurement->cycleType['month'] = 'Monthly %s';
+
+$lang->measurement->scopeList[''] = '';
+$lang->measurement->scopeList['project'] = 'project';
+$lang->measurement->scopeList['product'] = $lang->productCommon;
+$lang->measurement->scopeList['sprint'] = 'stage';
+
+$lang->measurement->purposeList[''] = '';
+$lang->measurement->purposeList['scale'] = 'Scale';
+$lang->measurement->purposeList['duration'] = 'Duration';
+$lang->measurement->purposeList['workload'] = 'Workload';
+$lang->measurement->purposeList['cost'] = 'Cost';
+$lang->measurement->purposeList['quality'] = 'Quality';
+
+$lang->measurement->objectList[''] = '';
+$lang->measurement->objectList['staff'] = 'Staff';
+$lang->measurement->objectList['finance'] = 'Task';
+$lang->measurement->objectList['case'] = 'Case';
+$lang->measurement->objectList['bug'] = 'Bug';
+$lang->measurement->objectList['review'] = 'Review';
+$lang->measurement->objectList['stage'] = 'Stage';
+$lang->measurement->objectList['program'] = 'Project';
+$lang->measurement->objectList['softRequest'] = 'Story';
+$lang->measurement->objectList['userRequest'] = 'Requirements';
+
+$lang->measurement->typeList['basic'] = $lang->measurement->basicMeas;
+
+$lang->measurement->sysData = $lang->measurement->typeList;
+$lang->measurement->sysData['report'] = 'A single report';
+
+$lang->measurement->collectTypeList['crontab'] = 'Crontab';
+$lang->measurement->collectTypeList['action'] = 'Action';
+
+$lang->measurement->buildinParams = new stdclass;
+$lang->measurement->buildinParams->program = 'Project';
+$lang->measurement->buildinParams->day = 'Day';
+
+$lang->measurement->codeExistence = 'Measurement code: %s already exists.';
+$lang->measurement->codeEmpty = 'The measurement code for ID: %s cannot be empty.';
+$lang->measurement->nameEmpty = 'The measurement name for ID: %s cannot be empty.';
+$lang->measurement->unitEmpty = 'The measurement unit for ID: %s cannot be empty.';
+$lang->measurement->definitionEmpty = 'The measurement definition for ID: %s cannot be empty.';
+
+$lang->measurement->noticeScope = 'Notice Scope';
+$lang->measurement->design = 'Design';
+$lang->measurement->browse = 'Measurements';
+$lang->measurement->browseBasic = 'Basic Measurement';
+$lang->measurement->browseDerivation = 'Derived Measurement';
+$lang->measurement->create = 'Create Measurement';
+$lang->measurement->createBasic = $lang->measurement->create;
+$lang->measurement->editBasic = 'Edit Basic Measurement';
+$lang->measurement->editDerivation = 'Edit Derived Measurement';
+$lang->measurement->delete = 'Delete';
+$lang->measurement->deleted = 'Deleted';
+$lang->measurement->collectType = 'Collect Type';
+$lang->measurement->collectConf = 'Collect Config';
+$lang->measurement->collectedBy = 'CollectedBy';
+$lang->measurement->unit = 'Unit';
+$lang->measurement->save = 'Save';
+$lang->measurement->saveSuccess = 'SaveSuccess';
+$lang->measurement->reDesign = 'The redesign';
+$lang->measurement->confirmDelete = 'Do you want to delete?';
+$lang->measurement->options = 'Options';
+$lang->measurement->id = 'ID';
+$lang->measurement->createTemplate = 'Creating a composite template';
+$lang->measurement->createSingle = 'Creating a single template';
+$lang->measurement->editTemplate = 'Edit Template';
+$lang->measurement->viewTemplate = 'View Template';
+$lang->measurement->content = 'Content';
+$lang->measurement->addMeas = 'Add Measurement';
+$lang->measurement->dataSource = 'Data Source';
+$lang->measurement->dataName = 'Data Name';
+$lang->measurement->setSQL = 'Set SQL';
+$lang->measurement->setPHP = 'Set PHP';
+$lang->measurement->callSqlBuilder = 'Call Sql Builder';
+$lang->measurement->query = 'Query';
+$lang->measurement->byQuery = 'Search';
+$lang->measurement->call = 'Call';
+$lang->measurement->queryResult = 'Query Result:';
+$lang->measurement->setParams = 'Set Params';
+$lang->measurement->createdBy = 'CreatedBy';
+$lang->measurement->createdDate = 'CreatedDate';
+$lang->measurement->purpose = 'Purpose';
+$lang->measurement->aim = 'Aim';
+$lang->measurement->analyst = 'Analyst';
+$lang->measurement->analysisMethod = 'Analysis Method';
+
+$lang->measurement->placeholder = new stdclass();
+$lang->measurement->placeholder->sql = 'Please complete the MySQL custom function statement.';
+$lang->measurement->placeholder->php = 'Please follow the code required by the system. The class name cannot be changed and must include the GET method.';
+$lang->measurement->codeTemplate = <<
+EOT;
+
+$lang->measurement->sqlTemplate = <<measurement->param = new stdclass();
+$lang->measurement->param->varName = 'Variable Name';
+$lang->measurement->param->showName = 'Show Name';
+$lang->measurement->param->varType = 'Type';
+$lang->measurement->param->defaultValue = 'Default Value';
+$lang->measurement->param->queryValue = 'Query Value';
+
+$lang->measurement->param->typeList['input'] = 'Input';
+$lang->measurement->param->typeList['date'] = 'Date';
+$lang->measurement->param->typeList['select'] = 'Select';
+
+$lang->measurement->param->options['project'] = 'Project List';
+$lang->measurement->param->options['product'] = $lang->productCommon . 'List';
+$lang->measurement->param->options['sprint'] = $lang->executionCommon . 'List';
+
+$lang->measurement->tips = new stdclass();
+$lang->measurement->tips->nameError = 'Error in MySQL function name, please check function name.';
+$lang->measurement->tips->createError = 'Failed to create a custom function for MySQL. Error message:
%s';
+$lang->measurement->tips->noticeSelect = 'SQL statements can only be query statements';
+$lang->measurement->tips->noticeBlack = 'SQL contains the disable SQL keyword %s';
+$lang->measurement->tips->noticeVarName = 'The variable name is not set';
+$lang->measurement->tips->noticeVarType = 'The type of the variable %s is not set';
+$lang->measurement->tips->noticeShowName = 'The show name of the variable %s is not set';
+$lang->measurement->tips->noticeQueryValue = 'The query value of the variable %s is not set';
+$lang->measurement->tips->showNameMissed = 'The show name of the variable %s is not set';
+$lang->measurement->tips->errorSql = 'SQL statement error! Error:';
+$lang->measurement->tips->click2SetParams = 'Please click on the red variable block to set parameters, and then';
+$lang->measurement->tips->view = 'View';
+$lang->measurement->tips->click2InsertData = "Click to insert a metric or report";
+
+$lang->basicmeas = new stdclass();
+$lang->basicmeas->name = $lang->measurement->name;
+$lang->basicmeas->code = $lang->measurement->code;
+$lang->basicmeas->unit = $lang->measurement->unit;
+$lang->basicmeas->definition = $lang->measurement->definition;
+
+$lang->derivemeas = new stdclass();
+$lang->derivemeas->name = $lang->measurement->name;
+$lang->derivemeas->purpose = $lang->measurement->purpose;
+
+$lang->meastemplate = new stdclass();
+$lang->meastemplate->id = 'ID';
+$lang->meastemplate->single = 'A Single Template';
+$lang->meastemplate->complex = 'Composite Template';
+$lang->meastemplate->name = 'Template Name';
+$lang->meastemplate->desc = 'Description';
+$lang->meastemplate->content = 'Content';
+$lang->meastemplate->createdBy = 'CreatedBy';
+$lang->meastemplate->createdDate = 'CreatedDate';
+$lang->meastemplate->addedBy = 'AddedBy';
+$lang->meastemplate->addedDate = 'AddedDate';
+
+$lang->meastemplate->actions = array();
+$lang->measurement->actions[] = 'Before unit testing';
+$lang->measurement->actions[] = 'After the test is complete';
+$lang->measurement->actions[] = 'Test report review completed';
+$lang->measurement->actions[] = 'Test Plan Review';
+$lang->measurement->actions[] = 'Milestone report after review';
+$lang->measurement->actions[] = 'After Milestone Review';
+$lang->measurement->actions[] = 'Quantified project monitoring process';
+$lang->measurement->actions[] = 'Upon completion of stories';
+$lang->measurement->actions[] = 'After requirements stories';
+$lang->measurement->actions[] = 'After compiling the stories specification';
+$lang->measurement->actions[] = 'Stories specification review';
+$lang->measurement->actions[] = 'After the stories milestone';
+$lang->measurement->actions[] = 'After the project plan review';
+$lang->measurement->actions[] = 'The project plan review is completed';
+
+$lang->measurement->actions['project.close'] = 'End of the project';
diff --git a/module/measurement/lang/fr.php b/module/measurement/lang/fr.php
new file mode 100644
index 0000000000..2d3b61b28b
--- /dev/null
+++ b/module/measurement/lang/fr.php
@@ -0,0 +1,234 @@
+measurement->common = 'Measurement';
+$lang->measurement->setTips = 'Interval Reminder';
+$lang->measurement->scope = 'Scope';
+$lang->measurement->object = 'Object';
+$lang->measurement->purpose = 'Purpose';
+$lang->measurement->code = 'Code';
+$lang->measurement->order = 'Order';
+$lang->measurement->returns = 'Return Type';
+$lang->measurement->definition = 'Measurement Definition';
+$lang->measurement->name = 'Measurement Name';
+$lang->measurement->type = 'Measurement Type';
+$lang->measurement->params = 'Parameter Settings';
+$lang->measurement->basicMeas = 'Basic Measurement';
+$lang->measurement->deriveMeas = 'Derived Measurement';
+$lang->measurement->measList = 'Measurement List';
+$lang->measurement->reportList = 'Report List';
+$lang->measurement->saveReport = 'Save Report';
+$lang->measurement->saveReportAB = 'Save Report';
+$lang->measurement->test = 'Test Measurement';
+$lang->measurement->batchEdit = 'Batch Edit';
+$lang->measurement->sqlBuilder = 'SQL Builder';
+$lang->measurement->template = 'Report template';
+$lang->measurement->model = 'Project Model';
+
+$lang->measurement->modelList['waterfall'] = 'Waterfall';
+$lang->measurement->modelList['scrum'] = 'Scrum';
+
+$lang->measurement->report = new stdclass;
+$lang->measurement->report->name = 'Report name';
+$lang->measurement->report->program = 'project';
+$lang->measurement->report->product = $lang->productCommon;
+$lang->measurement->report->project = 'stage';
+$lang->measurement->report->createdBy = 'CreatedBy';
+$lang->measurement->report->createdDate = 'CreatedDate';
+
+$lang->measurement->searchMeas = 'Search Measurement';
+$lang->measurement->designPHP = 'Design PHP';
+$lang->measurement->designSQL = 'Design SQL';
+$lang->measurement->initCrontabQueue = 'Initialize Crontab queue';
+$lang->measurement->execCrontabQueue = 'Execute Crontab queue';
+$lang->measurement->saveSqlMeasSuccess = 'Query successful, test result: %s';
+
+$lang->measurement->actionConfig = "Action Config";
+$lang->measurement->moduleName = 'Module Name';
+$lang->measurement->actionName = 'Action Name';
+$lang->measurement->cycleConfig = "Cycle Config";
+$lang->measurement->execTime = "Execution Time";
+$lang->measurement->cycleDay = 'Day';
+$lang->measurement->cycleWeek = 'Week';
+$lang->measurement->cycleMonth = 'Month';
+$lang->measurement->every = 'Interval';
+$lang->measurement->dayNames = array(1 => 'Monday', 2 => 'Tuesday', 3 => 'Wednesday', 4 => 'Thursday', 5 => 'Friday', 6 => 'Saturday', 0 => 'Saturday');
+
+$lang->measurement->cycleType['day'] = 'Every day %s';
+$lang->measurement->cycleType['week'] = 'Weekly %s';
+$lang->measurement->cycleType['month'] = 'Monthly %s';
+
+$lang->measurement->scopeList[''] = '';
+$lang->measurement->scopeList['project'] = 'project';
+$lang->measurement->scopeList['product'] = $lang->productCommon;
+$lang->measurement->scopeList['sprint'] = 'stage';
+
+$lang->measurement->purposeList[''] = '';
+$lang->measurement->purposeList['scale'] = 'Scale';
+$lang->measurement->purposeList['duration'] = 'Duration';
+$lang->measurement->purposeList['workload'] = 'Workload';
+$lang->measurement->purposeList['cost'] = 'Cost';
+$lang->measurement->purposeList['quality'] = 'Quality';
+
+$lang->measurement->objectList[''] = '';
+$lang->measurement->objectList['staff'] = 'Staff';
+$lang->measurement->objectList['finance'] = 'Task';
+$lang->measurement->objectList['case'] = 'Case';
+$lang->measurement->objectList['bug'] = 'Bug';
+$lang->measurement->objectList['review'] = 'Review';
+$lang->measurement->objectList['stage'] = 'Stage';
+$lang->measurement->objectList['program'] = 'Project';
+$lang->measurement->objectList['softRequest'] = 'Story';
+$lang->measurement->objectList['userRequest'] = 'Requirements';
+
+$lang->measurement->typeList['basic'] = $lang->measurement->basicMeas;
+
+$lang->measurement->sysData = $lang->measurement->typeList;
+$lang->measurement->sysData['report'] = 'A single report';
+
+$lang->measurement->collectTypeList['crontab'] = 'Crontab';
+$lang->measurement->collectTypeList['action'] = 'Action';
+
+$lang->measurement->buildinParams = new stdclass;
+$lang->measurement->buildinParams->program = 'Project';
+$lang->measurement->buildinParams->day = 'Day';
+
+$lang->measurement->codeExistence = 'Measurement code: %s already exists.';
+$lang->measurement->codeEmpty = 'The measurement code for ID: %s cannot be empty.';
+$lang->measurement->nameEmpty = 'The measurement name for ID: %s cannot be empty.';
+$lang->measurement->unitEmpty = 'The measurement unit for ID: %s cannot be empty.';
+$lang->measurement->definitionEmpty = 'The measurement definition for ID: %s cannot be empty.';
+
+$lang->measurement->noticeScope = 'Notice Scope';
+$lang->measurement->design = 'Design';
+$lang->measurement->browse = 'Measurements';
+$lang->measurement->browseBasic = 'Basic Measurement';
+$lang->measurement->browseDerivation = 'Derived Measurement';
+$lang->measurement->create = 'Create Measurement';
+$lang->measurement->createBasic = $lang->measurement->create;
+$lang->measurement->editBasic = 'Edit Basic Measurement';
+$lang->measurement->editDerivation = 'Edit Derived Measurement';
+$lang->measurement->delete = 'Delete';
+$lang->measurement->deleted = 'Deleted';
+$lang->measurement->collectType = 'Collect Type';
+$lang->measurement->collectConf = 'Collect Config';
+$lang->measurement->collectedBy = 'CollectedBy';
+$lang->measurement->unit = 'Unit';
+$lang->measurement->save = 'Save';
+$lang->measurement->saveSuccess = 'SaveSuccess';
+$lang->measurement->reDesign = 'The redesign';
+$lang->measurement->confirmDelete = 'Do you want to delete?';
+$lang->measurement->options = 'Options';
+$lang->measurement->id = 'ID';
+$lang->measurement->createTemplate = 'Creating a composite template';
+$lang->measurement->createSingle = 'Creating a single template';
+$lang->measurement->editTemplate = 'Edit Template';
+$lang->measurement->viewTemplate = 'View Template';
+$lang->measurement->content = 'Content';
+$lang->measurement->addMeas = 'Add Measurement';
+$lang->measurement->dataSource = 'Data Source';
+$lang->measurement->dataName = 'Data Name';
+$lang->measurement->setSQL = 'Set SQL';
+$lang->measurement->setPHP = 'Set PHP';
+$lang->measurement->callSqlBuilder = 'Call Sql Builder';
+$lang->measurement->query = 'Query';
+$lang->measurement->byQuery = 'Search';
+$lang->measurement->call = 'Call';
+$lang->measurement->queryResult = 'Query Result:';
+$lang->measurement->setParams = 'Set Params';
+$lang->measurement->createdBy = 'CreatedBy';
+$lang->measurement->createdDate = 'CreatedDate';
+$lang->measurement->purpose = 'Purpose';
+$lang->measurement->aim = 'Aim';
+$lang->measurement->analyst = 'Analyst';
+$lang->measurement->analysisMethod = 'Analysis Method';
+
+$lang->measurement->placeholder = new stdclass();
+$lang->measurement->placeholder->sql = 'Please complete the MySQL custom function statement.';
+$lang->measurement->placeholder->php = 'Please follow the code required by the system. The class name cannot be changed and must include the GET method.';
+$lang->measurement->codeTemplate = <<
+EOT;
+
+$lang->measurement->sqlTemplate = <<measurement->param = new stdclass();
+$lang->measurement->param->varName = 'Variable Name';
+$lang->measurement->param->showName = 'Show Name';
+$lang->measurement->param->varType = 'Type';
+$lang->measurement->param->defaultValue = 'Default Value';
+$lang->measurement->param->queryValue = 'Query Value';
+
+$lang->measurement->param->typeList['input'] = 'Input';
+$lang->measurement->param->typeList['date'] = 'Date';
+$lang->measurement->param->typeList['select'] = 'Select';
+
+$lang->measurement->param->options['project'] = 'Project List';
+$lang->measurement->param->options['product'] = $lang->productCommon . 'List';
+$lang->measurement->param->options['sprint'] = $lang->executionCommon . 'List';
+
+$lang->measurement->tips = new stdclass();
+$lang->measurement->tips->nameError = 'Error in MySQL function name, please check function name.';
+$lang->measurement->tips->createError = 'Failed to create a custom function for MySQL. Error message:
%s';
+$lang->measurement->tips->noticeSelect = 'SQL statements can only be query statements';
+$lang->measurement->tips->noticeBlack = 'SQL contains the disable SQL keyword %s';
+$lang->measurement->tips->noticeVarName = 'The variable name is not set';
+$lang->measurement->tips->noticeVarType = 'The type of the variable %s is not set';
+$lang->measurement->tips->noticeShowName = 'The show name of the variable %s is not set';
+$lang->measurement->tips->noticeQueryValue = 'The query value of the variable %s is not set';
+$lang->measurement->tips->showNameMissed = 'The show name of the variable %s is not set';
+$lang->measurement->tips->errorSql = 'SQL statement error! Error:';
+$lang->measurement->tips->click2SetParams = 'Please click on the red variable block to set parameters, and then';
+$lang->measurement->tips->view = 'View';
+$lang->measurement->tips->click2InsertData = "Click to insert a metric or report";
+
+$lang->basicmeas = new stdclass();
+$lang->basicmeas->name = $lang->measurement->name;
+$lang->basicmeas->code = $lang->measurement->code;
+$lang->basicmeas->unit = $lang->measurement->unit;
+$lang->basicmeas->definition = $lang->measurement->definition;
+
+$lang->derivemeas = new stdclass();
+$lang->derivemeas->name = $lang->measurement->name;
+$lang->derivemeas->purpose = $lang->measurement->purpose;
+
+$lang->meastemplate = new stdclass();
+$lang->meastemplate->id = 'ID';
+$lang->meastemplate->single = 'A Single Template';
+$lang->meastemplate->complex = 'Composite Template';
+$lang->meastemplate->name = 'Template Name';
+$lang->meastemplate->desc = 'Description';
+$lang->meastemplate->content = 'Content';
+$lang->meastemplate->createdBy = 'CreatedBy';
+$lang->meastemplate->createdDate = 'CreatedDate';
+$lang->meastemplate->addedBy = 'AddedBy';
+$lang->meastemplate->addedDate = 'AddedDate';
+
+$lang->meastemplate->actions = array();
+$lang->measurement->actions[] = 'Before unit testing';
+$lang->measurement->actions[] = 'After the test is complete';
+$lang->measurement->actions[] = 'Test report review completed';
+$lang->measurement->actions[] = 'Test Plan Review';
+$lang->measurement->actions[] = 'Milestone report after review';
+$lang->measurement->actions[] = 'After Milestone Review';
+$lang->measurement->actions[] = 'Quantified project monitoring process';
+$lang->measurement->actions[] = 'Upon completion of stories';
+$lang->measurement->actions[] = 'After requirements stories';
+$lang->measurement->actions[] = 'After compiling the stories specification';
+$lang->measurement->actions[] = 'Stories specification review';
+$lang->measurement->actions[] = 'After the stories milestone';
+$lang->measurement->actions[] = 'After the project plan review';
+$lang->measurement->actions[] = 'The project plan review is completed';
+
+$lang->measurement->actions['project.close'] = 'End of the project';
diff --git a/module/measurement/lang/zh-cn.php b/module/measurement/lang/zh-cn.php
new file mode 100644
index 0000000000..59e003e78a
--- /dev/null
+++ b/module/measurement/lang/zh-cn.php
@@ -0,0 +1,234 @@
+measurement->common = '度量';
+$lang->measurement->setTips = '区间提示';
+$lang->measurement->scope = '范围';
+$lang->measurement->object = '对象';
+$lang->measurement->purpose = '目的';
+$lang->measurement->code = '代号';
+$lang->measurement->order = '排序';
+$lang->measurement->returns = '返回类型';
+$lang->measurement->definition = '度量定义';
+$lang->measurement->name = '度量名称';
+$lang->measurement->type = '度量类型';
+$lang->measurement->params = '参数设置';
+$lang->measurement->basicMeas = '基本度量';
+$lang->measurement->deriveMeas = '衍生度量';
+$lang->measurement->measList = '度量列表';
+$lang->measurement->reportList = '报告列表';
+$lang->measurement->saveReport = '保存此报告';
+$lang->measurement->saveReportAB = '保存报告';
+$lang->measurement->test = '测试度量';
+$lang->measurement->batchEdit = '批量编辑';
+$lang->measurement->sqlBuilder = '度量数据';
+$lang->measurement->template = '报表模板';
+$lang->measurement->model = '项目模型';
+
+$lang->measurement->modelList['waterfall'] = '瀑布';
+$lang->measurement->modelList['scrum'] = '敏捷';
+
+$lang->measurement->report = new stdclass;
+$lang->measurement->report->name = '报告名称';
+$lang->measurement->report->program = '项目';
+$lang->measurement->report->product = '产品';
+$lang->measurement->report->project = '阶段';
+$lang->measurement->report->createdBy = '创建人';
+$lang->measurement->report->createdDate = '创建时间';
+
+$lang->measurement->searchMeas = "搜索度量";
+$lang->measurement->designPHP = "设置PHP";
+$lang->measurement->designSQL = "设置度量SQL";
+$lang->measurement->initCrontabQueue = "初始化计划任务队列";
+$lang->measurement->execCrontabQueue = "处理计划任务队列";
+$lang->measurement->saveSqlMeasSuccess = "查询成功,测试结果:%s";
+
+$lang->measurement->actionConfig = "动作设置";
+$lang->measurement->moduleName = '模块名称';
+$lang->measurement->actionName = '动作名称';
+$lang->measurement->cycleConfig = "定时设置";
+$lang->measurement->execTime = "执行时间";
+$lang->measurement->cycleDay = '天';
+$lang->measurement->cycleWeek = '周';
+$lang->measurement->cycleMonth = '月';
+$lang->measurement->every = '间隔';
+$lang->measurement->dayNames = array(1 => '星期一', 2 => '星期二', 3 => '星期三', 4 => '星期四', 5 => '星期五', 6 => '星期六', 0 => '星期日');
+
+$lang->measurement->cycleType['day'] = '每隔%s天';
+$lang->measurement->cycleType['week'] = '每周%s';
+$lang->measurement->cycleType['month'] = '每月%s';
+
+$lang->measurement->scopeList[''] = '';
+$lang->measurement->scopeList['project'] = '项目';
+$lang->measurement->scopeList['product'] = '产品';
+$lang->measurement->scopeList['sprint'] = '阶段';
+
+$lang->measurement->purposeList[''] = '';
+$lang->measurement->purposeList['scale'] = '规模';
+$lang->measurement->purposeList['duration'] = '工期';
+$lang->measurement->purposeList['workload'] = '工作量';
+$lang->measurement->purposeList['cost'] = '成本';
+$lang->measurement->purposeList['quality'] = '质量';
+
+$lang->measurement->objectList[''] = '';
+$lang->measurement->objectList['staff'] = '人员';
+$lang->measurement->objectList['finance'] = '任务';
+$lang->measurement->objectList['case'] = '用例';
+$lang->measurement->objectList['bug'] = '缺陷';
+$lang->measurement->objectList['review'] = '评审';
+$lang->measurement->objectList['stage'] = '阶段';
+$lang->measurement->objectList['program'] = '项目';
+$lang->measurement->objectList['softRequest'] = '软件需求';
+$lang->measurement->objectList['userRequest'] = '用户需求';
+
+$lang->measurement->typeList['basic'] = $lang->measurement->basicMeas;
+
+$lang->measurement->sysData = $lang->measurement->typeList;
+$lang->measurement->sysData['report'] = '单一报表';
+
+$lang->measurement->collectTypeList['crontab'] = '定时计划';
+$lang->measurement->collectTypeList['action'] = '动作触发';
+
+$lang->measurement->buildinParams = new stdclass;
+$lang->measurement->buildinParams->program = '项目';
+$lang->measurement->buildinParams->day = '日期';
+
+$lang->measurement->codeExistence = '度量代号:%s已存在。';
+$lang->measurement->codeEmpty = 'ID:%s的度量代号不能为空。';
+$lang->measurement->nameEmpty = 'ID:%s的度量名称不能为空。';
+$lang->measurement->unitEmpty = 'ID:%s的度量单位不能为空。';
+$lang->measurement->definitionEmpty = 'ID:%s的度量定义不能为空。';
+
+$lang->measurement->noticeScope = '通知范围';
+$lang->measurement->design = '设计';
+$lang->measurement->browse = '浏览列表';
+$lang->measurement->browseBasic = '基本度量';
+$lang->measurement->browseDerivation = '衍生度量';
+$lang->measurement->create = '创建新度量';
+$lang->measurement->createBasic = $lang->measurement->create;
+$lang->measurement->editBasic = '编辑基本度量';
+$lang->measurement->editDerivation = '编辑衍生度量';
+$lang->measurement->delete = '删除';
+$lang->measurement->deleted = '已删除';
+$lang->measurement->collectType = '收集方式';
+$lang->measurement->collectConf = '收集配置';
+$lang->measurement->collectedBy = '收集人';
+$lang->measurement->unit = '度量单位';
+$lang->measurement->save = '保存';
+$lang->measurement->saveSuccess = '保存成功';
+$lang->measurement->reDesign = '重新设计';
+$lang->measurement->confirmDelete = '确定删除吗?';
+$lang->measurement->options = '操作';
+$lang->measurement->id = '编号';
+$lang->measurement->createTemplate = '创建复合模板';
+$lang->measurement->createSingle = '创建单一模板';
+$lang->measurement->editTemplate = '编辑模板';
+$lang->measurement->viewTemplate = '查看模板';
+$lang->measurement->content = '内容';
+$lang->measurement->addMeas = '添加度量项';
+$lang->measurement->dataSource = '数据源';
+$lang->measurement->dataName = '数据标识';
+$lang->measurement->setSQL = '设置SQL语句';
+$lang->measurement->setPHP = '设置PHP代码';
+$lang->measurement->callSqlBuilder = '调用SQL构建器';
+$lang->measurement->query = '查询';
+$lang->measurement->byQuery = '搜索';
+$lang->measurement->call = '调用';
+$lang->measurement->queryResult = '查询结果:';
+$lang->measurement->setParams = '设置参数';
+$lang->measurement->createdBy = '由谁创建';
+$lang->measurement->createdDate = '创建日期';
+$lang->measurement->purpose = '目的';
+$lang->measurement->aim = '度量目标';
+$lang->measurement->analyst = '分析人';
+$lang->measurement->analysisMethod = '分析方法';
+
+$lang->measurement->placeholder = new stdclass();
+$lang->measurement->placeholder->sql = '请填写完整的mysql自定义函数语句。';
+$lang->measurement->placeholder->php = '请按照系统要求的编码,类名不能修改,必须包含get 方法。';
+$lang->measurement->codeTemplate = <<
+EOT;
+
+$lang->measurement->sqlTemplate = <<measurement->param = new stdclass();
+$lang->measurement->param->varName = '变量名';
+$lang->measurement->param->showName = '显示名';
+$lang->measurement->param->varType = '类型';
+$lang->measurement->param->defaultValue = '默认值';
+$lang->measurement->param->queryValue = '测试值';
+
+$lang->measurement->param->typeList['input'] = '文本框';
+$lang->measurement->param->typeList['date'] = '日期';
+$lang->measurement->param->typeList['select'] = '下拉菜单';
+
+$lang->measurement->param->options['project'] = '项目列表';
+$lang->measurement->param->options['product'] = $lang->productCommon . '列表';
+$lang->measurement->param->options['sprint'] = $lang->executionCommon . '列表';
+
+$lang->measurement->tips = new stdclass();
+$lang->measurement->tips->nameError = 'Mysql 自定义函数名错误,请检查函数名。';
+$lang->measurement->tips->createError = "创建 Mysql 自定义函数失败,错误信息:
%s";
+$lang->measurement->tips->noticeSelect = 'SQL语句只能是查询语句';
+$lang->measurement->tips->noticeBlack = 'SQL中含有禁用SQL关键字 %s';
+$lang->measurement->tips->noticeVarName = '变量名称没有设置';
+$lang->measurement->tips->noticeVarType = '变量 %s 的类型没有设置';
+$lang->measurement->tips->noticeShowName = '变量 %s 的显示名称没有设置';
+$lang->measurement->tips->noticeQueryValue = '变量 %s 的测试值没有设置。';
+$lang->measurement->tips->showNameMissed = '变量 %s 的显示名没有设置。';
+$lang->measurement->tips->errorSql = 'SQL语句有错!错误:';
+$lang->measurement->tips->click2SetParams = '请先点击红色变量块设置参数,然后';
+$lang->measurement->tips->view = '预览';
+$lang->measurement->tips->click2InsertData = "点击 来插入度量指标或报表";
+
+$lang->basicmeas = new stdclass();
+$lang->basicmeas->name = $lang->measurement->name;
+$lang->basicmeas->code = $lang->measurement->code;
+$lang->basicmeas->unit = $lang->measurement->unit;
+$lang->basicmeas->definition = $lang->measurement->definition;
+
+$lang->derivemeas = new stdclass();
+$lang->derivemeas->name = $lang->measurement->name;
+$lang->derivemeas->purpose = $lang->measurement->purpose;
+
+$lang->meastemplate = new stdclass();
+$lang->meastemplate->id = '编号';
+$lang->meastemplate->single = '单一模板';
+$lang->meastemplate->complex = '复合模板';
+$lang->meastemplate->name = '模板名称';
+$lang->meastemplate->desc = '描述';
+$lang->meastemplate->content = '内容';
+$lang->meastemplate->createdBy = '由谁创建';
+$lang->meastemplate->createdDate = '创建时间';
+$lang->meastemplate->addedBy = '由谁创建';
+$lang->meastemplate->addedDate = '创建时间';
+
+$lang->meastemplate->actions = array();
+$lang->measurement->actions[] = '单元测试前';
+$lang->measurement->actions[] = '测试完成后';
+$lang->measurement->actions[] = '测试报告评审结束';
+$lang->measurement->actions[] = '测试计划评审';
+$lang->measurement->actions[] = '里程碑报告评审后';
+$lang->measurement->actions[] = '里程碑评审过后';
+$lang->measurement->actions[] = '量化项目监控过程中';
+$lang->measurement->actions[] = '需求完成后';
+$lang->measurement->actions[] = '需求评审后';
+$lang->measurement->actions[] = '需求说明书编撰完成后';
+$lang->measurement->actions[] = '需求说明书评审';
+$lang->measurement->actions[] = '需求里程碑过后';
+$lang->measurement->actions[] = '项目计划评审后';
+$lang->measurement->actions[] = '项目计划评审结束';
+
+$lang->measurement->actions['project.close'] = '项目结束';
diff --git a/module/measurement/m.php b/module/measurement/m.php
new file mode 100644
index 0000000000..a99a78bfe7
--- /dev/null
+++ b/module/measurement/m.php
@@ -0,0 +1,1342 @@
+
+ * @package measurement
+ * @link https://www.zentao.net
+ */
+class measurementModel extends model
+{
+ /* SQL error message.*/
+ public $errorInfo = '';
+
+ public $programQueue = array();
+ public $productQueue = array();
+
+ /**
+ * Get by measurement id.
+ *
+ * @param int $measurementID
+ * @access public
+ * @return object
+ */
+ public function getByID(int $measurementID): object
+ {
+ $measurement = $this->dao->select('*')->from(TABLE_BASICMEAS)->where('id')->eq($measurementID)->fetch();
+ if(!$measurement) return false;
+
+ if($measurement->collectType == 'action')
+ {
+ $collectConf = json_decode($measurement->collectConf);
+ $measurement->collectConf = new stdclass();
+ $measurement->collectConf->action = $collectConf->action;
+ $measurement->collectConf->type = '';
+ $measurement->collectConf->week = '';
+ }
+ else
+ {
+ $measurement->collectConf = json_decode($measurement->collectConf);
+ if(is_object($measurement->collectConf))
+ {
+ $measurement->collectConf->module = '';
+ $measurement->collectConf->action = '';
+ }
+ }
+
+ return $measurement;
+ }
+
+ /**
+ * Get measurement list.
+ *
+ * @param string $browseType
+ * @param int $queryID
+ * @param string $orderBy
+ * @param object $pager
+ * @access public
+ * @return object
+ */
+ public function getList(string $browseType, int $queryID, string $orderBy, object $pager = null): object
+ {
+ $measurementQuery = '';
+ if($browseType == 'bysearch')
+ {
+ $query = $queryID ? $this->loadModel('search')->getQuery($queryID) : '';
+ if($query)
+ {
+ $this->session->set('measurementQuery', $query->sql);
+ $this->session->set('measurementForm', $query->form);
+ }
+
+ if($this->session->measurementQuery == false) $this->session->set('measurementQuery', ' 1 = 1');
+ $measurementQuery = $this->session->measurementQuery;
+ }
+
+ return $this->dao->select('*')->from(TABLE_BASICMEAS)
+ ->where('deleted')->eq(0)
+ ->beginIF($browseType == 'bysearch')->andWhere($measurementQuery)->fi()
+ ->orderby($orderBy)
+ ->page($pager)
+ ->fetchAll();
+ }
+
+ /**
+ * Get the basic meas key value pairs.
+ *
+ * @access public
+ * @return array
+ */
+ public function getPairs()
+ {
+ $pairs = $this->dao->select('id, name')->from(TABLE_BASICMEAS)->where('deleted')->eq(0)->orderBy('purpose, `order` desc')->fetchPairs();
+ return array('' => '') + $pairs;
+ }
+
+ /**
+ * Get the basic meas templates.
+ *
+ * @param object $pager
+ * @access public
+ * @return object
+ */
+ public function getTemplates(object $pager = null)
+ {
+ return $this->dao->select('*')->from(TABLE_MEASTEMPLATE)->page($pager)->fetchAll('id');
+ }
+
+ /**
+ * Get template by ID.
+ *
+ * @param int $templateID
+ * @access public
+ * @return object
+ */
+ public function getTemplateByID(int $templateID)
+ {
+ return $this->dao->select('*')->from(TABLE_MEASTEMPLATE)->where('id')->eq($templateID)->fetch();
+ }
+
+ /**
+ * Get the list of reports for the measurement.
+ *
+ * @param string $type
+ * @param object $pager
+ * @access public
+ * @return array
+ */
+ public function getReports(string $type, object $pager = null)
+ {
+ if($type == 'pairs') $pager = null;
+ $reports = $this->loadModel('report')->getReportList('cmmi', '', $pager);
+
+ $pairs = array();
+ foreach($reports as $report)
+ {
+ $name = json_decode($report->name, true);
+ if(!is_array($name) or empty($name)) $name[$this->app->getClientLang()] = $report->name;
+ $reportName = !isset($name[$this->app->getClientLang()]) ? $name['en'] : $name[$this->app->getClientLang()];
+ $report->name = $reportName;
+
+ if($type == 'pairs') $pairs[$report->id] = $reportName;
+ }
+
+ if($type == 'pairs') return array('' => '') + $pairs;
+
+ return $reports;
+ }
+
+ /**
+ * Get parameters in SQL statement.
+ *
+ * @param string $sql
+ * @access public
+ * @return array
+ */
+ public function getSqlVars(string $sql)
+ {
+ $sql = trim(stripslashes(trim($sql)), ';') . ' ';
+ preg_match_all('/\$(\w+)/i', $sql, $out);
+ return array_unique($out[1]);
+ }
+
+ /**
+ * Get the value of the basic measure by ID.
+ *
+ * @param int $measurementID
+ * @param string $params
+ * @access public
+ * @return string
+ */
+ public function getBasicMeasValue(int $measurementID, string $params = '')
+ {
+ $measurement = $this->getByID($measurementID);
+ return $this->execSqlMeasurement($measurement, $params);
+ }
+
+ /**
+ * Get unexecuted queues.
+ *
+ * @access public
+ * @return object
+ */
+ public function getUnexecutedQueues()
+ {
+ return $this->dao->select('*')->from(TABLE_MEASQUEUE)->where('status')->eq('')->andWhere('deleted')->eq('0')->fetchAll();
+ }
+
+ /**
+ * Get the key value pairs of all modules.
+ *
+ * @access public
+ * @return array
+ */
+ public function getModulePairs()
+ {
+ $this->app->loadLang('group');
+
+ $pairs = array();
+ foreach($this->lang->resource as $moduleName => $module)
+ {
+ if(isset($this->lang->$moduleName) and isset($this->lang->$moduleName->common))
+ {
+ $pairs[$moduleName] = $this->lang->$moduleName->common;
+ }
+ else
+ {
+ $pairs[$moduleName] = $moduleName;
+ }
+ }
+
+ return array('' => '') + $pairs;
+ }
+
+ /**
+ * Get module actions.
+ *
+ * @param string $moduleName
+ * @access public
+ * @return array
+ */
+ public function getModuleActions($moduleName = '')
+ {
+ $this->app->loadLang('group');
+ $this->app->loadLang($moduleName);
+ $pairs = array();
+ foreach($this->lang->resource->$moduleName as $action => $actionName)
+ {
+ $pairs[$action] = $this->lang->$moduleName->$actionName;
+ }
+ return $pairs;
+ }
+
+ /**
+ * Get trigger options.
+ *
+ * @access public
+ * @return array
+ */
+ public function getTriggerOptions()
+ {
+ $this->app->loadLang('group');
+ $options = array();
+ foreach($this->config->measurement->triggers as $trigger)
+ {
+ list($module, $method) = explode('.', $trigger);
+ $this->app->loadLang($module);
+ $methodAlias = $method;
+ $text = $this->lang->{$module}->common;
+ if(isset($this->lang->resource->$module) and isset($this->lang->resource->$module->$method)) $methodAlias = $this->lang->resource->$module->$method;
+ $text .= '/' . $this->lang->$module->$methodAlias;
+ $options[$trigger] = $text;
+ }
+ $options = array_merge($this->lang->measurement->actions, $options);
+ return $options;
+ }
+
+ /**
+ * Create derived measures.
+ *
+ * @access public
+ * @return int
+ */
+ public function createDerivation()
+ {
+ $data = fixer::input('post')
+ ->add('createdBy', $this->app->user->account)
+ ->add('createdDate', helper::now())
+ ->add('deleted', 0)
+ ->remove('definitionMethods')
+ ->get();
+
+ $data = $this->processCollectConf($data);
+ $data->definition = $this->post->definitionMethods;
+
+ $this->dao->insert(TABLE_DERIVEMEAS)->data($data)
+ ->autoCheck()
+ ->batchCheck($this->config->measurement->createderivation->requiredFields,'notempty')
+ ->exec();
+
+ if(!dao::isError())return $this->dao->lastInsertID();
+ }
+
+ /**
+ * Edit derived measures.
+ *
+ * @param int $measurementID
+ * @access public
+ * @return int
+ */
+ public function editDerivation($measurementID)
+ {
+ $data = fixer::input('post')
+ ->add('editedBy', $this->app->user->account)
+ ->add('editedDate', helper::now())
+ ->remove('measurementID,definitionMethods')
+ ->get();
+
+ $data = $this->processCollectConf($data);
+ $data->definition = $this->post->definitionMethods;
+
+ $this->dao->update(TABLE_DERIVEMEAS)
+ ->data($data)
+ ->autoCheck()
+ ->batchCheck($this->config->measurement->editderivation->requiredFields, 'notempty')
+ ->where('id')->eq((int)$measurementID)
+ ->exec();
+
+ if(!dao::isError()) return $measurementID;
+ }
+
+ /**
+ * Create basic measures.
+ *
+ * @access public
+ * @return int
+ */
+ public function createBasic()
+ {
+ $data = fixer::input('post')
+ ->add('createdBy', $this->app->user->account)
+ ->add('createdDate', helper::now())
+ ->get();
+
+ $data = $this->processCollectConf($data);
+
+ $this->dao->insert(TABLE_BASICMEAS)->data($data)
+ ->autoCheck()
+ ->batchCheck($this->config->measurement->createbasic->requiredFields, 'notempty')
+ ->checkIF(!empty($data->code), 'code', 'unique')
+ ->exec();
+
+ if(!dao::isError())
+ {
+ $insertID = $this->dao->lastInsertID();
+ $order = $insertID * 5;
+ $this->dao->update(TABLE_BASICMEAS)->set('order')->eq($order)->where('id')->eq($insertID)->exec();
+ return $insertID;
+ }
+ }
+
+ /**
+ * Edit basic measures.
+ *
+ * @param int $measurementID
+ * @access public
+ * @return int
+ */
+ public function editBasic($measurementID)
+ {
+ $data = fixer::input('post')
+ ->add('editedBy', $this->app->user->account)
+ ->add('editedDate', helper::now())
+ ->remove('measurementID')
+ ->get();
+
+ $data = $this->processCollectConf($data);
+ $this->dao->update(TABLE_BASICMEAS)
+ ->data($data)
+ ->autoCheck()
+ ->batchCheck($this->config->measurement->editbasic->requiredFields, 'notempty')
+ ->where('id')->eq((int)$measurementID)
+ ->exec();
+
+ if(!dao::isError()) return $measurementID;
+ }
+
+ /**
+ * Batch edit basic measurement.
+ *
+ * @access public
+ * @return bool
+ */
+ public function batchEdit()
+ {
+ $data = fixer::input('post')->get();
+ $oldMeasurement = $this->dao->select('id,code,name')->from(TABLE_BASICMEAS)->where('id')->in(array_keys($data->code))->fetchAll('id');
+ $newMeasurement = array();
+ $account = $this->app->user->account;
+ $editDate = helper::now();
+
+ $codeList = array();
+ foreach($data->code as $id => $code)
+ {
+ $newMeasurement[$id]['code'] = trim($code);
+ $newMeasurement[$id]['purpose'] = $data->purpose[$id];
+ $newMeasurement[$id]['scope'] = $data->scope[$id];
+ $newMeasurement[$id]['object'] = $data->object[$id];
+ $newMeasurement[$id]['name'] = trim($data->name[$id]);
+ $newMeasurement[$id]['unit'] = trim($data->unit[$id]);
+ $newMeasurement[$id]['definition'] = trim($data->definition[$id]);
+ $newMeasurement[$id]['editedBy'] = $account;
+ $newMeasurement[$id]['editedDate'] = $editDate;
+
+ if(in_array($code, $codeList)) die(js::error(sprintf($this->lang->measurement->codeExistence, $code)));
+ if(empty($code)) die(js::error(sprintf($this->lang->measurement->codeEmpty, $id)));
+ if(empty($data->name[$id])) die(js::error(sprintf($this->lang->measurement->nameEmpty, $id)));
+ if(empty($data->unit[$id])) die(js::error(sprintf($this->lang->measurement->unitEmpty, $id)));
+ if(empty($data->definition[$id])) die(js::error(sprintf($this->lang->measurement->definitionEmpty, $id)));
+ $codeList[$id] = $code;
+ }
+
+ foreach($newMeasurement as $id => $measurement)
+ {
+ $this->dao->update(TABLE_BASICMEAS)->data($measurement)->check('code', 'unique', "id != $id")->batchCheck($this->config->measurement->editbasic->requiredFields, 'notempty')->where('id')->eq($id)->exec();
+ if(dao::isError()) return false;
+ }
+ return true;
+ }
+
+ /**
+ * Process timing configuration.
+ *
+ * @param object $data
+ * @access public
+ * @return object
+ */
+ public function processCollectConf($data)
+ {
+ if($data->collectType == 'crontab')
+ {
+ if($data->config['type'] == 'week')
+ {
+ unset($data->config['month']);
+ if(isset($data->config['week'])) $data->config['week'] = join(',', $data->config['week']);
+ }
+ if($data->config['type'] == 'month')
+ {
+ unset($data->config['week']);
+ if(isset($data->config['month'])) $data->config['month'] = join(',', $data->config['month']);
+ }
+
+ $data->collectConf = json_encode($data->config);
+ }
+ else
+ {
+ $data->collectConf = json_encode(array('action' => $this->post->action));
+ }
+
+ unset($data->config);
+ unset($data->module);
+ unset($data->action);
+
+ return $data;
+ }
+
+ /**
+ * Init crontab queue from measurement table.
+ *
+ * @access public
+ * @return void
+ */
+ public function initCrontabQueue()
+ {
+ $measList = $this->dao->select('*')->from(TABLE_BASICMEAS)
+ ->where('deleted')->eq(0)
+ ->andWhere('collectType')->eq('crontab')
+ ->fetchAll('id');
+
+ foreach($measList as $meas) $this->createMeasQueue($meas);
+ }
+
+ /**
+ * Create meas queue.
+ *
+ * @param object $meas
+ * @param int $program
+ * @param int $product
+ * @param int $project
+ * @access public
+ * @return void
+ */
+ public function createMeasQueue(object $meas, int $program = 0, int $product = 0, int $project = 0)
+ {
+ if(empty($this->programQueue)) $this->programQueue = $this->getProgramQueue();
+ if(empty($this->productQueue)) $this->productQueue = $this->getProductQueue();
+
+ $day = date('d');
+ $week = date('w');
+ $collectConf = json_decode($meas->collectConf);
+
+ $params = json_decode($meas->params);
+
+ if(isset($collectConf->week) or isset($collectConf->month))
+ {
+ $queue = new stdclass();
+ $queue->type = $collectConf->type;
+ $queue->mid = $meas->id;
+ $queue->execTime = $meas->execTime;
+ $queue->createdDate = helper::now();
+
+ foreach($this->productQueue as $programID => $productList)
+ {
+ if($meas->scope != 'product') continue;
+
+ $params = array();
+ $params['project'] = $programID;
+
+ foreach($productList as $productID)
+ {
+ $params['product'] = $productID;
+ $queue->params = json_encode($params);
+ $this->dao->insert(TABLE_MEASQUEUE)->data($queue)->exec();
+ }
+ }
+
+ foreach($this->programQueue as $projectID => $projectIdList)
+ {
+ if($meas->scope != 'project') continue;
+
+ $params = array();
+ $params['project'] = $projectID;
+
+ $queue->params = json_encode($params);
+ $this->dao->insert(TABLE_MEASQUEUE)->data($queue)->exec();
+ }
+ }
+ }
+
+ /**
+ * Exec queue.
+ *
+ * @param object $queue
+ * @access public
+ * @return void
+ */
+ public function execQueue(object $queue)
+ {
+ /* Create basic meas recrods. */
+ $params = json_decode($queue->params);
+ $meas = $this->getByID($queue->mid);
+ $this->createBasicMeasRecord($meas, $params, $queue);
+ $this->dao->update(TABLE_MEASQUEUE)->set('status')->eq('executed')->where('id')->eq($queue->id)->exec();
+ }
+
+ /**
+ * Create meas record.
+ *
+ * @param object $meas
+ * @param object $params
+ * @param object $queue
+ * @access public
+ * @return void
+ */
+ public function createBasicMeasRecord(object $meas, object $params, object $queue)
+ {
+ $record = new stdclass();
+ $record->type = $queue->type;
+ $record->mid = $queue->mid;
+ $record->date = helper::today();
+ $record->year = date('Y');
+ $record->month = date('Ym');
+ $record->week = date('W');
+ $record->day = date('Ymd');
+ $record->value = $this->getBasicMeasValue($meas->id, $params);
+ $record->measCode = $meas->code;
+ $record->params = json_encode($params);
+
+ if(is_object($params))
+ {
+ $record->project = isset($params->project) ? $params->project : 0;
+ }
+ elseif(is_array($params))
+ {
+ $record->project = isset($params['project']) ? $params['project'] : 0;
+ }
+
+ $this->dao->insert(TABLE_MEASRECORDS)->data($record)->exec();
+ }
+
+ /**
+ * Create basic meas record by action.
+ *
+ * @param object $meas
+ * @param object $record
+ * @param string $pp
+ * @access public
+ * @return void
+ */
+ public function createBasicMeasRecordByAction(object $meas, object $record, string $pp = '')
+ {
+ $params = json_decode($meas->params);
+ if(empty($params))
+ {
+ $record->value = $this->getBasicMeasValue($meas->id);
+ $record = $this->dao->select('*')->from(TABLE_MEASRECORDS)->where('type')->eq('basic')->andWhere('mid')->eq($meas->id)->fetch();
+ if(!empty($record)) return;
+
+ $this->dao->insert(TABLE_MEASRECORDS)->data($record)->exec();
+ }
+ else
+ {
+ foreach($params as $param)
+ {
+ $hasProgramParam = ($param->varName == 'project') ? true : false;
+ $hasProductParam = ($param->varName == 'product') ? true : false;
+ $hasProjectParam = ($param->varName == 'sprint') ? true : false;
+ }
+
+ foreach($pp as $queryParams)
+ {
+ if($hasProgramParam) $record->project = $queryParams['project'];
+ if($hasProductParam) $record->product = $queryParams['product'];
+ if($hasProjectParam) $record->sprint = $queryParams['sprint'];
+
+ $record = $this->dao->select('*')->from(TABLE_MEASRECORDS)
+ ->where('type')->eq('basic')
+ ->andWhere('mid')->eq($meas->id)
+ ->beginIF($hasProgramParam)->andWhere('project')->eq($record->project)->fi()
+ ->beginIF($hasProductParam)->andWhere('product')->eq($record->product)->fi()
+ ->beginIF($hasProjectParam)->andWhere('sprint')->eq($record->sprint)->fi()
+ ->fetch();
+ if(!empty($record)) continue;
+
+ $record->value = $this->getBasicMeasValue($meas->id, $queryParams);
+ $this->dao->insert(TABLE_MEASRECORDS)->data($record)->exec();
+ }
+ }
+ }
+
+ /**
+ * Get program list for measurement queue.
+ *
+ * @access public
+ * @return object
+ */
+ public function getProgramQueue()
+ {
+ return $this->dao->select('id')->from(TABLE_PROJECT)->where('type')->eq('project')->andWhere('deleted')->eq('0')->fetchAll('id');
+ }
+
+ /**
+ * Get product list for measurement queue.
+ *
+ * @access public
+ * @return array
+ */
+ public function getProductQueue()
+ {
+ return $this->dao->select('program,id')->from(TABLE_PRODUCT)->where('deleted')->eq('0')->fetchGroup('program', 'id');
+ }
+
+ /**
+ * Save template.
+ *
+ * @access public
+ * @return int
+ */
+ public function saveTemplate()
+ {
+ $attributes = array();
+ $attributes[] = 'span|data-holder|Text';
+ $attributes[] = 'span|contenteditable|Text';
+ $attributes[] = 'span|data-holder-edit|Text';
+ $attributes[] = 'div|data-holder|Text';
+ $attributes[] = 'div|contenteditable|Text';
+ $attributes[] = 'div|data-holder-edit|Text';
+
+ $data = fixer::input('post')
+ ->add('createdBy', $this->app->user->account)
+ ->add('createdDate', helper::today())
+ ->stripTags($this->config->measurement->editor->createtemplate['id'], $this->config->allowedTags, $attributes)
+ ->remove('uid')
+ ->get();
+
+ $this->dao->insert(TABLE_MEASTEMPLATE)->data($data)
+ ->autoCheck()
+ ->batchCheck($this->config->measurement->createtemplate->requiredFields,'notempty')
+ ->exec();
+
+ if(!dao::isError()) return $this->dao->lastInsertID();
+ }
+
+ /**
+ * Edit template.
+ *
+ * @param int $templateID
+ * @access public
+ * @return array
+ */
+ public function editTemplate(int $templateID)
+ {
+ $attributes = array();
+ $attributes[] = 'span|data-holder|Text';
+ $attributes[] = 'span|contenteditable|Text';
+ $attributes[] = 'span|data-holder-edit|Text';
+ $attributes[] = 'div|data-holder|Text';
+ $attributes[] = 'div|contenteditable|Text';
+ $attributes[] = 'div|data-holder-edit|Text';
+
+ $oldTemplate = $this->getTemplateByID($templateID);
+ $template = fixer::input('post')
+ ->stripTags($this->config->measurement->editor->edittemplate['id'], $this->config->allowedTags, $attributes)
+ ->remove('uid')
+ ->get();
+
+ $this->dao->update(TABLE_MEASTEMPLATE)->data($template)
+ ->where('id')->eq($templateID)
+ ->autoCheck()
+ ->batchCheck($this->config->measurement->edittemplate->requiredFields,'notempty')
+ ->exec();
+
+ if(!dao::isError()) return common::createChanges($oldTemplate, $template);
+ }
+
+ /**
+ * Build param control of one param of measurement or report.
+ *
+ * @param string $name
+ * @param string $controlType
+ * @param string $value
+ * @param string $optionType
+ * @param string $controlID
+ * @param string $mode
+ * @access public
+ * @return string
+ */
+ public function buildParamControl(string $name, string $controlType, string $value = '', string $optionType = '', string $controlID = '', string $mode = 'design')
+ {
+ if($mode == 'design') $name = $name . '[]';
+ if($mode == 'report') $name = "params[$controlID][$name]";
+ if($controlType == 'date')
+ {
+ $control = html::input($name, $value, "class='form-control form-date' data-name='$name' data-control='$controlID'");
+ }
+ elseif($controlType == 'select')
+ {
+ $options = $this->getControlOptions($optionType);
+ $control = html::select($name, $options, $value, "class='form-control chosen' data-name=$name data-control=$controlID");
+ }
+ else
+ {
+ $control = html::input($name, $value, "class='form-control' data-name=$name data-control=$controlID");
+ }
+
+ return $control;
+ }
+
+ /**
+ * Build param control of one param of measurement or report.
+ *
+ * @param string $param
+ * @param object $setting
+ * @access public
+ * @return string
+ */
+ public function showParam(string $param, object $setting)
+ {
+ if($setting->varType == 'select')
+ {
+ $options = $this->getControlOptions($setting->options);
+ return zget($options, $param, '');
+ }
+
+ return $param;
+ }
+
+ /**
+ * Get opertions of one control.
+ *
+ * @param string $optionType
+ * @access public
+ * @return array
+ */
+ public function getControlOptions($optionType)
+ {
+ static $optionList = array();
+
+ $options = array();
+ if(!isset($optionList[$optionType]))
+ {
+ if($optionType == 'project')
+ {
+ $options = array('' => '') + $this->loadModel('project')->getPairsByProgram();
+ }
+ elseif($optionType == 'product')
+ {
+ $options = array('' => '') + $this->loadModel('product')->getPairs('nocode');
+ }
+ elseif($optionType == 'sprint')
+ {
+ $options = array('' => '') + $this->loadModel('execution')->getPairs();
+ }
+ elseif($optionType == 'user')
+ {
+ $options = $this->loadModel('user')->getPairs('noletter');
+ }
+ elseif(strpos($optionType, '.') !== false)
+ {
+ list($moduleName, $varListName) = explode('.', $optionType);
+ $this->app->loadLang($moduleName);
+ $varListName .= 'List';
+ $options = $this->lang->$moduleName->$varListName;
+ unset($options[0]);
+ unset($options['']);
+ $options = array('' => '') + $options;
+ }
+ else
+ {
+ $options = array('' => '');
+ }
+
+ $optionList[$optionType] = $options;
+ }
+ else
+ {
+ $options = $optionList[$optionType];
+ }
+
+ return $options;
+ }
+
+ /**
+ * Get params of an object parsed from holder.
+ *
+ * @param object $sysData
+ * @access public
+ * @return array
+ */
+ public function getParams($sysData)
+ {
+ if($sysData->type == 'basic')
+ {
+ $measurement = $this->getByID($sysData->measurementID);
+ return $measurement ? json_decode($measurement->params, true) : array();
+ }
+
+ if($sysData->type == 'report') return $this->getReportParams($sysData->reportID);
+ }
+
+ /**
+ * Get report's params.
+ *
+ * @param int $reportID
+ * @access public
+ * @return array
+ */
+ public function getReportParams($reportID)
+ {
+ $params = array();
+ $report = $this->loadModel('report')->getReportByID($reportID);
+ $vars = json_decode($report->vars, true);
+
+ if($vars and isset($vars['varName']))
+ {
+ foreach($vars['varName'] as $key => $varName)
+ {
+ $param = array();
+ $param['varName'] = $varName;
+ $param['showName'] = isset($vars['showName'][$key]) ? $vars['showName'][$key] : '';
+ $param['varType'] = isset($vars['requestType'][$key]) ? $vars['requestType'][$key] : '';
+ $param['options'] = isset($vars['selectList'][$key]) ? $vars['selectList'][$key] : '';
+ $param['default'] = isset($vars['default'][$key]) ? $vars['default'][$key] : '';
+
+ $params[] = $param;
+ }
+ }
+ return $params;
+ }
+
+ /**
+ * Parse measurement or report from holdervalue.
+ *
+ * @param string $holderValue
+ * @access public
+ * @return object
+ */
+ public function parseHolderValue($holderValue)
+ {
+ $holderValue = ltrim($holderValue, '{');
+ $holderValue = rtrim($holderValue, '}');
+
+ $sysData = new stdclass();
+
+ list($type, $value) = explode(':', $holderValue);
+ list($sysData->type, $sysData->controlID) = explode('_', $type);
+ if($sysData->type == 'report')
+ {
+ list($sysData->reportType, $sysData->reportID) = explode('.', $value);
+ }
+ else
+ {
+ list($sysData->type, $sysData->measurementID) = explode('.', $value);
+ }
+
+ return $sysData;
+ }
+
+ /**
+ * Parsing template content.
+ *
+ * @param string $content
+ * @access public
+ * @return content
+ */
+ public function parseTemplateContent($content)
+ {
+ $content = $this->parseMeasurementContent($content);
+ $content = $this->parseReportContent($content);
+
+ return $content;
+ }
+
+ /**
+ * Parse measurement content with post params.
+ *
+ * @param string $content
+ * @access public
+ * @return content
+ */
+ public function parseMeasurementContent($content)
+ {
+ $pattern = '/\{measurement_.*\:\w+\.\d+\}/U';
+ preg_match_all($pattern, $content, $matches);
+
+ $matches = isset($matches[0]) ? $matches[0] : array();
+ if(!empty($matches))
+ {
+ foreach($matches as $match)
+ {
+ $measurement = $this->parseHolderValue($match);
+ if($measurement->type == 'basic')
+ {
+ $params = zget($this->post->params, $measurement->controlID);
+ $replace[] = $this->getBasicMeasValue($measurement->measurementID, $params);
+ }
+ else
+ {
+ $replace[] = $this->getDeriveMeasValue($measurement->measurementID, $measurement->controlID);
+ }
+ }
+
+ $content = str_replace($matches, $replace, $content);
+ }
+
+ return $content;
+ }
+
+ /**
+ * Parse report content with post params.
+ *
+ * @param string $content
+ * @access public
+ * @return string
+ */
+ public function parseReportContent($content)
+ {
+ $pattern = '/\{report_.*\:\w+\.\d+\}/U';
+ preg_match_all($pattern, $content, $matches);
+
+ $matches = isset($matches[0]) ? $matches[0] : array();
+ if(!empty($matches))
+ {
+ foreach($matches as $match)
+ {
+ $param = $this->parseHolderValue($match);
+
+ $replace[] = $this->getReportContent($param->reportID, $param->controlID);
+ }
+
+ $content = str_replace($matches, $replace, $content);
+ }
+ return $content;
+ }
+
+ /**
+ * Get report content.
+ *
+ * @param int $reportID
+ * @param int $controlID
+ * @access public
+ * @return void
+ */
+ public function getReportContent($reportID, $controlID)
+ {
+ global $lang;
+ $report = $this->loadModel('report')->getReportByID($reportID);
+ $sql = $report->sql;
+ $params = $this->getReportParams($reportID);
+
+ $this->session->set('sqlLangs', $report->langs);
+
+ foreach($params as $param)
+ {
+ $varName = $param['varName'];
+ $paramValues = zget($this->post->params, $controlID, '');
+ $paramValue = zget($paramValues, $param['varName'], '');
+ $sql = str_replace('$' . $param['varName'], $this->dbh->quote($paramValue), $sql);
+ }
+
+ $sql = $this->report->replaceTableNames($sql);
+ $tableAndField = $this->report->getTables($sql);
+ $tables = $tableAndField['tables'];
+ $fields = $tableAndField['fields'];
+ $dataList = $this->dao->query($sql)->fetchAll();
+
+ if(empty($dataList)) return '';
+
+ $moduleNames = array();
+ foreach($tables as $table)
+ {
+ if(strpos($table, $this->config->db->prefix) === false) continue;
+ $module = str_replace($this->config->db->prefix, '', $table);
+ if($module == 'case') $module = 'testcase';
+ if($module == 'module') $module = 'tree';
+
+ /* Code for workflow.*/
+ if(strpos($module, 'flow_') !== false)
+ {
+ $moduleName = substr($module, 5);
+ $flowFields = $this->loadModel('workflowfield')->getFieldPairs($moduleName);
+ $this->lang->$moduleName = new stdclass();
+
+ foreach($flowFields as $flowField => $fieldName)
+ {
+ if(!$flowField) continue;
+ $this->lang->$moduleName->$flowField = $fieldName;
+ }
+ $moduleNames[$table] = $module;
+ }
+ else
+ {
+ if($this->app->loadLang($module)) $moduleNames[$table] = $module;
+ }
+ }
+
+ $step = $report->step;
+ $data = (array)current($dataList);
+ $moduleNames = array_reverse($moduleNames, true);
+ $reverseFields = array_reverse($fields, true);
+ $mergeFields = $this->report->mergeFields(array_keys($data), $reverseFields, $moduleNames);
+
+ if($step == 2)
+ {
+ $condition = json_decode($report->params, true);
+ if(!empty($condition['isUser'])) $this->view->users = $this->loadModel('user')->getPairs('noletter');
+
+ $groupLang['group1'] = $this->report->getGroupLang($condition['group1'], $reverseFields, $moduleNames);
+ $groupLang['group2'] = $this->report->getGroupLang($condition['group2'], $reverseFields, $moduleNames);
+ list($headers, $reportData) = $this->report->processData($dataList, $condition);
+
+ $headerNames = $this->report->getHeaderNames($fields, $moduleNames, $condition);
+ $headers = $headers;
+ $condition = $condition;
+ $reportData = $reportData;
+ $groupLang = $groupLang;
+ }
+
+ $fields = empty($mergeFields) ? array() : $mergeFields;
+
+ ob_start();
+ include '../report/ext/view/reportdata.html.php';
+ $reportContent = ob_get_contents();
+ ob_clean();
+ return $reportContent;
+ }
+
+ /**
+ * Get components from template.
+ *
+ * @param string $content
+ * @access public
+ * @return array
+ */
+ public function getTemplateComponents($content)
+ {
+ $pattern = '/\{measurement_.*\:\w+\.\d+\}/U';
+ preg_match_all($pattern, $content, $matches);
+ $components = array();
+ foreach($matches[0] as $holder) $components[] = $this->parseHolderValue($holder);
+ $pattern = '/\{report_.*\:\w+\.\d+\}/U';
+ preg_match_all($pattern, $content, $matches);
+ foreach($matches[0] as $holder) $components[] = $this->parseHolderValue($holder);
+ return $components;
+ }
+
+ /**
+ * Get templatePairs.
+ *
+ * @access public
+ * @return array
+ */
+ public function getTemplatePairs($model = '')
+ {
+ return $this->dao->select('id, name')->from(TABLE_MEASTEMPLATE)
+ ->where('deleted')->eq(0)
+ ->beginIF($model)->andWhere('model')->eq($model)->fi()
+ ->fetchPairs();
+ }
+
+ /**
+ * Get measure file.
+ *
+ * @param int $measurementID
+ * @access public
+ * @return string
+ */
+ public function getMeasureFile($measurementID)
+ {
+ if(!$measurementID) return false;
+ return $this->app->getTmpRoot() . 'model' . DS . "ext_{$measurementID}.php";
+ }
+
+ /**
+ * Get measure class.
+ *
+ * @param int $measurementID
+ * @access public
+ * @return string
+ */
+ public function getMeasureClass($measurementID)
+ {
+ if(!$measurementID) return false;
+ return "ext_{$measurementID}";
+ }
+
+ /**
+ * Process post params.
+ *
+ * @access public
+ * @return array
+ */
+ public function processPostParams()
+ {
+ return array_combine($this->post->varName, $this->post->queryValue);
+ }
+
+ /**
+ * Create sql function.
+ *
+ * @param string $sql
+ * @param object $measurement
+ * @access public
+ * @return array
+ */
+ public function createSqlFunction($sql, $measurement)
+ {
+ $measFunction = $this->getSqlFunctionName($measurement);
+ $postFunction = $this->parseSqlFunction($sql);
+ if(!$measFunction or !$postFunction) return array('result' => 'fail', 'errors' => $this->lang->measurement->tips->nameError);
+
+ $sql = str_replace($postFunction, $measFunction, $sql);
+
+ try
+ {
+ $this->dbh->exec("DROP FUNCTION IF EXISTS `$measFunction`");
+ $result = $this->dbh->exec($sql);
+ }
+ catch(PDOException $exception)
+ {
+ $message = sprintf($this->lang->measurement->tips->createError, $exception->getMessage());
+ return array('result' => 'fail', 'errors' => $message);
+ }
+
+ return array('result' => 'success');
+ }
+
+ /**
+ * Parsing SQL function.
+ *
+ * @param string $sql
+ * @access public
+ * @return string
+ */
+ public function parseSqlFunction($sql)
+ {
+ $pattern = "/create\s+function\s+`{0,1}([\$,a-z,A-z,_,0-9,\(,|)]+`{0,1})\(+/Ui";
+ preg_match_all($pattern, $sql, $matches);
+
+ if(empty($matches[1][0])) return null;
+ return trim($matches[1][0], '`');
+ }
+
+ /**
+ * Parsing SQL params.
+ *
+ * @param string $sql
+ * @access public
+ * @return array
+ */
+ public function parseSqlParams($sql)
+ {
+ $pattern = "/create\s+function.*\(([\$,a-z,A-z,_\,\(,\(),\s,0-9]+)\)+/Ui";
+ preg_match_all($pattern, $sql, $matches);
+ if(empty($matches[1][0])) return array();
+
+ $paramItem = array();
+ $paramItem['showName'] = '';
+ $paramItem['varType'] = '';
+ $paramItem['options'] = '';
+ $paramItem['defaultValue'] = '';
+ $paramItem['queryValue'] = '';
+
+ $paramList = explode(',', $matches[1][0]);
+ foreach($paramList as $param)
+ {
+ $param = trim($param);
+ $paramItem['varName'] = substr($param, 0, strpos($param, ' '));
+ $params[] = $paramItem;
+ }
+ return $params;
+ }
+
+ /**
+ * Get sql function name of a measurement.
+ *
+ * @param object $measurement
+ * @access public
+ * @return string
+ */
+ public function getSqlFunctionName($measurement)
+ {
+ if(!$measurement) return '';
+ return strtolower("qc_{$measurement->code}");
+ }
+
+ /**
+ * Exec a sql measurement.
+ *
+ * @param object $measurement
+ * @param array $vars
+ * @access public
+ * @return int|string
+ */
+ public function execSqlMeasurement($measurement, $vars, $action = 'save')
+ {
+ $function = $this->getSqlFunctionName($measurement);
+ if(!$function)
+ {
+ $this->errorInfo = $this->lang->measurement->tips->nameError;
+ return false;
+ }
+
+ $vars = (array) $vars;
+ foreach($vars as $key => $param)
+ {
+ if(is_object($param))
+ {
+ unset($vars[$key]);
+ continue;
+ }
+
+ $vars[$key] = $this->dbh->quote($param);
+ }
+
+ $params = join(',', $vars);
+
+ try
+ {
+ $result = $this->dbh->query("select $function($params)")->fetch(PDO::FETCH_NUM);
+ }
+ catch(PDOException $exception)
+ {
+ $this->errorInfo = $exception->getMessage();
+ return false;
+ }
+
+ $queryResult = isset($result[0]) . $measurement->unit ? $result[0] : null;
+ if($action == 'save') $this->loadModel('measrecord')->save($measurement, $vars, $queryResult);
+ return $queryResult;
+ }
+
+ /**
+ * Build template form.
+ *
+ * @param string $components
+ * @access public
+ * @return string
+ */
+ public function buildTemplateForm($components)
+ {
+ $form = "";
+ foreach($components as $component)
+ {
+ $params = $this->getParams($component);
+ if($component->type == 'basic')
+ {
+ $measurement = $this->getByID($component->measurementID);
+
+ if(!empty($params))
+ {
+ $form .="