model.php 130 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471
  1. <?php
  2. /**
  3. * The model file of screen module of ZenTaoPMS.
  4. *
  5. * @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.cnezsoft.com)
  6. * @license ZPL(http://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
  7. * @author Mengyi Liu <liumengyi@cnezsoft.com>
  8. * @package task
  9. * @version $Id: control.php 5106 2022-02-08 17:15:54Z $
  10. * @link https://www.zentao.net
  11. */
  12. class screenModel extends model
  13. {
  14. /**
  15. * 全局过滤器。
  16. * Global filter.
  17. *
  18. * @var object
  19. * @access public
  20. */
  21. public $filter;
  22. /**
  23. * 初始化函数。
  24. * Construct function.
  25. *
  26. * @access public
  27. * @return void
  28. */
  29. public function __construct()
  30. {
  31. parent::__construct();
  32. $this->loadBIDAO();
  33. $this->loadModel('bi');
  34. $this->filter = new stdclass();
  35. $this->filter->screen = '';
  36. $this->filter->year = '';
  37. $this->filter->month = '';
  38. $this->filter->dept = '';
  39. $this->filter->account = '';
  40. $this->filter->charts = array();
  41. }
  42. /**
  43. * 判断是否有权限访问。
  44. * Check screen access.
  45. *
  46. * @param int $dimensionID
  47. * @access public
  48. * @return array
  49. */
  50. public function checkAccess($screenID)
  51. {
  52. $viewableObjects = $this->bi->getViewableObject('screen');
  53. if(!in_array($screenID, $viewableObjects))
  54. {
  55. return $this->app->control->sendError($this->lang->screen->accessDenied, helper::createLink('screen', 'browse'));
  56. }
  57. }
  58. /**
  59. * 通过维度id获取大屏列表。
  60. * Get screen list by dimension id.
  61. *
  62. * @param int $dimensionID
  63. * @access public
  64. * @return array
  65. */
  66. public function getList($dimensionID)
  67. {
  68. return $this->dao->select('*')->from(TABLE_SCREEN)
  69. ->where('dimension')->eq($dimensionID)
  70. ->beginIF($this->config->edition == 'open')->andWhere('id')->ne($this->config->screen->phpScreen['usageReport'])->fi()
  71. ->andWhere('deleted')->eq('0')
  72. ->fetchAll('id', false);
  73. }
  74. /**
  75. * 通过id获取大屏信息。
  76. * Get screen by id.
  77. *
  78. * @param int $screenID
  79. * @param int $year
  80. * @param int $dept
  81. * @param string $account
  82. * @access public
  83. * @return object|bool
  84. * @param int $month
  85. */
  86. public function getByID($screenID, $year = 0, $month = 0, $dept = 0, $account = '', $withChartData = true)
  87. {
  88. $screen = $this->dao->select('*')->from(TABLE_SCREEN)->where('id')->eq($screenID)->fetch();
  89. if(!$screen) return false;
  90. if(empty($screen->scheme)) $screen->scheme = file_get_contents(__DIR__ . '/json/screen.json');
  91. if($withChartData) $screen->chartData = $this->genChartData($screen, $year, $month, $dept, $account);
  92. return $screen;
  93. }
  94. /**
  95. * 添加组件列表。
  96. * Add component list.
  97. *
  98. * @param object $scheme
  99. * @param array $componentList
  100. * @access public
  101. * @return object
  102. */
  103. public function addComponentList($scheme, $componentList)
  104. {
  105. if(is_string($scheme)) $scheme = json_decode($scheme);
  106. $scheme->componentList = $componentList;
  107. return $scheme;
  108. }
  109. /**
  110. * 构建大屏图表数据。
  111. * Generate chartData of screen.
  112. *
  113. * @param object $screen
  114. * @param int $year
  115. * @param int $dept
  116. * @param string $account
  117. * @access public
  118. * @return object
  119. * @param int $month
  120. */
  121. public function genChartData($screen, $year, $month, $dept, $account)
  122. {
  123. $this->filter = new stdclass();
  124. $this->filter->screen = $screen->id;
  125. $this->filter->year = $year;
  126. $this->filter->month = $month;
  127. $this->filter->dept = $dept;
  128. $this->filter->account = $account;
  129. $this->filter->charts = array();
  130. $scheme = json_decode($screen->scheme);
  131. if(!$screen->builtin or in_array($screen->id, $this->config->screen->builtinScreen))
  132. {
  133. foreach($scheme->componentList as $component)
  134. {
  135. if(!empty($component->isGroup))
  136. {
  137. foreach($component->groupList as $key => $groupComponent)
  138. {
  139. if(isset($groupComponent->key) and $groupComponent->key === 'Select') $groupComponent = $this->buildSelect($groupComponent);
  140. $this->setComponentDefaults($groupComponent);
  141. }
  142. }
  143. else
  144. {
  145. if(isset($component->key) and $component->key === 'Select') $component = $this->buildSelect($component);
  146. $this->setComponentDefaults($component);
  147. }
  148. }
  149. return $scheme;
  150. }
  151. $scheme->componentList = $this->buildComponentList($scheme->componentList);
  152. return $scheme;
  153. }
  154. /**
  155. * Get the latest chart.
  156. *
  157. * @param object $component
  158. * @access public
  159. * @return void
  160. */
  161. public function getLatestChart($component)
  162. {
  163. if(isset($component->key) and $component->key === 'Select') return $component;
  164. $chartID = zget($component->chartConfig, 'sourceID', '');
  165. if(!$chartID) return $component;
  166. $type = $component->chartConfig->package;
  167. $type = $this->getChartType($type);
  168. $table = $this->config->objectTables[$type];
  169. if($type == 'metric')
  170. {
  171. $chart = $this->loadModel('metric')->getByID($chartID);
  172. }
  173. else
  174. {
  175. $chart = $this->dao->select('*')->from($table)->where('id')->eq($chartID)->fetch();
  176. }
  177. if($type == 'metric') return $this->genMetricComponent($chart);
  178. return $this->genComponentData($chart, $type, $component);
  179. }
  180. /**
  181. * mergeChartAndPivotFilters
  182. *
  183. * @param int $type
  184. * @param int $chartOrPivot
  185. * @param int $sourceID
  186. * @param int $filters
  187. * @access public
  188. * @return void
  189. */
  190. public function mergeChartAndPivotFilters($type, $chartOrPivot, $sourceID, $filters)
  191. {
  192. $filterFormat = array();
  193. $chartOrPivotFilters = json_decode($chartOrPivot->filters, true);
  194. $mergeFilters = array();
  195. foreach($chartOrPivotFilters as $index => $chartOrPivotFilter)
  196. {
  197. if(!isset($filters[$index]['default'])) continue;
  198. $filterDefault = $filters[$index]['default'];
  199. if($filterDefault === null) continue;
  200. $filterType = $chartOrPivotFilter['type'];
  201. $filterFrom = zget($chartOrPivotFilter, 'from', '');
  202. if($filterType == 'date' or $filterType == 'datetime')
  203. {
  204. if($filterFrom == 'query')
  205. {
  206. if(is_numeric($filterDefault)) $filterDefault = date('Y-m-d H:i:s', $filterDefault / 1000);
  207. }
  208. else
  209. {
  210. if(is_array($filterDefault))
  211. {
  212. $begin = $filterDefault[0];
  213. $end = $filterDefault[1];
  214. $begin = date('Y-m-d H:i:s', $begin / 1000);
  215. $end = date('Y-m-d H:i:s', $end / 1000);
  216. $filterDefault = array('begin' => $begin, 'end' => $end);
  217. }
  218. else
  219. {
  220. $filterDefault = array('begin' => '', 'end' => '');
  221. }
  222. }
  223. }
  224. $chartOrPivotFilter['default'] = $filterDefault;
  225. $mergeFilters[] = $chartOrPivotFilter;
  226. }
  227. if($type == 'pivot')
  228. {
  229. list($sql, $filterFormat) = $this->loadModel($type)->getFilterFormat($chartOrPivot->sql, $mergeFilters);
  230. $chartOrPivot->sql = $sql;
  231. }
  232. else
  233. {
  234. $filterFormat = $this->loadModel($type)->getFilterFormat($mergeFilters);
  235. }
  236. return array($chartOrPivot, $filterFormat);
  237. }
  238. /**
  239. * Generate a component of screen.
  240. *
  241. * @param object $chart
  242. * @param string $type
  243. * @param object $component
  244. * @access public
  245. * @return object
  246. */
  247. public function genComponentData($chart, $type = 'chart', $component = null, $filters = array())
  248. {
  249. if(empty($chart) || ($chart->stage == 'draft' || $chart->deleted == '1'))
  250. {
  251. return $this->genNotFoundOrDraftComponentOption($component, $chart, $type);
  252. }
  253. $component = $this->unsetComponentDraftMarker($component);
  254. $chart = clone($chart);
  255. if($type == 'pivot' and $chart)
  256. {
  257. $this->loadModel('pivot')->processNameDesc($chart);
  258. }
  259. if(empty($filters) and !empty($chart->filters))
  260. {
  261. if($type == 'pivot')
  262. {
  263. list($sql, $filters) = $this->loadModel($type)->getFilterFormat($chart->sql, json_decode($chart->filters, true));
  264. $chart->sql = $sql;
  265. }
  266. else
  267. {
  268. $filters = $this->loadModel($type)->getFilterFormat(json_decode($chart->filters, true));
  269. }
  270. }
  271. list($component, $typeChanged) = $this->initComponent($chart, $type, $component);
  272. $component = $this->getChartOption($chart, $component, $filters);
  273. if($type == 'chart') $component = $this->getAxisRotateOption($chart, $component);
  274. $latestFilters = $this->getChartFilters($chart);
  275. $component = $this->updateComponentFilters($component, $latestFilters);
  276. if($type == 'chart' && (!$chart->builtin or in_array($chart->id, $this->config->screen->builtinChart)))
  277. {
  278. if(!empty($component->option->series))
  279. {
  280. $defaultSeries = $component->option->series;
  281. if($component->type == 'radar')
  282. {
  283. $component->option->radar->indicator = $component->option->dataset->radarIndicator;
  284. $defaultSeries[0]->data = $component->option->dataset->seriesData;
  285. $legends = array();
  286. foreach($component->option->dataset->seriesData as $seriesData) $legends[] = $seriesData->name;
  287. $component->option->legend->data = $legends;
  288. }
  289. elseif($component->type != 'waterpolo')
  290. {
  291. $series = array();
  292. for($i = 1; $i < count($component->option->dataset->dimensions); $i ++) $series[] = $defaultSeries[0];
  293. $component->option->series = $series;
  294. }
  295. }
  296. }
  297. return $component;
  298. }
  299. /**
  300. * 通过路径设置值。
  301. * Set value by path.
  302. *
  303. * @param object $option
  304. * @param string $path
  305. * @param mixed $value
  306. * @access public
  307. * @return void
  308. */
  309. public function setValueByPath(&$option, $path, $value)
  310. {
  311. $keys = explode('.', $path);
  312. $keyCount = count($keys);
  313. $current = &$option;
  314. foreach ($keys as $index => $key)
  315. {
  316. $isEnd = ($index + 1) >= $keyCount;
  317. $nextKey = $isEnd ? null : $keys[$index + 1];
  318. $isArray = is_numeric($key);
  319. $nextIsArray = is_numeric($nextKey);
  320. if($isArray && !isset($current[$key])) $current[$key] = $nextIsArray ? array() : new stdclass();
  321. if(!$isArray && !isset($current->$key)) $current->$key = $nextIsArray ? array() : new stdclass();
  322. if (is_array($current)) {
  323. $current = &$current[$key];
  324. } else {
  325. $current = &$current->$key;
  326. }
  327. }
  328. $current = $value;
  329. }
  330. /**
  331. * 通过静态数据生成组件。
  332. * Generate component.
  333. *
  334. * @param string $type
  335. * @param string $title
  336. * @param mixed $data
  337. * @param array $attr
  338. * @param array $options
  339. * @access public
  340. * @return object
  341. */
  342. public function genComponentFromData($type, $title, $data, $attr = array(), $options = array())
  343. {
  344. $component = new stdclass();
  345. $component->title = $title;
  346. $component->type = $type;
  347. $component->chartConfig = json_decode(zget($this->config->screen->chartConfig, $type));
  348. $component->key = $component->chartConfig->key;
  349. $component->attr = array('w' => 450, 'h' => 300, 'x' => 0, 'y' => 0, 'offsetX' => 0, 'offsetY' => 0, 'zIndex' => -1);
  350. $component->attr = array_merge($component->attr, $attr);
  351. $component->option = json_decode(zget($this->config->screen->chartOption, $type));
  352. $component->option->dataset = new stdclass();
  353. foreach($options as $keys => $value) $this->setValueByPath($component->option, $keys, $value);
  354. $component->chartConfig->title = $title;
  355. switch($type)
  356. {
  357. case 'line':
  358. case 'cluBarY':
  359. case 'stackedBarY':
  360. case 'cluBarX':
  361. case 'stackedBar':
  362. case 'bar':
  363. case 'pie':
  364. list($dimensions, $sourceData) = $data;
  365. return $this->prepareChartDataset($component, $dimensions, $sourceData);
  366. case 'box':
  367. return $this->prepareBoxDataset($component, $data);
  368. case 'table':
  369. list($headers, $align, $colspan, $dataset, $drills, $rowspans) = $data;
  370. $formattedDataset = array();
  371. foreach($dataset as $row)
  372. {
  373. $row = array_values($row);
  374. $formattedDataset[] = array_map('strval', $row);
  375. }
  376. $skipCellList = array();
  377. foreach($rowspans as $i => $rowspan)
  378. {
  379. foreach($rowspan as $j => $rowspanValue)
  380. {
  381. if(in_array(array($i, $j), $skipCellList)) continue;
  382. for($k = 1; $k < $rowspanValue; $k ++)
  383. {
  384. unset($formattedDataset[$i + $k][$j]);
  385. $skipCellList[] = array($i + $k, $j);
  386. }
  387. }
  388. }
  389. return $this->prepareTableDataset($component, $headers, $align, $colspan, $rowspans, $formattedDataset, $drills);
  390. case 'radar':
  391. list($radarIndicator, $seriesData) = $data;
  392. return $this->prepareRadarDataset($component, $radarIndicator, $seriesData);
  393. case 'card':
  394. return $this->prepareCardDataset($component, $data);
  395. case 'waterpolo':
  396. return $this->prepareWaterPoloDataset($component, $data);
  397. case 'sunburst':
  398. return $this->prepareSunburstDataset($component, $data);
  399. case 'group':
  400. return $this->prepareGroupDataset($component, $data);
  401. case 'text':
  402. return $this->prepareTextDataset($component, $data);
  403. case 'help':
  404. return $this->setComponentDefaults($component);
  405. default:
  406. return $component;
  407. }
  408. }
  409. /**
  410. * Update piovt or chart component chartConfig filters.
  411. *
  412. * @param object $component
  413. * @param array $latestFilters
  414. * @access public
  415. * @return object
  416. */
  417. public function updateComponentFilters($component, $latestFilters)
  418. {
  419. // 如果传过来的component->chartConfig中有filters,判断filters是否发生了改变,改变则重置filters,其中单个filter的linkedGlobalFilter属性就不存在了
  420. if(!isset($component->chartConfig->filters))
  421. {
  422. $component->chartConfig->filters = $latestFilters;
  423. $component->chartConfig->noSetupGlobalFilterList = array();
  424. }
  425. else
  426. {
  427. $oldFilters = $component->chartConfig->filters;
  428. $filterChanged = $this->isFilterChange($oldFilters, $latestFilters);
  429. if($filterChanged)
  430. {
  431. $component->chartConfig->filters = $latestFilters;
  432. $component->chartConfig->noSetupGlobalFilterList = array();
  433. }
  434. else
  435. {
  436. if(!isset($_POST['filters'])) // 如果不是前端进行筛选操作,则说明是第一次打开页面,此时看看后台的默认值有没有发生变更
  437. {
  438. foreach($oldFilters as $index => $filter)
  439. {
  440. $newFilter = $latestFilters[$index];
  441. $oldDefault = isset($filter->default) ? $filter->default : '';
  442. $newDefault = isset($newFilter->default) ? $newFilter->default : '';
  443. if($oldDefault !== $newDefault) $component->chartConfig->filters[$index]->default = $newDefault;
  444. }
  445. }
  446. foreach($oldFilters as $index => $filter)
  447. {
  448. $isSelect = $filter->type == 'select' && $latestFilters[$index]->type == 'select';
  449. $oldSaveAs = zget($filter, 'saveAs', '');
  450. $newSaveAs = zget($latestFilters[$index], 'saveAs', '');
  451. if($oldSaveAs !== $newSaveAs) $component->chartConfig->filters[$index]->default = $newSaveAs;
  452. if($isSelect and $oldSaveAs !== $newSaveAs) $component->chartConfig->filters[$index]->saveAs = $newSaveAs;
  453. }
  454. }
  455. }
  456. return $component;
  457. }
  458. /**
  459. * 判断图表/透视表的筛选器是否发生了变化。
  460. * Determine if the filter for the chart/pivot has changed.
  461. *
  462. * @param array $oldFilters
  463. * @param array $latestFilters
  464. * @access public
  465. * @return void
  466. */
  467. public function isFilterChange($oldFilters, $latestFilters)
  468. {
  469. $filterChanged = false;
  470. if(empty($latestFilters)) return true;
  471. if(count($oldFilters) != count($latestFilters)) $filterChanged = true;
  472. foreach($oldFilters as $index => $oldFilter)
  473. {
  474. $newFilter = $latestFilters[$index];
  475. // 结果筛选器和查询筛选器都有的三个字段
  476. if($oldFilter->field != $newFilter->field || $oldFilter->name != $newFilter->name || $oldFilter->type != $newFilter->type) $filterChanged = true;
  477. // 如果一个是查询筛选器一个不是,也判定为更改
  478. $oldHaveQuery = isset($oldFilter->from) and $oldFilter->from = 'query';
  479. $newHaveQuery = isset($newFilter->from) and $newFilter->from = 'query';
  480. if($oldHaveQuery != $newHaveQuery) $filterChanged = true;
  481. // 对于查询筛选器,可以再做进一步判断,如果下拉选择器的typeOption发生了变化,也应该判定为更改
  482. if($oldHaveQuery and $newHaveQuery)
  483. {
  484. if($oldFilter->type == 'select' && $newFilter->type == 'select' && $oldFilter->typeOption != $newFilter->typeOption) $filterChanged = true;
  485. }
  486. // 如果发生了变化,不必再去判断后续
  487. if($filterChanged) break;
  488. }
  489. return $filterChanged;
  490. }
  491. /**
  492. * 生成不存在的图表或者草稿图表的参数。
  493. * Generate not found or draft chart option.
  494. *
  495. * @param object $component
  496. * @param object $chart
  497. * @access public
  498. * @return void
  499. */
  500. public function genNotFoundOrDraftComponentOption($component, $chart, $type)
  501. {
  502. if(empty($component)) $component = new stdclass();
  503. $noDataLang = $type == 'chart' ? 'noChartData' : 'noPivotData';
  504. if(!isset($component->option)) $component->option = new stdclass();
  505. if(!isset($component->option->title)) $component->option->title = new stdclass();
  506. $name = zget($chart, 'name', '');
  507. $component->option->title->notFoundText = sprintf($this->lang->screen->$noDataLang, $name);
  508. $component->option->isDeleted = true;
  509. return $component;
  510. }
  511. /**
  512. * 生成已下架或者已删除度量项的参数。
  513. * Generate delist or deleted metric options.
  514. *
  515. * @param object $component
  516. * @access public
  517. * @return object
  518. */
  519. public function genDelistOrDeletedMetricOption($component)
  520. {
  521. if(empty($component)) $component = new stdclass();
  522. if(!isset($component->option)) $component->option = new stdclass();
  523. if(!isset($component->option->title)) $component->option->title = new stdclass();
  524. $component->option->title->notFoundText = $this->lang->screen->noMetricData;
  525. $component->option->isDeleted = true;
  526. return $component;
  527. }
  528. /**
  529. * 删除component的已删除标记。
  530. * Unset component option isDeleted.
  531. *
  532. * @param object $component
  533. * @access public
  534. * @return object
  535. */
  536. public function unsetComponentDraftMarker($component)
  537. {
  538. if(isset($component->option->isDeleted)) unset($component->option->isDeleted);
  539. if(isset($component->option->title)) unset($component->option->title->notFoundText);
  540. return $component;
  541. }
  542. /**
  543. * 生成全局筛选器的组件。
  544. * Generate component of global filters.
  545. *
  546. * @param string $filterType
  547. * @access public
  548. * @return object
  549. */
  550. public function genFilterComponent($filterType)
  551. {
  552. $this->loadModel('metric');
  553. $type = ucfirst($filterType);
  554. $component = new stdclass();
  555. $component->chartConfig = new stdclass();
  556. $component->chartConfig->id = $type;
  557. $component->chartConfig->key = "{$type}Filter";
  558. $component->chartConfig->chartKey = "V{$type}Filter";
  559. $component->chartConfig->conKey = "VC{$type}Filter";
  560. $component->chartConfig->category = 'Filters';
  561. $component->chartConfig->categoryName = $this->lang->screen->globalFilter;
  562. $component->chartConfig->package = 'Decorates';
  563. if(in_array($filterType, $this->config->screen->scopeList))
  564. {
  565. $objectPairs = $this->metric->getPairsByScope($filterType, true);
  566. $component->chartConfig->objectList = array_map(function($objectID, $objectTitle)
  567. {
  568. $object = new stdclass();
  569. $object->label = $objectTitle;
  570. $object->value = (string)$objectID;
  571. return $object;
  572. }, array_keys($objectPairs), array_values($objectPairs));
  573. }
  574. $firstAction = $this->dao->select('YEAR(date) AS year')->from(TABLE_ACTION)->orderBy('id_asc')->limit(1)->fetch();
  575. $yearRange = range((int)date('Y'), $firstAction->year);
  576. $component->chartConfig->yearList = array_map(function($year)
  577. {
  578. $yearObject = new stdclass();
  579. $yearObject->label = $year;
  580. $yearObject->value = $year;
  581. return $yearObject;
  582. }, $yearRange);
  583. return $component;
  584. }
  585. /**
  586. * Generate metric component.
  587. *
  588. * @param object $metric
  589. * @param object|null $component
  590. * @param array $filterParams
  591. * @access public
  592. * @return object
  593. */
  594. public function genMetricComponent($metric, $component = null, $filterParams = array())
  595. {
  596. $this->loadModel('metric');
  597. if($metric->deleted == '1' || $metric->stage == 'wait') return $this->genDelistOrDeletedMetricOption($component);
  598. $pagination = $this->getMetricPagination($component);
  599. $filters = $this->processMetricFilter($filterParams, $metric->dateType);
  600. list($pager, $pagination) = $this->preparePaginationBeforeFetchRecords($pagination);
  601. $result = $this->metric->getResultByCode($metric->code, $filters, 'cron', $pager);
  602. $pagination['total'] = $pager->recTotal;
  603. $pagination['pageTotal'] = $pager->pageTotal;
  604. $resultHeader = $this->metric->getViewTableHeader($metric);
  605. $resultData = $this->metric->getViewTableData($metric, $result);
  606. $isObjectMetric = $metric->scope != 'system';
  607. $isDateMetric = $metric->dateType != 'nodate';
  608. $tableOption = $this->getMetricTableOption($metric, $resultHeader, $resultData, $component);
  609. $chartOption = $this->getMetricChartOption($metric, $resultHeader, $resultData, $component);
  610. $card = $this->getMetricCardOption($metric, $resultData, $component);
  611. $tableOption->pagination = $pagination;
  612. $card->pagination = $pagination;
  613. list($component, $typeChanged) = $this->initMetricComponent($metric, $component);
  614. $component->chartConfig->title = $metric->name;
  615. $component->chartConfig->sourceID = $metric->id;
  616. $component->chartConfig->scope = $metric->scope;
  617. $component->chartConfig->dateType = $metric->dateType;
  618. $latestFilters = $this->buildMetricFilters($metric, $isObjectMetric, $isDateMetric);
  619. $component = $this->updateMetricFilters($component, $latestFilters);
  620. $component->option->chartOption = $chartOption;
  621. $component->option->tableOption = $tableOption;
  622. $component->option->card = $card;
  623. $component->option->card->isDateMetric = $isDateMetric;
  624. $component->option->card->isObjectMetric = $isObjectMetric;
  625. $component->option->card->cardDateDefault = $component->option->card->filterValue ? $component->option->card->filterValue->dateString : '';
  626. $component->option->noDataTip = $this->metric->getNoDataTip($metric->code);
  627. return $component;
  628. }
  629. public function getMetricPagination($component)
  630. {
  631. $tablePagination = array('index' => 1, 'size' => 5, 'total' => 0, 'pageTotal' => 1);
  632. $cardPagination = array('index' => 1, 'size' => 2 * 6, 'total' => 0, 'pageTotal' => 1);
  633. if(empty($component)) return $tablePagination;
  634. $option = $component->option;
  635. $displayType = $option->displayType;
  636. if(isset($option->card->pagination)) $cardPagination = array_merge($cardPagination, (array)$option->card->pagination);
  637. if(isset($option->tableOption->pagination)) $tablePagination = array_merge($tablePagination, (array)$option->tableOption->pagination);
  638. $tableOption = $option->tableOption;
  639. $cardOption = $option->card;
  640. if(isset($tableOption->rowNum)) $tablePagination['size'] = $tableOption->rowNum;
  641. $cardRow = isset($cardOption->countEachRow) ? $cardOption->countEachRow : 2;
  642. $cardColumn = isset($cardOption->countEachColumn) ? $cardOption->countEachColumn : 6;
  643. $cardPagination['size'] = $cardRow * $cardColumn;
  644. return $displayType == 'normal' ? $tablePagination : $cardPagination;
  645. }
  646. /**
  647. * Prepare pagination before fetch records.
  648. *
  649. * @param object $pagination
  650. * @access public
  651. * @return array
  652. */
  653. public function preparePaginationBeforeFetchRecords($pagination)
  654. {
  655. $defaultPagination = array('index' => 1, 'size' => 2 * 6, 'total' => 0);
  656. if(is_string($pagination)) $pagination = json_decode($pagination, true);
  657. if(empty($pagination)) return $pagination;
  658. $pagination = array_merge($defaultPagination, (array)$pagination);
  659. extract($pagination);
  660. $this->app->loadClass('pager', true);
  661. $pager = new pager($total, $size, $index);
  662. return array($pager, $pagination);
  663. }
  664. /**
  665. * Update metric component chartConfig filters.
  666. *
  667. * @param object $component
  668. * @param array $latestFilters
  669. * @access public
  670. * @return object
  671. */
  672. public function updateMetricFilters($component, $latestFilters)
  673. {
  674. if(!isset($component->chartConfig->filters))
  675. {
  676. $component->chartConfig->filters = $latestFilters;
  677. }
  678. return $component;
  679. }
  680. /**
  681. * Build filters for metric.
  682. *
  683. * @param object $metric
  684. * @access public
  685. * @return array
  686. */
  687. public function buildMetricFilters($metric, $isObjectMetric, $isDateMetric)
  688. {
  689. $this->loadModel('metric');
  690. $scope = $metric->scope;
  691. $filters = array();
  692. if($isObjectMetric)
  693. {
  694. $scopeFilter = new stdclass();
  695. $scopeFilter->belong = 'metric';
  696. $scopeFilter->field = $scope;
  697. $scopeFilter->name = $this->lang->screen->belong . $this->lang->$scope->common;
  698. $scopeFilter->type = 'select';
  699. $scopeFilter->typeOption = $scope;
  700. $scopeFilter->default = null;
  701. $filters[] = $scopeFilter;
  702. }
  703. if($isDateMetric)
  704. {
  705. $dateFilter = new stdclass();
  706. $dateFilter->belong = 'metric';
  707. $dateFilter->field = 'date';
  708. $dateFilter->name = $this->lang->screen->dateRange;
  709. $dateFilter->type = 'dateRange';
  710. $dateFilter->typeOption = null;
  711. $dateFilter->default = null;
  712. $filters[] = $dateFilter;
  713. }
  714. return $filters;
  715. }
  716. /**
  717. * Get chart option.
  718. *
  719. * @param object $chart
  720. * @param object $component
  721. * @param array $filters
  722. * @access public
  723. * @return object
  724. */
  725. public function getChartOption($chart, $component, $filters = '')
  726. {
  727. switch($component->type)
  728. {
  729. case 'line':
  730. return $this->getLineChartOption($component, $chart, $filters);
  731. case 'cluBarY':
  732. case 'stackedBarY':
  733. case 'cluBarX':
  734. case 'stackedBar':
  735. case 'bar':
  736. return $this->getBarChartOption($component, $chart, $filters);
  737. case 'piecircle':
  738. return $this->buildPieCircleChart($component, $chart);
  739. case 'pie':
  740. return $this->getPieChartOption($component, $chart, $filters);
  741. case 'table':
  742. return $this->getTableChartOption($component, $chart, $filters);
  743. case 'radar':
  744. return $this->getRadarChartOption($component, $chart, $filters);
  745. case 'card':
  746. return $this->buildCardChart($component, $chart);
  747. case 'waterpolo':
  748. if(strpos($chart->settings, 'waterpolo') === false) return $this->buildWaterPolo($component, $chart);
  749. return $this->getWaterPoloOption($component, $chart, $filters);
  750. case 'metric':
  751. return $this->getMetricOption($component, $chart, $filters);
  752. default:
  753. return '';
  754. }
  755. }
  756. /**
  757. * Get chart option about rotate.
  758. *
  759. * @param object $chart
  760. * @param object $component
  761. * @access public
  762. * @return object
  763. */
  764. public function getAxisRotateOption($chart, $component)
  765. {
  766. $this->loadModel('chart');
  767. if(!in_array($chart->type, $this->config->chart->canLabelRotate)) return $component;
  768. $settings = json_decode($chart->settings, true);
  769. if(!isset($settings[0])) return $component;
  770. $setting = $settings[0];
  771. $component->chartConfig->xAxis = new stdclass();
  772. $component->chartConfig->yAxis = new stdclass();
  773. $component->chartConfig->xAxis->axisLabel = new stdclass();
  774. $component->chartConfig->yAxis->axisLabel = new stdclass();
  775. $component->chartConfig->xAxis->axisLabel->rotate = 0;
  776. $component->chartConfig->yAxis->axisLabel->rotate = 0;
  777. if(isset($setting['rotateX']) && $setting['rotateX'] == 'use') $component->chartConfig->xAxis->axisLabel->rotate = 30;
  778. if(isset($setting['rotateY']) && $setting['rotateY'] == 'use') $component->chartConfig->yAxis->axisLabel->rotate = 30;
  779. return $component;
  780. }
  781. /**
  782. * 准备图表数据集。
  783. * Prepare chart dataset.
  784. *
  785. * @param object $component
  786. * @param array $dimensions
  787. * @param array $sourceData
  788. * @access public
  789. * @return object
  790. */
  791. public function prepareChartDataset($component, $dimensions, $sourceData)
  792. {
  793. $component->option->dataset->dimensions = $dimensions;
  794. $component->option->dataset->source = array_values($sourceData);
  795. return $this->setComponentDefaults($component);
  796. }
  797. /**
  798. * 准备箱线图数据集。
  799. * Prepare box dataset.
  800. *
  801. * @param object $component
  802. * @param array $data
  803. * @param object $layout
  804. * @param object $config
  805. * @access public
  806. * @return object
  807. */
  808. public function prepareBoxDataset($component, $data)
  809. {
  810. $component->option->data = $data;
  811. return $this->setComponentDefaults($component);
  812. }
  813. /**
  814. * 准备组合数据集。
  815. * Prepare group dataset.
  816. *
  817. * @param object $component
  818. * @param array $componentList
  819. * @access public
  820. * @return object
  821. */
  822. public function prepareGroupDataset($component, $componentList)
  823. {
  824. $component->isGroup = true;
  825. $component->groupList = $componentList;
  826. return $this->setComponentDefaults($component);
  827. }
  828. /**
  829. * 准备雷达图数据集。
  830. * Prepare radar chart dataset.
  831. *
  832. * @param object $component
  833. * @param array $radarIndicator
  834. * @param array $seriesData
  835. * @access public
  836. * @return object
  837. */
  838. public function prepareRadarDataset($component, $radarIndicator, $seriesData)
  839. {
  840. $component->option->dataset->radarIndicator = $radarIndicator;
  841. $component->option->dataset->seriesData = $seriesData;
  842. return $this->setComponentDefaults($component);
  843. }
  844. /**
  845. * 准备文本数据集。
  846. * Prepare text dataset.
  847. *
  848. * @param object $component
  849. * @param string $text
  850. * @access public
  851. * @return object
  852. */
  853. public function prepareTextDataset($component, $text)
  854. {
  855. $component->option->dataset = $text;
  856. return $this->setComponentDefaults($component);
  857. }
  858. /**
  859. * 准备表格数据集。
  860. * Prepare table dataset.
  861. *
  862. * @param object $component
  863. * @param array $headers
  864. * @param array $align
  865. * @param array $colspan
  866. * @param array $rowspan
  867. * @param array $dataset
  868. * @param array $drills
  869. * @access public
  870. * @return object
  871. */
  872. public function prepareTableDataset($component, $headers, $align, $colspan, $rowspan, $dataset, $drills)
  873. {
  874. if(!isset($component->chartConfig->tableInfo)) $component->chartConfig->tableInfo = new stdclass();
  875. $component->option->header = $headers;
  876. $component->option->align = $align;
  877. $component->option->columnWidth = array();
  878. $component->option->rowspan = $rowspan;
  879. $component->option->colspan = $colspan;
  880. $component->option->dataset = $dataset;
  881. $component->option->drills = $drills;
  882. return $this->setComponentDefaults($component);
  883. }
  884. /**
  885. * 准备卡片数据集。
  886. * Prepare card dataset.
  887. *
  888. * @param object $component
  889. * @param string $value
  890. * @access public
  891. * @return object
  892. */
  893. public function prepareCardDataset($component, $value)
  894. {
  895. $component->option->dataset = (string)$value;
  896. return $this->setComponentDefaults($component);
  897. }
  898. /**
  899. * 准备水球图数据集。
  900. * Prepare waterpolo dataset.
  901. *
  902. * @param object $component
  903. * @param float $value
  904. * @access public
  905. * @return object
  906. */
  907. public function prepareWaterPoloDataset($component, $value)
  908. {
  909. $component->option->dataset = $value;
  910. return $this->setComponentDefaults($component);
  911. }
  912. /**
  913. * 准备旭日图数据集。
  914. * Prepare sunburst dataset.
  915. *
  916. * @param object $component
  917. * @param array $data
  918. * @access public
  919. * @return object
  920. */
  921. public function prepareSunburstDataset($component, $data)
  922. {
  923. $component->option->series->data = $data;
  924. return $this->setComponentDefaults($component);
  925. }
  926. /**
  927. * Get bar chart option.
  928. *
  929. * @param object $component
  930. * @param object $chart
  931. * @access public
  932. * @return object
  933. */
  934. public function getBarChartOption($component, $chart, $filters = '')
  935. {
  936. $dimensions = array();
  937. $sourceData = array();
  938. if($chart->sql)
  939. {
  940. $settings = json_decode($chart->settings, true);
  941. $langs = json_decode($chart->langs, true);
  942. $settings = $settings[0];
  943. $isSort = in_array($chart->id, $this->config->screen->annualRankingChart) ? true : false;
  944. list($group, $metrics, $aggs, $xLabels, $yStats) = $this->bi->getMultiData($settings, $chart->sql, $filters, $chart->driver, $isSort);
  945. $fields = json_decode($chart->fields, true);
  946. $dimensions = array($settings['xaxis'][0]['field']);
  947. $clientLang = $this->app->getClientLang();
  948. $xLabelValues = $this->processXLabel($xLabels, $fields[$group]['type'], $fields[$group]['object'], $fields[$group]['field']);
  949. foreach($yStats as $index => $dataList)
  950. {
  951. $field = zget($fields, $metrics[$index]);
  952. $fieldName = $field['name'];
  953. if(isset($langs[$field['field']]) and !empty($langs[$field['field']][$clientLang])) $fieldName = $langs[$field['field']][$clientLang];
  954. $field = $fieldName . '(' . zget($this->lang->chart->aggList, $aggs[$index]) . ')';
  955. $dimensions[] = $field;
  956. foreach($dataList as $valueField => $value)
  957. {
  958. $valueField = $xLabelValues[$valueField];
  959. if(empty($sourceData[$valueField]))
  960. {
  961. $sourceData[$valueField] = new stdclass();
  962. $sourceData[$valueField]->{$settings['xaxis'][0]['field']} = $valueField;
  963. }
  964. $sourceData[$valueField]->{$field} = $value;
  965. }
  966. }
  967. }
  968. return $this->prepareChartDataset($component, $dimensions, $sourceData);
  969. }
  970. /**
  971. * Get line chart option.
  972. *
  973. * @param object $component
  974. * @param object $chart
  975. * @access public
  976. * @return object
  977. */
  978. public function getLineChartOption($component, $chart, $filters = '')
  979. {
  980. $dimensions = array();
  981. $sourceData = array();
  982. if($chart->sql)
  983. {
  984. $settings = json_decode($chart->settings, true);
  985. $langs = json_decode($chart->langs, true);
  986. $settings = $settings[0];
  987. list($group, $metrics, $aggs, $xLabels, $yStats) = $this->bi->getMultiData($settings, $chart->sql, $filters, $chart->driver);
  988. $fields = json_decode($chart->fields, true);
  989. $dimensions = array($settings['xaxis'][0]['field']);
  990. $clientLang = $this->app->getClientLang();
  991. $xLabelValues = $this->processXLabel($xLabels, $fields[$group]['type'], $fields[$group]['object'], $fields[$group]['field']);
  992. foreach($yStats as $index => $dataList)
  993. {
  994. $field = zget($fields, $metrics[$index]);
  995. $fieldName = $field['name'];
  996. if(isset($langs[$field['field']]) and !empty($langs[$field['field']][$clientLang])) $fieldName = $langs[$field['field']][$clientLang];
  997. $field = $fieldName . '(' . zget($this->lang->chart->aggList, $aggs[$index]) . ')';
  998. $dimensions[] = $field;
  999. foreach($dataList as $valueField => $value)
  1000. {
  1001. $valueField = $xLabelValues[$valueField];
  1002. if(empty($sourceData[$valueField]))
  1003. {
  1004. $sourceData[$valueField] = new stdclass();
  1005. $sourceData[$valueField]->{$settings['xaxis'][0]['field']} = $valueField;
  1006. }
  1007. $sourceData[$valueField]->{$field} = $value;
  1008. }
  1009. }
  1010. /* Completing empty values. */
  1011. foreach($sourceData as $lineData)
  1012. {
  1013. foreach($dimensions as $dimension)
  1014. {
  1015. if(empty($lineData->{$dimension})) $lineData->{$dimension} = 0;
  1016. }
  1017. }
  1018. }
  1019. return $this->prepareChartDataset($component, $dimensions, $sourceData);
  1020. }
  1021. /**
  1022. * Get pie chart option.
  1023. *
  1024. * @param object $component
  1025. * @param object $chart
  1026. * @access public
  1027. * @return object
  1028. */
  1029. public function getPieChartOption($component, $chart, $filters = '')
  1030. {
  1031. $dimensions = array();
  1032. $sourceData = array();
  1033. if($chart->sql)
  1034. {
  1035. $settings = json_decode($chart->settings, true);
  1036. $settings = $settings[0];
  1037. $options = $this->loadModel('chart')->genPie(json_decode($chart->fields, true), $settings, $chart->sql, $filters, $chart->driver);
  1038. if($settings['group'][0]['field'] == $settings['metric'][0]['field']) $settings['group'][0]['field'] = $settings['group'][0]['field'] . '1';
  1039. $dimensions = array($settings['group'][0]['field'], $settings['metric'][0]['field']);
  1040. $sourceData = array();
  1041. foreach($options['series'] as $dataList)
  1042. {
  1043. $field = $settings['metric'][0]['field'];
  1044. foreach($dataList['data'] as $data)
  1045. {
  1046. $fieldValue = $data['name'];
  1047. if(empty($sourceData[$fieldValue]))
  1048. {
  1049. $sourceData[$fieldValue] = new stdclass();
  1050. $sourceData[$fieldValue]->{$settings['group'][0]['field']} = (string)$fieldValue;
  1051. }
  1052. $sourceData[$fieldValue]->{$field} = $data['value'];
  1053. }
  1054. }
  1055. if(empty($sourceData)) $dimensions = array();
  1056. }
  1057. return $this->prepareChartDataset($component, $dimensions, $sourceData);
  1058. }
  1059. /**
  1060. * Get radar chart option.
  1061. *
  1062. * @param object $component
  1063. * @param object $chart
  1064. * @access public
  1065. * @return object
  1066. */
  1067. public function getRadarChartOption($component, $chart, $filters = '')
  1068. {
  1069. $indicator = array();
  1070. $seriesData = array();
  1071. $radarIndicator = array();
  1072. $seriesData = array();
  1073. if($chart->sql)
  1074. {
  1075. $settings = json_decode($chart->settings, true);
  1076. $langs = json_decode($chart->langs, true);
  1077. $settings = $settings[0];
  1078. list($group, $metrics, $aggs, $xLabels, $yStats) = $this->bi->getMultiData($settings, $chart->sql, $filters, $chart->driver);
  1079. $fields = json_decode($chart->fields, true);
  1080. $max = 0;
  1081. $clientLang = $this->app->getClientLang();
  1082. $xLabelValues = $this->processXLabel($xLabels, $fields[$group]['type'], $fields[$group]['object'], $fields[$group]['field']);
  1083. foreach($yStats as $index => $dataList)
  1084. {
  1085. $fieldObj = zget($fields, $metrics[$index]);
  1086. $fieldName = $fieldObj['name'];
  1087. $field = $fieldObj['field'];
  1088. if(isset($langs[$field])) $fieldName = zget($langs[$field], $clientLang, $fieldName);
  1089. $seriesData[$index] = new stdclass();
  1090. $seriesData[$index]->name = $fieldName . '(' . zget($this->lang->chart->aggList, $aggs[$index]) . ')';
  1091. $values = array();
  1092. foreach($dataList as $valueField => $value)
  1093. {
  1094. $values[] = (float)$value;
  1095. $max = $max < $value ? (float)$value : (float)$max;
  1096. }
  1097. $seriesData[$index]->value = $values;
  1098. }
  1099. if(!empty($dataList))
  1100. {
  1101. foreach($dataList as $valueField => $value)
  1102. {
  1103. $indicator = new stdclass();
  1104. $indicator->name = $xLabelValues[$valueField];
  1105. $indicator->max = $max;
  1106. $radarIndicator[] = $indicator;;
  1107. }
  1108. }
  1109. }
  1110. return $this->prepareRadarDataset($component, $radarIndicator, $seriesData);
  1111. }
  1112. /**
  1113. * Get table chart option.
  1114. *
  1115. * @param object $component
  1116. * @param object $chart
  1117. * @access public
  1118. * @return object
  1119. */
  1120. public function getTableChartOption($component, $chart, $filters = array())
  1121. {
  1122. $align = array();
  1123. $headers = array();
  1124. $colspans = array();
  1125. $dataset = array();
  1126. $drills = array();
  1127. $config = array();
  1128. if($chart->sql)
  1129. {
  1130. $chart->settings = json_decode($chart->settings, true);
  1131. $this->loadModel('pivot')->addDrills($chart);
  1132. $settings = $chart->settings;
  1133. $fields = json_decode($chart->fields, true);
  1134. $langs = json_decode($chart->langs, true);
  1135. $chartFilters = json_decode($chart->filters, true);
  1136. if(empty($langs)) $langs = array();
  1137. if(empty($fields)) $fields = array();
  1138. if(!is_array($filters)) $filters = array();
  1139. if(empty($chartFilters)) $filters = false;
  1140. if(isset($settings['summary']) and $settings['summary'] == 'notuse')
  1141. {
  1142. list($options, $config) = $this->loadModel('pivot')->genOriginSheet($fields, $settings, $chart->sql, $filters, $langs, $chart->driver);
  1143. }
  1144. else
  1145. {
  1146. list($options, $config) = $this->loadModel('pivot')->genSheet($fields, $settings, $chart->sql, $filters, $langs, $chart->driver);
  1147. }
  1148. $showColPosition = $this->pivot->getShowColPosition($options);
  1149. $isShowLastRow = $this->pivot->isShowLastRow($showColPosition);
  1150. if($isShowLastRow and !empty($options->array))
  1151. {
  1152. $count = count($options->array);
  1153. $colspans[$count - 1][0] = count($options->groups);
  1154. }
  1155. foreach($options->array as $data)
  1156. {
  1157. $data = array_values($data);
  1158. $dataset[] = array_map('strval', $data);
  1159. }
  1160. $skipCellList = array();
  1161. foreach($config as $i => $data)
  1162. {
  1163. foreach($data as $j => $rowspan)
  1164. {
  1165. if(in_array(array($i, $j), $skipCellList)) continue;
  1166. for($k = 1; $k < $rowspan; $k ++)
  1167. {
  1168. unset($dataset[$i + $k][$j]);
  1169. $skipCellList[] = array($i + $k, $j);
  1170. }
  1171. }
  1172. }
  1173. $groupCount = 0;
  1174. $drillConfigs = array();
  1175. foreach($options->cols as $cols)
  1176. {
  1177. $count = 1;
  1178. $header = array();
  1179. foreach($cols as $data)
  1180. {
  1181. $colspan = zget($data, 'colspan', 1);
  1182. $isDrilling = zget($data, 'isDrilling', 0);
  1183. $conditions = zget($data, 'condition', array());
  1184. $drillField = zget($data, 'drillField', '');
  1185. $isGroup = zget($data, 'isGroup', 0);
  1186. $isSlice = zget($data, 'isSlice', 0);
  1187. if($isGroup)
  1188. {
  1189. $groupCount += 1;
  1190. }
  1191. elseif(!$isSlice)
  1192. {
  1193. $drillConfigs[] = $isDrilling ? array('drillField' => $drillField, 'conditions' => $conditions) : false;
  1194. if($colspan > 1) $drillConfigs = array_merge($drillConfigs, array_fill(0, $colspan - 1, false));
  1195. }
  1196. $header[] = $data;
  1197. if($count == 1) $align[] = 'center';
  1198. }
  1199. $headers[] = $header;
  1200. $count ++;
  1201. }
  1202. $groupFields = array_fill(0, $groupCount, 'groupCol');
  1203. foreach($options->drills as $drill)
  1204. {
  1205. if(!isset($drill['drillFields'])) continue;
  1206. $drillFields = array_values($drill['drillFields']);
  1207. $drillRow = array();
  1208. foreach($drillFields as $index => $drillField)
  1209. {
  1210. $drillConfig = $drillConfigs[$index];
  1211. $drillRow[] = $drillConfig ? array('fields' => $drillField, 'config' => $drillConfig) : false;
  1212. }
  1213. $drillRow = array_merge($groupFields, $drillRow);
  1214. $drills[] = $drillRow;
  1215. }
  1216. }
  1217. return $this->prepareTableDataset($component, $headers, $align, $colspans, $config, $dataset, $drills);
  1218. }
  1219. /**
  1220. * Get chart filters
  1221. *
  1222. * @param object $chart
  1223. * @access public
  1224. * @return void
  1225. */
  1226. public function getChartFilters($chart)
  1227. {
  1228. $filters = json_decode($chart->filters, true);
  1229. $fields = json_decode($chart->fields, true);
  1230. if(empty($filters)) return array();
  1231. $this->loadModel('pivot');
  1232. $screenFilters = array();
  1233. foreach($filters as $filter)
  1234. {
  1235. $isQuery = (isset($filter['from']) and $filter['from'] == 'query');
  1236. if($isQuery)
  1237. {
  1238. if($filter['type'] == 'date' or $filter['type'] == 'datetime')
  1239. {
  1240. if(isset($filter['default']))
  1241. {
  1242. $default = $this->pivot->processDateVar($filter['default']);
  1243. $filter['default'] = empty($default) ? null : strtotime($default) * 1000;
  1244. }
  1245. }
  1246. $screenFilters[] = (object)$filter;
  1247. continue;
  1248. }
  1249. if($filter['type'] == 'date' or $filter['type'] == 'datetime')
  1250. {
  1251. if(isset($filter['default']))
  1252. {
  1253. $default = $filter['default'];
  1254. $begin = $default['begin'];
  1255. $end = $default['end'];
  1256. if(empty($begin) and empty($end))
  1257. {
  1258. $filter['default'] = null;
  1259. }
  1260. else if(empty($begin) or empty($end))
  1261. {
  1262. $filter['default'] = empty($begin) ? strtotime($end) * 1000 : strtotime($begin) * 1000;
  1263. }
  1264. else
  1265. {
  1266. $filter['default'] = array(strtotime($begin) * 1000, strtotime($end) * 1000);
  1267. }
  1268. }
  1269. else
  1270. {
  1271. $filter['default'] = null;
  1272. }
  1273. }
  1274. $screenFilters[] = (object)$filter;
  1275. }
  1276. return $screenFilters;
  1277. }
  1278. /**
  1279. * Process xLabel with lang
  1280. *
  1281. * @param array $xLabel
  1282. * @param string $type
  1283. * @param string $object
  1284. * @param string $field
  1285. * @access public
  1286. * @return array
  1287. */
  1288. public function processXLabel($xLabels, $type, $object, $field)
  1289. {
  1290. $options = $this->getSysOptions($type, $object, $field);
  1291. $xLabelValues = array();
  1292. foreach($xLabels as $index => $label)
  1293. {
  1294. $xLabelValues[$label] = isset($options[$label]) ? $options[$label] : $label;
  1295. }
  1296. return $xLabelValues;
  1297. }
  1298. /**
  1299. * Process metric filter.
  1300. *
  1301. * @param array $filterParams
  1302. * @param string $dateType
  1303. * @access public
  1304. * @return array
  1305. */
  1306. public function processMetricFilter($filterParams, $dateType)
  1307. {
  1308. $filters = array();
  1309. foreach($filterParams as $filterParam)
  1310. {
  1311. $field = $filterParam['field'];
  1312. $value = $filterParam['default'];
  1313. if(empty($value)) continue;
  1314. if($field == 'date')
  1315. {
  1316. $beginValue = $value[0];
  1317. $endValue = $value[1];
  1318. $beginFilter = $this->formatMetricDateByType($beginValue, $dateType);
  1319. $endFilter = $this->formatMetricDateByType($endValue, $dateType);
  1320. $beginFilter->value = $beginValue;
  1321. $endFilter->value = $endValue;
  1322. $filters['dateBegin'] = $beginFilter->$dateType;
  1323. $filters['dateEnd'] = $endFilter->$dateType;
  1324. }
  1325. else
  1326. {
  1327. $scopeFilter = new stdclass();
  1328. $scopeFilter->value = $value;
  1329. $scopeFilter->type = $field;
  1330. $filters['scope'] = implode(',', $value);
  1331. }
  1332. }
  1333. return $filters;
  1334. }
  1335. /**
  1336. * Format date of metric's filter by type.
  1337. *
  1338. * @param string $stamp
  1339. * @param string $dateType
  1340. * @access public
  1341. * @return array
  1342. */
  1343. public function formatMetricDateByType($stamp, $dateType)
  1344. {
  1345. $formatedDate = new stdclass();
  1346. $formatedDate->year = date('Y', $stamp/1000);
  1347. if($dateType == 'month') $formatedDate->month = date('Y-m', $stamp/1000);
  1348. if($dateType == 'week') $formatedDate->week = date('Y-W', $stamp/1000);
  1349. if($dateType == 'day') $formatedDate->day = date('Y-m-d', $stamp/1000);
  1350. return $formatedDate;
  1351. }
  1352. /**
  1353. * Get system options.
  1354. *
  1355. * @param string $type
  1356. * @param string $object
  1357. * @param string $field
  1358. * @param string $sql
  1359. * @param string $saveAs
  1360. * @access public
  1361. * @return array
  1362. */
  1363. public function getSysOptions($type, $object = '', $field = '', $sql = '', $saveAs = '')
  1364. {
  1365. $options = array();
  1366. switch($type)
  1367. {
  1368. case 'user':
  1369. $options = $this->loadModel('user')->getPairs();
  1370. break;
  1371. case 'product':
  1372. $options = $this->loadModel('product')->getPairs();
  1373. break;
  1374. case 'project':
  1375. $options = $this->loadModel('project')->getPairsByProgram();
  1376. break;
  1377. case 'execution':
  1378. $options = $this->loadModel('execution')->getPairs();
  1379. break;
  1380. case 'dept':
  1381. $options = $this->loadModel('dept')->getOptionMenu(0);
  1382. break;
  1383. case 'project.status':
  1384. $this->app->loadLang('project');
  1385. $options = $this->lang->project->statusList;
  1386. break;
  1387. case 'option':
  1388. if($field)
  1389. {
  1390. $path = $this->app->getModuleRoot() . 'dataview' . DS . 'table' . DS . "$object.php";
  1391. if(is_file($path))
  1392. {
  1393. include $path;
  1394. $options = $schema->fields[$field]['options'];
  1395. }
  1396. }
  1397. break;
  1398. case 'object':
  1399. if($field)
  1400. {
  1401. $useField = $field;
  1402. $useTable = $object;
  1403. $path = $this->app->getModuleRoot() . 'dataview' . DS . 'table' . DS . "$object.php";
  1404. if(is_file($path))
  1405. {
  1406. include $path;
  1407. if(isset($schema->fields[$field]['object']))
  1408. {
  1409. $fieldObject = $schema->fields[$field]['object'];
  1410. $fieldShow = explode('.', $schema->fields[$field]['show']);
  1411. if($fieldObject) $useTable = $fieldObject;
  1412. if(count($fieldShow) == 2) $useField = $fieldShow[1];
  1413. }
  1414. }
  1415. $table = isset($this->config->objectTables[$useTable]) ? $this->config->objectTables[$useTable] : zget($this->config->objectTables, $object, '');
  1416. if($table)
  1417. {
  1418. $columns = $this->dbh->query("SHOW COLUMNS FROM $table")->fetchAll();
  1419. foreach($columns as $id => $column) $columns[$id] = (array)$column;
  1420. $fieldList = array_column($columns, 'Field');
  1421. $useField = in_array($useField, $fieldList) ? $useField : 'id';
  1422. $options = $this->dao->select("id, {$useField}")->from($table)->fetchPairs();
  1423. }
  1424. }
  1425. break;
  1426. case strpos($type, '.') !== false:
  1427. $params = explode('.', $type);
  1428. if(empty(array_filter($params)))
  1429. {
  1430. $options = array();
  1431. }
  1432. else
  1433. {
  1434. $module = $params[0];
  1435. $typeList = $params[1] . 'List';
  1436. $this->app->loadLang($module);
  1437. $options = $this->lang->$module->$typeList;
  1438. }
  1439. break;
  1440. default:
  1441. if($field and $sql)
  1442. {
  1443. $keyField = $field;
  1444. $valueField = $saveAs ? $saveAs : $field;
  1445. $options = $this->getOptionsFromSql($sql, $keyField, $valueField);
  1446. }
  1447. break;
  1448. }
  1449. if($sql and $field and $saveAs and in_array($type, array('user', 'product', 'project', 'execution', 'dept', 'project.status', 'option', 'object')))
  1450. {
  1451. $options = $this->getOptionsFromSql($sql, $field, $saveAs);
  1452. }
  1453. return array_filter($options);
  1454. }
  1455. /**
  1456. * Get pairs from column by keyField and valueField.
  1457. *
  1458. * @param string $sql
  1459. * @param string $keyField
  1460. * @param string $valueField
  1461. * @access public
  1462. * @return array
  1463. */
  1464. public function getOptionsFromSql($sql, $keyField, $valueField)
  1465. {
  1466. $options = array();
  1467. $cols = $this->dbh->query($sql)->fetchAll();
  1468. $sample = current($cols);
  1469. if(!isset($sample->$keyField) or !isset($sample->$valueField)) return $options;
  1470. foreach($cols as $col)
  1471. {
  1472. $key = $col->$keyField;
  1473. $value = $col->$valueField;
  1474. $options[$key] = $value;
  1475. }
  1476. return $options;
  1477. }
  1478. /**
  1479. * Build component list.
  1480. *
  1481. * @param array $componentList
  1482. * @access public
  1483. * @return array
  1484. */
  1485. public function buildComponentList($componentList)
  1486. {
  1487. $components = array();
  1488. foreach($componentList as $component)
  1489. {
  1490. if($component) $components[] = $this->buildComponent($component);
  1491. }
  1492. return $components;
  1493. }
  1494. /**
  1495. * Build component.
  1496. *
  1497. * @param object $component
  1498. * @access public
  1499. * @return object
  1500. */
  1501. public function buildComponent($component)
  1502. {
  1503. /* If chart is builtin, build it. */
  1504. if(isset($component->sourceID) and $component->sourceID) return $this->buildChart($component);
  1505. if(isset($component->key) and $component->key === 'Select') return $this->buildSelect($component);
  1506. if(empty($component->isGroup)) return $this->setComponentDefaults($component);
  1507. $component->groupList = $this->buildComponentList($component->groupList);
  1508. return $this->buildGroup($component);
  1509. }
  1510. /**
  1511. * Build chart group.
  1512. *
  1513. * @param object $component
  1514. * @access public
  1515. * @return object
  1516. */
  1517. public function buildGroup($component)
  1518. {
  1519. return $this->setComponentDefaults($component);
  1520. }
  1521. /**
  1522. * Set component defaults.
  1523. *
  1524. * @param object $component
  1525. * @access public
  1526. * @return object
  1527. */
  1528. public function setComponentDefaults($component)
  1529. {
  1530. $this->loadModel('bi');
  1531. if(!isset($component->styles)) $component->styles = $this->config->bi->default->styles;
  1532. if(!isset($component->status)) $component->status = $this->config->bi->default->status;
  1533. if(!isset($component->request)) $component->request = $this->config->bi->default->request;
  1534. if(!isset($component->events)) $component->events = $this->config->bi->default->events;
  1535. return $component;
  1536. }
  1537. /**
  1538. * Build select.
  1539. *
  1540. * @param object $component
  1541. * @access public
  1542. * @return object
  1543. */
  1544. public function buildSelect($component)
  1545. {
  1546. switch($component->type)
  1547. {
  1548. case 'year':
  1549. $component->option->value = $this->filter->year;
  1550. $beginYear = $this->dao->select('YEAR(MIN(date)) year')->from(TABLE_ACTION)->where('date')->notZeroDate()->fetch('year');
  1551. if($beginYear < 2009) $beginYear = 2009;
  1552. $options = array();
  1553. for($year = date('Y'); $year >= $beginYear; $year--) $options[] = array('label' => $year, 'value' => $year);
  1554. $component->option->dataset = $options;
  1555. $url = "createLink('screen', 'view', 'screenID=" . $this->filter->screen. "&year=' + value + '&month=" . $this->filter->month . "&dept=" . $this->filter->dept . "&account=" . $this->filter->account . "')";
  1556. $component->option->onChange = "window.location.href = $url";
  1557. break;
  1558. case 'month':
  1559. $component->option->value = $this->filter->month;
  1560. $beginYear = $this->dao->select('YEAR(MIN(date)) year')->from(TABLE_ACTION)->where('date')->notZeroDate()->fetch('year');
  1561. $beginMonth = $this->dao->select('MONTH(MIN(date)) month')->from(TABLE_ACTION)->where('date')->notZeroDate()->fetch('month');
  1562. $currentYear = date('Y');
  1563. $currentMonth = date('n');
  1564. $options = array();
  1565. for($month = 12; $month >= 1; $month--)
  1566. {
  1567. if($currentYear == $this->filter->year && $month > $currentMonth) continue;
  1568. if($currentYear == $beginYear && $month < $beginMonth) continue;
  1569. $options[] = array('label' => $month, 'value' => $month);
  1570. }
  1571. $component->option->dataset = $options;
  1572. $url = "createLink('screen', 'view', 'screenID=" . $this->filter->screen. "&year=" . $this->filter->year . "&month=' + value + '&dept=" . $this->filter->dept . "&account=" . $this->filter->account . "')";
  1573. $component->option->onChange = "window.location.href = $url";
  1574. break;
  1575. case 'dept':
  1576. $component->option->value = (string)$this->filter->dept;
  1577. $options = array(array('label' => $this->lang->screen->allDepts, 'value' => '0'));
  1578. $depts = $this->dao->select('id,name')->from(TABLE_DEPT)->where('grade')->eq(1)->fetchAll();
  1579. foreach($depts as $dept)
  1580. {
  1581. $options[] = array('label' => $dept->name, 'value' => $dept->id);
  1582. }
  1583. $component->option->dataset = $options;
  1584. $url = "createLink('screen', 'view', 'screenID=" . $this->filter->screen . "&year=" . $this->filter->year . "&dept=' + value + '&account=')";
  1585. $component->option->onChange = "window.location.href = $url";
  1586. break;
  1587. case 'account':
  1588. $component->option->value = $this->filter->account;
  1589. $options = array(array('label' => $this->lang->screen->allUsers, 'value' => ''));
  1590. $depts = array();
  1591. if($this->filter->dept) $depts = $this->dao->select('id')->from(TABLE_DEPT)->where('path')->like(',' . $this->filter->dept . ',%')->fetchPairs();
  1592. $users = $this->dao->select('account,realname')->from(TABLE_USER)
  1593. ->where('deleted')->eq(0)
  1594. ->beginIF($this->filter->dept)->andWhere('dept')->in($depts)->fi()
  1595. ->fetchAll();
  1596. foreach($users as $user)
  1597. {
  1598. $options[] = array('label' => $user->realname, 'value' => $user->account);
  1599. }
  1600. $component->option->dataset = $options;
  1601. $url = "createLink('screen', 'view', 'screenID=" . $this->filter->screen . "&year=" . $this->filter->year . "&dept=" . $this->filter->dept . "&account=' + value)";
  1602. $component->option->onChange = "window.location.href = $url";
  1603. break;
  1604. }
  1605. foreach($component->filterCharts as $chart)
  1606. {
  1607. if(!isset($this->filter->charts[$chart->chart])) $this->filter->charts[$chart->chart] = array();
  1608. $this->filter->charts[$chart->chart][$component->type] = $chart->field;
  1609. }
  1610. return $this->setComponentDefaults($component);
  1611. }
  1612. /**
  1613. * Build chart.
  1614. *
  1615. * @param object $component
  1616. * @access public
  1617. * @return object
  1618. */
  1619. public function buildChart($component)
  1620. {
  1621. $chart = $this->dao->select('*')->from(TABLE_CHART)->where('id')->eq($component->sourceID)->fetch();
  1622. switch($chart->type)
  1623. {
  1624. case 'card':
  1625. return $this->buildCardChart($component, $chart);
  1626. break;
  1627. case 'line':
  1628. if($chart->builtin == '0')
  1629. {
  1630. $chart->sql = $this->setFilterSQL($chart);
  1631. return $this->getLineChartOption($component, $chart, array());
  1632. }
  1633. return $this->buildLineChart($component, $chart);
  1634. break;
  1635. case 'bar':
  1636. return $this->buildBarChart($component, $chart);
  1637. break;
  1638. case 'piecircle':
  1639. return $this->buildPieCircleChart($component, $chart);
  1640. break;
  1641. case 'pie':
  1642. if($chart->builtin == '0') return $this->getPieChartOption($component, $chart, array());
  1643. return $this->buildPieChart($component, $chart);
  1644. break;
  1645. case 'radar':
  1646. return $this->buildRadarChart($component, $chart);
  1647. break;
  1648. case 'org':
  1649. return $this->buildOrgChart($component, $chart);
  1650. break;
  1651. case 'funnel':
  1652. return $this->buildFunnelChart($component, $chart);
  1653. break;
  1654. case 'table':
  1655. return $this->buildTableChart($component, $chart);
  1656. break;
  1657. case 'cluBarY':
  1658. case 'stackedBarY':
  1659. case 'cluBarX':
  1660. case 'stackedBar':
  1661. $chart->sql = $this->setFilterSQL($chart);
  1662. return $this->getBarChartOption($component, $chart);
  1663. break;
  1664. case 'waterpolo':
  1665. return $this->getWaterPoloOption($component, $chart, array());
  1666. }
  1667. }
  1668. /**
  1669. * Set SQL filter
  1670. *
  1671. * @param object $chart
  1672. * @access public
  1673. * @return string
  1674. */
  1675. public function setFilterSQL($chart)
  1676. {
  1677. if(isset($this->filter->charts[$chart->id]))
  1678. {
  1679. $conditions = array();
  1680. foreach($this->filter->charts[$chart->id] as $key => $field)
  1681. {
  1682. switch($key)
  1683. {
  1684. case 'year':
  1685. $conditions[] = $field . " = '" . $this->filter->$key . "'";
  1686. break;
  1687. case 'month':
  1688. $conditions[] = $field . " = '" . $this->filter->$key . "'";
  1689. break;
  1690. case 'dept':
  1691. if($this->filter->dept and !$this->filter->account)
  1692. {
  1693. $accountField = $this->filter->charts[$chart->id]['account'];
  1694. $users = $this->dao->select('account')->from(TABLE_USER)->alias('t1')
  1695. ->leftJoin(TABLE_DEPT)->alias('t2')
  1696. ->on('t1.dept = t2.id')
  1697. ->where('t2.path')->like(',' . $this->filter->dept . ',%')
  1698. ->fetchPairs('account');
  1699. $accounts = array();
  1700. foreach($users as $account) $accounts[] = "'" . $account . "'";
  1701. $conditions[] = $accountField . ' IN (' . implode(',', $accounts) . ')';
  1702. }
  1703. break;
  1704. case 'account':
  1705. if($this->filter->account) $conditions[] = $field . " = '" . $this->filter->$key . "'";
  1706. break;
  1707. }
  1708. }
  1709. if($conditions) return 'SELECT * FROM (' . str_replace(';', '', $chart->sql) . ') AS t1 WHERE ' . implode(' AND ', $conditions);
  1710. }
  1711. return $chart->sql;
  1712. }
  1713. /**
  1714. * Build card chart.
  1715. *
  1716. * @param object $component
  1717. * @param object $chart
  1718. * @access public
  1719. * @return object
  1720. */
  1721. public function buildCardChart($component, $chart)
  1722. {
  1723. $value = '?';
  1724. if($chart->settings)
  1725. {
  1726. $value = 0;
  1727. if($chart->sql)
  1728. {
  1729. $settings = json_decode($chart->settings);
  1730. if($settings and isset($settings->value))
  1731. {
  1732. $field = $settings->value->field;
  1733. $sql = $this->setFilterSQL($chart);
  1734. $results = $this->buildDataset($chart->id, $chart->driver, $sql);
  1735. if($settings->value->type === 'text')
  1736. {
  1737. $value = empty($results[0]) ? '' : $results[0]->$field;
  1738. }
  1739. if($settings->value->type === 'value')
  1740. {
  1741. $value = empty($results[0]) ? 0 : $results[0]->$field;
  1742. }
  1743. if($settings->value->agg === 'count')
  1744. {
  1745. $value = count($results);
  1746. }
  1747. else if($settings->value->agg === 'sum')
  1748. {
  1749. foreach($results as $result)
  1750. {
  1751. $value += intval($result->$field);
  1752. }
  1753. $value = round($value);
  1754. }
  1755. }
  1756. else
  1757. {
  1758. $value = '?';
  1759. }
  1760. }
  1761. }
  1762. return $this->prepareCardDataset($component, $value);
  1763. }
  1764. /**
  1765. * Build line chart.
  1766. *
  1767. * @param object $component
  1768. * @param object $chart
  1769. * @access public
  1770. * @return object
  1771. */
  1772. public function buildLineChart($component, $chart)
  1773. {
  1774. if(!$chart->settings)
  1775. {
  1776. $component->request = json_decode('{"requestDataType":0,"requestHttpType":"get","requestUrl":"","requestIntervalUnit":"second","requestContentType":0,"requestParamsBodyType":"none","requestSQLContent":{"sql":"select * from where"},"requestParams":{"Body":{"form-data":{},"x-www-form-urlencoded":{},"json":"","xml":""},"Header":{},"Params":{}}}');
  1777. $component->events = json_decode('{"baseEvent":{},"advancedEvents":{}}');
  1778. $component->key = "LineCommon";
  1779. $component->chartConfig = json_decode('{"key":"LineCommon","chartKey":"VLineCommon","conKey":"VCLineCommon","title":"折线图","category":"Lines","categoryName":"折线图","package":"Charts","chartFrame":"echarts","image":"/static/png/line-e714bc74.png"}');
  1780. $component->option = json_decode('{"legend":{"show":true,"top":"5%","textStyle":{"color":"#B9B8CE"}},"xAxis":{"type":"category"},"yAxis":{"show":true,"axisLine":{"show":true},"type":"value"},"backgroundColor":"rgba(0,0,0,0)"}');
  1781. return $this->setComponentDefaults($component);
  1782. }
  1783. else
  1784. {
  1785. if($chart->sql)
  1786. {
  1787. $settings = json_decode($chart->settings);
  1788. if($settings and isset($settings->xaxis))
  1789. {
  1790. $dimensions = array($settings->xaxis[0]->name);
  1791. foreach($settings->yaxis as $yaxis) $dimensions[] = $yaxis->name;
  1792. $sourceData = array();
  1793. $sql = $this->setFilterSQL($chart);
  1794. $results = $this->bi->queryWithDriver($chart->driver, $sql);
  1795. foreach($results as $result)
  1796. {
  1797. $key = $settings->xaxis[0]->name;
  1798. $field = $settings->xaxis[0]->field;
  1799. $row = array($key => $result->$field);
  1800. foreach($settings->yaxis as $yaxis)
  1801. {
  1802. $field = $yaxis->field;
  1803. $row[$yaxis->name] = $result->$field;
  1804. }
  1805. $sourceData[] = $row;
  1806. }
  1807. $component->option->dataset->dimensions = $dimensions;
  1808. $component->option->dataset->source = $sourceData;
  1809. }
  1810. }
  1811. return $this->setComponentDefaults($component);
  1812. }
  1813. }
  1814. /**
  1815. * Build table chart
  1816. *
  1817. * @param object $component
  1818. * @param object $chart
  1819. * @access public
  1820. * @return object
  1821. */
  1822. public function buildTableChart($component, $chart)
  1823. {
  1824. if(!$chart->settings)
  1825. {
  1826. $component->request = json_decode('{"requestDataType":0,"requestHttpType":"get","requestUrl":"","requestIntervalUnit":"second","requestContentType":0,"requestParamsBodyType":"none","requestSQLContent":{"sql":"select * from where"},"requestParams":{"Body":{"form-data":{},"x-www-form-urlencoded":{},"json":"","xml":""},"Header":{},"Params":{}}}');
  1827. $component->events = json_decode('{"baseEvent":{},"advancedEvents":{}}');
  1828. $component->key = "TableScrollBoard";
  1829. $component->chartConfig = json_decode('{"key":"TableScrollBoard","chartKey":"VTableScrollBoard","conKey":"VCTableScrollBoard","title":"轮播列表","category":"Tables","categoryName":"表格","package":"Tables","chartFrame":"common","image":"/static/png/table_scrollboard-fb642e78.png"}');
  1830. $component->option = json_decode('{"header":["列1","列2","列3"],"dataset":[["行1列1","行1列2","行1列3"],["行2列1","行2列2","行2列3"],["行3列1","行3列2","行3列3"]],"rowNum":2,"waitTime":2,"headerHeight":35,"carousel":"single","headerBGC":"#00BAFF","oddRowBGC":"#003B51","evenRowBGC":"#0A2732"}');
  1831. return $this->setComponentDefaults($component);
  1832. }
  1833. else
  1834. {
  1835. if($chart->sql)
  1836. {
  1837. $settings = json_decode($chart->settings);
  1838. if($settings and isset($settings->column))
  1839. {
  1840. $header = array();
  1841. $dataset = array();
  1842. foreach($settings->column as $column)
  1843. {
  1844. $header[$column->field] = $column->name;
  1845. }
  1846. $sql = $this->setFilterSQL($chart);
  1847. $results = $this->buildDataset($chart->id, $chart->driver, $sql);
  1848. foreach($results as $result)
  1849. {
  1850. $row = array();
  1851. foreach($header as $field => $name)
  1852. {
  1853. $row[] = $result->$field;
  1854. }
  1855. $dataset[] = $row;
  1856. }
  1857. $component->option->header = array_values($header);
  1858. $component->option->dataset = $dataset;
  1859. }
  1860. }
  1861. return $this->setComponentDefaults($component);
  1862. }
  1863. }
  1864. /**
  1865. * Build bar chart.
  1866. *
  1867. * @param object $component
  1868. * @param object $chart
  1869. * @access public
  1870. * @return object
  1871. */
  1872. public function buildBarChart($component, $chart)
  1873. {
  1874. if(!$chart->settings)
  1875. {
  1876. $component->request = json_decode('{"requestDataType": 0, "requestHttpType": "get", "requestUrl": "", "requestIntervalUnit": "second", "requestContentType": 0, "requestParamsBodyType": "none", "requestSQLContent": { "sql": "select * from where" }, "requestParams": { "Body": { "form-data": {}, "x-www-form-urlencoded": {}, "json": "", "xml": "" }, "Header": {}, "Params": {}}}');
  1877. $component->events = json_decode('{"baseEvent": {}, "advancedEvents": {}}');
  1878. $component->key = "BarCrossrange";
  1879. $component->chartConfig = json_decode('{"key": "BarCrossrange", "chartKey": "VBarCrossrange", "conKey": "VCBarCrossrange", "title": "横向柱状图", "category": "Bars", "categoryName": "柱状图", "package": "Charts", "chartFrame": "echarts", "image": "/static/png/bar_y-05067169.png" }');
  1880. $component->option = json_decode('{"xAxis": { "show": true, "type": "category" }, "yAxis": { "show": true, "axisLine": { "show": true }, "type": "value" }, "series": [], "backgroundColor": "rgba(0,0,0,0)"}');
  1881. return $this->setComponentDefaults($component);
  1882. }
  1883. else
  1884. {
  1885. if($chart->sql)
  1886. {
  1887. $settings = json_decode($chart->settings);
  1888. if($settings and isset($settings->xaxis))
  1889. {
  1890. $dimensions = array($settings->xaxis[0]->name);
  1891. foreach($settings->yaxis as $yaxis) $dimensions[] = $yaxis->name;
  1892. $sourceData = array();
  1893. $sql = $this->setFilterSQL($chart);
  1894. $results = $this->bi->queryWithDriver($chart->driver, $sql);
  1895. foreach($results as $result)
  1896. {
  1897. $key = $settings->xaxis[0]->name;
  1898. $field = $settings->xaxis[0]->field;
  1899. if($settings->yaxis[0]->agg == 'sum')
  1900. {
  1901. if(!isset($sourceData[$result->$field])) $sourceData[$result->$field] = array($key => $result->$field);
  1902. foreach($settings->yaxis as $yaxis)
  1903. {
  1904. $valueField = $yaxis->field;
  1905. if(!isset($sourceData[$result->$field][$yaxis->name])) $sourceData[$result->$field][$yaxis->name] = 0;
  1906. $sourceData[$result->$field][$yaxis->name] += $result->$valueField;
  1907. }
  1908. }
  1909. else
  1910. {
  1911. $row = array($key => $result->$field);
  1912. foreach($settings->yaxis as $yaxis)
  1913. {
  1914. $field = $yaxis->field;
  1915. $row[$yaxis->name] = $result->$field;
  1916. }
  1917. $sourceData[] = $row;
  1918. }
  1919. }
  1920. $component->option->dataset->dimensions = $dimensions;
  1921. $component->option->dataset->source = array_values($sourceData);
  1922. }
  1923. }
  1924. return $this->setComponentDefaults($component);
  1925. }
  1926. }
  1927. /**
  1928. * Build pie chart.
  1929. *
  1930. * @param object $component
  1931. * @param object $chart
  1932. * @access public
  1933. * @return object
  1934. */
  1935. public function buildPieChart($component, $chart)
  1936. {
  1937. if(!$chart->settings)
  1938. {
  1939. $component->request = json_decode('{"requestDataType":0,"requestHttpType":"get","requestUrl":"","requestIntervalUnit":"second","requestContentType":0,"requestParamsBodyType":"none","requestSQLContent":{"sql":"select * from where"},"requestParams":{"Body":{"form-data":{},"x-www-form-urlencoded":{},"json":"","xml":""},"Header":{},"Params":{}}}');
  1940. $component->events = json_decode('{"baseEvent":{},"advancedEvents":{}}');
  1941. $component->key = "PieCommon";
  1942. $component->chartConfig = json_decode('{"key":"PieCommon","chartKey":"VPieCommon","conKey":"VCPieCommon","title":"饼图","category":"Pies","categoryName":"饼图","package":"Charts","chartFrame":"echarts","image":"/static/png/pie-9620f191.png"}');
  1943. $component->option = json_decode('{"type":"nomal","series":[{"type":"pie","radius":"70%","roseType":false}],"backgroundColor":"rgba(0,0,0,0)"}');
  1944. return $this->setComponentDefaults($component);
  1945. }
  1946. else
  1947. {
  1948. if($chart->sql)
  1949. {
  1950. $settings = json_decode($chart->settings);
  1951. if($settings and isset($settings->metric))
  1952. {
  1953. $dimensions = array($settings->group[0]->name, $settings->metric[0]->field);
  1954. $sourceData = array();
  1955. $sql = $this->setFilterSQL($chart);
  1956. $results = $this->bi->queryWithDriver($chart->driver, $sql);
  1957. $group = $settings->group[0]->field;
  1958. $groupCount = array();
  1959. foreach($results as $result)
  1960. {
  1961. if($settings->metric[0]->agg == 'count')
  1962. {
  1963. if(!isset($groupCount[$result->$group])) $groupCount[$result->$group] = 0;
  1964. $groupCount[$result->$group]++;
  1965. }
  1966. }
  1967. arsort($groupCount);
  1968. foreach($groupCount as $groupValue => $groupCount)
  1969. {
  1970. $sourceData[] = array($settings->group[0]->name => $groupValue, $settings->metric[0]->field => $groupCount);
  1971. }
  1972. }
  1973. if(empty($sourceData)) $dimensions = array();
  1974. $component->option->dataset->dimensions = $dimensions;
  1975. $component->option->dataset->source = $sourceData;
  1976. }
  1977. return $this->setComponentDefaults($component);
  1978. }
  1979. }
  1980. /**
  1981. * Build piecircle chart.
  1982. *
  1983. * @param object $component
  1984. * @param object $chart
  1985. * @access public
  1986. * @return object
  1987. */
  1988. public function buildPieCircleChart($component, $chart)
  1989. {
  1990. $option = new stdclass();
  1991. $option->type = 'nomal';
  1992. $option->series = array();
  1993. $option->series[0] = new stdclass();
  1994. $option->series[0]->type = 'pie';
  1995. $option->series[0]->radius = '70%';
  1996. $option->series[0]->roseType = false;
  1997. $option->backgroundColor = 'rgba(0,0,0,0)';
  1998. $option->series[0]->data = array();
  1999. $option->series[0]->data[0] = new stdclass();
  2000. $option->series[0]->data[0]->value = array();
  2001. $option->series[0]->data[0]->value[0] = 0;
  2002. $option->series[0]->data[1] = new stdclass();
  2003. $option->series[0]->data[1]->value = array();
  2004. $option->series[0]->data[1]->value[0] = 0;
  2005. if(!$chart->settings)
  2006. {
  2007. $component->request = json_decode('{"requestDataType":0,"requestHttpType":"get","requestUrl":"","requestIntervalUnit":"second","requestContentType":0,"requestParamsBodyType":"none","requestSQLContent":{"sql":"select * from where"},"requestParams":{"Body":{"form-data":{},"x-www-form-urlencoded":{},"json":"","xml":""},"Header":{},"Params":{}}}');
  2008. $component->events = json_decode('{"baseEvent":{},"advancedEvents":{}}');
  2009. $component->key = "PieCircle";
  2010. $component->chartConfig = json_decode('{"key":"PieCircle","chartKey":"VPieCircle","conKey":"VCPieCircle","title":"饼图","category":"Pies","categoryName":"饼图","package":"Charts","chartFrame":"echarts","image":"/static/png/pie-circle-258fcce7.png"}');
  2011. $component->option = $option;
  2012. return $this->setComponentDefaults($component);
  2013. }
  2014. else
  2015. {
  2016. if($chart->sql)
  2017. {
  2018. $settings = json_decode($chart->settings);
  2019. if($settings and isset($settings->metric))
  2020. {
  2021. $sourceData = array();
  2022. $sql = $this->setFilterSQL($chart);
  2023. $results = $this->bi->queryWithDriver($chart->driver, $sql);
  2024. $group = $settings->group[0]->field;
  2025. $groupCount = array();
  2026. foreach($results as $result)
  2027. {
  2028. if($settings->metric[0]->agg == 'count')
  2029. {
  2030. if(!isset($groupCount[$result->$group])) $groupCount[$result->$group] = 0;
  2031. $groupCount[$result->$group]++;
  2032. }
  2033. }
  2034. foreach($groupCount as $groupValue => $groupCount) $sourceData[$groupValue] = $groupCount;
  2035. }
  2036. $doneData = round((array_sum($sourceData) != 0 and !empty($sourceData['done'])) ? $sourceData['done'] / array_sum($sourceData) : 0, 4);
  2037. $component->option->dataset = $doneData;
  2038. if(!isset($component->option->series) || !is_array($component->option->series)) $component->option->series = $option->series;
  2039. if(!isset($component->option)) $component->option = $option;
  2040. $component->option->series[0]->data = array((object)array('value'=> $doneData), (object)array('value'=> 1 - $doneData));
  2041. }
  2042. return $this->setComponentDefaults($component);
  2043. }
  2044. }
  2045. /**
  2046. * Build water polo chart.
  2047. *
  2048. * @param object $component
  2049. * @param object $chart
  2050. * @access public
  2051. * @return object
  2052. */
  2053. public function buildWaterPolo($component, $chart)
  2054. {
  2055. if(!$chart->settings)
  2056. {
  2057. $component->request = json_decode('{"requestDataType":0,"requestHttpType":"get","requestUrl":"","requestIntervalUnit":"second","requestContentType":0,"requestParamsBodyType":"none","requestSQLContent":{"sql":"select * from where"},"requestParams":{"Body":{"form-data":{},"x-www-form-urlencoded":{},"json":"","xml":""},"Header":{},"Params":{}}}');
  2058. $component->events = json_decode('{"baseEvent":{},"advancedEvents":{}}');
  2059. $component->key = "PieCircle";
  2060. $component->chartConfig = json_decode('{"key":"WaterPolo","chartKey":"VWaterPolo","conKey":"VCWaterPolo","title":"水球图","category":"Mores","categoryName":"更多","package":"Charts","chartFrame":"common","image":"water_WaterPolo.png"}');
  2061. $component->option = json_decode('{"type":"nomal","series":[{"type":"liquidFill","radius":"90%","roseType":false}],"backgroundColor":"rgba(0,0,0,0)"}');
  2062. return $this->setComponentDefaults($component);
  2063. }
  2064. else
  2065. {
  2066. if($chart->sql)
  2067. {
  2068. $settings = json_decode($chart->settings);
  2069. $sourceData = 0;
  2070. if($settings and isset($settings->metric))
  2071. {
  2072. $sql = $this->setFilterSQL($chart);
  2073. $result = $this->dao->query($sql)->fetch();
  2074. $group = $settings->group[0]->field;
  2075. $sourceData = zget($result, $group, 0);
  2076. if(empty($sourceData)) $sourceData = 0;
  2077. }
  2078. $component->option->dataset = $sourceData;
  2079. }
  2080. return $this->setComponentDefaults($component);
  2081. }
  2082. }
  2083. /**
  2084. * Get waterpolo option.
  2085. *
  2086. * @param object $component
  2087. * @param object $chart
  2088. * @access public
  2089. * @return object
  2090. */
  2091. public function getWaterPoloOption($component, $chart, $filters)
  2092. {
  2093. $value = 0;
  2094. if(!$chart->settings)
  2095. {
  2096. $component->chartConfig = json_decode($this->config->screen->chartConfig['WaterPolo']);
  2097. $component->option = json_decode($this->config->screen->chartOption['WaterPolo']);
  2098. $component->key = $component->chartConfig->key;
  2099. }
  2100. else
  2101. {
  2102. $setting = json_decode($chart->settings, true)[0];
  2103. $options = $this->bi->genWaterPolo(json_decode($chart->fields, true), $setting, $chart->sql, $filters, $chart->driver);
  2104. $value = $options['series'][0]['data'][0];
  2105. }
  2106. return $this->prepareWaterPoloDataset($component, $value);
  2107. }
  2108. /**
  2109. * Get option of metric chart.
  2110. *
  2111. * @param object $metric
  2112. * @param array $resultHeader
  2113. * @param array $resultData
  2114. * @param object $component
  2115. * @access public
  2116. * @return object
  2117. */
  2118. public function getMetricChartOption($metric, $resultHeader, $resultData, $component = null)
  2119. {
  2120. $chartOption = $this->metric->getEchartsOptions($resultHeader, $resultData);
  2121. if(!$chartOption) return false;
  2122. if(isset($component) && isset($component->option->chartOption))
  2123. {
  2124. $preChartOption = $component->option->chartOption;
  2125. $preChartOption->series = $chartOption['series'];
  2126. if(!isset($preChartOption->xAxis)) $preChartOption->xAxis = $chartOption['xAxis'];
  2127. $preChartOption->xAxis->data = $chartOption['xAxis']['data'];
  2128. return $preChartOption;
  2129. }
  2130. if(!isset($chartOption['title']))
  2131. {
  2132. $chartOption['title'] = array();
  2133. $chartOption['title']['show'] = false;
  2134. $chartOption['title']['titleShow'] = true;
  2135. $chartOption['title']['textStyle'] = array('color' => '#BFBFBF');
  2136. }
  2137. $chartOption['title']['text'] = $metric->name;
  2138. $chartOption['backgroundColor'] = "#0B1727FF";
  2139. if(!isset($chartOption['legend'])) $chartOption['legend'] = array();
  2140. $chartOption['legend']['textStyle']['color'] = 'white';
  2141. $chartOption['legend']['inactiveColor'] = 'gray';
  2142. return $chartOption;
  2143. }
  2144. /**
  2145. * Get option of metric table.
  2146. *
  2147. * @param array $resultHeader
  2148. * @param array $resultData
  2149. * @param array $filterParams
  2150. * @param object $component
  2151. * @access public
  2152. * @return object
  2153. */
  2154. public function getMetricTableOption($metric, $resultHeader, $resultData, $component = null)
  2155. {
  2156. $this->loadModel('metric');
  2157. $isObjectMetric = $this->metric->isObjectMetric($resultHeader);
  2158. $dateType = $metric->dateType;
  2159. list($groupHeader, $groupData) = $this->metric->getGroupTable($resultHeader, $resultData, $metric->dateType, false);
  2160. $tableOption = new stdclass();
  2161. if(!empty($component) && isset($component->option->tableOption)) $tableOption = $component->option->tableOption;
  2162. $tableOption->headers = $isObjectMetric ? $this->getMetricHeaders($groupHeader, $dateType) : array($groupHeader);
  2163. $tableOption->data = $groupData;
  2164. $tableOption->scope = $metric->scope;
  2165. return $tableOption;
  2166. }
  2167. /**
  2168. * Filter metric data.
  2169. *
  2170. * @param array $data
  2171. * @param string $dateType
  2172. * @param bool $isObjectMetric
  2173. * @param array $filters
  2174. * @access public
  2175. * @return array
  2176. */
  2177. public function filterMetricData($data, $dateType, $isObjectMetric, $filters = array())
  2178. {
  2179. if(empty($filters)) return $data;
  2180. if($isObjectMetric)
  2181. {
  2182. if(isset($filters['scope']))
  2183. {
  2184. $scopeFilter = $filters['scope'];
  2185. $objectPairs = $this->loadModel('metric')->getPairsByScope($scopeFilter->type);
  2186. $selectedObjects = array_intersect_key($objectPairs, array_flip($scopeFilter->value));
  2187. foreach($data as $index => $row)
  2188. {
  2189. if(!in_array($row['scope'], $selectedObjects)) unset($data[$index]);
  2190. }
  2191. }
  2192. $filteredData = array();
  2193. if(isset($filters['begin'], $filters['end']))
  2194. {
  2195. $beginFilter = $filters['begin']->$dateType;
  2196. $endFilter = $filters['end']->$dateType;
  2197. foreach($data as $index => $row)
  2198. {
  2199. $filteredData[$index] = array_filter($row, function($value, $key) use ($beginFilter, $endFilter)
  2200. {
  2201. if($key == 'scope') return true;
  2202. if($key >= $beginFilter && $key <= $endFilter) return true;
  2203. return false;
  2204. }, ARRAY_FILTER_USE_BOTH);
  2205. }
  2206. }
  2207. else
  2208. {
  2209. $filteredData = $data;
  2210. }
  2211. }
  2212. else
  2213. {
  2214. $beginFilter = $filters['begin']->$dateType;
  2215. $endFilter = $filters['end']->$dateType;
  2216. $filteredData = array_filter($data, function($row) use ($beginFilter, $endFilter)
  2217. {
  2218. if($row['date'] >= $beginFilter && $row['date'] <= $endFilter) return true;
  2219. });
  2220. }
  2221. return array_values($filteredData);
  2222. }
  2223. /**
  2224. * 获取度量项卡片参数。
  2225. * Get card option of metric.
  2226. *
  2227. * @param object $metric
  2228. * @access public
  2229. * @return object
  2230. */
  2231. public function getMetricCardOption($metric, $resultData, $component = null)
  2232. {
  2233. $this->loadModel('metric');
  2234. $option = new stdclass();
  2235. if(empty($component))
  2236. {
  2237. $option->displayType = 'normal';
  2238. $option->cardType = 'A';
  2239. $option->dateType = $metric->dateType;
  2240. $option->bgColor = '#26292EFF';
  2241. $option->border = array('color' => '#515458FF', 'width' => 1, 'radius' => 2);
  2242. $option->scope = $metric->scope;
  2243. $option->objectPairs = array();
  2244. }
  2245. else
  2246. {
  2247. $option = $component->option->card;
  2248. }
  2249. $option->data = $resultData;
  2250. $option->filterValue = (is_array($option->data) && !empty($option->data)) ? current($option->data) : array();
  2251. return $option;
  2252. }
  2253. /**
  2254. * Get table headers of metric in screen designer.
  2255. *
  2256. * @param array $resultHeaders
  2257. * @param string $dateType
  2258. * @param object $filters
  2259. * @access public
  2260. * @return object
  2261. */
  2262. public function getMetricHeaders($resultHeader, $dateType)
  2263. {
  2264. $headers = array_fill(0, 2, array());
  2265. foreach($resultHeader as $head)
  2266. {
  2267. if($head['name'] == 'scope')
  2268. {
  2269. if($dateType != 'year') $head['rowspan'] = 2;
  2270. $headers[0][] = $head;
  2271. }
  2272. else
  2273. {
  2274. $row = $dateType == 'year' ? 0 : 1;
  2275. $head['type'] = $dateType;
  2276. $head['value'] = $head['name'];
  2277. $headers[$row][] = $head;
  2278. }
  2279. }
  2280. $dateGroups = array_count_values(array_column($resultHeader, 'headerGroup'));
  2281. foreach($dateGroups as $date => $count)
  2282. {
  2283. $dateGroup = array();
  2284. $dateGroup['colspan'] = $count;
  2285. $dateGroup['title'] = $date;
  2286. $dateGroup['name'] = $date;
  2287. $dateGroup['value'] = substr($date, 0, 4);
  2288. $dateGroup['type'] = 'year';
  2289. $headers[0][] = $dateGroup;
  2290. }
  2291. if($dateType == 'year') unset($headers[1]);
  2292. return $headers;
  2293. }
  2294. /**
  2295. * Build radar chart.
  2296. *
  2297. * @param object $component
  2298. * @param object $chart
  2299. * @access public
  2300. * @return object
  2301. */
  2302. public function buildRadarChart($component, $chart)
  2303. {
  2304. if(!$chart->settings)
  2305. {
  2306. $component->request = json_decode('{"requestDataType":0,"requestHttpType":"get","requestUrl":"","requestIntervalUnit":"second","requestContentType":0,"requestParamsBodyType":"none","requestSQLContent":{"sql":"select * from where"},"requestParams":{"Body":{"form-data":{},"x-www-form-urlencoded":{},"json":"","xml":""},"Header":{},"Params":{}}}');
  2307. $component->events = json_decode('{"baseEvent":{},"advancedEvents":{}}');
  2308. $component->key = "Radar";
  2309. $component->chartConfig = json_decode('{"key":"Radar","chartKey":"VRadar","conKey":"VCRadar","title":"雷达图","category":"Mores","categoryName":"更多","package":"Charts","chartFrame":"common","image":"/static/png/radar-91567f95.png"}');
  2310. $component->option = json_decode('{"radar":{"indicator":[{"name":"数据1","max":6500},{"name":"数据2","max":16000},{"name":"数据3","max":30000},{"name":"数据4","max":38000},{"name":"数据5","max":52000}]},"series":[{"name":"radar","type":"radar","areaStyle":{"opacity":0.1},"data":[{"name":"data1","value":[4200,3000,20000,35000,50000]}]}],"backgroundColor":"rgba(0,0,0,0)"}');
  2311. return $this->setComponentDefaults($component);
  2312. }
  2313. else
  2314. {
  2315. $indicator = array();
  2316. $seriesData = array();
  2317. if($chart->sql)
  2318. {
  2319. $settings = json_decode($chart->settings);
  2320. if($settings and isset($settings->metric))
  2321. {
  2322. $sql = $this->setFilterSQL($chart);
  2323. $results = $this->bi->queryWithDriver($chart->driver, $sql);
  2324. $group = $settings->group[0]->field;
  2325. $metrics = array();
  2326. foreach($settings->metric as $metric)
  2327. {
  2328. $metrics[$metric->key] = array('field' => $metric->field, 'name' => $metric->name, 'value' => 0);
  2329. }
  2330. foreach($results as $result)
  2331. {
  2332. if(isset($metrics[$result->$group]))
  2333. {
  2334. $field = $metrics[$result->$group]['field'];
  2335. $metrics[$result->$group]['value'] += $result->$field;
  2336. }
  2337. }
  2338. $max = 0;
  2339. foreach($metrics as $data)
  2340. {
  2341. if($data['value'] > $max) $max = $data['value'];
  2342. }
  2343. $data = array('name' => '', 'value' => array());
  2344. $value = array();
  2345. foreach($metrics as $key => $metric)
  2346. {
  2347. $indicator[] = array('name' => $metric['name'], 'max' => $max);
  2348. $data['value'][] = $metric['value'];
  2349. $value[] = $metric['value'];
  2350. }
  2351. $seriesData[] = $data;
  2352. }
  2353. $component->option->dataset->radarIndicator = $indicator;
  2354. $component->option->radar->indicator = $indicator;
  2355. $component->option->dataset->seriesData = $seriesData;
  2356. $component->option->series[0]->data[0]->value = $value;
  2357. }
  2358. return $this->setComponentDefaults($component);
  2359. }
  2360. }
  2361. /**
  2362. * Build org chart.
  2363. *
  2364. * @param object $component
  2365. * @param object $chart
  2366. * @access public
  2367. * @return object
  2368. */
  2369. public function buildOrgChart($component, $chart)
  2370. {
  2371. //TODO
  2372. }
  2373. /**
  2374. * Build funnel chart.
  2375. *
  2376. * @param object $component
  2377. * @param object $chart
  2378. * @access public
  2379. * @return object
  2380. */
  2381. public function buildFunnelChart($component, $chart)
  2382. {
  2383. if(!$chart->settings)
  2384. {
  2385. $component->request = json_decode('{"requestDataType":0,"requestHttpType":"get","requestUrl":"","requestIntervalUnit":"second","requestContentType":0,"requestParamsBodyType":"none","requestSQLContent":{"sql":"select * from where"},"requestParams":{"Body":{"form-data":{},"x-www-form-urlencoded":{},"json":"","xml":""},"Header":{},"Params":{}}}');
  2386. $component->events = json_decode('{"baseEvent":{},"advancedEvents":{}}');
  2387. $component->key = "Funnel";
  2388. $component->chartConfig = json_decode('{"key":"Funnel","chartKey":"VFunnel","conKey":"VCFunnel","title":"漏斗图","category":"Mores","categoryName":"更多","package":"Charts","chartFrame":"echarts","image":"/static/png/funnel-d032fdf6.png"}');
  2389. $component->option = json_decode('{"dataset":{"dimensions":["product","dataOne"],"source":[{"product":"data1","dataOne":20},{"product":"data2","dataOne":40},{"product":"data3","dataOne":60},{"product":"data4","dataOne":80},{"product":"data5","dataOne":100}]},"series":[{"name":"Funnel","type":"funnel","gap":5,"label":{"show":true,"position":"inside"}}],"backgroundColor":"rgba(0,0,0,0)"}');
  2390. return $this->setComponentDefaults($component);
  2391. }
  2392. }
  2393. /**
  2394. * Get burn data.
  2395. *
  2396. * @access public
  2397. * @return array
  2398. */
  2399. public function getBurnData()
  2400. {
  2401. $type = 'withdelay';
  2402. $this->loadModel('execution');
  2403. $executionData = array();
  2404. $executions = $this->execution->getList(0, 'sprint', 'doing') + $this->execution->getList(0, 'stage', 'doing');
  2405. $projectIdList = array_column($executions, 'project');
  2406. $parents = array_column($executions, 'parent');
  2407. $projectPairs = $this->dao->select('id,name')->from(TABLE_PROJECT)->where('id')->in($projectIdList)->fetchPairs();
  2408. foreach($executions as $executionID => $execution)
  2409. {
  2410. if(in_array($executionID, $parents)) continue; // 过滤父阶段
  2411. /* Splice project name for the execution name. */
  2412. $projectName = zget($projectPairs, $execution->project, '');
  2413. $execution->name = $projectName ? $projectName . '--' . $execution->name : $execution->name;
  2414. /* Get date list. */
  2415. if(((strpos('closed,suspended', $execution->status) === false and helper::today() > $execution->end)
  2416. or ($execution->status == 'closed' and substr($execution->closedDate, 0, 10) > $execution->end)
  2417. or ($execution->status == 'suspended' and $execution->suspendedDate > $execution->end))
  2418. and strpos($type, 'delay') === false)
  2419. $type .= ',withdelay';
  2420. $deadline = $execution->status == 'closed' ? substr($execution->closedDate, 0, 10) : $execution->suspendedDate;
  2421. $deadline = strpos('closed,suspended', $execution->status) === false ? helper::today() : $deadline;
  2422. $endDate = (strpos($type, 'withdelay') !== false and $deadline > $execution->end) ? $deadline : $execution->end;
  2423. list($dateList, $interval) = $this->execution->getDateList($execution->begin, $endDate, $type, 0, 'Y-m-d', $deadline);
  2424. $executionEnd = strpos($type, 'withdelay') !== false ? $execution->end : '';
  2425. $chartData = $this->execution->buildBurnData($executionID, $dateList, 'left', $executionEnd);
  2426. $execution->chartData = $chartData;
  2427. $executionData[$executionID] = $execution;
  2428. }
  2429. return $executionData;
  2430. }
  2431. /**
  2432. * Init component.
  2433. *
  2434. * @param object $chart
  2435. * @param string $type
  2436. * @param object $component
  2437. * @access public
  2438. * @return void
  2439. */
  2440. public function initComponent($chart, $type, $component = null)
  2441. {
  2442. if($type == 'metric') return $this->initMetricComponent($chart, $component);
  2443. if($type == 'chart' || $type == 'pivot') return $this->initChartAndPivotComponent($chart, $type, $component);
  2444. return array($component, false);
  2445. }
  2446. /**
  2447. * Init metric component.
  2448. *
  2449. * @param object $metric
  2450. * @param object $component
  2451. * @access public
  2452. * @return array
  2453. */
  2454. public function initMetricComponent($metric, $component = null)
  2455. {
  2456. if(!$component) $component = new stdclass();
  2457. if(!isset($component->id)) $component->id = $metric->id;
  2458. if(!isset($component->sourceID)) $component->sourceID = $metric->id;
  2459. if(!isset($component->title)) $component->title = $metric->name;
  2460. if(!isset($component->type)) $component->type = 'metric';
  2461. if(!isset($component->chartConfig)) $component->chartConfig = json_decode($this->config->screen->chartConfig['metric']);
  2462. if(!isset($component->option)) $component->option = new stdclass();
  2463. return array($component, false);
  2464. }
  2465. /**
  2466. * Init chart or pivot component.
  2467. *
  2468. * @param object $metric
  2469. * @param string $type
  2470. * @param object $component
  2471. * @access public
  2472. * @return array
  2473. */
  2474. public function initChartAndPivotComponent($chart, $type, $component = null)
  2475. {
  2476. if(!$component) $component = new stdclass();
  2477. if(!$chart) return array($component, false);
  2478. $chartID = $chart->id;
  2479. $chartName = $chart->name;
  2480. $settings = is_string($chart->settings) ? json_decode($chart->settings) : $chart->settings;
  2481. $builtin = $chart->builtin;
  2482. $isBuiltin = ($builtin and !in_array($chartID, $this->config->screen->builtinChart));
  2483. if(!isset($component->id)) $component->id = $chartID;
  2484. if(!isset($component->sourceID)) $component->sourceID = $chartID;
  2485. if(!isset($component->title)) $component->title = $chartName;
  2486. if($type == 'chart') $chartType = ($chart->builtin and !in_array($chart->id, $this->config->screen->builtinChart)) ? $chart->type : $settings[0]->type;
  2487. if($type == 'pivot') $chartType = 'table';
  2488. if($type == 'metric') $chartType = 'metric';
  2489. $component->type = $chartType;
  2490. $typeChanged = false;
  2491. // Get type is changed or not.
  2492. if(isset($component->chartConfig))
  2493. {
  2494. $componentType = $chartType;
  2495. foreach($this->config->screen->chartConfig as $type => $chartConfig)
  2496. {
  2497. $chartConfig = json_decode($chartConfig, true);
  2498. if($chartConfig['key'] == $component->chartConfig->key) $componentType = $type;
  2499. }
  2500. $typeChanged = $chartType != $componentType;
  2501. }
  2502. // New component type or change component type.
  2503. if(!isset($component->chartConfig) or $typeChanged)
  2504. {
  2505. $chartConfig = json_decode(zget($this->config->screen->chartConfig, $chartType));
  2506. if(empty($chartConfig)) return null;
  2507. $component->chartConfig = $chartConfig;
  2508. $component->key = $chartConfig->key;
  2509. }
  2510. if(!isset($component->option) or $typeChanged)
  2511. {
  2512. $component->option = new stdclass();
  2513. $component->option->dataset = new stdclass();
  2514. }
  2515. $component = $this->initOptionTitle($component, $type, $chartName);
  2516. if(!isset($component->option->dataset)) $component->option->dataset = new stdclass();
  2517. $component->chartConfig->title = $chartName;
  2518. $component->chartConfig->sourceID = $component->sourceID;
  2519. if($component->type != 'metric') $component->chartConfig->version = $chart->version;
  2520. return array($component, $typeChanged);
  2521. }
  2522. public function initOptionTitle($component, $type, $chartName)
  2523. {
  2524. if($type == 'pivot')
  2525. {
  2526. if(!isset($component->option->caption)) $component->option->caption = $chartName;
  2527. }
  2528. elseif($type == 'chart')
  2529. {
  2530. if(!isset($component->option->title))
  2531. {
  2532. $component->option->title = new stdclass();
  2533. $component->option->title->text = $chartName;
  2534. $component->option->title->show = false;
  2535. $component->option->title->titleShow = true;
  2536. }
  2537. }
  2538. return $component;
  2539. }
  2540. /**
  2541. * Check if the Chart is in use.
  2542. *
  2543. * @param int $chartID
  2544. * @param string $type
  2545. * @access public
  2546. * @return void
  2547. */
  2548. public function checkIFChartInUse($chartID, $type = 'chart')
  2549. {
  2550. static $screenList = array();
  2551. if(empty($screenList)) $screenList = $this->dao->select('scheme')->from(TABLE_SCREEN)->where('deleted')->eq(0)->andWhere('status')->eq('published')->fetchAll();
  2552. foreach($screenList as $screen)
  2553. {
  2554. $scheme = json_decode($screen->scheme);
  2555. if(empty($scheme->componentList)) continue;
  2556. foreach($scheme->componentList as $component)
  2557. {
  2558. if(!empty($component->isGroup))
  2559. {
  2560. foreach($component->groupList as $key => $groupComponent)
  2561. {
  2562. if(!isset($groupComponent->chartConfig)) continue;
  2563. $sourceID = zget($groupComponent->chartConfig, 'sourceID', '');
  2564. $package = zget($groupComponent->chartConfig, 'package', '');
  2565. $sourceType = $this->getChartType($package);
  2566. if($chartID == $sourceID and $type == $sourceType) return true;
  2567. }
  2568. }
  2569. else
  2570. {
  2571. if(!isset($component->chartConfig)) continue;
  2572. $sourceID = zget($component->chartConfig, 'sourceID', '');
  2573. $package = zget($component->chartConfig, 'package', '');
  2574. $sourceType = $this->getChartType($package);
  2575. if($chartID == $sourceID and $type == $sourceType) return true;
  2576. }
  2577. }
  2578. }
  2579. return false;
  2580. }
  2581. /**
  2582. * Get chart type.
  2583. *
  2584. * @param string $type
  2585. * @access public
  2586. * @return string
  2587. */
  2588. public function getChartType($type)
  2589. {
  2590. if($type == 'Tables' || $type == 'pivot') return 'pivot';
  2591. if($type == 'Metrics') return 'metric';
  2592. return 'chart';
  2593. }
  2594. /**
  2595. * 构建大屏图表的数据源。
  2596. * Build dataset of chart.
  2597. *
  2598. * @param int $chartID
  2599. * @param string $sql
  2600. * @access public
  2601. * @return array
  2602. */
  2603. public function buildDataset($chartID, $driver, $sql = '')
  2604. {
  2605. if(in_array($chartID, $this->config->screen->phpChart)) return $this->getDatasetForUsageReport($chartID);
  2606. return $this->bi->queryWithDriver($driver, $sql);
  2607. }
  2608. /**
  2609. * 获取应用健康度体检报告的数据源。
  2610. * Build dataset for usage report.
  2611. *
  2612. * @param int $chartID
  2613. * @access public
  2614. * @return array
  2615. */
  2616. public function getDatasetForUsageReport($chartID)
  2617. {
  2618. $year = $this->filter->year;
  2619. $month = $this->filter->month;
  2620. $projectList = $this->getUsageReportProjects($year, $month);
  2621. $productList = $this->getUsageReportProducts($year, $month);
  2622. if($chartID == 20002) return $this->getActiveUserTable($year, $month, $projectList);
  2623. if($chartID == 20012) return $this->getProductStoryTable($year, $month, $productList);
  2624. if($chartID == 20011) return $this->getProductTestTable($year, $month, $productList);
  2625. if($chartID == 20004) return $this->getActiveProductCard($year, $month);
  2626. if($chartID == 20007) return $this->getActiveProjectCard($year, $month);
  2627. if($chartID == 20013) return $this->getProjectStoryTable($year, $month, $projectList);
  2628. if($chartID == 20010) return $this->getProjectTaskTable($year, $month, $projectList);
  2629. }
  2630. /**
  2631. * 获取应用健康度体检报告的活跃账号数项目间对比表格。
  2632. * Get table of active account per project in usage report.
  2633. *
  2634. * @param string $year
  2635. * @param string $month
  2636. * @param array $projectList
  2637. * @access public
  2638. * @return array
  2639. */
  2640. public function getActiveUserTable($year, $month, $projectList)
  2641. {
  2642. $date = date("Y-m-t", strtotime("$year-$month"));
  2643. $loginUserList = $this->dao->select('distinct actor')->from(TABLE_ACTION)
  2644. ->where('objectType')->eq('user')
  2645. ->andWhere('action')->eq('login')
  2646. ->andWhere('year(date)')->eq($year)
  2647. ->andWhere('month(date)')->eq($month)
  2648. ->fetchPairs();
  2649. $dataset = array();
  2650. foreach($projectList as $projectID => $projectName)
  2651. {
  2652. $teamMemberList = $this->dao->select('t2.id, t2.account')->from(TABLE_TEAM)->alias('t1')
  2653. ->leftJoin(TABLE_USER)->alias('t2')->on('t1.account=t2.account')
  2654. ->where('t1.root')->eq($projectID)
  2655. ->andWhere('t1.type')->eq('project')
  2656. ->andWhere('date(t1.join)')->le($date)
  2657. ->andWhere('t2.deleted')->eq('0')
  2658. ->fetchPairs();
  2659. $activeUser = array_filter($teamMemberList, function($item) use ($loginUserList)
  2660. {
  2661. return in_array($item, $loginUserList);
  2662. });
  2663. $row = new stdclass();
  2664. $row->id = $projectID;
  2665. $row->name = $projectName;
  2666. $row->year = $year;
  2667. $row->month = $month;
  2668. $row->totalAccount = count($teamMemberList);
  2669. $row->activeAccount = count($activeUser);
  2670. $row->ratio = $row->totalAccount == 0 ? '0.00%' : number_format(($row->activeAccount/$row->totalAccount) * 100, 2) . '%';
  2671. $dataset[] = $row;
  2672. }
  2673. return $dataset;
  2674. }
  2675. /**
  2676. * 获取应用健康度体检报告的活跃项目数卡片。
  2677. * Get card of active project in usage report.
  2678. *
  2679. * @param string $year
  2680. * @param string $month
  2681. * @access public
  2682. * @return array
  2683. */
  2684. public function getActiveProjectCard($year, $month)
  2685. {
  2686. return $this->dao->select('count(distinct t1.project) as count')->from(TABLE_ACTION)->alias('t1')
  2687. ->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.project=t2.id')
  2688. ->where('t1.project')->ne(0)
  2689. ->andWhere('year(t1.date)')->eq($year)
  2690. ->andWhere('month(t1.date)')->eq($month)
  2691. ->andWhere('t2.type')->eq('project')
  2692. ->andWhere('t2.deleted')->eq('0')
  2693. ->andWhere("NOT FIND_IN_SET('or', t2.vision)")
  2694. ->fetchAll();
  2695. }
  2696. /**
  2697. * 获取应用健康度体检报告的活跃产品数卡片。
  2698. * Get card of active product in usage report.
  2699. *
  2700. * @param string $year
  2701. * @param string $month
  2702. * @access public
  2703. * @return array
  2704. */
  2705. public function getActiveProductCard($year, $month)
  2706. {
  2707. $noDeletedProductList = $this->dao->select('id')->from(TABLE_PRODUCT)
  2708. ->where('deleted')->eq('0')
  2709. ->andWhere('shadow')->eq(0)
  2710. ->fetchPairs();
  2711. $activeProductList = $this->dao->select('distinct product')->from(TABLE_ACTION)
  2712. ->where('product')->ne(',0,')
  2713. ->andWhere('product')->ne(',,')
  2714. ->andWhere('product')->ne(',,0,,')
  2715. ->andWhere('objectType')->notin('project,execution,task')
  2716. ->andWhere('year(date)')->eq($year)
  2717. ->andWhere('month(date)')->eq($month)
  2718. ->fetchPairs();
  2719. $activeProductCount = 0;
  2720. foreach($activeProductList as $product)
  2721. {
  2722. $productID = trim($product, ',');
  2723. if(in_array($productID, $noDeletedProductList)) $activeProductCount ++;
  2724. }
  2725. $activeProductCard = new stdclass();
  2726. $activeProductCard->count = $activeProductCount;
  2727. $activeProductCard->year = $year;
  2728. $activeProductCard->month = $month;
  2729. return array($activeProductCard);
  2730. }
  2731. /**
  2732. * 获取应用健康度体检报告的 产品测试表。
  2733. * Get table of product test summary in usage report.
  2734. *
  2735. * @param string $year
  2736. * @param string $month
  2737. * @param array $productList
  2738. * @access public
  2739. * @return array
  2740. */
  2741. public function getProductTestTable($year, $month, $productList)
  2742. {
  2743. $dataset = array();
  2744. foreach($productList as $productID => $productName)
  2745. {
  2746. $createdCaseCount = $this->dao->select('count(t2.id) as count')->from(TABLE_PRODUCT)->alias('t1')
  2747. ->leftJoin(TABLE_CASE)->alias('t2')->on('t1.id=t2.product')
  2748. ->where('t1.deleted')->eq('0')
  2749. ->andWhere('t2.deleted')->eq('0')
  2750. ->andWhere('year(t2.openedDate)')->eq($year)
  2751. ->andWhere('month(t2.openedDate)')->eq($month)
  2752. ->andWhere('t1.id')->eq($productID)
  2753. ->fetch();
  2754. $linkedBugCount = $this->dao->select('count(t3.id) as count')->from(TABLE_PRODUCT)->alias('t1')
  2755. ->leftJoin(TABLE_CASE)->alias('t2')->on('t1.id=t2.product')
  2756. ->leftJoin(TABLE_BUG)->alias('t3')->on('t2.id=t3.case')
  2757. ->where('t1.deleted')->eq('0')
  2758. ->andWhere('t2.deleted')->eq('0')
  2759. ->andWhere('t3.deleted')->eq('0')
  2760. ->andWhere('year(t2.openedDate)')->eq($year)
  2761. ->andWhere('month(t2.openedDate)')->eq($month)
  2762. ->andWhere('t1.id')->eq($productID)
  2763. ->fetch();
  2764. $createdBugCount = $this->dao->select('count(t2.id) as count')->from(TABLE_PRODUCT)->alias('t1')
  2765. ->leftJoin(TABLE_BUG)->alias('t2')->on('t1.id=t2.product')
  2766. ->where('t1.deleted')->eq('0')
  2767. ->andWhere('t2.deleted')->eq('0')
  2768. ->andWhere('year(t2.openedDate)')->eq($year)
  2769. ->andWhere('month(t2.openedDate)')->eq($month)
  2770. ->andWhere('t1.id')->eq($productID)
  2771. ->fetch();
  2772. $fixedBugList = $this->dao->select('t2.id,datediff(t2.closedDate, t2.openedDate) as fixedCycle')->from(TABLE_PRODUCT)->alias('t1')
  2773. ->leftJoin(TABLE_BUG)->alias('t2')->on('t1.id=t2.product')
  2774. ->where('t1.deleted')->eq('0')
  2775. ->andWhere('t2.deleted')->eq('0')
  2776. ->andWhere('t2.status')->eq('closed')
  2777. ->andWhere('t2.resolution')->eq('fixed')
  2778. ->andWhere('year(t2.closedDate)')->eq($year)
  2779. ->andWhere('month(t2.closedDate)')->eq($month)
  2780. ->andWhere('t1.id')->eq($productID)
  2781. ->fetchPairs();
  2782. $row = new stdclass();
  2783. $row->id = $productID;
  2784. $row->name = $productName;
  2785. $row->year = $year;
  2786. $row->month = $month;
  2787. $row->createdCases = $createdCaseCount->count;
  2788. $row->avgBugsOfCase = $createdCaseCount->count == 0 ? 0 : round($linkedBugCount->count/$createdCaseCount->count, 2);
  2789. $row->createdBugs = $createdBugCount->count;
  2790. $row->fixedBugs = count($fixedBugList);
  2791. $row->avgFixedCycle = count($fixedBugList) == 0 ? 0 : round(array_sum($fixedBugList)/count($fixedBugList), 2);
  2792. if($row->createdCases === 0 && $row->avgBugsOfCase === 0 && $row->createdBugs === 0 && $row->fixedBugs === 0 && $row->avgFixedCycle === 0) continue;
  2793. $dataset[] = $row;
  2794. }
  2795. return $dataset;
  2796. }
  2797. /**
  2798. * 获取应用健康度体检报告的项目任务概况表。
  2799. * Get table of project task summary in usage report.
  2800. *
  2801. * @param string $year
  2802. * @param string $month
  2803. * @param array $projectList
  2804. * @access public
  2805. * @return array
  2806. */
  2807. public function getProjectTaskTable($year, $month, $projectList)
  2808. {
  2809. $deletedExecutionList = $this->dao->select('id')->from(TABLE_EXECUTION)
  2810. ->where('deleted')->eq('1')
  2811. ->andWhere('type')->in('sprint,stage,kanban')
  2812. ->fetchPairs();
  2813. $dataset = array();
  2814. foreach($projectList as $projectID => $projectName)
  2815. {
  2816. $createdTaskList = $this->dao->select('id,openedBy')->from(TABLE_TASK)
  2817. ->where('project')->eq($projectID)
  2818. ->andWhere('year(openedDate)')->eq($year)
  2819. ->andWhere('month(openedDate)')->eq($month)
  2820. ->andWhere('deleted')->eq('0')
  2821. ->andWhere("NOT FIND_IN_SET('or', vision)")
  2822. ->andWhere('execution')->notin($deletedExecutionList)
  2823. ->fetchPairs();
  2824. $finishedTaskList = $this->dao->select('id')->from(TABLE_TASK)
  2825. ->where('project')->eq($projectID)
  2826. ->andWhere('year(finishedDate)')->eq($year)
  2827. ->andWhere('month(finishedDate)')->eq($month)
  2828. ->andWhere('deleted')->eq('0')
  2829. ->andWhere("NOT FIND_IN_SET('or', vision)")
  2830. ->andWhere('execution')->notin($deletedExecutionList)
  2831. ->fetchPairs();
  2832. $row = new stdclass();
  2833. $row->name = $projectName;
  2834. $row->year = $year;
  2835. $row->month = $month;
  2836. $row->createdTasks = count(array_unique(array_keys($createdTaskList)));
  2837. $row->finishedTasks = count(array_unique($finishedTaskList));
  2838. $row->contributors = count(array_unique(array_values($createdTaskList)));
  2839. if($row->createdTasks === 0 && $row->finishedTasks === 0 && $row->contributors === 0) continue;
  2840. $dataset[] = $row;
  2841. }
  2842. return $dataset;
  2843. }
  2844. /**
  2845. * 获取应用健康度体检报告的产品需求概况表。
  2846. * Get table of product story summary in usage report.
  2847. *
  2848. * @param string $year
  2849. * @param string $month
  2850. * @param array $productList
  2851. * @access public
  2852. * @return array
  2853. */
  2854. public function getProductStoryTable($year, $month, $productList)
  2855. {
  2856. $releasedStories = $this->dao->select('t2.id, t1.id as product')->from(TABLE_PRODUCT)->alias('t1')
  2857. ->leftJoin(TABLE_STORY)->alias('t2')->on('t1.id=t2.product')
  2858. ->leftJoin(TABLE_ACTION)->alias('t3')->on('t2.id=t3.objectID')
  2859. ->where('t1.deleted')->eq('0')
  2860. ->andWhere('t2.deleted')->eq('0')
  2861. ->andWhere('t2.type')->eq('story')
  2862. ->andWhere("NOT FIND_IN_SET('or', t2.vision)")
  2863. ->andWhere('t2.stage')->eq('released')
  2864. ->andWhere('t3.objectType')->eq('story')
  2865. ->andWhere('t3.action')->eq('linked2release')
  2866. ->andWhere('year(t3.date)')->eq($year)
  2867. ->andWhere('month(t3.date)')->eq($month)
  2868. ->fetchPairs();
  2869. $releasedStoryGroups = array();
  2870. foreach($releasedStories as $storyID => $productID)
  2871. {
  2872. if(!isset($releasedStoryGroups[$productID])) $releasedStoryGroups[$productID] = array();
  2873. $releasedStoryGroups[$productID][] = $storyID;
  2874. }
  2875. $dataset = array();
  2876. foreach($productList as $productID => $productName)
  2877. {
  2878. $createdStoryCount = $this->dao->select('count(t2.id) as count')->from(TABLE_PRODUCT)->alias('t1')
  2879. ->leftJoin(TABLE_STORY)->alias('t2')->on('t1.id=t2.product')
  2880. ->where('t1.deleted')->eq('0')
  2881. ->andWhere('t2.deleted')->eq('0')
  2882. ->andWhere('t2.type')->eq('story')
  2883. ->andWhere("NOT FIND_IN_SET('or', t2.vision)")
  2884. ->andWhere('t1.id')->eq($productID)
  2885. ->andWhere('year(t2.openedDate)')->eq($year)
  2886. ->andWhere('month(t2.openedDate)')->eq($month)
  2887. ->fetch();
  2888. $finishedStoryList = $this->dao->select('t2.id')->from(TABLE_PRODUCT)->alias('t1')
  2889. ->leftJoin(TABLE_STORY)->alias('t2')->on('t1.id=t2.product')
  2890. ->where('t1.deleted')->eq('0')
  2891. ->andWhere('t2.deleted')->eq('0')
  2892. ->andWhere('t2.type')->eq('story')
  2893. ->andWhere("NOT FIND_IN_SET('or', t2.vision)")
  2894. ->andWhere('t1.id')->eq($productID)
  2895. ->andWhere('t2.closedReason')->eq('done')
  2896. ->andWhere('year(t2.closedDate)')->eq($year)
  2897. ->andWhere('month(t2.closedDate)')->eq($month)
  2898. ->fetchPairs();
  2899. $releasedStoryList = isset($releasedStoryGroups[$productID]) ? $releasedStoryGroups[$productID] : array();
  2900. $deliveredStoryCount = count(array_merge($finishedStoryList, (array)$releasedStoryList));
  2901. $row = new stdclass();
  2902. $row->id = $productID;
  2903. $row->name = $productName;
  2904. $row->createdStories = $createdStoryCount->count;
  2905. $row->deliveredStories = $deliveredStoryCount;
  2906. if($row->createdStories === 0 && $row->deliveredStories === 0) continue;
  2907. $dataset[] = $row;
  2908. }
  2909. return $dataset;
  2910. }
  2911. /**
  2912. * 获取应用健康度体检报告的项目需求概况表。
  2913. * Get table of project story summary in usage report.
  2914. *
  2915. * @param string $year
  2916. * @param string $month
  2917. * @param array $projectList
  2918. * @access public
  2919. * @return array
  2920. */
  2921. public function getProjectStoryTable($year, $month, $projectList)
  2922. {
  2923. $releasedStories = $this->dao->select('t3.id,t1.id as projectID')->from(TABLE_PROJECT)->alias('t1')
  2924. ->leftJoin(TABLE_PROJECTSTORY)->alias('t2')->on('t1.id=t2.project')
  2925. ->leftJoin(TABLE_STORY)->alias('t3')->on('t2.story=t3.id')
  2926. ->leftJoin(TABLE_ACTION)->alias('t4')->on('t3.id=t4.objectID')
  2927. ->where('t1.deleted')->eq('0')
  2928. ->andWhere('t1.type')->eq('project')
  2929. ->andWhere('t3.deleted')->eq('0')
  2930. ->andWhere('t3.type')->eq('story')
  2931. ->andWhere("NOT FIND_IN_SET('or', t1.vision)")
  2932. ->andWhere("NOT FIND_IN_SET('or', t3.vision)")
  2933. ->andWhere('t3.stage')->eq('released')
  2934. ->andWhere('t4.objectType')->eq('story')
  2935. ->andWhere('t4.action')->eq('linked2release')
  2936. ->andWhere('year(t4.date)')->eq($year)
  2937. ->andWhere('month(t4.date)')->eq($month)
  2938. ->fetchPairs();
  2939. $releasedStoryGroups = array();
  2940. foreach($releasedStories as $storyID => $projectID)
  2941. {
  2942. if(!isset($releasedStoryGroups[$projectID])) $releasedStoryGroups[$projectID] = array();
  2943. $releasedStoryGroups[$projectID][] = $storyID;
  2944. }
  2945. $dataset = array();
  2946. foreach($projectList as $projectID => $projectName)
  2947. {
  2948. $createdStoryCount = $this->dao->select('count(t3.id) as count')->from(TABLE_PROJECT)->alias('t1')
  2949. ->leftJoin(TABLE_PROJECTSTORY)->alias('t2')->on('t1.id=t2.project')
  2950. ->leftJoin(TABLE_STORY)->alias('t3')->on('t2.story=t3.id')
  2951. ->where('t1.deleted')->eq('0')
  2952. ->andWhere('t1.type')->eq('project')
  2953. ->andWhere('t3.type')->eq('story')
  2954. ->andWhere('t3.deleted')->eq('0')
  2955. ->andWhere("NOT FIND_IN_SET('or', t1.vision)")
  2956. ->andWhere("NOT FIND_IN_SET('or', t3.vision)")
  2957. ->andWhere('t1.type')->eq('project')
  2958. ->andWhere('t3.type')->eq('story')
  2959. ->andWhere('t1.id')->eq($projectID)
  2960. ->andWhere('year(t3.openedDate)')->eq($year)
  2961. ->andWhere('month(t3.openedDate)')->eq($month)
  2962. ->fetch();
  2963. $finishedStoryList = $this->dao->select('t3.id')->from(TABLE_PROJECT)->alias('t1')
  2964. ->leftJoin(TABLE_PROJECTSTORY)->alias('t2')->on('t1.id=t2.project')
  2965. ->leftJoin(TABLE_STORY)->alias('t3')->on('t2.story=t3.id')
  2966. ->where('t1.deleted')->eq('0')
  2967. ->andWhere('t1.type')->eq('project')
  2968. ->andWhere('t3.deleted')->eq('0')
  2969. ->andWhere('t3.type')->eq('story')
  2970. ->andWhere("NOT FIND_IN_SET('or', t1.vision)")
  2971. ->andWhere("NOT FIND_IN_SET('or', t3.vision)")
  2972. ->andWhere('t1.id')->eq($projectID)
  2973. ->andWhere('t3.closedReason')->eq('done')
  2974. ->andWhere('year(t3.closedDate)')->eq($year)
  2975. ->andWhere('month(t3.closedDate)')->eq($month)
  2976. ->fetchPairs();
  2977. $releasedStoryList = isset($releasedStoryGroups[$projectID]) ? $releasedStoryGroups[$projectID] : array();
  2978. $deliveredStoryCount = count(array_merge($finishedStoryList, (array)$releasedStoryList));
  2979. $row = new stdclass();
  2980. $row->id = $projectID;
  2981. $row->name = $projectName;
  2982. $row->createdStories = $createdStoryCount->count;
  2983. $row->deliveredStories = $deliveredStoryCount;
  2984. if($row->createdStories === 0 && $row->deliveredStories === 0) continue;
  2985. $dataset[] = $row;
  2986. }
  2987. return $dataset;
  2988. }
  2989. /**
  2990. * 获取应用健康度体检报告的项目列表。
  2991. * Get project list for usage report.
  2992. *
  2993. * @param string $year
  2994. * @param string $month
  2995. * @access public
  2996. * @return array
  2997. */
  2998. public function getUsageReportProjects($year, $month)
  2999. {
  3000. $date = date("Y-m-t", strtotime("$year-$month"));
  3001. return $this->dao->select('id,name')->from(TABLE_PROJECT)
  3002. ->where('type')->eq('project')
  3003. ->andWhere('deleted')->eq('0')
  3004. ->andWhere('date(openedDate)')->le($date)
  3005. ->andWhere("NOT FIND_IN_SET('or', vision)")
  3006. ->andWhere('date(closedDate)', true)->gt($date)
  3007. ->orWhere('date(closedDate)')->eq('0000-00-00')
  3008. ->orWhere('closedDate')->in(NULL)
  3009. ->markRight(true)
  3010. ->fetchPairs();
  3011. }
  3012. /**
  3013. * 获取应用健康度体检报告的产品列表。
  3014. * Get product list for usage report.
  3015. *
  3016. * @param string $year
  3017. * @param string $month
  3018. * @access public
  3019. * @return array
  3020. */
  3021. public function getUsageReportProducts($year, $month)
  3022. {
  3023. $date = date("Y-m-t", strtotime("$year-$month"));
  3024. return $this->dao->select('id,name')->from(TABLE_PRODUCT)
  3025. ->where('deleted')->eq('0')
  3026. ->andWhere('shadow')->eq(0)
  3027. ->andWhere('date(createdDate)')->le($date)
  3028. ->fetchPairs();
  3029. }
  3030. /**
  3031. * Get screen thumbnail.
  3032. *
  3033. * @param array $screens
  3034. * @access public
  3035. * @return array
  3036. */
  3037. public function getThumbnail($screens)
  3038. {
  3039. $screenIds = array_column($screens, 'id');
  3040. $images = $this->loadModel('file')->getByObject('screen', $screenIds);
  3041. foreach($screens as $screen)
  3042. {
  3043. $currentImages = array_filter($images, function($image) use ($screen)
  3044. {
  3045. return $image->objectID == $screen->id;
  3046. });
  3047. if(empty($currentImages)) continue;
  3048. $image = end($currentImages);
  3049. $screen->cover = helper::createLink('file', 'read', "fileID={$image->id}", 'png');
  3050. }
  3051. return $screens;
  3052. }
  3053. /**
  3054. * Remove scheme field of screen.
  3055. *
  3056. * @param array $screens
  3057. * @access public
  3058. * @return array
  3059. */
  3060. public function removeScheme($screens)
  3061. {
  3062. foreach($screens as $screen) unset($screen->scheme);
  3063. return $screens;
  3064. }
  3065. }