model.php 91 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945
  1. <?php
  2. use function zin\wg;
  3. /**
  4. * The model file of metric module of ZenTaoPMS.
  5. *
  6. * @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.cnezsoft.com)
  7. * @license ZPL(http://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
  8. * @author zhouxin <zhouxin@easycorp.ltd>
  9. * @package metric
  10. * @version $Id: model.php 5145 2013-07-15 06:47:26Z zhouxin@easycorp.ltd $
  11. * @link http://www.zentao.net
  12. */
  13. class metricModel extends model
  14. {
  15. public $errorInfo = '';
  16. /**
  17. * 获取度量数据表的表头。
  18. * Get header of result table in view.
  19. *
  20. * @param object $metric
  21. * @access public
  22. * @return array
  23. */
  24. public function getViewTableHeader($metric)
  25. {
  26. $dataFields = $this->getMetricRecordDateField($metric);
  27. $dateType = $this->getDateTypeByCode($metric->code);
  28. $dataFields[] = 'id';
  29. $dataFields[] = 'value';
  30. $dataFields[] = 'date';
  31. $dataFieldStr = implode(',', $dataFields);
  32. $header = array();
  33. if($metric->scope != 'system')
  34. {
  35. $scope = $metric->scope;
  36. $header[] = array('name' => 'scope', 'title' => $this->lang->metric->tableHeader[$scope], 'width' => 159);
  37. }
  38. if($dateType != 'nodate') $header[] = array('name' => 'date', 'title' => $this->lang->metric->date, 'width' => 96);
  39. $header[] = array('name' => 'value', 'title' => $this->lang->metric->value, 'width' => 96);
  40. $header[] = array('name' => 'calcTime', 'title' => $this->lang->metric->calcTime, 'width' => 128);
  41. return $header;
  42. }
  43. /**
  44. * 获取度量数据表的数据。
  45. * Get data of result table.
  46. *
  47. * @param object $metric
  48. * @param array $result
  49. * @access public
  50. * @return array|false
  51. */
  52. public function getViewTableData($metric, $result)
  53. {
  54. if(empty($result)) return array();
  55. $scope = $metric->scope;
  56. $dateType = $this->getDateTypeByCode($metric->code);
  57. if($scope != 'system') $objectPairs = $this->getPairsByScope($scope);
  58. $tableData = array();
  59. foreach($result as $record)
  60. {
  61. $record = (array)$record;
  62. if(isset($record['date'])) $record['calcTime'] = date("Y-m-d H:i", strtotime($record['date']));
  63. $row = $this->buildDateCell($record, $dateType);
  64. if($scope != 'system')
  65. {
  66. $row->scope = isset($objectPairs[$record[$scope]]) ? $objectPairs[$record[$scope]] : $record[$scope];
  67. $row->scopeID = $record[$scope];
  68. }
  69. $row->value = is_numeric($record['value']) ? round((float)$record['value'], 2) : $record['value'];
  70. $row->calcType = zget($record, 'calcType', '');
  71. $row->calculatedBy = zget($record, 'calculatedBy', '');
  72. $tableData[] = $row;
  73. }
  74. return $tableData;
  75. }
  76. /**
  77. * 对 headers 进行分组满足表头合并单元格, 返回经过分组的 headers 和 data。
  78. * Return grouped headers and data for table headers merges cell.
  79. *
  80. * @param array $header
  81. * @param array $data
  82. * @param bool $withCalcTime
  83. * @access public
  84. * @return array
  85. */
  86. public function getGroupTable($header, $data, $dateType, $withCalcTime = true)
  87. {
  88. if(!$header or !$data) return array(array(), array());
  89. $headerLength = count($header);
  90. if($headerLength == 2)
  91. {
  92. return $this->getTimeTable($data, $dateType, $withCalcTime); // $dateType should eq nodate
  93. }
  94. elseif($headerLength == 3)
  95. {
  96. if($this->isObjectMetric($header))
  97. {
  98. return $this->getObjectTable($header, $data, $dateType, $withCalcTime); // $dateType should eq nodate
  99. }
  100. else
  101. {
  102. return $this->getTimeTable($data, $dateType, $withCalcTime);
  103. }
  104. }
  105. elseif($headerLength == 4)
  106. {
  107. return $this->getObjectTable($header, $data, $dateType, $withCalcTime);
  108. }
  109. return array($header, $data);
  110. }
  111. /**
  112. * 构建有时间属性的度量项的表格数据。
  113. * Build table data for metric with time.
  114. *
  115. * @param array $data
  116. * @param string $dateType
  117. * @param bool $withCalcTime
  118. * @access public
  119. * @return array
  120. */
  121. public function getTimeTable($data, $dateType = 'day', $withCalcTime = true)
  122. {
  123. usort($data, function($a, $b) use ($dateType)
  124. {
  125. if($dateType == 'week')
  126. {
  127. list($yearA, $weekA) = explode('-', $a->dateString);
  128. list($yearB, $weekB) = explode('-', $b->dateString);
  129. list($firstDayOfWeekA, $lastDayOfWeekA) = $this->getStartAndEndOfWeek($yearA, $weekA, 'date');
  130. list($firstDayOfWeekB, $lastDayOfWeekB) = $this->getStartAndEndOfWeek($yearB, $weekB, 'date');
  131. $dateA = strtotime($firstDayOfWeekA);
  132. $dateB = strtotime($firstDayOfWeekB);
  133. }
  134. else
  135. {
  136. $dateA = strtotime($a->dateString);
  137. $dateB = strtotime($b->dateString);
  138. }
  139. if ($dateA == $dateB) return 0;
  140. return ($dateA > $dateB) ? -1 : 1;
  141. });
  142. $groupHeader = array();
  143. $groupHeader[] = array('name' => 'date', 'title' => zget($this->lang->metric, $dateType, ''), 'align' => 'center', 'width' => 96);
  144. $groupHeader[] = array('name' => 'value', 'title' => $this->lang->metric->value, 'align' => 'center', 'width' => 68);
  145. $groupData = array();
  146. $users = $this->loadModel('user')->getPairs('noletter');
  147. foreach($data as $dataInfo)
  148. {
  149. $value = $withCalcTime ? array($dataInfo->value, $dataInfo->calcTime, $dataInfo->calcType, zget($users, $dataInfo->calculatedBy)) : $dataInfo->value;
  150. $date = isset($dataInfo->date) ? $dataInfo->date : $dataInfo->dateString;
  151. $dataSeries = array('date' => $date, 'value' => $value);
  152. $groupData[] = $dataSeries;
  153. }
  154. return array($groupHeader, $groupData);
  155. }
  156. /**
  157. * 构建有对象属性的度量项的表格数据。
  158. * Build table data for metric with object.
  159. *
  160. * @param array $data
  161. * @param string $dateType
  162. * @param bool $withCalcTime
  163. * @access public
  164. * @return array
  165. */
  166. public function getObjectTable($header, $data, $dateType = 'day', $withCalcTime = true)
  167. {
  168. $groupHeader = array();
  169. $groupData = array();
  170. $headerField = current($header)['name'];
  171. $headerTitle = current($header)['title'];
  172. $groupHeader[] = array('name' => $headerField, 'title' => $headerTitle, 'fixed' => 'left', 'width' => 128);
  173. $dateField = 'dateString';
  174. usort($data, function($a, $b) use($dateField)
  175. {
  176. $dateA = strtotime($a->$dateField);
  177. $dateB = strtotime($b->$dateField);
  178. if ($dateA == $dateB) {
  179. return 0;
  180. }
  181. return ($dateA > $dateB) ? -1 : 1;
  182. });
  183. $times = array();
  184. $objects = array();
  185. $users = $this->loadModel('user')->getPairs('noletter');
  186. foreach($data as $dataInfo)
  187. {
  188. $time = substr($dataInfo->$dateField, 0, 10);
  189. $calcTime = $dataInfo->calcTime;
  190. $object = $dataInfo->scope;
  191. $value = $dataInfo->value;
  192. $calcType = $dataInfo->calcType;
  193. $calculatedBy = zget($users, $dataInfo->calculatedBy);
  194. if(!isset($times[$time])) $times[$time] = $time;
  195. if(!isset($objects[$object])) $objects[$object] = array();
  196. $objects[$object][$time] = $withCalcTime ? array($value, $calcTime, $calcType, $calculatedBy) : $value;
  197. }
  198. /* e.g $times = array('2023-10-14', '2023-10-15'), $objects = array('object1' => array('2023-10-14' => 2, '2023-10-15 => 3)) */
  199. $numberHeaderWidth = 68;
  200. foreach($times as $time)
  201. {
  202. $header = array('name' => $time, 'align' => 'center', 'width' => $numberHeaderWidth);
  203. if($dateType == 'year')
  204. {
  205. $header['title'] = sprintf($this->lang->metric->yearFormat, $time);
  206. }
  207. elseif($dateType == 'month')
  208. {
  209. list($year, $month) = explode('-', $time);
  210. $header['title'] = $this->lang->datepicker->monthNames[(int)$month - 1];
  211. $header['headerGroup'] = sprintf($this->lang->metric->yearFormat, $year);
  212. }
  213. elseif($dateType == 'week')
  214. {
  215. list($year, $week) = explode('-', $time);
  216. $header['title'] = sprintf($this->lang->metric->weekFormat, $week);
  217. $header['headerGroup'] = sprintf($this->lang->metric->yearFormat, $year);
  218. }
  219. elseif($dateType == 'day' or $dateType == 'nodate')
  220. {
  221. list($year, $month, $day) = explode('-', $time);
  222. $header['title'] = sprintf($this->lang->metric->monthDayFormat, $month, $day);
  223. $header['headerGroup'] = sprintf($this->lang->metric->yearFormat, $year);
  224. }
  225. $groupHeader[] = $header;
  226. }
  227. foreach($objects as $object => $datas)
  228. {
  229. $objectData = array($headerField => $object);
  230. foreach($times as $time) $objectData[$time] = isset($datas[$time]) ? $datas[$time] : 0;
  231. $groupData[] = $objectData;
  232. }
  233. return array($groupHeader, $groupData);
  234. }
  235. /**
  236. * 获取所有范围的键值对。
  237. * Get scope pairs.
  238. *
  239. * @param bool $all
  240. * @access public
  241. * @return array
  242. */
  243. public function getScopePairs($all = true)
  244. {
  245. $metrics = $all ? array() : $this->metricTao->fetchMetricsByScope($this->config->metric->scopeList);
  246. $scopePairs = array();
  247. foreach($this->lang->metric->scopeList as $scope => $name)
  248. {
  249. if(!$all && !isset($metrics[$scope])) continue;
  250. $scopePair = new stdclass();
  251. $scopePair->value = $scope;
  252. $scopePair->label = $name;
  253. $scopePairs[] = $scopePair;
  254. }
  255. return $scopePairs;
  256. }
  257. /**
  258. * 获取度量项数据列表。
  259. * Get metric data list.
  260. *
  261. * @param string $scope
  262. * @param string $stage
  263. * @param int $param
  264. * @param string $type
  265. * @param int $queryID
  266. * @param string $sort
  267. * @param object $pager
  268. * @access public
  269. * @return array|false
  270. */
  271. public function getList($scope, $stage = 'all', $param = 0, $type = '', $queryID = 0, $sort = 'id_desc', $pager = null)
  272. {
  273. if($queryID)
  274. {
  275. $query = $this->loadModel('search')->getQuery($queryID);
  276. if($query)
  277. {
  278. $this->session->set('metricQuery', $query->sql);
  279. $this->session->set('metricForm', $query->form);
  280. }
  281. else
  282. {
  283. $this->session->set('metricQuery', ' 1 = 1');
  284. }
  285. }
  286. $object = null;
  287. $purpose = null;
  288. if($type == 'byTree')
  289. {
  290. $object_purpose = explode('_', $param);
  291. $object = $object_purpose[0];
  292. if(count($object_purpose) == 2) $purpose = $object_purpose[1];
  293. }
  294. $query = $type == 'bysearch' ? $this->session->metricQuery : '';
  295. $metrics = $this->metricTao->fetchMetrics($scope, $stage, $object, $purpose, $query, $sort, $pager);
  296. $metrics = $this->processOldMetrics($metrics);
  297. return $metrics;
  298. }
  299. /**
  300. * 以对象为维度分组度量项。
  301. * Group metrics by object.
  302. *
  303. * @param array $metrics
  304. * @access public
  305. * @return array
  306. */
  307. public function groupMetricByObject($metrics)
  308. {
  309. $groupMetrics = array_fill_keys(array_keys($this->lang->metric->objectList), array());
  310. foreach($metrics as $metric)
  311. {
  312. $group = isset($groupMetrics[$metric->object]) ? $metric->object : 'other';
  313. $groupMetrics[$group][] = $metric;
  314. }
  315. $purposes = array_keys($this->lang->metric->purposeList);
  316. foreach($groupMetrics as $key => $metrics)
  317. {
  318. if(empty($metrics))
  319. {
  320. unset($groupMetrics[$key]);
  321. continue;
  322. }
  323. usort($metrics, function($a, $b) use ($purposes) {
  324. $aIndex = array_search($a->purpose, $purposes);
  325. $bIndex = array_search($b->purpose, $purposes);
  326. if($aIndex === $bIndex) return 0;
  327. return ($aIndex < $bIndex) ? -1 : 1;
  328. });
  329. $groupMetrics[$key] = $metrics;
  330. }
  331. return $groupMetrics;
  332. }
  333. /**
  334. * 根据id列表获取度量项列表。
  335. * Get metrics by id list.
  336. *
  337. * @param array $metricIDList
  338. * @access public
  339. * @return array|false
  340. */
  341. public function getMetricsByIDList($metricIDList)
  342. {
  343. return $this->metricTao->fetchMetricsByIDList($metricIDList);
  344. }
  345. /**
  346. * 根据code列表获取度量项列表。
  347. * Get metrics by code list.
  348. *
  349. * @param array $codeList
  350. * @access public
  351. * @return array|false
  352. */
  353. public function getMetricsByCodeList($codeList)
  354. {
  355. return $this->metricTao->fetchMetricsByCodeList($codeList);
  356. }
  357. /**
  358. * 获取度量库数据的收集方式和采集人。
  359. * Get calculate type and calculate people by metric record id.
  360. *
  361. * @param int $recordID
  362. * @access public
  363. * @return object|false
  364. */
  365. public function getRecordCalcInfo($recordID)
  366. {
  367. return $this->dao->select('calcType, calculatedBy')->from(TABLE_METRICLIB)->where('id')->eq($recordID)->fetch();
  368. }
  369. /**
  370. * 获取旧度量项列表。
  371. * Get old metric list.
  372. *
  373. * @param string $orderBy
  374. * @access public
  375. * @return array
  376. */
  377. public function getOldMetricList($orderBy = 'id_desc')
  378. {
  379. return $this->dao->select('*')->from(TABLE_BASICMEAS)->where('deleted')->eq(0)->orderby($orderBy)->fetchAll('id');
  380. }
  381. /**
  382. * 通过筛选器筛选度量项。
  383. * Get metric list by filters.
  384. *
  385. * @param array $filters
  386. * @param string $stage
  387. * @access public
  388. * @return array|false
  389. */
  390. public function getListByFilter($filters, $stage)
  391. {
  392. return $this->metricTao->fetchMetricsWithFilter($filters, $stage);
  393. }
  394. /**
  395. * 通过我的收藏筛选度量项。
  396. * Get metric list by collect.
  397. *
  398. * @param string $stage
  399. * @access public
  400. * @return array|false
  401. */
  402. public function getListByCollect($stage)
  403. {
  404. return $this->metricTao->fetchMetricsByCollect($stage);
  405. }
  406. /**
  407. * 获取模块树数据。
  408. * Get module tree data.
  409. *
  410. * @param string $scope
  411. * @access public
  412. * @return void
  413. */
  414. public function getModuleTreeList($scope)
  415. {
  416. return $this->metricTao->fetchModules($scope);
  417. }
  418. /**
  419. * 根据代号获取度量项信息。
  420. * Get metric info by code.
  421. *
  422. * @param string $code
  423. * @param string|array $fieldList
  424. * @access public
  425. * @return object|false
  426. */
  427. public function getByCode($code, $fieldList = '*')
  428. {
  429. static $metricsWithCode;
  430. if(is_array($fieldList)) $fieldList = implode(',', $fieldList);
  431. if(!isset($metricsWithCode[$code . $fieldList])) $metricsWithCode[$code . $fieldList] = $this->fetchMetricByCode($code, $fieldList);
  432. return $metricsWithCode[$code . $fieldList];
  433. }
  434. /**
  435. * 根据ID获取度量项信息。
  436. * Get metric info by id.
  437. *
  438. * @param int $metricID
  439. * @param string|array $fieldList
  440. * @access public
  441. * @return object|false
  442. */
  443. public function getByID($metricID, $fieldList = '*')
  444. {
  445. if($metricID === 0) return false;
  446. if(is_array($fieldList)) $fieldList = implode(',', $fieldList);
  447. $metric = $this->dao->select($fieldList)->from(TABLE_METRIC)->where('id')->eq($metricID)->fetch();
  448. if(!$metric) return false;
  449. if($this->isOldMetric($metric))
  450. {
  451. $oldMetric = $this->getOldMetricByID($metric->fromID);
  452. $metric->sql = $oldMetric->configure;
  453. $metric->params = $oldMetric->params;
  454. $metric->oldUnit = $oldMetric->unit;
  455. $metric->collectType = $oldMetric->collectType;
  456. $metric->collectConf = $oldMetric->collectConf;
  457. $metric->execTime = $oldMetric->execTime;
  458. }
  459. if(empty($metric->dateType) and isset($metric->code)) $metric->dateType = $this->getDateTypeByCode($metric->code);
  460. return $metric;
  461. }
  462. /**
  463. * 根据ID获取旧版度量项信息。
  464. * Get old metric info by id.
  465. *
  466. * @param int $measurementID
  467. * @access public
  468. * @return object|false
  469. */
  470. public function getOldMetricByID($measurementID)
  471. {
  472. $measurement = $this->dao->select('*')->from(TABLE_BASICMEAS)->where('id')->eq($measurementID)->fetch();
  473. if(!$measurement) return false;
  474. if($measurement->collectType == 'action')
  475. {
  476. $collectConf = json_decode($measurement->collectConf);
  477. $measurement->collectConf = new stdclass();
  478. $measurement->collectConf->action = $collectConf->action;
  479. $measurement->collectConf->type = '';
  480. $measurement->collectConf->week = '';
  481. }
  482. else
  483. {
  484. $measurement->collectConf = json_decode($measurement->collectConf);
  485. if(is_object($measurement->collectConf))
  486. {
  487. $measurement->collectConf->module = '';
  488. $measurement->collectConf->action = '';
  489. }
  490. }
  491. return $measurement;
  492. }
  493. /**
  494. * 获取dao对象。
  495. * Get dao object.
  496. *
  497. * @access public
  498. * @return object
  499. */
  500. public function getDAO()
  501. {
  502. $dbType = $this->config->metricDB->type;
  503. if($dbType === 'sqlite')
  504. {
  505. $dao = clone $this->dao;
  506. $dao->reset();
  507. $dao->dbh = $this->app->connectSqlite();
  508. $dao->driver = 'sqlite';
  509. return $dao;
  510. }
  511. return $this->dao;
  512. }
  513. /**
  514. * 获取度量项数据源句柄。
  515. * Get data source statement of calculator.
  516. *
  517. * @param object $calculator
  518. * @param string $returnType
  519. * @access public
  520. * @return PDOStatement|string
  521. */
  522. public function getDataStatement($calculator, $returnType = 'statement', $vision = 'rnd')
  523. {
  524. $dao = $this->getDAO();
  525. $statement = null;
  526. if(!empty($calculator->dataset))
  527. {
  528. include_once $this->getDatasetPath();
  529. $dataset = new dataset($dao, $this->config, $vision);
  530. $dataSource = $calculator->dataset;
  531. $fieldList = implode(',', $calculator->fieldList);
  532. $statement = $dataset->$dataSource($fieldList);
  533. }
  534. if($calculator->useSCM)
  535. {
  536. $calculator->setDAO($dao);
  537. $scm = $this->app->loadClass('scm');
  538. $calculator->setSCM($scm);
  539. }
  540. if($returnType == 'sql') return $statement->get();
  541. return $statement;
  542. }
  543. /**
  544. * 获取度量数据的日期字段。
  545. * Get date field of metric data.
  546. *
  547. * @param object $metric
  548. * @access protected
  549. * @return array
  550. */
  551. protected function getMetricRecordDateField($metric)
  552. {
  553. $dataFields = array();
  554. $dateType = $this->getDateTypeByCode($metric->code);
  555. if($dateType != 'nodate') $dataFields[] = 'year';
  556. if($dateType == 'month') $dataFields[] = 'month';
  557. if($dateType == 'week') $dataFields[] = 'week';
  558. if($dateType == 'day')
  559. {
  560. $dataFields[] = 'month';
  561. $dataFields[] = 'day';
  562. }
  563. if($metric->scope != 'system') $dataFields[] = $metric->scope;
  564. return $dataFields;
  565. }
  566. /**
  567. * 设置默认的度量数据筛选参数。
  568. * Set default options of metric data.
  569. *
  570. * @param array $options
  571. * @param array $dataFields
  572. * @access public
  573. * @return array
  574. */
  575. public function setDefaultOptions($options, $dataFields)
  576. {
  577. if(!empty($options)) return $options;
  578. $dateType = $this->getDateType($dataFields);
  579. if($dateType != 'nodate')
  580. {
  581. $dateLabels = $this->getDateLabels($dateType);
  582. $defaultDate = $this->getDefaultDate($dateLabels);
  583. $options = array('dateType' => $dateType, 'dateLabel' => $defaultDate);
  584. }
  585. return $options;
  586. }
  587. /**
  588. * 根据代号计算度量项。
  589. * Calculate metric by code.
  590. *
  591. * @param string $code
  592. * @access public
  593. * @return void
  594. */
  595. public function calculateMetricByCode($code)
  596. {
  597. $metric = $this->getByCode($code);
  598. if(!$metric) return false;
  599. $calculator = $this->getCalculator($metric->scope, $metric->purpose, $metric->code);
  600. $this->calculateDefaultMetric($calculator, $this->config->vision);
  601. return $calculator;
  602. }
  603. /**
  604. * 获取数组格式的度量项结果。
  605. * Get result by code with array format.
  606. *
  607. * @param int $code
  608. * @param array $options
  609. * @param string $type
  610. * @param int $pager
  611. * @param string $vision
  612. * @access public
  613. * @return void
  614. */
  615. public function getResultByCodeWithArray($code, $options = array(), $type = 'realtime', $pager = null, $vision = 'rnd')
  616. {
  617. $metric = $this->getByCode($code);
  618. if(!$metric) return array();
  619. $dataFields = $this->getMetricRecordDateField($metric);
  620. $result = array();
  621. if($vision == 'rnd')
  622. {
  623. $records = $this->metricTao->fetchMetricRecordsWithOption($code, $dataFields, $options, $pager);
  624. if(empty($records)) return array();
  625. foreach($records as $index => $record)
  626. {
  627. $record = (array)$record;
  628. $record['value'] = (float)$record['value'];
  629. $result[] = $record;
  630. }
  631. return $result;
  632. }
  633. return $this->getResultByCode($code, $options, 'realtime', $pager, $vision);
  634. }
  635. /**
  636. * 合并度量数据。
  637. * Merge record.
  638. *
  639. * @param array $record
  640. * @param array $result
  641. * @access public
  642. * @return array
  643. */
  644. public function mergeRecord($record, $result)
  645. {
  646. $uniqueKey = $this->getUniqueKeyByRecord($record);
  647. if(isset($result[$uniqueKey]))
  648. {
  649. $result[$uniqueKey]['value'] += $record['value'];
  650. return $result;
  651. }
  652. $result[$uniqueKey] = $record;
  653. return $result;
  654. }
  655. /**
  656. * 根据度量数据的字段生成唯一键。
  657. * Get unique key by record field.
  658. *
  659. * @param array $record
  660. * @access public
  661. * @return string
  662. */
  663. public function getUniqueKeyByRecord($record)
  664. {
  665. $record = (array)$record;
  666. $uniqueKeys = array();
  667. $ignoreLibFields = $this->config->metric->ignoreLibFields;
  668. foreach($record as $field => $value)
  669. {
  670. if(in_array($field, $ignoreLibFields) || empty($value)) continue;
  671. $uniqueKeys[] = $field . $value;
  672. }
  673. return empty($uniqueKeys) ? 'none' : implode('_', $uniqueKeys);
  674. }
  675. /**
  676. * 根据代号获取计算实时度量项的结果。
  677. * Get result of calculate metric by code.
  678. *
  679. * @param string $code
  680. * @param array $options e.g. array('product' => '1,2,3', 'year' => '2023')
  681. * @param string $type
  682. * @param object|null $pager
  683. * @access public
  684. * @return array
  685. */
  686. public function getResultByCode($code, $options = array(), $type = 'realtime', $pager = null, $vision = 'rnd')
  687. {
  688. if($type == 'cron')
  689. {
  690. $metric = $this->getByCode($code);
  691. if(!empty($metric))
  692. {
  693. $dataFields = $this->getMetricRecordDateField($metric);
  694. $options = $this->setDefaultOptions($options, $dataFields);
  695. return $this->metricTao->fetchMetricRecords($code, $dataFields, $options, $pager);
  696. }
  697. }
  698. $metric = $this->getByCode($code);
  699. if(!$metric) return false;
  700. $calculator = $this->getCalculator($metric->scope, $metric->purpose, $metric->code);
  701. if(!$calculator) return array();
  702. /* 因为是单个度量项的计算,所以需要优先查看是否支持可用的性能优化,如果有的话,使用性能优化方式计算。*/
  703. /* Because this is a single metric calculation, it is important to first look to see if any performance optimizations are supported and, if so, use them. */
  704. $calculated = false;
  705. $calculated += $this->calculateReuseMetric($calculator, $options, $type, $pager, $vision);
  706. $calculated += $this->calculateSingleMetric($calculator, $vision);
  707. /* 如果没有可用的性能优化方式,那么使用默认的方式计算。*/
  708. /* If no optimizations are available, the default calculation is used. */
  709. if(!$calculated) $this->calculateDefaultMetric($calculator, $vision);
  710. $records = $calculator->getResult($options);
  711. if(!empty($records))
  712. {
  713. $records = json_decode(json_encode($records), true);
  714. $time = helper::now();
  715. foreach($records as $index => $record)
  716. {
  717. $records[$index]['date'] = $time;
  718. $records[$index]['calcType'] = 'cron';
  719. $records[$index]['calculatedBy'] = 'system';
  720. }
  721. }
  722. return $records;
  723. }
  724. /**
  725. * 根据度量项和数据计算度量项结果。
  726. * Calculate metric result by metrics and data.
  727. *
  728. * @param array $metrics
  729. * @param array $data
  730. * @access public
  731. * @return void
  732. */
  733. public function getResultByCodeFromData($metrics, $data)
  734. {
  735. $results = array();
  736. foreach($metrics as $metric)
  737. {
  738. $calculator = $this->getCalculator($metric->scope, $metric->purpose, $metric->code);
  739. foreach($data as $row) $calculator->calculate($row);
  740. $results[$metric->code] = $calculator->getResult();
  741. }
  742. return $results;
  743. }
  744. /**
  745. * 获取度量项计算对象。
  746. * Get metric calculator.
  747. *
  748. * @param string $scope
  749. * @param string $purpose
  750. * @param string $code
  751. * @access public
  752. * @return object
  753. */
  754. public function getCalculator($scope, $purpose, $code)
  755. {
  756. $calcPath = $this->getCalcRoot() . $scope . DS . $purpose . DS . $code . '.php';
  757. if(!is_file($calcPath)) return false;
  758. include_once $this->getBaseCalcPath();
  759. include_once $calcPath;
  760. $calculator = new $code();
  761. $calculator->setHolidays($this->loadModel('holiday')->getList());
  762. $calculator->setWeekend(isset($this->config->project->weekend) ? $this->config->project->weekend : 2);
  763. if($calculator->useSCM && ($this->config->inQuickon || $this->config->inCompose)) $calculator->setGitFoxRepos($this->loadModel('repo')->getGitFoxRepos());
  764. return $calculator;
  765. }
  766. /**
  767. * 计算重用度量项。
  768. * Calculate reuse metric.
  769. *
  770. * @param object $calculator
  771. * @param array $options
  772. * @param string $type
  773. * @param object $pager
  774. * @param string $vision
  775. * @access public
  776. * @return bool
  777. */
  778. public function calculateReuseMetric($calculator, $options, $type, $pager, $vision)
  779. {
  780. if(!$calculator->reuse) return false;
  781. $reuseMetrics = array();
  782. foreach($calculator->reuseMetrics as $key => $reuseMetric)
  783. {
  784. $reuseMetrics[$key] = $this->getResultByCode($reuseMetric, $options, $type, $pager, $vision);
  785. }
  786. $calculator->calculate($reuseMetrics);
  787. return true;
  788. }
  789. /**
  790. * 计算可独立计算度量项。
  791. * Calculate single metric.
  792. *
  793. * @param object $calculator
  794. * @param string $vision
  795. * @access public
  796. * @return bool
  797. */
  798. public function calculateSingleMetric($calculator, $vision)
  799. {
  800. if(!$calculator->supportSingleQuery) return false;
  801. $sql = $this->getDataStatement($calculator, 'sql', $vision);
  802. $calculator->setDAO($this->getDAO());
  803. $calculator->setSingleSql($sql);
  804. $calculator->enableSingleQuery();
  805. return true;
  806. }
  807. /**
  808. * 计算普通度量项。
  809. * Calculate default metric.
  810. *
  811. * @param object $calculator
  812. * @param string $vision
  813. * @access public
  814. * @return void
  815. */
  816. public function calculateDefaultMetric($calculator, $vision)
  817. {
  818. $statement = $this->getDataStatement($calculator, 'statement', $vision);
  819. $dbType = $this->config->metricDB->type;
  820. if($dbType == 'duckdb')
  821. {
  822. $this->loadModel('bi');
  823. $sql = $statement->get();
  824. $dbh = $this->app->loadDriver('duckdb');
  825. $statement = $dbh->query($sql);
  826. }
  827. if($statement) $rows = $statement->fetchAll('', false);
  828. if(!empty($rows)) foreach($rows as $row) $calculator->calculate($row);
  829. }
  830. /**
  831. * 根据代号获取计算最新度量项的结果。
  832. * Get result of latest calculate metric by code.
  833. *
  834. * @param string $code
  835. * @param array $options e.g. array('product' => '1,2,3', 'year' => '2023')
  836. * @param string $type
  837. * @param object|null $pager
  838. * @access public
  839. * @return array
  840. */
  841. public function getLatestResultByCode($code, $options = array(), $pager = null, $vision = 'rnd')
  842. {
  843. $metric = $this->getByCode($code);
  844. $dataFields = $this->getMetricRecordDateField($metric);
  845. $options = $this->setDefaultOptions($options, $dataFields);
  846. return $this->metricTao->fetchLatestMetricRecords($code, $dataFields, $options, $pager);
  847. }
  848. /**
  849. * 根据代号列表批量获取度量项的结果。
  850. * Get result of calculate metric by code list.
  851. *
  852. * @param array $codes e.g. array('code1', 'code2')
  853. * @param array $options e.g. array('product' => '1,2,3', 'year' => '2023')
  854. * @access public
  855. * @return array
  856. */
  857. public function getResultByCodes($codes, $options = array(), $vision = 'rnd')
  858. {
  859. $results = array();
  860. foreach($codes as $code)
  861. {
  862. $result = $this->getResultByCode($code, $options, 'realtime', null, $vision);
  863. if($result) $results[$code] = $result;
  864. }
  865. return $results;
  866. }
  867. /**
  868. * 获取可计算的度量项列表。
  869. * Get executable metric list.
  870. *
  871. * @access public
  872. * @return array
  873. */
  874. public function getExecutableMetric($includes = 'all')
  875. {
  876. return $this->dao->select('id, code')->from(TABLE_METRIC)
  877. ->where('deleted')->eq('0')
  878. ->beginIF(is_array($includes))->andWhere('code')->in($includes)->fi()
  879. ->fetchPairs();
  880. }
  881. /**
  882. * 删除度量数据中的重复数据。
  883. * Delete duplication record in metric data.
  884. *
  885. * @param string $code
  886. * @access public
  887. * @return bool
  888. */
  889. public function deduplication($code)
  890. {
  891. $metric = $this->getByCode($code);
  892. $fields = $this->getRecordFields($code);
  893. if(!in_array($metric->scope, $fields)) $fields[] = $metric->scope;
  894. if(empty($fields)) return false;
  895. $this->metricTao->setDeleted($code, 1);
  896. $this->metricTao->keepLatestRecords($code, $fields);
  897. $this->metricTao->executeDelete($code);
  898. return dao::isError();
  899. }
  900. /**
  901. * 获取日志文件路径。
  902. * Get log file.
  903. *
  904. * @access public
  905. * @return string
  906. */
  907. public function getLogFile()
  908. {
  909. return $this->app->getTmpRoot() . 'log/metriclib.' . date('Ymd') . '.log.php';
  910. }
  911. /**
  912. * 存储日志。
  913. * Save logs.
  914. *
  915. * @param string $log
  916. * @access public
  917. * @return void
  918. */
  919. public function saveLogs($log)
  920. {
  921. $logFile = $this->getLogFile();
  922. $log = date('Y-m-d H:i:s') . ' ' . trim($log) . "\n";
  923. if(!file_exists($logFile)) $log = "<?php\ndie();\n?" . ">\n" . $log;
  924. file_put_contents($logFile, $log, FILE_APPEND);
  925. }
  926. /**
  927. * 获取度量数据有效字段。
  928. * Get metric record fields.
  929. *
  930. * @param string $code
  931. * @access public
  932. * @return array|false
  933. */
  934. public function getRecordFields($code)
  935. {
  936. $metric = $this->getByCode($code);
  937. $dateType = $this->getDateTypeByCode($code);
  938. $fields = array($metric->scope);
  939. if($dateType == 'nodate') return $fields;
  940. $fields[] = 'year';
  941. if($dateType == 'month' || $dateType == 'day') $fields[] = 'month';
  942. if($dateType == 'week') $fields[] = 'week';
  943. if($dateType == 'day') $fields[] = 'day';
  944. return $fields;
  945. }
  946. /**
  947. * 重建主键顺序。
  948. * Rebuild primary key order.
  949. *
  950. * @access public
  951. * @return void
  952. */
  953. public function rebuildPrimaryKey()
  954. {
  955. $this->metricTao->rebuildIdColumn();
  956. }
  957. /**
  958. * 根据度量项收集周期来清理过期的度量库数据。
  959. * Clear outdated metric records by cycle.
  960. *
  961. * @param string $code
  962. * @param string $cycle
  963. * @access public
  964. * @return array
  965. */
  966. public function clearOutDatedRecords($code, $cycle)
  967. {
  968. $year = date('Y');
  969. $month = date('n');
  970. $week = date('W');
  971. $day = date('j');
  972. if($cycle == 'year')
  973. {
  974. $this->dao->delete()->from(TABLE_METRICLIB)
  975. ->where('metricCode')->eq($code)
  976. ->andWhere('year')->eq($year)
  977. ->exec();
  978. }
  979. elseif($cycle == 'month')
  980. {
  981. $this->dao->delete()->from(TABLE_METRICLIB)
  982. ->where('metricCode')->eq($code)
  983. ->andWhere('year')->eq($year)
  984. ->andWhere('month')->eq($month)
  985. ->exec();
  986. }
  987. elseif($cycle == 'week')
  988. {
  989. $this->dao->delete()->from(TABLE_METRICLIB)
  990. ->where('metricCode')->eq($code)
  991. ->andWhere('year')->eq($year)
  992. ->andWhere('week')->eq($week)
  993. ->exec();
  994. }
  995. elseif($cycle == 'day')
  996. {
  997. $this->dao->delete()->from(TABLE_METRICLIB)
  998. ->where('metricCode')->eq($code)
  999. ->andWhere('year')->eq($year)
  1000. ->andWhere('month')->eq($month)
  1001. ->andWhere('day')->eq($day)
  1002. ->exec();
  1003. }
  1004. }
  1005. /**
  1006. * 插入度量库数据。
  1007. * Insert into metric lib.
  1008. *
  1009. * @param array $recordWithCode
  1010. * @param string $calcType
  1011. * @access public
  1012. * @return void
  1013. */
  1014. public function insertMetricLib($recordWithCode, $calcType = 'cron')
  1015. {
  1016. foreach($recordWithCode as $code => $records)
  1017. {
  1018. $schema = '';
  1019. $values = array();
  1020. foreach($records as $record)
  1021. {
  1022. if(empty($record)) continue;
  1023. $record->calcType = $calcType;
  1024. $record->calculatedBy = $calcType == 'inference' ? $this->app->user->account : 'system';
  1025. $sql = $this->dao->insert(TABLE_METRICLIB)->data($record)->get();
  1026. $position = strpos($sql, 'VALUES');
  1027. if(empty($schema)) $schema .= substr($sql, 0, $position + 6);
  1028. $values[] = substr($sql, $position + 6);
  1029. }
  1030. $rows = count($records);
  1031. $time = helper::now();
  1032. $this->dao->update(TABLE_METRIC)
  1033. ->set('lastCalcRows')->eq($rows)
  1034. ->set('lastCalcTime')->eq($time)
  1035. ->where('code')->eq($code)
  1036. ->exec();
  1037. while($rows > 0)
  1038. {
  1039. $sql = $schema . implode(',', array_splice($values, 0, 10000));
  1040. $this->dao->exec($sql);
  1041. $rows -= 10000;
  1042. }
  1043. }
  1044. return dao::isError();
  1045. }
  1046. /**
  1047. * 获取可计算的度量项对象列表。
  1048. * Get executable calculator list.
  1049. *
  1050. * @access public
  1051. * @return array
  1052. */
  1053. public function getExecutableCalcList($includes = 'all')
  1054. {
  1055. $funcRoot = $this->getCalcRoot();
  1056. $fileList = array();
  1057. foreach($this->config->metric->scopeList as $scope)
  1058. {
  1059. foreach($this->config->metric->purposeList as $purpose)
  1060. {
  1061. $pattern = $funcRoot . $scope . DS . $purpose . DS . '*.php';
  1062. $matchedFiles = glob($pattern);
  1063. if($matchedFiles !== false) $fileList = array_merge($fileList, $matchedFiles);
  1064. }
  1065. }
  1066. $calcList = array();
  1067. $excutableMetric = $this->getExecutableMetric($includes);
  1068. foreach($fileList as $file)
  1069. {
  1070. $code = rtrim(basename($file), '.php');
  1071. if(!in_array($code, $excutableMetric)) continue;
  1072. $id = array_search($code, $excutableMetric);
  1073. $calc = new stdclass();
  1074. $calc->code = $code;
  1075. $calc->file = $file;
  1076. $calcList[$id] = $calc;
  1077. }
  1078. return $calcList;
  1079. }
  1080. /**
  1081. * 获取度量项计算实例列表。
  1082. * Get calculator instance list.
  1083. *
  1084. * @access public
  1085. * @return array
  1086. */
  1087. public function getCalcInstanceList($includes = 'all')
  1088. {
  1089. $calcList = $this->getExecutableCalcList($includes);
  1090. include_once $this->getBaseCalcPath();
  1091. $calcInstances = array();
  1092. foreach($calcList as $id => $calc)
  1093. {
  1094. $file = $calc->file;
  1095. $className = $calc->code;
  1096. require_once $file;
  1097. $metricInstance = new $className();
  1098. $metricInstance->id = $id;
  1099. $calcInstances[$className] = $metricInstance;
  1100. }
  1101. return $this->filterCalcByEdition($calcInstances);
  1102. }
  1103. /**
  1104. * 获取通用数据集对象。
  1105. * Get instance of data set object.
  1106. *
  1107. * @access public
  1108. * @return dataset
  1109. */
  1110. public function getDataset($dao)
  1111. {
  1112. $datasetPath = $this->getDatasetPath();
  1113. include_once $datasetPath;
  1114. return new dataset($dao, $this->config);
  1115. }
  1116. /**
  1117. * 对度量项按照通用数据集进行归类,没有数据集不做归类。
  1118. * Classify calculator instance list by its data set.
  1119. *
  1120. * @param array $calcList
  1121. * @access public
  1122. * @return array
  1123. */
  1124. public function classifyCalc($calcList)
  1125. {
  1126. $datasetCalcGroup = array();
  1127. $otherCalcList = array();
  1128. foreach($calcList as $code => $calc)
  1129. {
  1130. if(empty($calc->dataset))
  1131. {
  1132. $otherCalcList[$code] = $calc;
  1133. continue;
  1134. }
  1135. $dataset = $calc->dataset;
  1136. if(!isset($datasetCalcGroup[$dataset])) $datasetCalcGroup[$dataset] = array();
  1137. $datasetCalcGroup[$dataset][$code] = $calc;
  1138. }
  1139. $classifiedCalcGroup = array();
  1140. foreach($datasetCalcGroup as $dataset => $calcList) $classifiedCalcGroup[] = (object)array('dataset' => $dataset, 'calcList' => $calcList);
  1141. foreach($otherCalcList as $code => $calc) $classifiedCalcGroup[] = (object)array('dataset' => '', 'calcList' => array($code => $calc));
  1142. return $classifiedCalcGroup;
  1143. }
  1144. /**
  1145. * 对度量项的字段列表取并集。
  1146. * Unite field list of each calculator.
  1147. *
  1148. * @param array $calcList
  1149. * @access public
  1150. * @return string
  1151. */
  1152. public function uniteFieldList($calcList)
  1153. {
  1154. $fieldList = array();
  1155. foreach($calcList as $calcInstance) $fieldList = array_merge($fieldList, $calcInstance->fieldList);
  1156. $uniqueList = array_unique($fieldList);
  1157. $aliasList = array();
  1158. foreach($uniqueList as $field)
  1159. {
  1160. if(strpos(strtoupper($field), ' AS ') !== false)
  1161. {
  1162. $aliasList[] = $field;
  1163. continue;
  1164. }
  1165. if(strpos($field, '.') !== false)
  1166. {
  1167. $alias = str_replace('.', '_', $field);
  1168. list($table, $field) = explode('.', $field);
  1169. $aliasList[] = "`$table`.`$field` AS `$alias`";
  1170. continue;
  1171. }
  1172. $aliasList[] = "`$field`";
  1173. }
  1174. return implode(',', $aliasList);
  1175. }
  1176. /**
  1177. * Build search form.
  1178. *
  1179. * @param int $queryID
  1180. * @param string $actionURL
  1181. * @access public
  1182. * @return void
  1183. */
  1184. public function buildSearchForm($queryID, $actionURL)
  1185. {
  1186. $this->config->metric->browse->search['actionURL'] = $actionURL;
  1187. $this->config->metric->browse->search['queryID'] = $queryID;
  1188. $this->loadModel('search')->setSearchParams($this->config->metric->browse->search);
  1189. }
  1190. /**
  1191. * 为度量详情页构建操作按钮
  1192. * Build operate menu.
  1193. *
  1194. * @param object $metric
  1195. * @access public
  1196. * @return array
  1197. */
  1198. public function buildOperateMenu($metric)
  1199. {
  1200. $menuList = array
  1201. (
  1202. 'main' => array(),
  1203. 'suffix' => array()
  1204. );
  1205. if($metric->builtin === '0')
  1206. {
  1207. $stage = $metric->stage;
  1208. if($stage == 'wait' and common::haspriv('metric', 'edit'))
  1209. {
  1210. $editAction = $this->config->metric->actionList['edit'];
  1211. $editAction['data-toggle'] = 'modal';
  1212. $editAction['url'] = helper::createLink('metric', 'edit', "metricID={$metric->id}&viewType=view");
  1213. $menuList['suffix']['edit'] = $editAction;
  1214. }
  1215. if($stage == 'wait' and common::haspriv('metric', 'implement') and !$this->isOldMetric($metric))
  1216. {
  1217. $menuList['main']['implement'] = $this->config->metric->actionList['implement'];
  1218. }
  1219. if($stage != 'wait' and common::haspriv('metric', 'delist'))
  1220. {
  1221. $menuList['main']['delist'] = $this->config->metric->actionList['delist'];
  1222. }
  1223. if(common::haspriv('metric', 'delete'))
  1224. {
  1225. $deleteAction = $this->config->metric->actionList['delete'];
  1226. if(isset($metric->isUsed) && $metric->isUsed) $deleteAction['data-confirm'] = $this->lang->metric->confirmDeleteInUsed;
  1227. $menuList['suffix']['delete'] = $deleteAction;
  1228. }
  1229. }
  1230. if($metric->stage == 'released' && !empty($metric->dateType) && $metric->dateType != 'nodate' && common::haspriv('metric', 'recalculate'))
  1231. {
  1232. $menuList['main']['recalculate'] = $this->config->metric->actionList['recalculate'];
  1233. $menuList['main']['recalculate']['text'] = $this->lang->metric->recalculateBtnText;
  1234. $menuList['main']['recalculate']['hint'] = $this->lang->metric->recalculateBtnText;
  1235. }
  1236. return $menuList;
  1237. }
  1238. /**
  1239. * 获取瀑布范围的瀑布对象列表。
  1240. * Get object pairs by scope.
  1241. *
  1242. * @param string $vision
  1243. * @access public
  1244. * @return array
  1245. */
  1246. public function getWaterfullProjectPairs($vision = 'rnd')
  1247. {
  1248. return $this->dao->select('id, name')->from(TABLE_PROJECT)
  1249. ->where('deleted')->eq(0)
  1250. ->andWhere('type')->eq('project')
  1251. ->andWhere('model')->in(array('waterfall', 'waterfallplus'))
  1252. ->andWhere("vision LIKE '%{$vision}%'", true)
  1253. ->orWhere("vision")->isNull()->markRight(1)
  1254. ->fetchPairs();
  1255. }
  1256. /**
  1257. * 获取范围的对象列表。
  1258. * Get object pairs by scope.
  1259. *
  1260. * @param string $scope
  1261. * @param bool $withHierarchy
  1262. * @access public
  1263. * @return array
  1264. */
  1265. public function getPairsByScope($scope, $withHierarchy = false, $vision = 'rnd')
  1266. {
  1267. if(empty($scope) || $scope == 'system') return array();
  1268. if($scope == 'dept' && $withHierarchy) $scope = 'deptWithHierarchy';
  1269. if($scope == 'program' && $withHierarchy) $scope = 'programWithHierarchy';
  1270. $objectPairs = array();
  1271. switch($scope)
  1272. {
  1273. case 'dept':
  1274. $objectPairs = $this->loadModel('dept')->getDeptPairs();
  1275. break;
  1276. case 'deptWithHierarchy':
  1277. $objectPairs = $this->loadModel('dept')->getOptionMenu();
  1278. break;
  1279. case 'user':
  1280. $objectPairs = $this->loadModel('user')->getPairs('noletter|noempty|noclosed');
  1281. break;
  1282. case 'program':
  1283. $objectPairs = $this->dao->select('id, name')->from(TABLE_PROGRAM)
  1284. ->where('deleted')->eq(0)
  1285. ->andWhere('type')->eq('program')
  1286. ->fetchPairs();
  1287. break;
  1288. case 'programWithHierarchy':
  1289. $objectPairs = $this->loadModel('program')->getParentPairs();
  1290. break;
  1291. case 'product':
  1292. $objectPairs = $this->dao->select('id, name')->from(TABLE_PRODUCT)
  1293. ->where('deleted')->eq(0)
  1294. ->andWhere('shadow')->eq(0)
  1295. ->andWhere("vision LIKE '%{$vision}%'", true)
  1296. ->orWhere("vision")->isNull()->markRight(1)
  1297. ->fetchPairs();
  1298. break;
  1299. case 'project':
  1300. $objectPairs = $this->dao->select('id, name')->from(TABLE_PROJECT)
  1301. ->where('deleted')->eq(0)
  1302. ->andWhere('type')->eq('project')
  1303. ->andWhere("vision LIKE '%{$vision}%'", true)
  1304. ->orWhere("vision")->isNull()->markRight(1)
  1305. ->fetchPairs();
  1306. break;
  1307. case 'execution':
  1308. $objectPairs = $this->dao->select('id, name')->from(TABLE_PROJECT)
  1309. ->where('deleted')->eq(0)
  1310. ->andWhere('type')->in('sprint,stage,kanban')
  1311. ->andWhere("vision LIKE '%{$vision}%'", true)
  1312. ->orWhere("vision")->isNull()->markRight(1)
  1313. ->fetchPairs();
  1314. break;
  1315. case 'repo':
  1316. $objectPairs = $this->loadModel('repo')->getRepoPairs('repo');
  1317. break;
  1318. case 'artifactrepo':
  1319. $serverID = 0;
  1320. $this->loadModel('instance');
  1321. if(method_exists($this->instance, 'getSystemServer'))
  1322. {
  1323. $server = $this->instance->getSystemServer();
  1324. if(!empty($server)) $serverID = $server->id;
  1325. }
  1326. $objectPairs = $this->dao->select('id, name')->from(TABLE_ARTIFACTREPO)
  1327. ->where('deleted')->eq(0)
  1328. ->andWhere('type')->eq('gitfox')
  1329. ->andWhere('serverID')->eq($serverID)
  1330. ->fetchPairs();
  1331. break;
  1332. default:
  1333. $objectPairs = $this->loadModel($scope)->getPairs();
  1334. break;
  1335. }
  1336. return $objectPairs;
  1337. }
  1338. /**
  1339. * 根据范围和创建日期获取对象列表。
  1340. * Get object pairs by scope and createdDate.
  1341. *
  1342. * @param string $scope
  1343. * @param string $date
  1344. * @param string $vision
  1345. * @access public
  1346. * @return array
  1347. */
  1348. public function getPairsByScopeAndDate($scope, $date, $vision = 'rnd')
  1349. {
  1350. $objectPairs = array();
  1351. switch($scope)
  1352. {
  1353. case 'product':
  1354. $objectPairs = $this->dao->select('id, name')->from(TABLE_PRODUCT)
  1355. ->where('deleted')->eq(0)
  1356. ->andWhere('shadow')->eq(0)
  1357. ->andWhere('createdDate')->le($date)
  1358. ->andWhere("`closedDate` IS NULL OR YEAR(`closedDate`)='0000'", true)
  1359. ->orWhere('closedDate')->ge($date)
  1360. ->markRight(1)
  1361. ->andWhere("vision LIKE '%{$vision}%'", true)
  1362. ->orWhere("vision")->isNull()->markRight(1)
  1363. ->fetchPairs();
  1364. break;
  1365. case 'project':
  1366. $objectPairs = $this->dao->select('id, name')->from(TABLE_PROJECT)
  1367. ->where('deleted')->eq(0)
  1368. ->andWhere('type')->eq('project')
  1369. ->andWhere('openedDate')->le($date)
  1370. ->andWhere("`closedDate` IS NULL OR YEAR(`closedDate`)='0000'", true)
  1371. ->orWhere('closedDate')->ge($date)
  1372. ->markRight(1)
  1373. ->andWhere("vision LIKE '%{$vision}%'", true)
  1374. ->orWhere("vision")->isNull()->markRight(1)
  1375. ->fetchPairs();
  1376. break;
  1377. case 'execution':
  1378. $objectPairs = $this->dao->select('id, name')->from(TABLE_PROJECT)
  1379. ->where('deleted')->eq(0)
  1380. ->andWhere('type')->in('sprint,stage,kanban')
  1381. ->andWhere('openedDate')->le($date)
  1382. ->andWhere("`closedDate` IS NULL OR YEAR(`closedDate`)='0000'", true)
  1383. ->orWhere('closedDate')->ge($date)
  1384. ->markRight(1)
  1385. ->andWhere("vision LIKE '%{$vision}%'", true)
  1386. ->orWhere("vision")->isNull()->markRight(1)
  1387. ->fetchPairs();
  1388. break;
  1389. case 'user':
  1390. $objectPairs = $this->dao->select('account,realname')->from(TABLE_USER)
  1391. ->where('deleted')->eq(0)
  1392. ->andWhere('join', true)->le(substr($date, 0, 10))
  1393. ->orWhere("`join` IS NULL")->markRight(1)
  1394. ->andWhere("visions LIKE '%{$vision}%'", true)
  1395. ->orWhere("visions")->isNull()->markRight(1)
  1396. ->fetchPairs();
  1397. break;
  1398. }
  1399. return $objectPairs;
  1400. }
  1401. /**
  1402. * Get object pairs by id list.
  1403. *
  1404. * @param string $scope
  1405. * @param array $idList
  1406. * @access public
  1407. * @return array
  1408. */
  1409. public function getPairsByIdList($scope, $idList)
  1410. {
  1411. $field = $scope == 'user' ? 'realname' : 'name';
  1412. $where = $scope == 'user' ? 'account' : 'id';
  1413. return $this->dao->select("id, $field")->from($this->config->objectTables[$scope])
  1414. ->where($where)->in($idList)
  1415. ->fetchPairs();
  1416. }
  1417. /**
  1418. * 获取度量项的日期字符串。
  1419. * Build date cell.
  1420. *
  1421. * @param array $record
  1422. * @param string $dateType
  1423. * @access public
  1424. * @return object
  1425. */
  1426. public function buildDateCell($record, $dateType)
  1427. {
  1428. $row = new stdclass();
  1429. $year = isset($record['year']) ? $record['year'] : '';
  1430. $month = isset($record['month']) ? $record['month'] : '';
  1431. $week = isset($record['week']) ? $record['week'] : '';
  1432. $day = isset($record['day']) ? $record['day'] : '';
  1433. $calcTime = isset($record['calcTime']) ? $record['calcTime'] : '';
  1434. $date = $dateString = false;
  1435. if($dateType == 'nodate')
  1436. {
  1437. $date = $dateString = substr($calcTime, 0, 10);
  1438. }
  1439. elseif($dateType == 'day')
  1440. {
  1441. $date = $dateString = "{$year}-{$month}-{$day}";
  1442. }
  1443. elseif($dateType == 'week')
  1444. {
  1445. $date = sprintf($this->lang->metric->weekCell, $year, $week);
  1446. $dateString = "{$year}-{$week}";
  1447. }
  1448. elseif($dateType == 'month')
  1449. {
  1450. $date = sprintf($this->lang->metric->yearMonthFormat, $year, $month);
  1451. $dateString = "{$year}-{$month}";
  1452. }
  1453. elseif($dateType == 'year')
  1454. {
  1455. $date = sprintf($this->lang->metric->yearFormat, $year);
  1456. $dateString = $year;
  1457. }
  1458. $row->date = $date;
  1459. $row->dateString = $dateString;
  1460. $row->dateType = $dateType;
  1461. $row->calcTime = $calcTime;
  1462. return $row;
  1463. }
  1464. /**
  1465. * 检查度量项的计算文件是否存在。
  1466. * Check if metric's calculator exists.
  1467. *
  1468. * @param object $metric
  1469. * @access public
  1470. * @return bool
  1471. */
  1472. public function checkCalcExists($metric)
  1473. {
  1474. $calcName = $this->getCalcRoot() . $metric->scope . DS . $metric->purpose . DS . $metric->code . '.php';
  1475. return file_exists($calcName);
  1476. }
  1477. /**
  1478. * 没有度量的显示范围不做显示。
  1479. * Unset scope item that have no metric.
  1480. *
  1481. * @param string $stage
  1482. * @access public
  1483. * @return void
  1484. */
  1485. public function processScopeList($stage = 'all')
  1486. {
  1487. $metrics = $this->metricTao->fetchMetricsByScope($this->config->metric->scopeList);
  1488. foreach($this->lang->metric->scopeList as $scope => $name)
  1489. {
  1490. if(!isset($metrics[$scope]))
  1491. {
  1492. unset($this->lang->metric->scopeList[$scope]);
  1493. unset($this->lang->metric->featureBar['preview'][$scope]);
  1494. }
  1495. }
  1496. if(count($this->lang->metric->featureBar['preview']) >= 7)
  1497. {
  1498. $i = 0;
  1499. foreach($this->lang->metric->featureBar['preview'] as $key => $name)
  1500. {
  1501. $i ++;
  1502. if($i >= 7)
  1503. {
  1504. unset($this->lang->metric->featureBar['preview'][$key]);
  1505. $this->lang->metric->moreSelects[$key] = $name;
  1506. }
  1507. }
  1508. $this->lang->metric->featureBar['preview']['more'] = $this->lang->metric->more;
  1509. }
  1510. $this->lang->metric->featureBar['preview']['collect'] = $this->lang->metric->collect;
  1511. }
  1512. /**
  1513. * 根据后台配置的估算单位对列表赋值。
  1514. * Assign unitList['measure'] by custom hourPoint.
  1515. *
  1516. * @access public
  1517. * @return void
  1518. */
  1519. public function processUnitList()
  1520. {
  1521. $this->app->loadLang('custom');
  1522. $key = zget($this->config->custom, 'hourPoint', '0');
  1523. $this->lang->metric->unitList['measure'] = $this->lang->custom->conceptOptions->hourPoint[$key];
  1524. }
  1525. /**
  1526. * 根据后台配置的是否开启用户需求设置对象列表。
  1527. * Unset objectList['requirement'] if custom requirement is close.
  1528. *
  1529. * @access public
  1530. * @return void
  1531. */
  1532. public function processObjectList()
  1533. {
  1534. if(!isset($this->config->custom->URAndSR) or !$this->config->custom->URAndSR) unset($this->lang->metric->objectList['requirement']);
  1535. }
  1536. public function buildFilterCheckList($filters)
  1537. {
  1538. $filterItems = array();
  1539. $onchange = 'window.handleFilterCheck(this)';
  1540. $scopeItems = array();
  1541. foreach($this->lang->metric->scopeList as $value => $text)
  1542. {
  1543. $isChecked = (isset($filters['scope']) and in_array($value, $filters['scope']));
  1544. $scopeItems[] = array('text' => $text, 'value' => $value, 'onchange' => $onchange, 'checked' => $isChecked);
  1545. }
  1546. $filterItems['scope'] = array
  1547. (
  1548. 'class' => 'flex3 divider',
  1549. 'items' => $scopeItems
  1550. );
  1551. $objectItems = array();
  1552. foreach($this->lang->metric->objectList as $value => $text)
  1553. {
  1554. $isChecked = (isset($filters['object']) and in_array($value, $filters['object']));
  1555. $collapse = in_array($value, $this->config->metric->collapseList) && !$isChecked;
  1556. $objectItems[] = array('text' => $text, 'value' => $value, 'onchange' => $onchange, 'checked' => $isChecked, 'data-collapse' => $collapse);
  1557. }
  1558. $filterItems['object'] = array
  1559. (
  1560. 'class' => 'flex5 divider',
  1561. 'items' => $objectItems
  1562. );
  1563. $purposeItems = array();
  1564. foreach($this->lang->metric->purposeList as $value => $text)
  1565. {
  1566. $isChecked = (isset($filters['purpose']) and in_array($value, $filters['purpose']));
  1567. $purposeItems[] = array('text' => $text, 'value' => $value, 'onchange' => $onchange, 'checked' => $isChecked);
  1568. }
  1569. $filterItems['purpose'] = array
  1570. (
  1571. 'class' => 'flex2',
  1572. 'items' => $purposeItems
  1573. );
  1574. return $filterItems;
  1575. }
  1576. /**
  1577. * 根据数据初始化操作按钮。
  1578. * Init action button by data.
  1579. *
  1580. * @param array $metrics
  1581. * @access public
  1582. * @return array
  1583. * @param mixed[] $cols
  1584. */
  1585. public function initActionBtn($metrics, $cols)
  1586. {
  1587. foreach($metrics as $metric)
  1588. {
  1589. foreach($metric->actions as $key => $action)
  1590. {
  1591. $isClick = true;
  1592. if($action['name'] == 'edit') $isClick = $metric->canEdit;
  1593. if($action['name'] == 'implement') $isClick = $metric->canImplement;
  1594. if($action['name'] == 'delist')
  1595. {
  1596. $isClick = $metric->canDelist;
  1597. if(!$isClick && $metric->builtin == '1') $metric->actions[$key]['hint'] = $this->lang->metric->builtinMetric;
  1598. }
  1599. if($action['name'] == 'recalculate')
  1600. {
  1601. $isClick = $metric->canRecalculate;
  1602. if($metric->stage != 'released' || (!empty($metric->dateType) && $metric->dateType == 'nodate')) $metric->actions[$key]['hint'] = $this->lang->metric->tips->banRecalculate;
  1603. }
  1604. $metric->actions[$key]['disabled'] = !$isClick;
  1605. }
  1606. }
  1607. $hasAction = !empty($metrics) && !empty($metrics[0]->actions);
  1608. if(!$hasAction) unset($cols['actions']);
  1609. return array($cols, $metrics);
  1610. }
  1611. /**
  1612. * 判断度量项是否是旧版度量项。
  1613. * Judge if the metric is old.
  1614. *
  1615. * @param object $metric
  1616. * @access public
  1617. * @return bool
  1618. */
  1619. public function isOldMetric($metric)
  1620. {
  1621. return isset($metric->type) && $metric->type == 'sql';
  1622. }
  1623. /**
  1624. * 创建SQL函数。
  1625. * Create sql function.
  1626. *
  1627. * @param string $sql
  1628. * @param object $measurement
  1629. * @access public
  1630. * @return array
  1631. */
  1632. public function createSqlFunction($sql, $measurement)
  1633. {
  1634. $measFunction = $this->getSqlFunctionName($measurement);
  1635. $postFunction = $this->parseSqlFunction($sql);
  1636. if(!$measFunction || !$postFunction) return array('result' => 'fail', 'errors' => $this->lang->metric->tips->nameError);
  1637. $sql = str_replace($postFunction, $measFunction, $sql);
  1638. try
  1639. {
  1640. $this->dbh->exec("DROP FUNCTION IF EXISTS `$measFunction`");
  1641. $result = $this->dbh->exec($sql);
  1642. }
  1643. catch(PDOException $exception)
  1644. {
  1645. $message = sprintf($this->lang->metric->tips->createError, $exception->getMessage());
  1646. return array('result' => 'fail', 'errors' => $message);
  1647. }
  1648. return array('result' => 'success');
  1649. }
  1650. /**
  1651. * 获取旧度量项的SQL函数名。
  1652. * Get sql function name of a old metric.
  1653. *
  1654. * @param object $measurement
  1655. * @access public
  1656. * @return string
  1657. */
  1658. public function getSqlFunctionName($measurement)
  1659. {
  1660. if(!$measurement) return '';
  1661. return strtolower("qc_{$measurement->code}");
  1662. }
  1663. /**
  1664. * 处理请求参数。
  1665. * Process post params.
  1666. *
  1667. * @access public
  1668. * @return array
  1669. */
  1670. public function processPostParams()
  1671. {
  1672. return array_combine($this->post->varName, $this->post->queryValue);
  1673. }
  1674. /**
  1675. * 执行旧版度量项。
  1676. * Execute a sql metric.
  1677. *
  1678. * @param object $measurement
  1679. * @param array $vars
  1680. * @access public
  1681. * @return int|string
  1682. */
  1683. public function execSqlMeasurement($measurement, $vars)
  1684. {
  1685. $function = $this->getSqlFunctionName($measurement);
  1686. if(!$function)
  1687. {
  1688. $this->errorInfo = $this->lang->metric->tips->nameError;
  1689. return false;
  1690. }
  1691. $vars = (array) $vars;
  1692. foreach($vars as $key => $param)
  1693. {
  1694. if(is_object($param))
  1695. {
  1696. unset($vars[$key]);
  1697. continue;
  1698. }
  1699. $vars[$key] = $this->dbh->quote($param);
  1700. }
  1701. $params = join(',', $vars);
  1702. try
  1703. {
  1704. $result = $this->dbh->query("select $function($params)")->fetch(PDO::FETCH_NUM);
  1705. }
  1706. catch(PDOException $exception)
  1707. {
  1708. $this->errorInfo = $exception->getMessage();
  1709. return false;
  1710. }
  1711. return isset($result[0]) && $measurement->unit ? $result[0] : null;
  1712. }
  1713. /**
  1714. * 获取度量筛选器的下拉选项。
  1715. * Get options of a control.
  1716. *
  1717. * @param string $optionType
  1718. * @access public
  1719. * @return array
  1720. */
  1721. public function getControlOptions($optionType)
  1722. {
  1723. $optionList = array();
  1724. if($optionType == 'project')
  1725. {
  1726. $options = $this->loadModel('project')->getPairsByProgram();
  1727. }
  1728. elseif($optionType == 'product')
  1729. {
  1730. $options = $this->loadModel('product')->getPairs('nocode');
  1731. }
  1732. elseif($optionType == 'sprint')
  1733. {
  1734. $options = $this->loadModel('execution')->getPairs();
  1735. }
  1736. elseif($optionType == 'user')
  1737. {
  1738. $options = $this->loadModel('user')->getPairs('noletter');
  1739. }
  1740. elseif(strpos($optionType, '.') !== false)
  1741. {
  1742. list($moduleName, $varListName) = explode('.', $optionType);
  1743. $this->app->loadLang($moduleName);
  1744. $varListName .= 'List';
  1745. $options = $this->lang->$moduleName->$varListName;
  1746. unset($options[0]);
  1747. unset($options['']);
  1748. }
  1749. else
  1750. {
  1751. $options = array('' => '');
  1752. }
  1753. return $options;
  1754. }
  1755. /**
  1756. * 处理实现提示文本信息。
  1757. * Process implement tips.
  1758. *
  1759. * @param string $code
  1760. * @access public
  1761. * @return void
  1762. */
  1763. public function processImplementTips($code)
  1764. {
  1765. $tmpRoot = $this->app->getTmpRoot();
  1766. $instructionTips = $this->lang->metric->implement->instructionTips;
  1767. foreach($instructionTips as $index => $tip)
  1768. {
  1769. $instructionTips[$index] = str_replace("{code}", $code, $instructionTips[$index]);
  1770. $instructionTips[$index] = str_replace("{tmpRoot}", $tmpRoot, $instructionTips[$index]);
  1771. }
  1772. $this->lang->metric->implement->instructionTips = $instructionTips;
  1773. }
  1774. /**
  1775. * 更新一个度量项的字段内容。
  1776. * Update metric fields
  1777. *
  1778. * @param string $metricID
  1779. * @param object $metric
  1780. * @access public
  1781. * @return void
  1782. */
  1783. public function updateMetricFields($metricID, $metric)
  1784. {
  1785. $this->dao->update(TABLE_METRIC)->data($metric)->where('id')->eq($metricID)->exec();
  1786. }
  1787. /**
  1788. * 将旧度量项的信息附加到度量项列表中。
  1789. * Append old metric info to metric list.
  1790. *
  1791. * @param array $metrics
  1792. * @access public
  1793. * @return array
  1794. */
  1795. public function processOldMetrics($metrics)
  1796. {
  1797. $metricList = array();
  1798. if(!in_array($this->config->edition, array('max', 'ipd')))
  1799. {
  1800. foreach($metrics as $metric)
  1801. {
  1802. $metric->isOldMetric = false;
  1803. $metricList[] = $metric;
  1804. }
  1805. }
  1806. else
  1807. {
  1808. $oldMetricList = $this->getOldMetricList();
  1809. foreach($metrics as $metric)
  1810. {
  1811. $metric->isOldMetric = $this->isOldMetric($metric);
  1812. $fromID = $metric->fromID;
  1813. if($metric->isOldMetric && isset($oldMetricList[$fromID])) $metric->unit = $oldMetricList[$fromID]->unit;
  1814. $metricList[] = $metric;
  1815. }
  1816. }
  1817. return $metricList;
  1818. }
  1819. /**
  1820. * 获取定时设置的标签。
  1821. * Get label of collect configure.
  1822. *
  1823. * @param object $metric
  1824. * @access public
  1825. * @return string
  1826. */
  1827. public function getCollectConfText($metric)
  1828. {
  1829. $collectConf = $metric->collectConf;
  1830. $dateType = $this->lang->metric->dateList[$collectConf->type];
  1831. $dateConf = $collectConf->type == 'week' ? $collectConf->week : $collectConf->month;
  1832. $dateListText = '';
  1833. if($collectConf->type == 'week')
  1834. {
  1835. foreach(explode(',', $dateConf) as $date) $dateListText .= $this->lang->metric->weekList[$date] . ',';
  1836. }
  1837. else
  1838. {
  1839. foreach(explode(',', $dateConf) as $date) $dateListText .= sprintf($this->lang->metric->monthText, $date) . ',';
  1840. }
  1841. return sprintf($this->lang->metric->collectConfText, $dateType, rtrim($dateListText, ','), $metric->execTime);
  1842. }
  1843. /**
  1844. * 获取度量项的数据类型。
  1845. * Get data type of metric.
  1846. *
  1847. * @param array $tableData
  1848. * @access public
  1849. * @return string|false
  1850. */
  1851. public function getMetricRecordType($code, $scope)
  1852. {
  1853. if(!$code) return false;
  1854. $type = array();
  1855. $dateType = $this->getDateTypeByCode($code);
  1856. if($scope != 'system') $type[] = 'scope';
  1857. if($dateType != 'nodate') $type[] = 'date';
  1858. if(empty($type)) $type[] = 'system';
  1859. return implode('-', $type);
  1860. }
  1861. /**
  1862. * 获取一个echarts的legend配置。
  1863. * Get lengend options of echarts by head and series.
  1864. *
  1865. * @param array $series
  1866. * @param string $range object|time
  1867. * @access public
  1868. * @return array
  1869. */
  1870. public function getEchartLegend($series, $range = 'time')
  1871. {
  1872. $legend = array('type' => 'scroll');
  1873. if($range == 'object')
  1874. {
  1875. $selectedScope = array();
  1876. foreach($series as $index => $se)
  1877. {
  1878. $selectedScope[$se['name']] = $index == 0 ? true : false;
  1879. }
  1880. $legend['selector'] = true;
  1881. $legend['selected'] = $selectedScope;
  1882. }
  1883. return $legend;
  1884. }
  1885. /**
  1886. * 通过header来判断一个度量项有没有对象的概念。
  1887. * Judge whether a metric has the concept of an object.
  1888. *
  1889. * header 通过 metric 模块的 getViewTableHeader 方法取得
  1890. * @param array $header 表头
  1891. * @access public
  1892. * @return bool
  1893. */
  1894. public function isObjectMetric($header)
  1895. {
  1896. return in_array('scope', array_column($header, 'name'));
  1897. }
  1898. /**
  1899. * 通过header来判断一个度量项有没有日期的概念。
  1900. * Judge whether a metric has the concept of an date.
  1901. *
  1902. * header 通过 metric 模块的 getViewTableHeader 方法取得
  1903. * @param array $header 表头
  1904. * @access public
  1905. * @return bool
  1906. */
  1907. public function isDateMetric($header)
  1908. {
  1909. return in_array('date', array_column($header, 'name'));
  1910. }
  1911. /**
  1912. * 获取一个echarts的配置项。
  1913. * Get options of echarts by head and data.
  1914. *
  1915. * @param array $head 表头
  1916. * @param array $data 数据
  1917. * @param string $chartType 图表类型 barX|barY|line|pie
  1918. * @access public
  1919. * @return array|false
  1920. * @param mixed[] $header
  1921. */
  1922. public function getEchartsOptions($header, $data, $chartType = 'line')
  1923. {
  1924. if(!$header || !$data) return false;
  1925. $type = in_array($chartType, array('barX', 'barY')) ? 'bar' : $chartType;
  1926. if($type == 'pie') return $this->getPieEchartsOptions($header, $data);
  1927. $headLength = count($header);
  1928. $options = array();
  1929. if($headLength == 2)
  1930. {
  1931. $options = $this->getTimeOptions($header, $data, $type, $chartType);
  1932. }
  1933. elseif($headLength == 3)
  1934. {
  1935. if($this->isObjectMetric($header))
  1936. {
  1937. $options = $this->getObjectOptions($data, $type, $chartType);
  1938. }
  1939. else
  1940. {
  1941. $options = $this->getTimeOptions($header, $data, $type, $chartType);
  1942. }
  1943. }
  1944. elseif($headLength == 4)
  1945. {
  1946. $options = $this->getObjectOptions($data, $type, $chartType);
  1947. }
  1948. if($type == 'bar')
  1949. {
  1950. $xAxis = $options['xAxis'];
  1951. $yAxis = $options['yAxis'];
  1952. $options['xAxis'] = $chartType == 'barY' ? $yAxis : $xAxis;
  1953. $options['yAxis'] = $chartType == 'barY' ? $xAxis : $yAxis;
  1954. }
  1955. return $options;
  1956. }
  1957. /**
  1958. * 获取一个 对象属性度量的 echart options。
  1959. * Get options of echarts by data.
  1960. *
  1961. * @param array $data 数据
  1962. * @param string $type 类型 line|bar
  1963. * @access public
  1964. * @return array
  1965. * @param string $chartType
  1966. */
  1967. public function getObjectOptions($data, $type, $chartType)
  1968. {
  1969. $dateField = !isset(current($data)->dateString) ? 'calcTime' : 'dateString';
  1970. usort($data, function($a, $b) use ($dateField)
  1971. {
  1972. $keyA = $a->$dateField;
  1973. $keyB = $b->$dateField;
  1974. if($keyA == $keyB) return 0;
  1975. return $keyA < $keyB ? -1 : 1;
  1976. });
  1977. $times = array();
  1978. $objects = array();
  1979. foreach($data as $dataInfo)
  1980. {
  1981. $time = substr($dataInfo->$dateField, 0, 10);
  1982. $object = $dataInfo->scope;
  1983. $value = $dataInfo->value;
  1984. if(!isset($times[$time])) $times[$time] = $time;
  1985. if(!isset($objects[$object])) $objects[$object] = array();
  1986. $objects[$object][$time] = $value;
  1987. }
  1988. $xAxis = array('type' => 'category', 'data' => array_keys($times));
  1989. $yAxis = array('type' => 'value', 'min' => 'dataMin', 'max' => 'dataMax');
  1990. $series = array();
  1991. if($type != 'line') $yAxis = array('type' => 'value');
  1992. foreach($objects as $object => $datas)
  1993. {
  1994. $seriesData = array();
  1995. foreach($times as $time)
  1996. {
  1997. $seriesData[] = isset($datas[$time]) ? $datas[$time] : 0;
  1998. }
  1999. $series[] = array('type' => $type, 'name' => $object, 'data' => $seriesData);
  2000. }
  2001. $legend = $this->getEchartLegend($series, 'object');
  2002. $options = array();
  2003. $options['xAxis'] = $xAxis;
  2004. $options['yAxis'] = $yAxis;
  2005. $options['legend'] = $legend;
  2006. $options['series'] = $series;
  2007. $options['grid'] = $this->config->metric->chartConfig->grid;
  2008. $options['tooltip'] = $this->config->metric->chartConfig->tooltip;
  2009. $dataLength = count($series[0]['data']);
  2010. if($dataLength > 15) $options['dataZoom'] = $this->genDataZoom($dataLength, 15, $chartType == 'barY' ? 'y' : 'x');
  2011. return $options;
  2012. }
  2013. /**
  2014. * 获取一个 时间属性度量的 echart options。
  2015. * Get options of echarts by head and data.
  2016. *
  2017. * @param array $head 表头
  2018. * @param string $type 类型 line|bar
  2019. * @access public
  2020. * @return array
  2021. * @param mixed[] $header
  2022. * @param mixed[] $data
  2023. * @param string $chartType
  2024. */
  2025. public function getTimeOptions($header, $data, $type, $chartType)
  2026. {
  2027. list($x, $y) = $this->getEchartXY($header);
  2028. usort($data, function($a, $b) use ($x)
  2029. {
  2030. $keyA = $a->$x;
  2031. $keyB = $b->$x;
  2032. if($keyA == $keyB) return 0;
  2033. return $keyA < $keyB ? -1 : 1;
  2034. });
  2035. $xTime = array_column($data, $x);
  2036. $xAxis = array('type' => 'category', 'data' => $xTime);
  2037. $yAxis = array('type' => 'value', 'min' => 'dataMin', 'max' => 'dataMax');
  2038. if($type != 'line') $yAxis = array('type' => 'value');
  2039. $series = array('type' => $type, 'data' => array_column($data, $y));
  2040. $legend = $this->getEchartLegend($series);
  2041. $options = array();
  2042. $options['xAxis'] = $xAxis;
  2043. $options['yAxis'] = $yAxis;
  2044. $options['legend'] = $legend;
  2045. $options['series'] = $series;
  2046. $options['grid'] = $this->config->metric->chartConfig->grid;
  2047. $options['tooltip'] = $this->config->metric->chartConfig->tooltip;
  2048. $dataLength = count($data);
  2049. if($dataLength > 15) $options['dataZoom'] = $this->genDataZoom($dataLength, 15, $chartType == 'barY' ? 'y' : 'x');
  2050. return $options;
  2051. }
  2052. /**
  2053. * 生成数据缩放的配置。
  2054. * Generate data zoom config.
  2055. *
  2056. * @param int $dataLength
  2057. * @param int $initZoom
  2058. * @param string $axis
  2059. * @access public
  2060. * @return array
  2061. */
  2062. public function genDataZoom($dataLength, $initZoom = 10, $axis = 'x')
  2063. {
  2064. $percent = $initZoom / $dataLength * 100;
  2065. $percent = $percent > 100 ? 100 : $percent;
  2066. $dataZoom = $this->config->metric->chartConfig->dataZoom;
  2067. $dataZoom['start'] = 0;
  2068. $dataZoom['end'] = $percent;
  2069. if($axis == 'x') $dataZoom['xAxisIndex'] = array(0);
  2070. if($axis == 'y') $dataZoom['yAxisIndex'] = array(0);
  2071. return array($dataZoom);
  2072. }
  2073. /**
  2074. * 获取一个echarts pie的配置项。
  2075. * Get options of echarts pie by head and data.
  2076. *
  2077. * @param array $head 表头
  2078. * @param array $datas 数据
  2079. * @access public
  2080. * @return array
  2081. * @param mixed[] $header
  2082. */
  2083. public function getPieEchartsOptions($header, $datas)
  2084. {
  2085. list($x, $y) = $this->getEchartXY($header);
  2086. $seriesData = array();
  2087. foreach($datas as $data)
  2088. {
  2089. $seriesData[] = array('name' => $data->$x, 'value' => $data->$y);
  2090. }
  2091. $options = array();
  2092. $options['tooltip'] = array('trigger' => 'item');
  2093. $options['legend'] = array('orient' => 'vertical', 'left' => 'left', 'type' => 'scroll');
  2094. $options['series'] = array(array('type' => 'pie', 'radius' => '50%', 'data' => $seriesData, 'emphasis' => array('itemStyle' => array('shadowBlur' => 10, 'shadowOffsetX' => 0, 'shadowColor' => 'rgba(0, 0, 0, 0.5)'))));
  2095. return $options;
  2096. }
  2097. /**
  2098. * 获取图表 x轴 和 y轴的字段。
  2099. * Get echart x and y field.
  2100. *
  2101. * @param array $header 表头
  2102. * @access public
  2103. * @return array
  2104. */
  2105. public function getEchartXY($header)
  2106. {
  2107. $x = $y = '';
  2108. $headLength = count($header);
  2109. if($headLength == 2)
  2110. {
  2111. $x = $header[1]['name'];
  2112. $y = $header[0]['name'];
  2113. }
  2114. elseif($headLength == 3)
  2115. {
  2116. $x = $header[0]['name'];
  2117. $y = $header[1]['name'];
  2118. }
  2119. return array($x, $y);
  2120. }
  2121. /**
  2122. * 获取一个图表可选的类型。
  2123. * Get a echart typeList.
  2124. *
  2125. * @param array $header 表头
  2126. * @access public
  2127. * @return array
  2128. */
  2129. public function getChartTypeList($header)
  2130. {
  2131. $chartTypeList = $this->lang->metric->chartTypeList;
  2132. if($this->isObjectMetric($header)) unset($chartTypeList['pie']);
  2133. return $chartTypeList;
  2134. }
  2135. /**
  2136. * 更新度量项的创建时间
  2137. * Update created date of metrics.
  2138. *
  2139. * @access public
  2140. * @return void
  2141. */
  2142. public function updateMetricDate()
  2143. {
  2144. $this->dao->update(TABLE_METRIC)->set('createdDate')->eq(helper::now())->where('createdDate')->isNull()->exec();
  2145. }
  2146. /**
  2147. * 获取度量项计算文件的根目录。
  2148. * Get root of metric calculator.
  2149. *
  2150. * @access public
  2151. * @return string
  2152. */
  2153. public function getCalcRoot()
  2154. {
  2155. return $this->app->getModuleRoot() . 'metric' . DS . 'calc' . DS;
  2156. }
  2157. /**
  2158. * 获取数据集文件的路径
  2159. * Get path of calculator data set.
  2160. *
  2161. * @access public
  2162. * @return string
  2163. */
  2164. public function getDatasetPath()
  2165. {
  2166. return $this->app->getModuleRoot() . 'metric' . DS . 'dataset.php';
  2167. }
  2168. /**
  2169. * 获取度量项基类文件的路径。
  2170. * Get path of base calculator class.
  2171. *
  2172. * @access public
  2173. * @return string
  2174. */
  2175. public function getBaseCalcPath()
  2176. {
  2177. return $this->app->getModuleRoot() . 'metric' . DS . 'calc.class.php';
  2178. }
  2179. /**
  2180. * 处理度量数据查询条件。
  2181. * Process metric data query.
  2182. *
  2183. * @param array $query
  2184. * @param string $key
  2185. * @param string $type
  2186. * @access public
  2187. * @return object|string|false
  2188. */
  2189. public function processRecordQuery($query, $key, $type = 'common')
  2190. {
  2191. if($key == 'dateLabel')
  2192. {
  2193. if(isset($query[$key]))
  2194. {
  2195. $dateType = $query['dateType'];
  2196. $dateLabel = $query['dateLabel'];
  2197. if($dateLabel == 'all') $dateStr = '1970-01-01';
  2198. if(is_numeric($dateLabel)) $dateStr = date('Y-m-d', strtotime('-' . ((int)$dateLabel - 1) . " {$dateType}s"));
  2199. $query['dateBegin'] = $dateStr;
  2200. $query['dateEnd'] = date('Y-m-d');
  2201. }
  2202. $begin = $this->processRecordQuery($query, 'dateBegin', 'date');
  2203. $end = $this->processRecordQuery($query, 'dateEnd', 'date');
  2204. return array($begin, $end);
  2205. }
  2206. if($key == 'calcDate' && $query['dateType'] == 'nodate')
  2207. {
  2208. if(!isset($query[$key])) $query['calcDate'] = 7;
  2209. return date('Y-m-d', strtotime("-{$query[$key]} days"));
  2210. }
  2211. if(!isset($query[$key]) || empty($query[$key])) return false;
  2212. if($type == 'date')
  2213. {
  2214. $dateStr = $query[$key];
  2215. if($query['dateType'] == 'year')
  2216. {
  2217. $dateStr = "{$dateStr}-01-01";
  2218. }
  2219. elseif($query['dateType'] == 'month')
  2220. {
  2221. $dateStr = "{$dateStr}-01";
  2222. }
  2223. $timestamp = strtotime($dateStr);
  2224. $year = date('Y', $timestamp);
  2225. $month = date('m', $timestamp);
  2226. $day = date('d', $timestamp);
  2227. $week = date('oW', $timestamp);
  2228. $dateParse = new stdClass();
  2229. $dateParse->year = $year;
  2230. $dateParse->month = "{$year}{$month}";
  2231. $dateParse->week = $week;
  2232. $dateParse->day = "{$year}{$month}{$day}";
  2233. return $dateParse;
  2234. }
  2235. return $query[$key];
  2236. }
  2237. /**
  2238. * 根据日期类型和日期,拆解出日期的年、月、周、日。
  2239. * Get date values by date type and date.
  2240. *
  2241. * @param string $date
  2242. * @param string $dateType
  2243. * @access public
  2244. * @return object|array
  2245. */
  2246. public function parseDateStr($date, $dateType = 'all')
  2247. {
  2248. $timestamp = strtotime($date);
  2249. $year = date('Y', $timestamp);
  2250. $month = date('m', $timestamp);
  2251. $day = date('d', $timestamp);
  2252. $week = date('oW', $timestamp);
  2253. $week = substr($week, -2);
  2254. $dateValues = new stdClass();
  2255. $dateValues->year = array('year' => $year);
  2256. $dateValues->month = array('year' => $year, 'month' => $month);
  2257. $dateValues->week = array('year' => $year, 'week' => $week);
  2258. $dateValues->day = array('year' => $year, 'month' => $month, 'day' => $day);
  2259. $dateValues->nodate = array();
  2260. return $dateType == 'all' ? $dateValues : $dateValues->$dateType;
  2261. }
  2262. /**
  2263. * 获取度量数据的日期类型。
  2264. * Get date type of metric data.
  2265. *
  2266. * @param array $dateFields
  2267. * @access public
  2268. * @return string
  2269. */
  2270. public function getDateType($dateFields)
  2271. {
  2272. $dateList = array_intersect($dateFields, $this->config->metric->dateList);
  2273. if(in_array('day', $dateList)) return 'day';
  2274. if(in_array('week', $dateList)) return 'week';
  2275. if(in_array('month', $dateList)) return 'month';
  2276. if(in_array('year', $dateList)) return 'year';
  2277. return 'nodate';
  2278. }
  2279. /**
  2280. * 根据编号获取度量项的日期属性。
  2281. * Get date type by metric code.
  2282. *
  2283. * @param string $code
  2284. * @access public
  2285. * @return string
  2286. */
  2287. public function getDateTypeByCode($code)
  2288. {
  2289. static $dateTypes = array();
  2290. if(!isset($dateTypes[$code]))
  2291. {
  2292. /* Get dateType form db first. */
  2293. $metric = $this->getByCode($code, 'dateType');
  2294. if(!empty($metric->dateType)) $dateTypes[$code] = $metric->dateType;
  2295. /* Get dateType from config second. */
  2296. elseif(isset($this->config->metric->dateType[$code])) $dateTypes[$code] = $this->config->metric->dateType[$code];
  2297. /* Return nodate if no matches. */
  2298. else $dateTypes[$code] = 'nodate';
  2299. }
  2300. return $dateTypes[$code];
  2301. }
  2302. /**
  2303. * 获取度量数据的日期类型。
  2304. * Get date type of metric data.
  2305. *
  2306. * @param array $dateFields
  2307. * @access public
  2308. * @return string
  2309. * @param string $dateType
  2310. */
  2311. public function getDateByDateType($dateType)
  2312. {
  2313. if($dateType == 'day') $sub = '-7 days';
  2314. if($dateType == 'week') $sub = '-1 month';
  2315. if($dateType == 'month') $sub = '-1 year';
  2316. if($dateType == 'year') $sub = '-3 years';
  2317. return date('Y-m-d', strtotime($sub));
  2318. }
  2319. /**
  2320. * 解析SQL函数。
  2321. * Parsing SQL function.
  2322. *
  2323. * @param string $sql
  2324. * @access public
  2325. * @return string
  2326. */
  2327. public function parseSqlFunction($sql)
  2328. {
  2329. $pattern = "/create\s+function\s+`{0,1}([\$,a-z,A-z,_,0-9,\(,|)]+`{0,1})\(+/Ui";
  2330. preg_match_all($pattern, $sql, $matches);
  2331. if(empty($matches[1][0])) return null;
  2332. return trim($matches[1][0], '`');
  2333. }
  2334. /**
  2335. * 替换换行符和回车符为指定字符。
  2336. * Replace CR and LF to char.
  2337. *
  2338. * @param string $str
  2339. * @param string $replace
  2340. * @access public
  2341. * @return string
  2342. */
  2343. public function replaceCRLF($str, $replace = ';')
  2344. {
  2345. $str = trim($str);
  2346. if(strpos($str, "\n\r") !== false) $str = str_replace("\n\r", $replace, $str);
  2347. if(strpos($str, "\r\n") !== false) $str = str_replace("\r\n", $replace, $str);
  2348. if(strpos($str, "\n") !== false) $str = str_replace("\n", $replace, $str);
  2349. if(strpos($str, "\r") !== false) $str = str_replace("\r", $replace, $str);
  2350. return $str;
  2351. }
  2352. /**
  2353. * 判断 header 是否有分组(合并单元格)。
  2354. * Judge header whether there are merges cell.
  2355. *
  2356. * @param array $header
  2357. * @access protected
  2358. * @return array
  2359. */
  2360. protected function isHeaderGroup($header)
  2361. {
  2362. if(!$header) return false;
  2363. foreach($header as $head)
  2364. {
  2365. if(isset($head['headerGroup'])) return true;
  2366. }
  2367. return false;
  2368. }
  2369. /**
  2370. * 判断是否是按系统统计的度量项。
  2371. * Determine whether it is metric in system.
  2372. *
  2373. * @param array $results
  2374. * @access public
  2375. * @return bool
  2376. */
  2377. public function isSystemMetric($results)
  2378. {
  2379. $firstRecord = (object)current($results);
  2380. foreach($this->config->metric->excludeGlobal as $exclude)
  2381. {
  2382. if(isset($firstRecord->$exclude)) return false;
  2383. }
  2384. return true;
  2385. }
  2386. /**
  2387. * 获取某一周的第一天和最后一天的日期。
  2388. * Get the first and last day of a week.
  2389. *
  2390. * @param int|string $year
  2391. * @param int|string $week
  2392. * @param string $type date|datetime
  2393. * @access public
  2394. * @return bool
  2395. */
  2396. public function getStartAndEndOfWeek($year, $week, $type = 'datetime')
  2397. {
  2398. $firstDayOfYear = date('Y-01-01', strtotime("$year-01-01"));
  2399. $firstDayOfWeek = date('N', strtotime($firstDayOfYear));
  2400. $offsetDays = ($week - 1) * 7 - ($firstDayOfWeek - 1);
  2401. $firstDayOfWeek = date('Y-m-d', strtotime("$firstDayOfYear +$offsetDays days"));
  2402. $lastDayOfWeek = date('Y-m-d', strtotime("$firstDayOfWeek +6 days"));
  2403. if($type == 'datetime') return array("$firstDayOfWeek 00:00:00", "$lastDayOfWeek 23:59:59");
  2404. if($type == 'date') return array($firstDayOfWeek, $lastDayOfWeek);
  2405. }
  2406. /**
  2407. * 判断某个度量项在某天是否被定时任务执行过。
  2408. * Determine whether a metric has been executed by scheduled task on a certain day.
  2409. *
  2410. * @param string $code
  2411. * @param string $date
  2412. * @param string $dateType
  2413. * @access public
  2414. * @return bool
  2415. */
  2416. public function isCalcByCron($code, $date, $dateType)
  2417. {
  2418. $startDate = '';
  2419. $endDate = '';
  2420. $parsedDate = $this->parseDateStr($date, $dateType);
  2421. if($dateType == 'year')
  2422. {
  2423. $startDate = "{$parsedDate['year']}-01-01 00:00:00";
  2424. $endDate = "{$parsedDate['year']}-12-31 23:59:59";
  2425. }
  2426. if($dateType == 'month')
  2427. {
  2428. $startDate = "{$parsedDate['year']}-{$parsedDate['month']}-01 00:00:00";
  2429. $nextMonth = date('Y-m-01', strtotime("$startDate +1 month"));
  2430. $endDate = date('Y-m-d', strtotime("$nextMonth -1 day"));
  2431. $endDate = "{$endDate} 23:59:59";
  2432. }
  2433. if($dateType == 'day')
  2434. {
  2435. $startDate = "{$parsedDate['year']}-{$parsedDate['month']}-{$parsedDate['day']} 00:00:00";
  2436. $endDate = "{$parsedDate['year']}-{$parsedDate['month']}-{$parsedDate['day']} 23:59:59";
  2437. }
  2438. if($dateType == 'week') list($startDate, $endDate) = $this->getStartAndEndOfWeek($parsedDate['year'], $parsedDate['week']);
  2439. $record = $this->dao->select('id')->from(TABLE_METRICLIB)
  2440. ->where('metricCode')->eq($code)
  2441. ->andWhere('calcType')->eq('cron')
  2442. ->andWhere('date')->ge($startDate)
  2443. ->andWhere('date')->le($endDate)
  2444. ->limit(1)
  2445. ->fetch();
  2446. return !empty($record);
  2447. }
  2448. /**
  2449. * 获取度量项的收集周期。
  2450. * Get collect cycle of metric.
  2451. *
  2452. * @param array|object $record
  2453. * @access public
  2454. * @return string|null
  2455. */
  2456. public function getMetricCycle($record)
  2457. {
  2458. $record = (object)$record;
  2459. if(isset($record->year) && !isset($record->month) && !isset($record->week)) return 'year';
  2460. if(isset($record->year, $record->month) && !isset($record->day)) return 'month';
  2461. if(isset($record->year, $record->month, $record->day)) return 'day';
  2462. if(isset($record->year, $record->week)) return 'week';
  2463. return null;
  2464. }
  2465. /**
  2466. * 获取日期标签。
  2467. * Get date labels.
  2468. *
  2469. * @param string $dateType
  2470. * @access public
  2471. * @return array|false
  2472. */
  2473. public function getDateLabels($dateType)
  2474. {
  2475. if($dateType == 'nodate') return array();
  2476. $objectKey = "{$dateType}Labels";
  2477. return $this->lang->metric->query->$objectKey;
  2478. }
  2479. /**
  2480. * 获取默认选中的日期。
  2481. * Get default selected date.
  2482. *
  2483. * @param array $dateLabels
  2484. * @param array $filters
  2485. * @access public
  2486. * @return string
  2487. */
  2488. public function getDefaultDate($dateLabels, $filters = array())
  2489. {
  2490. $defaultDate = '';
  2491. $dates = array_keys($dateLabels);
  2492. return (string)current($dates);
  2493. }
  2494. /**
  2495. * 获取没有数据时的提示信息。
  2496. * Get no data tip.
  2497. *
  2498. * @param string $code
  2499. * @access public
  2500. * @return string
  2501. */
  2502. public function getNoDataTip($code)
  2503. {
  2504. $recordOfCode = $this->metricTao->fetchMetricRecordByDate($code, null, 1);
  2505. $recordOfAll = $this->metricTao->fetchMetricRecordByDate('all', null, 1);
  2506. /* 如果度量库没有任何数据,则说明度量项从来没有被采集过。*/
  2507. /* If the metric library does not have any data, it means that the metric has never been collected. */
  2508. if(empty($recordOfAll)) return $this->lang->metric->noDataBeforeCollect;
  2509. /* 如果度量库有数据但没有该度量项的数据,则可以理解为收集过数据但是该度量项无数据。*/
  2510. /* If the metric library has data but does not have data for this metric, it means that metric has been collected but there is no data for this metric. */
  2511. if(!empty($recordOfAll) && empty($recordOfCode)) return $this->lang->metric->noDataAfterCollect;
  2512. return $this->lang->metric->noData;
  2513. }
  2514. /**
  2515. * 检查某个度量项在某个日期中是否被推算过。
  2516. * Check whether a metric has been inferenced on a date
  2517. *
  2518. * @param string $code
  2519. * @param string $dateType
  2520. * @param string $date
  2521. * @access public
  2522. * @return bool
  2523. */
  2524. public function checkHasInferenceOfDate($code, $dateType, $date)
  2525. {
  2526. if($dateType == 'day' || $dateType == 'nodate') return false;
  2527. $date = $this->parseDateStr($date, $dateType);
  2528. $records = $this->dao->select('id')->from(TABLE_METRICLIB)
  2529. ->where('metricCode')->eq($code)
  2530. ->andWhere('calcType')->eq('inference')
  2531. ->beginIF($dateType == 'year')->andWhere('year')->eq($date['year'])->fi()
  2532. ->beginIF($dateType == 'month')->andWhere('year')->eq($date['year'])->andWhere('month')->eq($date['month'])->fi()
  2533. ->beginIF($dateType == 'week')->andWhere('year')->eq($date['year'])->andWhere('week')->eq($date['week'])->fi()
  2534. ->fetch();
  2535. return !empty($records);
  2536. }
  2537. /**
  2538. * 检查是否是第一次执行重算。
  2539. * Check if this is the first time inference record.
  2540. *
  2541. * @param string|array|null $codes
  2542. * @access public
  2543. * @return bool
  2544. */
  2545. public function isFirstInference($codes = null)
  2546. {
  2547. $inferenceRecordCount = $this->dao->select('COUNT(id) AS count')->from(TABLE_METRICLIB)
  2548. ->where('calcType')->eq('inference')
  2549. ->beginIF($codes != null && !is_array($codes))->andWhere('metricCode')->eq($codes)->fi()
  2550. ->beginIF($codes != null && is_array($codes))->andWhere('metricCode')->in($codes)->fi()
  2551. ->fetch('count');
  2552. return $inferenceRecordCount == 0;
  2553. }
  2554. /**
  2555. * 根据动态获取安装禅道的大概时间。
  2556. * Get date of install zentao accorrading action.
  2557. *
  2558. * @access public
  2559. * @return string
  2560. */
  2561. public function getInstallDate()
  2562. {
  2563. $installedDate = $this->dao->select('value')->from(TABLE_CONFIG)
  2564. ->where('section')->eq('global')
  2565. ->andWhere('key')->eq('installedDate')
  2566. ->limit(1)
  2567. ->fetch('value');
  2568. if(!empty($installedDate) && substr($installedDate, 0 ,4) != '0000') return $installedDate;
  2569. return $this->dao->select('date')->from(TABLE_ACTION)
  2570. ->orderBy('date_asc')
  2571. ->limit(1)
  2572. ->fetch('date');
  2573. }
  2574. /**
  2575. * 根据版本过滤度量项。
  2576. * Filter metric instance by edition.
  2577. *
  2578. * @param array $calcInstances
  2579. * @access public
  2580. * @return array
  2581. */
  2582. public function filterCalcByEdition($calcInstances)
  2583. {
  2584. foreach($calcInstances as $code => $instance)
  2585. {
  2586. $excludeDatasetConfig = $this->config->metric->excludeDatasetList;
  2587. if(!empty($instance->dataset) && !empty($excludeDatasetConfig[$this->config->edition]))
  2588. {
  2589. $excludeDatasetList = $excludeDatasetConfig[$this->config->edition];
  2590. if(in_array($instance->dataset, $excludeDatasetList)) unset($calcInstances[$code]);
  2591. }
  2592. }
  2593. return $calcInstances;
  2594. }
  2595. }