model.php 162 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026
  1. <?php
  2. /**
  3. * The model file of common 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 Chunsheng Wang <chunsheng@cnezsoft.com>
  8. * @package common
  9. * @version $Id$
  10. * @link https://www.zentao.net
  11. * @property router $app
  12. */
  13. class commonModel extends model
  14. {
  15. /**
  16. * 网络请求客户端。
  17. * HTTP client.
  18. *
  19. * @var object
  20. * @access public
  21. */
  22. public static $httpClient;
  23. /**
  24. * 网络请求错误。
  25. * Request errors.
  26. *
  27. * @var array
  28. * @access public
  29. */
  30. public static $requestErrors = array();
  31. /**
  32. * 缓存用户是否有某个模块、方法的访问权限。
  33. * Cache the user's access rights to a module or method.
  34. *
  35. * @var array
  36. * @access public
  37. */
  38. public static $userPrivs = array();
  39. /**
  40. * 设置用户配置信息。
  41. * Set config of user.
  42. *
  43. * @access public
  44. * @return void
  45. */
  46. public function setUserConfig()
  47. {
  48. $this->sendHeader();
  49. $this->setCompany();
  50. $this->setUser();
  51. $this->setApproval();
  52. $this->loadConfigFromDB();
  53. $this->loadCustomFromDB();
  54. $this->initAuthorize();
  55. if(!$this->checkIP()) return print($this->lang->ipLimited);
  56. }
  57. /**
  58. * 同步执行、项目、项目集的状态。
  59. * Set the status of execution, project, and program to doing.
  60. *
  61. * @param int $objectID
  62. * @access public
  63. * @return void
  64. */
  65. public function syncPPEStatus($objectID)
  66. {
  67. global $app;
  68. $rawModule = $app->rawModule;
  69. $rawMethod = strtolower($app->rawMethod);
  70. if($rawModule == 'marketresearch' and strpos($rawMethod, 'stage') !== false) $rawModule = 'execution';
  71. if($rawModule == 'task' || $rawModule == 'effort' || $rawModule == 'researchtask')
  72. {
  73. $taskID = $objectID;
  74. $execution = $this->syncExecutionStatus($taskID);
  75. $project = $this->syncProjectStatus($execution);
  76. $this->syncProgramStatus($project);
  77. }
  78. if($rawModule == 'execution')
  79. {
  80. $executionID = $objectID;
  81. $execution = $this->dao->select('id, project, grade, parent, status, deleted')->from(TABLE_EXECUTION)->where('id')->eq($executionID)->fetch();
  82. $this->syncExecutionByChild($execution);
  83. $project = $this->syncProjectStatus($execution);
  84. $this->syncProgramStatus($project);
  85. }
  86. if($rawModule == 'project')
  87. {
  88. $projectID = $objectID;
  89. $project = $this->dao->select('id, parent, path')->from(TABLE_PROJECT)->where('id')->eq($projectID)->fetch();
  90. $this->syncProgramStatus($project);
  91. }
  92. if($rawModule == 'program' and $this->config->systemMode == 'ALM')
  93. {
  94. $programID = $objectID;
  95. $program = $this->dao->select('id, parent, path')->from(TABLE_PROGRAM)->where('id')->eq($programID)->fetch();
  96. $this->syncProgramStatus($program);
  97. }
  98. }
  99. /**
  100. * 项目开始时,设置项目所属的项目集的状态为进行中。
  101. * Set the status of the program to which theproject is linked as Ongoing.
  102. *
  103. * @param object $project
  104. * @access public
  105. * @return void
  106. */
  107. public function syncProgramStatus($project)
  108. {
  109. if($project->parent == 0) return;
  110. $parentPath = str_replace(",{$project->id},", '', $project->path);
  111. $parentPath = explode(',', trim($parentPath, ','));
  112. $waitList = $this->dao->select('id')->from(TABLE_PROGRAM)
  113. ->where('id')->in($parentPath)
  114. ->andWhere('status')->eq('wait')
  115. ->orderBy('id_desc')
  116. ->fetchPairs();
  117. $this->dao->update(TABLE_PROGRAM)->set('status')->eq('doing')->set('realBegan')->eq(helper::now())->where('id')->in($waitList)->exec();
  118. $this->loadModel('action');
  119. foreach($waitList as $programID)
  120. {
  121. $this->action->create('program', $programID, 'syncprogram');
  122. }
  123. }
  124. /**
  125. * 执行开始时,设置执行所属的项目和项目所属的项目集的状态为进行中。
  126. * Set the status of the project to which the execution is linked as Ongoing.
  127. *
  128. * @param object $execution
  129. * @access public
  130. * @return object $project
  131. */
  132. public function syncProjectStatus($execution)
  133. {
  134. $projectID = $execution->project;
  135. $project = $this->dao->select('*')->from(TABLE_PROJECT)->where('id')->eq($projectID)->fetch();
  136. $today = helper::today();
  137. if($project->status == 'wait')
  138. {
  139. $realBegan = helper::isZeroDate($project->realBegan) ? $this->dao->select('realBegan')->from(TABLE_PROJECT)->where('id')->eq($execution->id)->fetch('realBegan') : $project->realBegan;
  140. $this->dao->update(TABLE_PROJECT)
  141. ->set('status')->eq('doing')
  142. ->set('realBegan')->eq($realBegan)
  143. ->where('id')->eq($projectID)
  144. ->exec();
  145. $this->loadModel('project')->recordFirstEnd($projectID);
  146. $actionType = $project->multiple ? 'syncproject' : 'syncmultipleproject';
  147. $this->loadModel('action')->create('project', $projectID, $actionType);
  148. }
  149. return $project;
  150. }
  151. /**
  152. * 子阶段开始时,设置子阶段所属的父阶段和项目的状态为进行中。
  153. * Set the status of the execution to which the sub execution is linked as Ongoing.
  154. *
  155. * @param object $execution
  156. * @access public
  157. * @return object|false $parentExecution
  158. */
  159. public function syncExecutionByChild($execution)
  160. {
  161. if($execution->grade == 1) return false;
  162. $today = helper::today();
  163. $parentExecution = $this->dao->select('*')->from(TABLE_EXECUTION)->where('id')->eq($execution->parent)->fetch();
  164. if($execution->deleted == '0' and $execution->status == 'doing' and in_array($parentExecution->status, array('wait', 'closed')))
  165. {
  166. $this->dao->update(TABLE_EXECUTION)
  167. ->set('status')->eq('doing')
  168. ->beginIf(helper::isZeroDate($parentExecution->realBegan))->set('realBegan')->eq($today)->fi()
  169. ->where('id')->eq($execution->parent)
  170. ->exec();
  171. $this->loadModel('action')->create('execution', $execution->parent, 'syncexecutionbychild');
  172. }
  173. $project = $this->loadModel('project')->getByID($execution->project);
  174. if(strpos(',waterfall,waterfallplus,ipd,research,', ",$project->model,") !== false) $this->loadModel('programplan')->computeProgress($execution->id);
  175. return $parentExecution;
  176. }
  177. /**
  178. * 任务开始时,设置任务所属的执行、项目和项目所属的项目集的状态为进行中。
  179. * Set the status of the execution to which the task is linked as Ongoing.
  180. *
  181. * @param int $taskID
  182. * @access public
  183. * @return object $execution
  184. */
  185. public function syncExecutionStatus($taskID)
  186. {
  187. $execution = $this->dao->select('t1.*')->from(TABLE_EXECUTION)->alias('t1')
  188. ->leftJoin(TABLE_TASK)->alias('t2')->on('t1.id=t2.execution')
  189. ->where('t2.id')->eq($taskID)
  190. ->fetch();
  191. $today = helper::today();
  192. if($execution->status == 'wait')
  193. {
  194. $this->dao->update(TABLE_EXECUTION)->set('status')->eq('doing')->set('realBegan')->eq($today)->where('id')->eq($execution->id)->exec();
  195. $this->loadModel('project')->recordFirstEnd($execution->id);
  196. $this->loadModel('action')->create('execution', $execution->id, 'syncexecution');
  197. if($execution->parent)
  198. {
  199. $execution = $this->dao->select('*')->from(TABLE_EXECUTION)->where('id')->eq($execution->id)->fetch(); // Get updated execution.
  200. $this->syncExecutionByChild($execution);
  201. }
  202. }
  203. return $execution;
  204. }
  205. /**
  206. * 设置HTTP标头。
  207. * Set the header info.
  208. *
  209. * @access public
  210. * @return void
  211. */
  212. public function sendHeader()
  213. {
  214. helper::header('Content-Type', "text/html; Language={$this->config->charset}");
  215. helper::header('Cache-Control', 'private');
  216. /* Send HTTP header. */
  217. if($this->config->framework->sendXCTO) helper::header('X-Content-Type-Options', 'nosniff');
  218. if($this->config->framework->sendXXP) helper::header('X-XSS-Protection', '1; mode=block');
  219. if($this->config->framework->sendHSTS) helper::header('Strict-Transport-Security', 'max-age=3600; includeSubDomains');
  220. if($this->config->framework->sendRP) helper::header('Referrer-Policy', 'no-referrer-when-downgrade');
  221. if($this->config->framework->sendXPCDP) helper::header('X-Permitted-Cross-Domain-Policies', 'master-only');
  222. if($this->config->framework->sendXDO) helper::header('X-Download-Options', 'noopen');
  223. /* Set Content-Security-Policy header. */
  224. if($this->config->CSPs)
  225. {
  226. foreach($this->config->CSPs as $CSP) helper::header('Content-Security-Policy', "$CSP;");
  227. }
  228. if(!empty($this->config->xFrameOptions)) helper::header('X-Frame-Options', $this->config->xFrameOptions);
  229. if($this->loadModel('setting')->getItem('owner=system&module=sso&key=turnon'))
  230. {
  231. if(isset($_SERVER["HTTPS"]) and $_SERVER["HTTPS"] == 'on')
  232. {
  233. $session = $this->config->sessionVar . '=' . session_id();
  234. helper::header('Set-Cookie', "$session; SameSite=None; Secure=true", false);
  235. }
  236. }
  237. }
  238. /**
  239. * 设置公司信息。
  240. * Set the company.
  241. *
  242. * First, search company by the http host. If not found, search by the default domain. Last, use the first as the default.
  243. * After get the company, save it to session.
  244. * @access public
  245. * @return void
  246. */
  247. public function setCompany()
  248. {
  249. if($this->session->company)
  250. {
  251. $this->app->company = $this->session->company;
  252. }
  253. else
  254. {
  255. $httpHost = $this->server->http_host;
  256. $company = $this->loadModel('company')->getFirst();
  257. if(!$company) $this->app->triggerError(sprintf($this->lang->error->companyNotFound, $httpHost), __FILE__, __LINE__, true);
  258. $this->session->set('company', $company);
  259. $this->app->company = $company;
  260. }
  261. }
  262. /**
  263. * 设置用户信息。
  264. * Set the user info.
  265. *
  266. * @access public
  267. * @return void
  268. */
  269. public function setUser()
  270. {
  271. if($this->session->user)
  272. {
  273. $this->app->user = $this->session->user;
  274. }
  275. elseif($this->app->company->guest || (PHP_SAPI == 'cli' && (!isset($_SERVER['RR_MODE']) || $_SERVER['RR_MODE'] == 'jobs')))
  276. {
  277. $user = new stdClass();
  278. $user->id = 0;
  279. $user->account = 'guest';
  280. $user->realname = 'guest';
  281. $user->dept = 0;
  282. $user->avatar = '';
  283. $user->role = 'guest';
  284. $user->admin = false;
  285. $user->groups = array('group');
  286. $user->visions = $this->config->vision;
  287. $this->session->set('user', $user);
  288. $this->app->user = $this->session->user;
  289. }
  290. }
  291. /**
  292. * 初始化用户权限和视图。
  293. * Init user authorize.
  294. *
  295. * @access private
  296. * @return void
  297. */
  298. private function initAuthorize()
  299. {
  300. if(isset($this->app->user))
  301. {
  302. $user = $this->app->user;
  303. if(!$this->app->upgrading) $user->rights = $this->loadModel('user')->authorize($user->account);
  304. if(!$this->app->upgrading) $user->view = $this->user->grantUserView($user->account, $user->rights['acls']);
  305. $this->session->set('user', $user);
  306. $this->app->user = $this->session->user;
  307. }
  308. }
  309. /**
  310. * 设置审批配置。
  311. * Set approval config.
  312. *
  313. * @access public
  314. * @return void
  315. */
  316. public function setApproval()
  317. {
  318. $this->config->openedApproval = in_array($this->config->edition, array('biz', 'max', 'ipd')) && $this->config->vision != 'lite';
  319. }
  320. /**
  321. * 获取表单的配置项。
  322. * Obtain the config for the form.
  323. *
  324. * @param string $module
  325. * @param string $method
  326. * @param int $objectID
  327. * @static
  328. * @access public
  329. * @return array
  330. */
  331. public static function formConfig($module, $method, $objectID = 0)
  332. {
  333. global $config, $app;
  334. if($config->edition == 'open') return array();
  335. $formConfig = array();
  336. $common = new self();
  337. $required = $common->dao->select('*')->from(TABLE_WORKFLOWRULE)->where('type')->eq('system')->andWhere('rule')->eq('notempty')->fetch();
  338. $fields = $common->loadModel('flow')->getExtendFields($module, $method, $objectID);
  339. $type = 'string';
  340. foreach($fields as $fieldObject)
  341. {
  342. if(strpos($fieldObject->type, 'int') !== false) $type = 'int';
  343. if(strpos($fieldObject->type, 'date') !== false) $type = 'date';
  344. if(in_array($fieldObject->type, array('float', 'decimal'))) $type = 'float';
  345. $formConfig[$fieldObject->field] = array('type' => $type, 'default' => $fieldObject->default, 'control' => $fieldObject->control, 'rules' => $fieldObject->rules);
  346. $formConfig[$fieldObject->field]['required'] = strpos(",{$fieldObject->rules},", ",{$required->id},") !== false;
  347. if(in_array($fieldObject->control, array('multi-select', 'checkbox'))) $formConfig[$fieldObject->field]['filter'] = 'join';
  348. }
  349. return $formConfig;
  350. }
  351. /**
  352. * 从数据库加载配置信息。
  353. * Load configs from database and save it to config->system and config->personal.
  354. *
  355. * @access public
  356. * @return void
  357. */
  358. public function loadConfigFromDB()
  359. {
  360. /* Get configs of system and current user. */
  361. $account = isset($this->app->user->account) ? $this->app->user->account : '';
  362. if($this->config->db->name) $config = $this->loadModel('setting')->getSysAndPersonalConfig($account);
  363. $this->config->systemDB = isset($config['system']) ? $config['system'] : array();
  364. $this->config->personalDB = isset($config[$account]) ? $config[$account] : array();
  365. /* Web root cannot be changed by api request. */
  366. if(!$this->app->apiVersion)
  367. {
  368. $this->commonTao->updateDBWebRoot($this->config->systemDB);
  369. }
  370. /* Override the items defined in config/config.php and config/my.php. */
  371. if(isset($this->config->systemDB->common)) $this->app->mergeConfig($this->config->systemDB->common, 'common');
  372. if(isset($this->config->personalDB->common)) $this->app->mergeConfig($this->config->personalDB->common, 'common');
  373. $this->config->disabledFeatures = $this->config->disabledFeatures . ',' . $this->config->closedFeatures;
  374. /* Sanplex hide features. */
  375. $this->app->loadConfig('common');
  376. if(!empty($this->config->hiddenFeature)) $this->config->disabledFeatures .= ',' . implode(',', $this->config->hiddenFeature);
  377. }
  378. /**
  379. * 从数据库加载自定义信息。
  380. * Load custom lang from db.
  381. *
  382. * @access public
  383. * @return void
  384. */
  385. public function loadCustomFromDB()
  386. {
  387. $this->loadModel('custom');
  388. if($this->app->upgrading) return;
  389. if(!$this->config->db->name) return;
  390. $records = $this->custom->getAllLang();
  391. if(!$records) return;
  392. $this->lang->db = new stdclass();
  393. $this->lang->db->custom = $records;
  394. }
  395. /**
  396. * 判断哪些方法不需要鉴权。
  397. * Judge a method of one module is open or not.
  398. *
  399. * @param string $module
  400. * @param string $method
  401. * @access public
  402. * @return bool
  403. */
  404. public function isOpenMethod($module, $method)
  405. {
  406. if(in_array("$module.$method", $this->config->openMethods)) return true;
  407. if($this->loadModel('user')->isLogon() or ($this->app->company->guest and $this->app->user->account == 'guest'))
  408. {
  409. if(in_array("$module.$method", $this->config->logonMethods)) return true;
  410. if(stripos($method, 'ajax') !== false) return true;
  411. if($module == 'block' && stripos(',dashboard,printblock,create,edit,delete,close,reset,layout,', ",{$method},") !== false) return true;
  412. if($module == 'index' and $method == 'app') return true;
  413. if($module == 'my' and $method == 'guidechangetheme') return true;
  414. if($module == 'product' and $method == 'showerrornone') return true;
  415. if($module == 'misc' and in_array($method, array('downloadclient', 'changelog'))) return true;
  416. if($module == 'tutorial' and in_array($method, array('start', 'index', 'quit', 'wizard'))) return true;
  417. }
  418. return false;
  419. }
  420. /**
  421. * 拒绝访问的页面。
  422. * Deny access.
  423. *
  424. * @param string $module
  425. * @param string $method
  426. * @param bool $reload
  427. * @access public
  428. * @return mixed
  429. */
  430. public function deny($module, $method, $reload = true)
  431. {
  432. if($reload && $this->loadModel('user')->isLogon())
  433. {
  434. /* Get authorize again. */
  435. $user = $this->app->user;
  436. $user->rights = $this->user->authorize($user->account);
  437. $user->groups = $this->user->getGroups($user->account);
  438. $user->admin = strpos($this->app->company->admins, ",{$user->account},") !== false;
  439. $this->session->set('user', $user);
  440. $this->app->user = $this->session->user;
  441. if(commonModel::hasPriv($module, $method)) return true;
  442. }
  443. if($this->app->apiVersion)
  444. {
  445. header('HTTP/1.1 403 Forbidden', true, 403);
  446. header('Content-Type: application/json');
  447. helper::end(json_encode(['status' => 'error', 'message' => 'Not allowed']));
  448. }
  449. $vars = "module=$module&method=$method";
  450. if(isset($this->server->http_referer))
  451. {
  452. $referer = helper::safe64Encode($this->server->http_referer);
  453. $vars .= "&referer=$referer";
  454. }
  455. $denyLink = helper::createLink('user', 'deny', $vars);
  456. if(helper::isAjaxRequest())
  457. {
  458. $isModal = helper::isAjaxRequest('modal');
  459. if($isModal) header("Location: $denyLink");
  460. if(!$isModal) echo json_encode(array('load' => $denyLink));
  461. }
  462. else
  463. {
  464. header("Location: $denyLink");
  465. }
  466. helper::end();
  467. }
  468. /**
  469. * 格式化日期,将日期格式化为YYYY-mm-dd,将日期时间格式化为YYYY-mm-dd HH:ii:ss。
  470. * Format the date to YYYY-mm-dd, format the datetime to YYYY-mm-dd HH:ii:ss.
  471. *
  472. * @param string $date
  473. * @param string $type date|datetime|''
  474. * @access public
  475. * @return string
  476. */
  477. public function formatDate($date, $type = '')
  478. {
  479. if(helper::isZeroDate($date))
  480. {
  481. if($type == 'date') return '0000-00-00';
  482. if($type == 'datetime') return '0000-00-00 00:00:00';
  483. }
  484. $datePattern = '\w{4}(\/|-)\w{1,2}(\/|-)\w{1,2}';
  485. $datetimePattern = $datePattern . ' \w{1,2}\:\w{1,2}\:\w{1,2}';
  486. if(empty($type))
  487. {
  488. if(!preg_match("/$datePattern/", $date) and !preg_match("/$datetimePattern/", $date)) return $date;
  489. if(preg_match("/$datePattern/", $date) === 1) $type = 'date';
  490. if(preg_match("/$datetimePattern/", $date) === 1) $type = 'datetime';
  491. }
  492. if($type == 'date') $format = 'Y-m-d';
  493. if($type == 'datetime') $format = 'Y-m-d H:i:s';
  494. return date($format, strtotime($date));
  495. }
  496. /**
  497. * 检查字符串是否以给定的子字符串结尾。
  498. * Checks if a string ends with a given substring.
  499. *
  500. * @param string $haystack
  501. * @param string $needle
  502. * @return bool
  503. */
  504. public static function strEndsWith($haystack, $needle)
  505. {
  506. $length = strlen($needle);
  507. if ($length === 0) return true;
  508. $position = strpos($haystack, $needle);
  509. return $position !== false && $position === strlen($haystack) - $length;
  510. }
  511. /**
  512. * 创建菜单项链接。
  513. * Create menu item link
  514. *
  515. * @param object $menuItem
  516. *
  517. * @static
  518. * @access public
  519. * @return string
  520. */
  521. public static function createMenuLink($menuItem)
  522. {
  523. $link = $menuItem->link;
  524. if(is_array($menuItem->link))
  525. {
  526. $vars = isset($menuItem->link['vars']) ? $menuItem->link['vars'] : '';
  527. if(isset($menuItem->tutorial) and $menuItem->tutorial)
  528. {
  529. if(!empty($vars)) $vars = helper::safe64Encode($vars);
  530. $link = helper::createLink('tutorial', 'wizard', "module={$menuItem->link['module']}&method={$menuItem->link['method']}&params=$vars");
  531. }
  532. else
  533. {
  534. $link = helper::createLink($menuItem->link['module'], $menuItem->link['method'], $vars);
  535. }
  536. }
  537. return $link;
  538. }
  539. /**
  540. * 获取左侧一级导航。
  541. * Get main nav items list
  542. *
  543. * @param string $moduleName
  544. * @param bool $useDefault 是否使用语言项中的默认值
  545. *
  546. * @static
  547. * @access public
  548. * @return array
  549. */
  550. public static function getMainNavList($moduleName, $useDefault = false)
  551. {
  552. global $lang, $app, $config;
  553. $app->loadLang('my');
  554. if($config->edition != 'open' && $app->isServing()) $app->control->loadModel('common')->mergePrimaryFlows();
  555. /* Ensure user has latest rights set. */
  556. $app->user->rights = $app->control->loadModel('user')->authorize($app->user->account);
  557. $menuOrder = $lang->mainNav->menuOrder;
  558. $hasCustomMenu = false;
  559. if(isset($config->customMenu->nav) && !$useDefault && !commonModel::isTutorialMode())
  560. {
  561. $customMenuOrder = array();
  562. $items = json_decode($config->customMenu->nav);
  563. $hiddenItems = array();
  564. foreach($items as $item)
  565. {
  566. if(!empty($item->hidden))
  567. {
  568. $hiddenItems[] = $item->name;
  569. continue;
  570. }
  571. $customMenuOrder[$item->order] = $item->name;
  572. }
  573. $customMenuItems = array_values($customMenuOrder);
  574. foreach($menuOrder as $order => $name)
  575. {
  576. if(in_array($name, $customMenuItems) || in_array($name, $hiddenItems)) continue;
  577. while(isset($customMenuOrder[$order])) $order ++;
  578. $customMenuOrder[$order] = $name;
  579. }
  580. $menuOrder = $customMenuOrder;
  581. $hasCustomMenu = true;
  582. }
  583. ksort($menuOrder);
  584. $items = array();
  585. $lastItem = end($menuOrder);
  586. $printDivider = false;
  587. $prev = '';
  588. foreach($menuOrder as $key => $group)
  589. {
  590. // 如果有自定义菜单,则直接用自定义后的divider分隔符
  591. if($hasCustomMenu && $group == 'divider' && $prev != 'divider')
  592. {
  593. $items[] = 'divider';
  594. $prev = 'divider';
  595. continue;
  596. }
  597. $prev = $group;
  598. if($group != 'my' && !$app->user->admin && !empty($app->user->rights['acls']['views']) && !isset($app->user->rights['acls']['views'][$group])) continue; // 后台权限分组中没有给导航视图
  599. if(!isset($lang->mainNav->$group)) continue;
  600. $nav = $lang->mainNav->$group;
  601. list($title, $currentModule, $currentMethod, $vars) = explode('|', $nav);
  602. // 没有自定义过菜单,用默认语言项中的divider分隔符
  603. if(!$hasCustomMenu)
  604. {
  605. /* When last divider is not used in mainNav, use it next menu. */
  606. $printDivider = ($printDivider or ($lastItem != $key) and strpos($lang->dividerMenu, ",{$group},") !== false) ? true : false;
  607. if($printDivider and !empty($items))
  608. {
  609. $items[] = 'divider';
  610. $printDivider = false;
  611. }
  612. }
  613. $display = false;
  614. /* 1. 有权限则展示导航. */
  615. if(common::hasPriv($currentModule, $currentMethod)) $display = true;
  616. /* 2. 如果没有资产库落地页的权限,则查看是否有资产库其他方法的权限. */
  617. if($currentModule == 'assetlib' && !$display) list($display, $currentMethod) = commonTao::setAssetLibMenu($display, $currentModule, $currentMethod);
  618. /* 3. 可以个性化设置的导航,如果没有落地页的权限,则查看是否有其他落地页的权限。 */
  619. $moduleLinkList = $currentModule . 'LinkList';
  620. if(!$display and isset($lang->my->$moduleLinkList) and $config->vision != 'or') list($display, $currentMethod) = commonTao::setPreferenceMenu($display, $currentModule, $currentMethod);
  621. /* 4. 不可以个性化设置的导航,如果没有落地页的权限,则查看是否有对应app下其他方法的权限. */
  622. if(!$display and isset($lang->$currentModule->menu) and !in_array($currentModule, array('program', 'product', 'project', 'execution', 'demandpool'))) list($display, $currentMethod) = commonTao::setOtherMenu($display, $currentModule, $currentMethod);
  623. /* 5. 如果以上权限都没有,则最后查看是否有该应用下任意一个顶部一级导航的权限。 */
  624. if(!$display and isset($lang->$group->menu)) list($display, $currentModule, $currentMethod) = commonTao::setMenuByGroup($group, $display, $currentModule, $currentMethod);
  625. /* Check whether the homeMenu of this group have permissions. If yes, point to them. */
  626. if($display == false and isset($lang->$group->homeMenu))
  627. {
  628. foreach($lang->$group->homeMenu as $menu)
  629. {
  630. if(!isset($menu['link'])) continue;
  631. $linkPart = explode('|', $menu['link']);
  632. if(count($linkPart) < 3) continue;
  633. list($label, $module, $method) = $linkPart;
  634. if(common::hasPriv($module, $method))
  635. {
  636. $display = true;
  637. $currentModule = $module;
  638. $currentMethod = $method;
  639. if(!isset($menu['target'])) break;
  640. }
  641. }
  642. }
  643. if(!$display) continue;
  644. /* Assign vars. */
  645. $item = new stdClass();
  646. $item->group = $group;
  647. $item->code = $group;
  648. $item->active = zget($lang->navGroup, $moduleName, '') == $group or $moduleName != 'program' and $moduleName == $group;
  649. $item->title = $title;
  650. $item->moduleName = $currentModule;
  651. $item->methodName = $currentMethod;
  652. $item->vars = $vars;
  653. $isTutorialMode = commonModel::isTutorialMode();
  654. if($isTutorialMode and $currentModule == 'project')
  655. {
  656. if(!empty($vars)) $vars = helper::safe64Encode($vars);
  657. $item->url = helper::createLink('tutorial', 'wizard', "module={$currentModule}&method={$currentMethod}&params={$vars}", '', false, 0, 1);
  658. }
  659. else
  660. {
  661. $item->url = helper::createLink($currentModule, $currentMethod, $vars, '', false, 0, 1);
  662. }
  663. $items[] = $item;
  664. }
  665. // 如果最后一个是分割线,则删除
  666. while(!empty($items) && end($items) === 'divider') { array_pop($items); }
  667. return $items;
  668. }
  669. /**
  670. * 获取高亮的顶部一级导航。
  671. * Get active main menu.
  672. *
  673. * @static
  674. * @access public
  675. * @return string
  676. */
  677. public static function getActiveMainMenu()
  678. {
  679. global $app;
  680. $isTutorialMode = commonModel::isTutorialMode();
  681. $currentModule = $app->rawModule;
  682. $currentMethod = $app->rawMethod;
  683. if($isTutorialMode && defined('WIZARD_MODULE')) $currentModule = WIZARD_MODULE;
  684. if($isTutorialMode && defined('WIZARD_METHOD')) $currentMethod = WIZARD_METHOD;
  685. /* Print all main menus. */
  686. $menu = customModel::getMainMenu();
  687. $activeMenu = '';
  688. foreach($menu as $menuItem)
  689. {
  690. if(isset($menuItem->hidden) and $menuItem->hidden and (!isset($menuItem->tutorial) or !$menuItem->tutorial)) continue;
  691. if(empty($menuItem->link)) continue;
  692. /* Init the these vars. */
  693. $alias = isset($menuItem->alias) ? $menuItem->alias : '';
  694. $subModule = isset($menuItem->subModule) ? explode(',', $menuItem->subModule) : array();
  695. $exclude = isset($menuItem->exclude) ? $menuItem->exclude : '';
  696. if($menuItem->name == $currentModule and strpos(",$exclude,", ",$currentModule-$currentMethod,") === false) $activeMenu = $menuItem->name;
  697. if($subModule and in_array($currentModule, $subModule) and strpos(",$exclude,", ",$currentModule-$currentMethod,") === false) $activeMenu = $menuItem->name;
  698. if($menuItem->link)
  699. {
  700. $module = '';
  701. $method = '';
  702. if(is_array($menuItem->link))
  703. {
  704. if(isset($menuItem->link['module'])) $module = $menuItem->link['module'];
  705. if(isset($menuItem->link['method'])) $method = $menuItem->link['method'];
  706. }
  707. if($module == $currentModule and ($method == $currentMethod or strpos(",$alias,", ",$currentMethod,") !== false) and strpos(",$exclude,", ",$currentMethod,") === false) $activeMenu = $menuItem->name;
  708. /* Print drop menus. */
  709. if(isset($menuItem->dropMenu))
  710. {
  711. foreach($menuItem->dropMenu as $dropMenuName => $dropMenuItem)
  712. {
  713. if(empty($dropMenuItem)) continue;
  714. if(isset($dropMenuItem->hidden) and $dropMenuItem->hidden) continue;
  715. /* Parse drop menu link. */
  716. if(!empty($dropMenuItem['links']))
  717. {
  718. $dropMenuLink = common::getHasPrivLink($dropMenuItem);
  719. if(empty($dropMenuLink)) continue;
  720. list($subLabel, $subModule, $subMethod, $subParams) = $dropMenuLink;
  721. }
  722. else
  723. {
  724. $dropMenuLink = zget($dropMenuItem, 'link', $dropMenuItem);
  725. list($subLabel, $subModule, $subMethod, $subParams) = explode('|', $dropMenuLink);
  726. if(!common::hasPriv($subModule, $subMethod)) continue;
  727. }
  728. $activeMainMenu = false;
  729. if($currentModule == strtolower($subModule) and $currentMethod == strtolower($subMethod))
  730. {
  731. $activeMainMenu = true;
  732. }
  733. else
  734. {
  735. $subModule = isset($dropMenuItem['subModule']) ? explode(',', $dropMenuItem['subModule']) : array();
  736. $subExclude = isset($dropMenuItem['exclude']) ? $dropMenuItem['exclude'] : $exclude;
  737. $subAlias = zget($dropMenuItem, 'alias', '');
  738. if($subModule and in_array($currentModule, $subModule) and strpos(",$subExclude,", ",$currentModule-$currentMethod,") === false) $activeMainMenu = true;
  739. if(strpos(",$subAlias,", ",$currentModule-$currentMethod,") !== false) $activeMainMenu = true;
  740. }
  741. if($activeMainMenu) $activeMenu = $dropMenuName;
  742. }
  743. }
  744. }
  745. }
  746. return $activeMenu;
  747. }
  748. /**
  749. * 获取有权限的链接。
  750. * Get the authorized link.
  751. *
  752. * @param array $menu
  753. * @access public
  754. * @return array
  755. */
  756. public static function getHasPrivLink($menu)
  757. {
  758. if(empty($menu['link'])) return array();
  759. list($label, $module, $method, $params) = explode('|', $menu['link']);
  760. if(common::hasPriv($module, $method)) return array($label, $module, $method, $params);
  761. if(empty($menu['links'])) return array();
  762. $link = array();
  763. foreach($menu['links'] as $menuLink)
  764. {
  765. list($module, $method, $params) = explode('|', $menuLink);
  766. if(common::hasPriv($module, $method))
  767. {
  768. $link = array($label, $module, $method, $params);
  769. break;
  770. }
  771. }
  772. return $link;
  773. }
  774. /**
  775. * 当对象编辑后,比较前后差异。
  776. * Create changes of one object.
  777. *
  778. * @param mixed $old the old object
  779. * @param mixed $new the new object
  780. * @param string $moduleName
  781. * @static
  782. * @access public
  783. * @return array
  784. */
  785. public static function createChanges($old, $new, $moduleName = '')
  786. {
  787. global $app, $config, $dao;
  788. /**
  789. * 当主状态改变并且未设置子状态的值时把子状态的值设置为默认值并记录日志。
  790. * Change sub status when status is changed and sub status is not set, and record the changes.
  791. */
  792. if($config->edition != 'open')
  793. {
  794. $oldID = zget($old, 'id', '');
  795. $oldStatus = zget($old, 'status', '');
  796. $newStatus = zget($new, 'status', '');
  797. $newSubStatus = zget($new, 'subStatus', '');
  798. if(empty($moduleName)) $moduleName = $app->getModuleName();
  799. if($oldID and $oldStatus and $newStatus and !$newSubStatus and $oldStatus != $newStatus)
  800. {
  801. $field = $dao->select('options')->from(TABLE_WORKFLOWFIELD)->where('`module`')->eq($moduleName)->andWhere('`field`')->eq('subStatus')->fetch();
  802. if(!empty($field->options)) $field->options = json_decode($field->options, true);
  803. if(!empty($field->options[$newStatus]['default']))
  804. {
  805. $flow = $dao->select('`table`')->from(TABLE_WORKFLOW)->where('`module`')->eq($moduleName)->fetch();
  806. $default = $field->options[$newStatus]['default'];
  807. $common = new self();
  808. $common->dao->update($flow->table)->set('subStatus')->eq($default)->where('id')->eq($oldID)->exec();
  809. $new->subStatus = $default;
  810. }
  811. }
  812. $dateFields = array();
  813. $rows = $dao->select('`field`')->from(TABLE_WORKFLOWFIELD)->where('`module`')->eq($moduleName)->andWhere('`control`')->in(array('data', 'datetime'))->fetchAll();
  814. foreach($rows as $row) $dateFields[$row->field] = $row->field;
  815. }
  816. $changes = array();
  817. foreach($new as $key => $value)
  818. {
  819. if($key == 'addedFiles')
  820. {
  821. foreach($value as $addedFile) $changes[] = array('field' => 'addDiff', 'old' => '', 'new' => '', 'diff' => $addedFile);
  822. }
  823. elseif($key == 'deleteFiles')
  824. {
  825. foreach($value as $deleteFile) $changes[] = array('field' => 'removeDiff', 'old' => '', 'new' => '', 'diff' => $deleteFile);
  826. }
  827. elseif($key == 'renameFiles')
  828. {
  829. foreach($value as $renameFile) $changes[] = array('field' => 'fileName', 'old' => $renameFile['old'], 'new' => $renameFile['new'], 'diff' => '');
  830. }
  831. if(is_object($value) || is_array($value)) continue;
  832. $check = strtolower($key);
  833. if(in_array($check, array('lastediteddate', 'lasteditedby', 'assigneddate', 'editedby', 'editeddate', 'editingdate', 'uid', 'prevstatus', 'prevassignedto'))) continue;
  834. if(in_array($check, array('finisheddate', 'canceleddate', 'hangupeddate', 'lastcheckeddate', 'activateddate', 'closeddate', 'actualcloseddate')) && $value == '') continue;
  835. if(isset($old->$key) && !is_object($old->$key) && !is_array($old->$key))
  836. {
  837. if($config->edition != 'open' && isset($dateFields[$key])) $old->$key = formatTime($old->$key);
  838. if($value != stripslashes((string)$old->$key))
  839. {
  840. $diff = '';
  841. $showDiff = (substr_count((string)$value, "\n") > 1 || substr_count((string)$old->$key, "\n") > 1 || strpos(',name,title,desc,spec,steps,content,digest,verify,report,definition,analysis,summary,prevention,resolution,outline,schedule,minutes,sql,interface,ui,langs,performance,privileges,search,actions,deploy,bi,safe,other,', ',' . strtolower($key) . ',') !== false);
  842. $hiddenDiff = array('bug' => 'resolution');
  843. if($showDiff && (!isset($hiddenDiff[$moduleName]) || (isset($hiddenDiff[$moduleName]) && strpos(",$hiddenDiff[$moduleName],", ",$key,") === false)))
  844. {
  845. $diff = commonModel::diff((string)$old->$key, (string)$value);
  846. }
  847. $changes[] = array('field' => $key, 'old' => $old->$key, 'new' => $value, 'diff' => $diff);
  848. }
  849. }
  850. }
  851. return $changes;
  852. }
  853. /**
  854. * 比较两个字符串的差异。
  855. * Diff two string. (see phpt)
  856. *
  857. * @param string $text1
  858. * @param string $text2
  859. * @static
  860. * @access public
  861. * @return string
  862. */
  863. public static function diff($text1, $text2)
  864. {
  865. $text1 = str_replace('&nbsp;', '', trim($text1));
  866. $text2 = str_replace('&nbsp;', '', trim($text2));
  867. $w = explode("\n", $text1);
  868. $o = explode("\n", $text2);
  869. $w1 = array_diff_assoc($w,$o);
  870. $o1 = array_diff_assoc($o,$w);
  871. $w2 = array();
  872. $o2 = array();
  873. foreach($w1 as $idx => $val) $w2[sprintf("%03d<",$idx)] = sprintf("%03d- ", $idx+1) . "<del>" . trim($val) . "</del>";
  874. foreach($o1 as $idx => $val) $o2[sprintf("%03d>",$idx)] = sprintf("%03d+ ", $idx+1) . "<ins>" . trim($val) . "</ins>";
  875. $diff = array_merge($w2, $o2);
  876. ksort($diff);
  877. return implode("\n", $diff);
  878. }
  879. /**
  880. * 判断post数据大小是否超过Suhosin设置大小。
  881. * Judge Suhosin Setting whether the actual size of post data is large than the setting size.
  882. *
  883. * @param int $countInputVars
  884. * @static
  885. * @access public
  886. * @return bool
  887. */
  888. public static function judgeSuhosinSetting($countInputVars)
  889. {
  890. if(extension_loaded('suhosin'))
  891. {
  892. $maxPostVars = ini_get('suhosin.post.max_vars');
  893. $maxRequestVars = ini_get('suhosin.request.max_vars');
  894. if($countInputVars > $maxPostVars or $countInputVars > $maxRequestVars) return true;
  895. }
  896. else
  897. {
  898. $maxInputVars = ini_get('max_input_vars');
  899. if($maxInputVars and $countInputVars > (int)$maxInputVars) return true;
  900. }
  901. return false;
  902. }
  903. /**
  904. * 获取详情页面上一页和下一页的对象。
  905. * Get the previous and next object.
  906. *
  907. * @param string $type story|task|bug|case
  908. * @param int $objectID
  909. * @access public
  910. * @return object
  911. */
  912. public function getPreAndNextObject($type, $objectID)
  913. {
  914. $queryCondition = $type . 'QueryCondition';
  915. $queryCondition = $this->session->$queryCondition;
  916. $preAndNextObject = new stdClass();
  917. $preAndNextObject->pre = '';
  918. $preAndNextObject->next = '';
  919. if(empty($queryCondition)) return $preAndNextObject;
  920. $sql = $this->commonTao->getPreAndNextSQL($type);
  921. $objectList = $this->commonTao->queryListForPreAndNext($type, $sql);
  922. $preAndNextObject = $this->commonTao->searchPreAndNextFromList($objectID, $objectList);
  923. $preAndNextObject = $this->commonTao->fetchPreAndNextObject($type, $objectID, $preAndNextObject);
  924. return $preAndNextObject;
  925. }
  926. /**
  927. * 保存列表页的查询条件到session中,用于其他页面返回、导出等操作。
  928. * Save one executed query.
  929. *
  930. * @param string $sql
  931. * @param string $objectType story|task|bug|testcase
  932. * @param bool $onlyCondition
  933. * @access public
  934. * @return void
  935. */
  936. public function saveQueryCondition($sql, $objectType, $onlyCondition = true)
  937. {
  938. /* Set the query condition session. */
  939. if($onlyCondition)
  940. {
  941. $queryCondition = substr($sql, strpos($sql, ' WHERE ') + 7);
  942. if($queryCondition)
  943. {
  944. if(strpos($queryCondition, ' ORDER BY') !== false) $queryCondition = substr($queryCondition, 0, strpos($queryCondition, ' ORDER BY '));
  945. $queryCondition = str_replace('t1.', '', $queryCondition);
  946. }
  947. }
  948. else
  949. {
  950. $queryCondition = strpos($sql, ' ORDER BY') !== false ? substr($sql, 0, strpos($sql, ' ORDER BY ')) : $sql;
  951. }
  952. $queryCondition = trim($queryCondition);
  953. if(empty($queryCondition)) $queryCondition = "1=1";
  954. $this->session->set($objectType . 'QueryCondition', $queryCondition, $this->app->tab);
  955. $this->session->set($objectType . 'OnlyCondition', $onlyCondition, $this->app->tab);
  956. /* Set the query condition session. */
  957. $orderBy = explode(' ORDER BY ', $sql);
  958. $orderBy = isset($orderBy[1]) ? $orderBy[1] : '';
  959. if($orderBy)
  960. {
  961. $orderBy = explode(' LIMIT ', $orderBy);
  962. $orderBy = $orderBy[0];
  963. if($onlyCondition) $orderBy = str_replace('t1.', '', $orderBy);
  964. }
  965. $this->session->set($objectType . 'OrderBy', $orderBy, $this->app->tab);
  966. $this->session->set($objectType . 'BrowseList', array(), $this->app->tab);
  967. }
  968. /**
  969. * 追加排序字段。
  970. * Append order by.
  971. *
  972. * @param string $orderBy
  973. * @param string $append
  974. * @access public
  975. * @return string
  976. */
  977. public static function appendOrder($orderBy, $append = 'id')
  978. {
  979. if(empty($orderBy)) return $append;
  980. list($firstOrder) = explode(',', $orderBy);
  981. $sort = strpos($firstOrder, '_') === false ? '_asc' : strstr($firstOrder, '_');
  982. return strpos($orderBy, $append) === false ? $orderBy . ',' . $append . $sort : $orderBy;
  983. }
  984. /**
  985. * 检查字段是否存在。
  986. * Check field exists
  987. *
  988. * @param string $table
  989. * @param string $field
  990. * @access public
  991. * @return bool
  992. */
  993. public function checkField($table, $field)
  994. {
  995. $fields = $this->dao->descTable($table);
  996. $hasField = false;
  997. foreach($fields as $fieldObj)
  998. {
  999. if($field == $fieldObj->field)
  1000. {
  1001. $hasField = true;
  1002. break;
  1003. }
  1004. }
  1005. return $hasField;
  1006. }
  1007. /**
  1008. * 检查验证文件是否正确创建。
  1009. * Check safe file.
  1010. *
  1011. * @access public
  1012. * @return string|false
  1013. */
  1014. public function checkSafeFile()
  1015. {
  1016. if($this->config->inContainer) return false;
  1017. if($this->app->hasValidSafeFile()) return false;
  1018. if($this->app->getModuleName() == 'upgrade' and $this->session->upgrading) return false;
  1019. if($this->app->getModuleName() == 'upgrade' && $this->app->getMethodName() == 'safedelete') return false;
  1020. $statusFile = $this->app->getAppRoot() . 'www' . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'ok.txt';
  1021. return (!is_file($statusFile) or (time() - filemtime($statusFile)) > $this->config->safeFileTimeout) ? $statusFile : false;
  1022. }
  1023. /**
  1024. * 检查系统是否处于维护状态,如果处于维护状态则给非管理员用户输出提示。
  1025. * Check whether system in maintenance status and show message for non-admin user.
  1026. *
  1027. * @return void
  1028. */
  1029. public function checkMaintenance()
  1030. {
  1031. $maintenance = $this->loadModel('setting')->getItem('owner=system&module=system&key=maintenance');
  1032. if(empty($maintenance)) return true;
  1033. $maintenance = json_decode($maintenance);
  1034. if($this->app->moduleName == 'user' && $this->app->methodName == 'login') return true;
  1035. if(!empty($this->app->user->admin)) return true;
  1036. if(isset($maintenance->action) && in_array($maintenance->action, array('upgrade', 'downgrade', 'restore'))) helper::setStatus(503);
  1037. $reason = sprintf($this->lang->maintainReason, isset($maintenance->reason) ? $maintenance->reason : $this->lang->unknown);
  1038. $message = <<<eof
  1039. <html><head><meta http-equiv='Content-Type' content='text/html; charset=utf-8' /></head><body>
  1040. <table align='center' style='margin-top:100px; border:1px solid gray; font-size:14px;padding:8px;'><tr><td>
  1041. <h4>{$this->lang->inMaintenance}</h4>
  1042. <p>{$reason}{$this->lang->systemMaintainer}</p>
  1043. </td></tr></table></body></html>'
  1044. eof;
  1045. helper::end($message);
  1046. }
  1047. /**
  1048. * 禅道鉴权核心方法,如果用户没有当前模块、方法的权限,则跳转到登录页面或者拒绝页面。
  1049. * Check the user has permission to access this method, if not, locate to the login page or deny page.
  1050. *
  1051. * @access public
  1052. * @return void
  1053. */
  1054. public function checkPriv()
  1055. {
  1056. try
  1057. {
  1058. $module = $this->app->getModuleName();
  1059. $method = $this->app->getMethodName();
  1060. if($this->app->isFlow)
  1061. {
  1062. $module = $this->app->rawModule;
  1063. $method = $this->app->rawMethod;
  1064. }
  1065. if($module == 'product' and $method == 'browse' and !empty($this->app->params['storyType']) and $this->app->params['storyType'] != 'story') $method = $this->app->params['storyType'];
  1066. if($module == 'productplan' && ($method == 'story' || $method == 'bug')) $method = 'view';
  1067. if($module == 'doc' && $method == 'edittemplate') $method = 'createtemplate';
  1068. $openMethods = array(
  1069. 'user' => array('deny', 'logout'),
  1070. 'my' => array('changepassword'),
  1071. 'message' => array('ajaxgetmessage'),
  1072. );
  1073. if(!empty($this->app->user->modifyPassword) and (!isset($openMethods[$module]) or !in_array($method, $openMethods[$module]))) return helper::header('location', helper::createLink('my', 'changePassword'));
  1074. if(!$this->loadModel('user')->isLogon() and $this->server->php_auth_user) $this->user->identifyByPhpAuth();
  1075. if(!$this->loadModel('user')->isLogon() and $this->cookie->za) $this->user->identifyByCookie();
  1076. if($this->isOpenMethod($module, $method)) return true;
  1077. if(isset($this->app->user))
  1078. {
  1079. if($this->app->tab == 'project' || $this->app->tab == 'execution')
  1080. {
  1081. static::$userPrivs = array();
  1082. $this->resetProjectPriv(); // 项目有继承和重新定义两种权限,在此处需要重置权限。
  1083. if(commonModel::hasPriv($module, $method)) return true;
  1084. }
  1085. $this->app->user = $this->session->user;
  1086. if(!commonModel::hasPriv($module, $method))
  1087. {
  1088. if($module === 'feedback' && stripos(',view,adminview,', ",$method,") !== false && ($method === 'view' && commonModel::hasPriv('feedback', 'adminview') || $method === 'adminview' && commonModel::hasPriv('feedback', 'view'))) return true; // Make both feedback view and adminview privs interchangeable.
  1089. if($module == 'story' and !empty($this->app->params['storyType']) and strpos(",story,requirement,", ",{$this->app->params['storyType']},") !== false) $module = $this->app->params['storyType'];
  1090. $this->deny($module, $method);
  1091. }
  1092. }
  1093. else
  1094. {
  1095. $uri = $this->app->getURI(true);
  1096. if($module == 'message' and $method == 'ajaxgetmessage')
  1097. {
  1098. $uri = helper::createLink('my');
  1099. }
  1100. elseif(helper::isAjaxRequest())
  1101. {
  1102. helper::end(json_encode(array('result' => false, 'message' => $this->lang->error->loginTimeout))); // Fix bug #14478.
  1103. }
  1104. $referer = helper::safe64Encode($uri);
  1105. helper::end(js::locate(helper::createLink('user', 'login', "referer=$referer")));
  1106. }
  1107. }
  1108. catch(EndResponseException $endResponseException)
  1109. {
  1110. echo $endResponseException->getContent();
  1111. helper::end();
  1112. }
  1113. }
  1114. /**
  1115. * 检查当前页面是否在 iframe 中打开,如果不是 iframe 并且不允许独立打开,则跳转到首页以 iframe 方式打开。
  1116. * Check current page whether is in iframe. If it is not iframe and not allowed to open independently, then redirect to index to open it in iframe.
  1117. *
  1118. * @access public
  1119. * @param string $whitelist
  1120. * @return void
  1121. */
  1122. public function checkIframe($whitelist = null)
  1123. {
  1124. /*
  1125. * 忽略如下情况:非 HTML 请求、Ajax 请求、特殊 GET 参数 _single。
  1126. * Ignore the following situations: non-HTML request, Ajax request, special GET parameter _single.
  1127. */
  1128. if($this->app->getViewType() != 'html' || helper::isAjaxRequest() || isset($_GET['_single'])) return;
  1129. /**
  1130. * 忽略无请求头 HTTP_SEC_FETCH_DEST 或者 HTTP_SEC_FETCH_DEST 为 iframe 的请求,较新的浏览器在启用 https 的情况下才会正确发送该请求头。
  1131. * Ignore the request without HTTP_SEC_FETCH_DEST or HTTP_SEC_FETCH_DEST is iframe, the latest browser will send this request header correctly when enable https.
  1132. */
  1133. if(!isset($_SERVER['HTTP_SEC_FETCH_DEST']) || $_SERVER['HTTP_SEC_FETCH_DEST'] == 'iframe') return;
  1134. /**
  1135. * 当有 HTTP_REFERER 请求头时,忽略 safari 浏览器,因为 safari 浏览器不会正确发送 HTTP_SEC_FETCH_DEST 请求头。
  1136. * Ignore safari browser when there is HTTP_REFERER request header, because safari browser will not send HTTP_SEC_FETCH_DEST request header correctly.
  1137. */
  1138. if(isset($_SERVER['HTTP_REFERER']) && !empty($_SERVER['HTTP_REFERER']))
  1139. {
  1140. $userAgent = strtolower($_SERVER['HTTP_USER_AGENT']);
  1141. if(strpos($userAgent, 'chrome') === false && strpos($userAgent, 'safari') !== false) return;
  1142. }
  1143. /**
  1144. * 忽略所有方法名以 ajax 开头的请求。
  1145. * Ignore all requests which it's method name starts with 'ajax'.
  1146. */
  1147. $method = $this->app->getMethodName();
  1148. if(strpos($method, 'ajax') === 0) return;
  1149. /**
  1150. * 以下页面可以允许在非 iframe 中打开,所以要忽略这些页面。
  1151. * The following pages can be allowed to open in non-iframe, so ignore these pages.
  1152. */
  1153. $module = $this->app->getModuleName();
  1154. $whitelist = is_string($whitelist) ? $whitelist : '|index|tutorial|install|upgrade|sso|cron|misc|user-login|user-deny|user-logout|user-reset|user-forgetpassword|user-resetpassword|my-changepassword|my-preference|file-read|file-download|file-preview|file-uploadimages|file-ajaxwopifiles|report-annualdata|misc-captcha|execution-printkanban|traincourse-ajaxuploadlargefile|traincourse-playvideo|screen-view|zanode-create|screen-ajaxgetchart|ai-chat|integration-wopi|instance-terminal|conference-getconferencepermissions|instance-logs|';
  1155. $iframeList = '|cron-index|zanode-create|';
  1156. if(strpos($iframeList, "|{$module}-{$method}|") === false && (strpos($whitelist, "|{$module}|") !== false || strpos($whitelist, "|{$module}-{$method}|") !== false)) return;
  1157. /**
  1158. * 如果以上条件都不满足,则视为当前页面必须在 iframe 中打开,使用 302 跳转实现。
  1159. * If none of the above conditions are missed, then the current page must be opened in iframe, using 302 jump to achieve.
  1160. */
  1161. $url = helper::safe64Encode($_SERVER['REQUEST_URI']);
  1162. $redirectUrl = helper::createLink('index', 'index');
  1163. $redirectUrl .= strpos($redirectUrl, '?') === false ? "?open=$url" : "&open=$url";
  1164. helper::header('location', $redirectUrl);
  1165. helper::end();
  1166. }
  1167. /**
  1168. * 检查用户是否有当前模块、方法的权限。
  1169. * Check the user has permission of one method of one module.
  1170. *
  1171. * @param string $module
  1172. * @param string $method
  1173. * @param mixed $object
  1174. * @param string $vars
  1175. * @static
  1176. * @access public
  1177. * @return bool
  1178. */
  1179. public static function hasPriv($module, $method, $object = null, $vars = '')
  1180. {
  1181. /* If the user is doing a tutorial, have all privileges. */
  1182. if(commonModel::isTutorialMode()) return true;
  1183. global $app;
  1184. if(empty($app->user->account)) return false;
  1185. $module = strtolower($module);
  1186. $method = strtolower($method);
  1187. if($module == 'product' and $method == 'browse' and !empty($app->params['storyType']) and $app->params['storyType'] != 'story') $method = $app->params['storyType'];
  1188. global $config;
  1189. if(isset($config->{$module}->groupPrivs[$method]))
  1190. {
  1191. $groupPriv = strtolower($config->{$module}->groupPrivs[$method]);
  1192. if(strpos($groupPriv, '|') !== false) list($module, $groupPriv) = explode('|', $groupPriv);
  1193. if($groupPriv && $groupPriv != $method)
  1194. {
  1195. if($object) return static::getUserPriv($module, $groupPriv, $object, $vars);
  1196. return static::hasPriv($module, $groupPriv, $object, $vars);
  1197. }
  1198. }
  1199. if($object) return static::getUserPriv($module, $method, $object, $vars);
  1200. if(!isset(static::$userPrivs[$module][$method][$vars])) static::$userPrivs[$module][$method][$vars] = static::getUserPriv($module, $method, $object, $vars);
  1201. return static::$userPrivs[$module][$method][$vars];
  1202. }
  1203. /**
  1204. * 获取用户是否有某个模块、方法的访问权限。
  1205. * Get the user has the access permission of one module and method.
  1206. *
  1207. * @param string $module
  1208. * @param string $method
  1209. * @param mixed $object
  1210. * @param string $vars
  1211. * @static
  1212. * @access public
  1213. * @return bool
  1214. */
  1215. public static function getUserPriv($module, $method, $object = null, $vars = '')
  1216. {
  1217. global $app,$config;
  1218. $module = strtolower($module);
  1219. $method = strtolower($method);
  1220. parse_str($vars, $params);
  1221. if(empty($app->user)) return false;
  1222. //list($module, $method) = commonTao::getStoryModuleAndMethod($module, $method, $params);
  1223. list($module, $method) = commonTao::getBoardModuleAndMethod($module, $method, $params);
  1224. /* Compatible with old search. */
  1225. if($module == 'search' && $method == 'buildoldform') $method = 'buildform';
  1226. if($module == 'search' && $method == 'saveoldquery') $method = 'savequery';
  1227. if($module == 'search' && $method == 'buildoldquery') $method = 'buildquery';
  1228. /* If the user is doing a tutorial, have all tutorial privileges. */
  1229. if(commonModel::isTutorialMode())
  1230. {
  1231. $app->loadLang('tutorial');
  1232. $app->loadConfig('tutorial');
  1233. foreach($app->config->tutorial->guides as $guide)
  1234. {
  1235. if(!isset($guide->modules)) continue;
  1236. $guideModules = explode(',', strtolower($guide->modules));
  1237. if(in_array($module, $guideModules)) return true;
  1238. }
  1239. }
  1240. /* Check the parent object is closed. */
  1241. if(!empty($method) and strpos('close|batchclose', $method) === false and !commonModel::canBeChanged($module, $object)) return false;
  1242. /* Check is the super admin or not. */
  1243. if(!empty($app->user->admin) or strpos($app->company->admins, ",{$app->user->account},") !== false) return true;
  1244. /* Check the method is openMethod. */
  1245. if($app->config->vision == 'or') $app->config->logonMethods[] = 'story.view';
  1246. if($app->config->vision != 'or') $app->config->logonMethods[] = 'demand.view';
  1247. if(in_array("$module.$method", $app->config->openMethods)) return true;
  1248. if(in_array("$module.$method", $app->config->logonMethods)) return true;
  1249. /* If is the program/project/product/execution admin, have all program privileges. */
  1250. if($app->config->vision != 'lite' && commonTao::isProjectAdmin($module, $object)) return true;
  1251. /* If not super admin, check the rights. */
  1252. $rights = $app->user->rights['rights'];
  1253. $acls = $app->user->rights['acls'];
  1254. /* White list of import method. */
  1255. $canImport = isset($rights[$module]['import']) && commonModel::hasDBPriv($object, $module, 'import');
  1256. if(in_array($module, $app->config->importWhiteList) && $method == 'showimport' && $canImport) return true;
  1257. if(isset($rights[$module][$method])) return commonTao::checkPrivByRights($module, $method, $acls, $object);
  1258. return false;
  1259. }
  1260. /**
  1261. * 项目有继承和重新定义两种权限,在此处需要重置权限。
  1262. * Reset project priv.
  1263. *
  1264. * @param int $projectID
  1265. * @access public
  1266. * @return void
  1267. */
  1268. public function resetProjectPriv($projectID = 0)
  1269. {
  1270. $module = $this->app->getModuleName();
  1271. $method = $this->app->getMethodName();
  1272. if($this->app->isFlow)
  1273. {
  1274. $module = $this->app->rawModule;
  1275. $method = $this->app->rawMethod;
  1276. }
  1277. if($this->app->tab == 'execution')
  1278. {
  1279. if($module == 'execution' && $method == 'all') return;
  1280. $executionID = $this->session->execution;
  1281. if(!empty($_GET['executionID'])) $executionID = $_GET['executionID'];
  1282. if(!empty($_GET['execution'])) $executionID = $_GET['execution'];
  1283. if(empty($executionID)) return;
  1284. $execution = $this->dao->findByID($executionID)->from(TABLE_PROJECT)->fetch();
  1285. if(empty($execution)) return;
  1286. $projectID = $execution->project;
  1287. }
  1288. else
  1289. {
  1290. if(($module == 'project' && $method == 'browse') || ($module == 'project' && $method == 'template')) return;
  1291. if(empty($projectID) && !empty($_GET['projectID'])) $projectID = $_GET['projectID'];
  1292. if(empty($projectID) && !empty($_GET['project'])) $projectID = $_GET['project'];
  1293. }
  1294. /* Get user program priv. */
  1295. if(empty($projectID) && $this->session->project) $projectID = $this->session->project;
  1296. if(empty($projectID)) return;
  1297. $program = $this->dao->findByID($projectID)->from(TABLE_PROJECT)->fetch();
  1298. if(empty($program)) return;
  1299. $programRights = $this->dao->select('t3.module, t3.method')->from(TABLE_GROUP)->alias('t1')
  1300. ->leftJoin(TABLE_USERGROUP)->alias('t2')->on('t1.id = t2.`group`')
  1301. ->leftJoin(TABLE_GROUPPRIV)->alias('t3')->on('t2.`group`=t3.`group`')
  1302. ->where('t1.project')->eq($program->id)
  1303. ->andWhere('t2.account')->eq($this->app->user->account)
  1304. ->fetchAll();
  1305. /* Group priv by module the same as rights. */
  1306. $programRightGroup = array();
  1307. foreach($programRights as $programRight) $programRightGroup[strtolower($programRight->module)][strtolower($programRight->method)] = 1;
  1308. /* Reset priv by program privway. */
  1309. $this->app->user = clone $_SESSION['user'];
  1310. $rights = $this->app->user->rights['rights'];
  1311. if($this->app->user->account == $program->openedBy or $this->app->user->account == $program->PM) $program->auth = 'extend';
  1312. if($program->auth == 'extend') $this->app->user->rights['rights'] = array_merge_recursive($programRightGroup, $rights);
  1313. if($program->auth == 'reset')
  1314. {
  1315. /* If priv way is reset, unset common program priv, and cover by program priv. */
  1316. $projectPrivs = $this->loadModel('project')->getPrivsByModel($program->multiple ? $program->model : 'noSprint', (int)$projectID, $program->hasProduct);
  1317. foreach($projectPrivs as $module => $methods)
  1318. {
  1319. foreach($methods as $method => $label)
  1320. {
  1321. $module = strtolower($module);
  1322. $method = strtolower($method);
  1323. if(isset($rights[$module][$method])) unset($rights[$module][$method]);
  1324. }
  1325. }
  1326. foreach($programRightGroup as $module => $methods)
  1327. {
  1328. foreach($methods as $method => $label)
  1329. {
  1330. $module = strtolower($module);
  1331. $method = strtolower($method);
  1332. $rights[$module][$method] = $label;
  1333. }
  1334. }
  1335. /* Set base priv for project. */
  1336. $projectRights = zget($this->app->user->rights['rights'], 'project', array());
  1337. if(isset($projectRights['browse']) and !isset($rights['project']['browse'])) $rights['project']['browse'] = 1;
  1338. if(isset($projectRights['kanban']) and !isset($rights['project']['kanban'])) $rights['project']['kanban'] = 1;
  1339. if(isset($projectRights['index']) and !isset($rights['project']['index'])) $rights['project']['index'] = 1;
  1340. $this->app->user->rights['rights'] = $rights;
  1341. }
  1342. }
  1343. /**
  1344. * 受限用户只能编辑自己相关的数据。
  1345. * Check db priv.
  1346. *
  1347. * @param mixed $object
  1348. * @param string $module
  1349. * @param string $method
  1350. * @static
  1351. * @access public
  1352. * @return bool
  1353. */
  1354. public static function hasDBPriv($object, $module = '', $method = '')
  1355. {
  1356. global $app;
  1357. if(!empty($app->user->admin)) return true;
  1358. if($module == 'todo' && ($method == 'create' || $method == 'batchcreate')) return true;
  1359. if($module == 'effort' && ($method == 'batchcreate' || $method == 'createforobject')) return true;
  1360. /* Limited execution. */
  1361. $limitedExecution = false;
  1362. $executionID = 0;
  1363. if(!empty($module))
  1364. {
  1365. if(in_array($module, array('task', 'story')) && !empty($object->execution)) $executionID = $object->execution;
  1366. if($module == 'execution' && !empty($object->id)) $executionID = $object->id;
  1367. if($module == 'execution' && in_array($method, array('editrelation', 'deleterelation')) && !empty($object->execution)) $executionID = $object->execution;
  1368. }
  1369. if($executionID)
  1370. {
  1371. $limitedExecutions = !empty($_SESSION['limitedExecutions']) ? $_SESSION['limitedExecutions'] : '';
  1372. if(strpos(",{$limitedExecutions},", ",$executionID,") !== false) $limitedExecution = true;
  1373. }
  1374. if(empty($app->user->rights['rights']['my']['limited']) && !$limitedExecution) return true;
  1375. if(strpos($method, 'batch') === 0) return false;
  1376. if(strpos($method, 'link') === 0) return false;
  1377. if(strpos($method, 'create') === 0) return false;
  1378. if(strpos($method, 'import') === 0) return false;
  1379. if(empty($object)) return true;
  1380. $account = $app->user->account;
  1381. if($module == 'task' && !empty($object->team))
  1382. {
  1383. $taskModel = $app->control->loadModel('task');
  1384. if($object->mode == 'linear')
  1385. {
  1386. if($method == 'assignto' && !in_array($object->status, array('done', 'cancel', 'closed'))) return false;
  1387. if($method == 'start' && in_array($object->status, array('wait', 'doing')))
  1388. {
  1389. if($object->assignedTo != $account) return false;
  1390. $currentTeam = $taskModel->getTeamByAccount($object->team, $account);
  1391. if($currentTeam && $currentTeam->status == 'wait') return true;
  1392. }
  1393. if($method == 'finish' && $object->assignedTo != $account) return false;
  1394. }
  1395. elseif($object->mode == 'multi')
  1396. {
  1397. $currentTeam = $taskModel->getTeamByAccount($object->team, $account);
  1398. if($method == 'start' && in_array($object->status, array('wait', 'doing')) && $currentTeam && $currentTeam->status == 'wait') return true;
  1399. if($method == 'finish' && (empty($currentTeam) || $currentTeam->status == 'done')) return false;
  1400. }
  1401. }
  1402. if($module == 'story')
  1403. {
  1404. $reviewer = $app->control->dao->select('*')->from(TABLE_STORYREVIEW)
  1405. ->where('story')->eq($object->id)
  1406. ->andWhere('version')->eq($object->version)
  1407. ->andWhere('reviewer')->eq($account)
  1408. ->fetch();
  1409. if(!empty($reviewer)) return true;
  1410. }
  1411. if(!empty($object->openedBy) && $object->openedBy == $account) return true;
  1412. if(!empty($object->addedBy) && $object->addedBy == $account) return true;
  1413. if(!empty($object->account) && $object->account == $account) return true;
  1414. if(!empty($object->assignedTo) && $object->assignedTo == $account) return true;
  1415. if(!empty($object->finishedBy) && $object->finishedBy == $account) return true;
  1416. if(!empty($object->canceledBy) && $object->canceledBy == $account) return true;
  1417. if(!empty($object->closedBy) && $object->closedBy == $account) return true;
  1418. if(!empty($object->lastEditedBy) && $object->lastEditedBy == $account) return true;
  1419. return false;
  1420. }
  1421. /**
  1422. * 检查IP是否在白名单中。
  1423. * Check whether IP in white list.
  1424. *
  1425. * @param string $ipWhiteList
  1426. * @access public
  1427. * @return bool
  1428. */
  1429. public function checkIP($ipWhiteList = '')
  1430. {
  1431. $ip = helper::getRemoteIp();
  1432. if(!$ipWhiteList) $ipWhiteList = $this->config->ipWhiteList;
  1433. /* If the ip white list is '*'. */
  1434. if($ipWhiteList == '*') return true;
  1435. /* The ip is same as ip in white list. */
  1436. if($ip == $ipWhiteList) return true;
  1437. /* If the ip in white list is like 192.168.1.1,192.168.1.10. */
  1438. if(strpos($ipWhiteList, ',') !== false)
  1439. {
  1440. $ipArr = explode(',', $ipWhiteList);
  1441. foreach($ipArr as $ipRule)
  1442. {
  1443. if($this->checkIP($ipRule)) return true;
  1444. }
  1445. return false;
  1446. }
  1447. /* If the ip in white list is like 192.168.1.1-192.168.1.10. */
  1448. if(strpos($ipWhiteList, '-') !== false)
  1449. {
  1450. list($min, $max) = explode('-', $ipWhiteList);
  1451. $min = ip2long(trim($min));
  1452. $max = ip2long(trim($max));
  1453. $ip = ip2long(trim($ip));
  1454. return $ip >= $min and $ip <= $max;
  1455. }
  1456. /* If the ip in white list is like 192.168.1.*. */
  1457. if(strpos($ipWhiteList, '*') !== false)
  1458. {
  1459. $regCount = substr_count($ipWhiteList, '.');
  1460. if($regCount == 3)
  1461. {
  1462. $min = str_replace('*', '0', $ipWhiteList);
  1463. $max = str_replace('*', '255', $ipWhiteList);
  1464. }
  1465. elseif($regCount == 2)
  1466. {
  1467. $min = str_replace('*', '0.0', $ipWhiteList);
  1468. $max = str_replace('*', '255.255', $ipWhiteList);
  1469. }
  1470. elseif($regCount == 1)
  1471. {
  1472. $min = str_replace('*', '0.0.0', $ipWhiteList);
  1473. $max = str_replace('*', '255.255.255', $ipWhiteList);
  1474. }
  1475. $min = ip2long(trim($min));
  1476. $max = ip2long(trim($max));
  1477. $ip = ip2long(trim($ip));
  1478. return ($ip >= $min and $ip <= $max);
  1479. }
  1480. /* If the ip in white list is in IP/CIDR format eg 127.0.0.1/24. Thanks to zcat. */
  1481. if(strpos($ipWhiteList, '/') === false) $ipWhiteList .= '/32';
  1482. list($ipWhiteList, $netmask) = explode('/', $ipWhiteList, 2);
  1483. $ip = ip2long($ip);
  1484. $ipWhiteList = ip2long($ipWhiteList);
  1485. $wildcard = pow(2, (32 - $netmask)) - 1;
  1486. $netmask = ~ $wildcard;
  1487. return (($ip & $netmask) == ($ipWhiteList & $netmask));
  1488. }
  1489. /**
  1490. * 获取禅道的完整URL。
  1491. * Get the full url of the system.
  1492. *
  1493. * @access public
  1494. * @return string
  1495. */
  1496. public static function getSysURL()
  1497. {
  1498. if(defined('RUN_MODE') && RUN_MODE == 'test') return '';
  1499. $httpType = (isset($_SERVER["HTTPS"]) and $_SERVER["HTTPS"] == 'on') ? 'https' : 'http';
  1500. if(isset($_SERVER['HTTP_X_FORWARDED_PROTO']) and strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https') $httpType = 'https';
  1501. if(isset($_SERVER['REQUEST_SCHEME']) and strtolower($_SERVER['REQUEST_SCHEME']) == 'https') $httpType = 'https';
  1502. $httpHost = $_SERVER['HTTP_HOST'];
  1503. return "$httpType://$httpHost";
  1504. }
  1505. /**
  1506. * 检查当前是否为新手教程模式。
  1507. * Check whether view type is tutorial
  1508. *
  1509. * @access public
  1510. * @return bool
  1511. */
  1512. public static function isTutorialMode()
  1513. {
  1514. return !empty($_SESSION['tutorialMode']);
  1515. }
  1516. /**
  1517. * 将数组中的值转为拼音, 以便搜索。
  1518. * Convert items to Pinyin.
  1519. *
  1520. * @param array $items
  1521. * @static
  1522. * @access public
  1523. * @return array
  1524. */
  1525. public static function convert2Pinyin($items)
  1526. {
  1527. foreach($items as $item)
  1528. {
  1529. if(!is_string($item)) return array();
  1530. }
  1531. global $app;
  1532. static $allConverted = array();
  1533. static $pinyin;
  1534. if(empty($pinyin)) $pinyin = $app->loadClass('pinyin');
  1535. $sign = ' aNdAnD ';
  1536. $notConvertedItems = array_diff($items, array_keys($allConverted));
  1537. if($notConvertedItems)
  1538. {
  1539. $convertedPinYin = $pinyin->convert(implode($sign, $notConvertedItems), PINYIN_KEEP_NUMBER | PINYIN_KEEP_ENGLISH);
  1540. $itemsPinYin = explode(trim($sign), implode("\t", $convertedPinYin));
  1541. foreach($notConvertedItems as $item)
  1542. {
  1543. $key = key($itemsPinYin);
  1544. $itemPinYin = $itemsPinYin[$key];
  1545. unset($itemsPinYin[$key]);
  1546. $wordsPinYin = explode("\t", trim($itemPinYin));
  1547. $abbr = '';
  1548. foreach($wordsPinYin as $wordPinyin)
  1549. {
  1550. if($wordPinyin)
  1551. {
  1552. $letter = $wordPinyin[0];
  1553. if(preg_match('/\w/', $letter)) $abbr .= $letter;
  1554. }
  1555. }
  1556. $allConverted[$item] = mb_strtolower(implode('', $wordsPinYin) . ' ' . $abbr);
  1557. }
  1558. }
  1559. $convertedItems = array();
  1560. foreach($items as $item) $convertedItems[$item] = zget($allConverted, $item, null);
  1561. return $convertedItems;
  1562. }
  1563. /**
  1564. * 检查旧版API调用是否合法。
  1565. * Check an entry.
  1566. *
  1567. * @access public
  1568. * @return void
  1569. */
  1570. public function checkEntry()
  1571. {
  1572. /* Old version. */
  1573. if(!isset($_GET[$this->config->moduleVar]) or !isset($_GET[$this->config->methodVar])) $this->response('EMPTY_ENTRY');
  1574. if($this->isOpenMethod($_GET[$this->config->moduleVar], $_GET[$this->config->methodVar])) return true;
  1575. if(!$this->get->code) $this->response('PARAM_CODE_MISSING');
  1576. if(!$this->get->token) $this->response('PARAM_TOKEN_MISSING');
  1577. $entry = $this->loadModel('entry')->getByCode($this->get->code);
  1578. if(!$entry) $this->response('EMPTY_ENTRY');
  1579. if(!$entry->key) $this->response('EMPTY_KEY');
  1580. if(!$this->checkIP($entry->ip)) $this->response('IP_DENIED');
  1581. if(!$this->checkEntryToken($entry)) $this->response('INVALID_TOKEN');
  1582. if($entry->freePasswd == 0 and empty($entry->account)) $this->response('ACCOUNT_UNBOUND');
  1583. $isFreepasswd = ($_GET['m'] == 'user' and strtolower($_GET['f']) == 'apilogin' and $_GET['account'] and $entry->freePasswd);
  1584. if($isFreepasswd) $entry->account = $_GET['account'];
  1585. $user = $this->dao->findByAccount($entry->account)->from(TABLE_USER)->andWhere('deleted')->eq(0)->fetch();
  1586. if(!$user) $this->response('INVALID_ACCOUNT');
  1587. $this->loadModel('user');
  1588. $user->last = helper::now();
  1589. $user->rights = $this->user->authorize($user->account);
  1590. $user->groups = $this->user->getGroups($user->account);
  1591. $user->view = $this->user->grantUserView($user->account, $user->rights['acls']);
  1592. $user->admin = strpos($this->app->company->admins, ",{$user->account},") !== false;
  1593. $this->session->set('user', $user);
  1594. $this->app->user = $user;
  1595. $this->dao->update(TABLE_USER)->set('last')->eq($user->last)->set('visits=visits+1')->where('account')->eq($user->account)->exec();
  1596. $this->loadModel('action')->create('user', $user->id, 'login');
  1597. $this->loadModel('score')->create('user', 'login');
  1598. if($isFreepasswd)
  1599. {
  1600. header("Location: {$this->config->webRoot}");
  1601. helper::end();
  1602. }
  1603. $this->session->set('ENTRY_CODE', $this->get->code);
  1604. $this->session->set('VALID_ENTRY', md5(md5($this->get->code) . helper::getRemoteIp()));
  1605. $this->loadModel('entry')->saveLog($entry->id, $this->server->request_uri);
  1606. /* Add for task #5384. */
  1607. if($_SERVER['REQUEST_METHOD'] == 'POST' and empty($_POST))
  1608. {
  1609. $post = file_get_contents("php://input");
  1610. if(!empty($post)) $post = json_decode($post, true);
  1611. if(!empty($post)) $_POST = $post;
  1612. }
  1613. unset($_GET['code']);
  1614. unset($_GET['token']);
  1615. }
  1616. /**
  1617. * 检查Token是否合法。
  1618. * Check token of an entry.
  1619. *
  1620. * @param object $entry
  1621. * @access public
  1622. * @return bool
  1623. */
  1624. public function checkEntryToken($entry)
  1625. {
  1626. parse_str($this->server->query_String, $queryString);
  1627. unset($queryString['token']);
  1628. /* Change for task #5384. */
  1629. if(isset($queryString['time']))
  1630. {
  1631. $timestamp = $queryString['time'];
  1632. if(strlen($timestamp) > 10) $timestamp = substr($timestamp, 0, 10);
  1633. if(strlen($timestamp) != 10 or $timestamp[0] >= '4') $this->response('ERROR_TIMESTAMP');
  1634. $result = $this->get->token == md5($entry->code . $entry->key . $queryString['time']);
  1635. if($result)
  1636. {
  1637. if($timestamp <= $entry->calledTime) $this->response('CALLED_TIME');
  1638. $this->loadModel('entry')->updateCalledTime($entry->code, (int)$timestamp);
  1639. unset($_GET['time']);
  1640. return $result;
  1641. }
  1642. }
  1643. $queryString = http_build_query($queryString);
  1644. return $this->get->token == md5(md5($queryString) . $entry->key);
  1645. }
  1646. /**
  1647. * 检查当前语言项是否为中文。
  1648. * Check Not CN Lang.
  1649. *
  1650. * @static
  1651. * @access public
  1652. * @return bool
  1653. */
  1654. public static function checkNotCN()
  1655. {
  1656. global $app;
  1657. return strpos('|zh-cn|zh-tw|', '|' . $app->getClientLang() . '|') === false;
  1658. }
  1659. /**
  1660. * 检查对象是否可被修改。
  1661. * Check the object can be changed.
  1662. *
  1663. * @param string $module
  1664. * @param mixed $object
  1665. * @static
  1666. * @access public
  1667. * @return bool
  1668. */
  1669. public static function canBeChanged($module, $object = null)
  1670. {
  1671. if(defined('RUN_MODE') && RUN_MODE == 'api') return true;
  1672. if(empty($object)) return true;
  1673. global $config;
  1674. static $productsStatus = array();
  1675. static $projectsStatus = array();
  1676. static $executionsStatus = array();
  1677. $commonModel = new commonModel();
  1678. if($config->edition != 'open')
  1679. {
  1680. $workflow = $commonModel->loadModel('workflow')->getByModule($module);
  1681. if($workflow && $workflow->buildin == '0') return true;
  1682. }
  1683. /* Check the product is closed. */
  1684. if(!empty($object->product) and is_numeric($object->product) and empty($config->CRProduct))
  1685. {
  1686. if(!isset($productsStatus[$object->product]))
  1687. {
  1688. $product = $commonModel->loadModel('product')->getByID((int)$object->product);
  1689. $productsStatus[$object->product] = $product ? $product->status : '';
  1690. }
  1691. if($productsStatus[$object->product] == 'closed') return false;
  1692. }
  1693. elseif(!empty($object->product) and is_string($object->product) and empty($config->CRProduct))
  1694. {
  1695. $products = array(0) + explode(',', $object->product);
  1696. $products = $commonModel->loadModel('product')->getByIdList($products);
  1697. $productStatus = array();
  1698. foreach($products as $product) $productStatus[$product->status] = 1;
  1699. if(!empty($productStatus['closed']) and count($productStatus) == 1) return false;
  1700. }
  1701. /* Check the project is closed. */
  1702. $productModuleList = array('story', 'bug', 'testcase', 'case', 'testtask', 'release');
  1703. if(!in_array($module, $productModuleList) and !empty($object->project) and is_numeric($object->project) and (empty($config->CRProject) || empty($config->CRExecution)))
  1704. {
  1705. if(!isset($projectsStatus[$object->project]))
  1706. {
  1707. $project = $commonModel->loadModel('project')->getByID((int)$object->project);
  1708. if($project && $project->status == 'closed')
  1709. {
  1710. /* 有的表项目和执行都存在project里。 */
  1711. return $project->type == 'project' ? !empty($config->CRProject) : !empty($config->CRExecution);
  1712. }
  1713. $projectsStatus[$object->project] = $project ? $project->status : '';
  1714. }
  1715. if($projectsStatus[$object->project] == 'closed') return false;
  1716. }
  1717. /* Check the execution is closed. */
  1718. $productModuleList = array('story', 'bug', 'testcase', 'case', 'testtask');
  1719. if(!in_array($module, $productModuleList) and !empty($object->execution) and is_numeric($object->execution) and empty($config->CRExecution))
  1720. {
  1721. if(!isset($executionsStatus[$object->execution]))
  1722. {
  1723. $execution = $commonModel->loadModel('execution')->getByID((int)$object->execution);
  1724. $executionsStatus[$object->execution] = $execution ? $execution->status : '';
  1725. }
  1726. if($executionsStatus[$object->execution] == 'closed') return false;
  1727. }
  1728. return true;
  1729. }
  1730. /**
  1731. * Check object can modify.
  1732. *
  1733. * @param string $type product|Execution
  1734. * @param object|null $object
  1735. * @static
  1736. * @access public
  1737. * @return bool
  1738. */
  1739. public static function canModify($type, $object)
  1740. {
  1741. global $config;
  1742. if(empty($object)) return true;
  1743. /* Judge that if the closed object(product|project|execution) is readonly from config table. The default is can modify. */
  1744. if($type == 'product' and empty($config->CRProduct) and $object->status == 'closed') return false;
  1745. if($type == 'project' and empty($config->CRProject) and $object->status == 'closed') return false;
  1746. if($type == 'execution' and empty($config->CRExecution))
  1747. {
  1748. if($object->status == 'closed') return false;
  1749. if(!isset($object->project) || !empty($config->CRProject)) return true;
  1750. /* Check the execution's project is closed. */
  1751. static $projectsStatus = array();
  1752. $commonModel = new commonModel();
  1753. if(!isset($projectsStatus[$object->project]))
  1754. {
  1755. $project = $commonModel->loadModel('project')->getByID((int)$object->project);
  1756. $projectsStatus[$object->project] = $project ? $project->status : '';
  1757. }
  1758. if($projectsStatus[$object->project] == 'closed') return false;
  1759. }
  1760. return true;
  1761. }
  1762. /**
  1763. * Response.
  1764. *
  1765. * @param string $reasonPhrase
  1766. * @access public
  1767. * @return void
  1768. */
  1769. public function response($reasonPhrase)
  1770. {
  1771. $response = new stdclass();
  1772. if(isset($this->config->entry->errcode))
  1773. {
  1774. $response->errcode = $this->config->entry->errcode[$reasonPhrase];
  1775. $response->errmsg = urlencode($this->lang->entry->errmsg[$reasonPhrase]);
  1776. helper::end(urldecode(json_encode($response)));
  1777. }
  1778. else
  1779. {
  1780. $response->error = $reasonPhrase;
  1781. helper::end(urldecode(json_encode($response)));
  1782. }
  1783. }
  1784. /**
  1785. * Http.
  1786. *
  1787. * @param string $url
  1788. * @param mixed $data
  1789. * @param array $options This is option and value pair, like CURLOPT_HEADER => true. Use curl_setopt function to set options.
  1790. * @param array $headers Set request headers.
  1791. * @param string $dataType
  1792. * @param string $method POST|PATCH|PUT
  1793. * @param int $timeout
  1794. * @param bool $httpCode Return a array contains response, http code, body, header. such as [response, http_code, 'body' => body, 'header' => header].
  1795. * @param bool $log Save to log or not
  1796. * @static
  1797. * @access public
  1798. * @return string|array|bool
  1799. */
  1800. public static function http($url, $data = null, $options = array(), $headers = array(), $dataType = 'data', $method = 'POST', $timeout = 30, $httpCode = false, $log = true)
  1801. {
  1802. global $lang, $app;
  1803. if(common::$httpClient)
  1804. {
  1805. return common::$httpClient->request($url, $data, $options, $headers, $dataType, $method, $timeout, $httpCode, $log);
  1806. }
  1807. elseif(!extension_loaded('curl'))
  1808. {
  1809. if($dataType == 'json') return print($lang->error->noCurlExt);
  1810. return json_encode(array('result' => 'fail', 'message' => $lang->error->noCurlExt));
  1811. }
  1812. commonModel::$requestErrors = array();
  1813. $requestType = 'GET';
  1814. if(func_num_args() >= 6) $requestType = $method; /* Specify $method parameter explicitly. */
  1815. if(!is_array($headers)) $headers = (array)$headers;
  1816. $headers[] = 'API-RemoteIP: ' . helper::getRemoteIp(); /* Real IP of real user. */
  1817. $headers[] = 'API-LocalIP: ' . zget($_SERVER, 'SERVER_ADDR', ''); /* Server IP of self. */
  1818. if($dataType == 'json')
  1819. {
  1820. $headers[] = 'Content-Type: application/json;charset=utf-8';
  1821. if(!empty($data)) $data = json_encode($data);
  1822. }
  1823. $curl = curl_init();
  1824. curl_setopt($curl, CURLOPT_USERAGENT, 'ZenTao PMS ' . $app->config->version);
  1825. curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, $timeout);
  1826. curl_setopt($curl, CURLOPT_TIMEOUT, $timeout);
  1827. curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
  1828. curl_setopt($curl, CURLOPT_ENCODING,'');
  1829. curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
  1830. curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
  1831. curl_setopt($curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
  1832. curl_setopt($curl, CURLOPT_HEADER, $httpCode);
  1833. curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 2);
  1834. curl_setopt($curl, CURLINFO_HEADER_OUT, true);
  1835. curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
  1836. curl_setopt($curl, CURLOPT_URL, $url);
  1837. if(!empty($data))
  1838. {
  1839. if(is_object($data)) $data = (array) $data;
  1840. if($method == 'POST') curl_setopt($curl, CURLOPT_POST, true);
  1841. if(in_array($method, array('PATCH', 'PUT'))) curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
  1842. $requestType = $method;
  1843. curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
  1844. }
  1845. if($options) curl_setopt_array($curl, $options);
  1846. $response = curl_exec($curl);
  1847. $errno = curl_errno($curl);
  1848. $errors = empty($errno) ? 0 : curl_error($curl);
  1849. $info = curl_getinfo($curl);
  1850. if(!$response) $response = '';
  1851. if($httpCode)
  1852. {
  1853. $httpCode = $info['http_code'] ?? curl_getinfo($curl, CURLINFO_HTTP_CODE);
  1854. $headerSize = $info['header_size'] ?? curl_getinfo($curl, CURLINFO_HEADER_SIZE);
  1855. $headerString = substr($response, 0, $headerSize);
  1856. $body = substr($response, $headerSize);
  1857. /* Parse header. */
  1858. $header = explode("\n", $headerString);
  1859. $newHeader = array();
  1860. foreach($header as $item)
  1861. {
  1862. $field = explode(':', $item);
  1863. if(count($field) < 2) continue;
  1864. $headerKey = array_shift($field);
  1865. $newHeader[$headerKey] = implode('', $field);
  1866. }
  1867. }
  1868. curl_close($curl);
  1869. if($log or $app->config->debug)
  1870. {
  1871. $runMode = PHP_SAPI == 'cli' ? '_cli' : '';
  1872. $logFile = $app->getLogRoot() . 'saas' . $runMode . '.' . date('Ymd') . '.log.php';
  1873. if(!file_exists($logFile)) file_put_contents($logFile, '<?php die(); ?' . '>');
  1874. $saasLog = date('Ymd H:i:s') . ': ' . $app->getURI() . "\n";
  1875. $saasLog .= "{$requestType} url: {$url}\n";
  1876. if($app->config->debug)
  1877. {
  1878. $saasLog .= 'request header: ' . json_encode($headers) . PHP_EOL;
  1879. if(isset($newHeader)) $saasLog .= 'response header: ' . json_encode($newHeader) . PHP_EOL;
  1880. }
  1881. if(!empty($data)) $saasLog .= 'data: ' . print_r($data, true) . "\n";
  1882. $saasLog .= 'results:' . print_r($response, true) . "\n";
  1883. if(!empty($errors))
  1884. {
  1885. $saasLog .= "errno: {$errno}\n";
  1886. $saasLog .= "errors: {$errors}\n";
  1887. $saasLog .= 'info: ' . print_r($info, true) . "\n";
  1888. }
  1889. file_put_contents($logFile, $saasLog, FILE_APPEND);
  1890. }
  1891. if($errors) commonModel::$requestErrors[] = $errors;
  1892. return $httpCode ? array($response, $httpCode, 'body' => $body, 'header' => $newHeader, 'errno' => $errno, 'info' => $info, 'response' => $response) : $response;
  1893. }
  1894. /**
  1895. * 设置要展示的顶部一级菜单。
  1896. * Set main menu.
  1897. *
  1898. * @static
  1899. * @access public
  1900. * @return bool
  1901. */
  1902. public static function setMainMenu()
  1903. {
  1904. global $app, $lang;
  1905. $tab = $app->tab;
  1906. $isTutorialMode = common::isTutorialMode();
  1907. $currentModule = $isTutorialMode ? $app->moduleName : $app->rawModule;
  1908. $currentMethod = $isTutorialMode ? $app->methodName : $app->rawMethod;
  1909. $currentMethod = strtolower($currentMethod);
  1910. /* If homeMenu is not exists or unset, display menu. */
  1911. $lang->menu = isset($lang->$tab->menu) ? $lang->$tab->menu : array();
  1912. $lang->menuOrder = isset($lang->$tab->menuOrder) ? $lang->$tab->menuOrder : array();
  1913. if(!isset($lang->$tab->homeMenu)) return false;
  1914. if($currentModule == $tab and $currentMethod == 'create')
  1915. {
  1916. $lang->menu = $lang->$tab->homeMenu;
  1917. return true;
  1918. }
  1919. /* If the method is in homeMenu, display homeMenu. */
  1920. foreach($lang->$tab->homeMenu as $menu)
  1921. {
  1922. $link = is_array($menu) ? $menu['link'] : $menu;
  1923. $params = explode('|', $link);
  1924. $method = strtolower($params[2]);
  1925. if($method == $currentMethod)
  1926. {
  1927. $lang->menu = $lang->$tab->homeMenu;
  1928. return true;
  1929. }
  1930. $alias = isset($menu['alias']) ? explode(',', strtolower($menu['alias'])) : array();
  1931. $exclude = isset($menu['exclude']) ? explode(',', strtolower($menu['exclude'])) : array();
  1932. if(in_array($currentMethod, $alias) && !in_array("{$currentModule}-{$currentMethod}", $exclude))
  1933. {
  1934. $lang->menu = $lang->$tab->homeMenu;
  1935. return true;
  1936. }
  1937. if(isset($menu['subModule']) and strpos(",{$menu['subModule']},", ",$currentModule,") !== false)
  1938. {
  1939. $lang->menu = $lang->$tab->homeMenu;
  1940. return true;
  1941. }
  1942. }
  1943. return false;
  1944. }
  1945. /**
  1946. * 获取两种对象的关联关系。
  1947. * Get relations for two object.
  1948. *
  1949. * @param string $AType
  1950. * @param int $AID
  1951. * @param string $BType
  1952. * @param int $BID
  1953. *
  1954. * @access public
  1955. * @return array
  1956. */
  1957. public function getRelations($AType = '', $AID = 0, $BType = '', $BID = 0)
  1958. {
  1959. if(common::isTutorialMode()) return array();
  1960. return $this->dao->select('*')->from(TABLE_RELATION)
  1961. ->where('AType')->eq($AType)
  1962. ->andWhere('AID')->eq($AID)
  1963. ->andwhere('BType')->eq($BType)
  1964. ->beginIF($BID)->andwhere('BID')->eq($BID)->fi()
  1965. ->fetchAll();
  1966. }
  1967. /**
  1968. * 将导航中的占位符替换为实际的值。
  1969. * Replace the %s of one key of a menu by objectID or $params.
  1970. *
  1971. * All the menus are defined in the common's language file. But there're many dynamic params, so in the defination,
  1972. * we used %s as placeholder. These %s should be setted in one module.
  1973. *
  1974. * @param string $moduleName
  1975. * @param int $objectID
  1976. * @param array $params
  1977. *
  1978. * @access public
  1979. * @return void
  1980. */
  1981. public static function setMenuVars($moduleName, $objectID, $params = array())
  1982. {
  1983. global $app, $lang;
  1984. $menuKey = 'menu';
  1985. if($app->viewType == 'mhtml') $menuKey = 'webMenu';
  1986. foreach($lang->$moduleName->$menuKey as $label => $menu)
  1987. {
  1988. if(!$menu) continue;
  1989. $lang->$moduleName->$menuKey->$label = static::setMenuVarsEx($menu, $objectID, $params);
  1990. if(isset($menu['subMenu']))
  1991. {
  1992. foreach($menu['subMenu'] as $key1 => $subMenu)
  1993. {
  1994. $lang->$moduleName->$menuKey->{$label}['subMenu']->$key1 = static::setMenuVarsEx($subMenu, $objectID, $params);
  1995. if(!isset($subMenu['dropMenu'])) continue;
  1996. foreach($subMenu['dropMenu'] as $key2 => $dropMenu)
  1997. {
  1998. $lang->$moduleName->$menuKey->{$label}['subMenu']->$key1['dropMenu']->$key2 = static::setMenuVarsEx($dropMenu, $objectID, $params);
  1999. }
  2000. }
  2001. }
  2002. if(!isset($menu['dropMenu'])) continue;
  2003. foreach($menu['dropMenu'] as $key2 => $dropMenu)
  2004. {
  2005. $lang->$moduleName->$menuKey->{$label}['dropMenu']->$key2 = static::setMenuVarsEx($dropMenu, $objectID, $params);
  2006. if(!isset($dropMenu['subMenu'])) continue;
  2007. foreach($dropMenu['subMenu'] as $key3 => $subMenu)
  2008. {
  2009. $lang->$moduleName->$menuKey->{$label}['dropMenu']->$key3 = static::setMenuVarsEx($subMenu, $objectID, $params);
  2010. }
  2011. }
  2012. }
  2013. /* If objectID is set, cannot use homeMenu. */
  2014. unset($lang->$moduleName->homeMenu);
  2015. }
  2016. /**
  2017. * 检查菜单中的占位符是否已经替换,如果没有,则替换。
  2018. * Check if the menu vars replaced, if not, replace them.
  2019. *
  2020. * @static
  2021. * @access public
  2022. * @return void
  2023. */
  2024. public static function checkMenuVarsReplaced()
  2025. {
  2026. global $app, $lang;
  2027. $tab = $app->tab;
  2028. $varsReplaced = true;
  2029. foreach($lang->menu as $menu)
  2030. {
  2031. if(!is_array($menu)) continue;
  2032. if(isset($menu['link']) and strpos($menu['link'], '%s') !== false) $varsReplaced = false;
  2033. if(!isset($menu['link']) and is_string($menu) and strpos($menu, '%s') !== false) $varsReplaced = false;
  2034. if(!$varsReplaced) break;
  2035. }
  2036. if(!$varsReplaced and strpos("|program|product|project|execution|qa|safe|", "|{$tab}|") !== false)
  2037. {
  2038. $isTutorialMode = common::isTutorialMode();
  2039. $currentModule = $isTutorialMode ? $app->moduleName : $app->rawModule;
  2040. if(isset($lang->$currentModule->menu))
  2041. {
  2042. $lang->menu = isset($lang->$currentModule->menu) ? $lang->$currentModule->menu : array();
  2043. $lang->menuOrder = isset($lang->$currentModule->menuOrder) ? $lang->$currentModule->menuOrder : array();
  2044. $app->tab = zget($lang->navGroup, $currentModule);
  2045. }
  2046. else
  2047. {
  2048. static::setMenuVars($tab, (int)$app->session->$tab);
  2049. }
  2050. }
  2051. }
  2052. /**
  2053. * 将导航中的占位符替换为实际的值。
  2054. * Replace the %s of one key of a menu by objectID or $params.
  2055. *
  2056. * @param array|string $menu
  2057. * @param int $objectID
  2058. * @param mixed[] $params
  2059. * @return mixed[]|string
  2060. */
  2061. public static function setMenuVarsEx($menu, $objectID, $params = array())
  2062. {
  2063. if(is_array($menu))
  2064. {
  2065. if(!isset($menu['link'])) return $menu;
  2066. $link = sprintf($menu['link'], $objectID);
  2067. $menu['link'] = vsprintf($link, $params);
  2068. if(!empty($menu['links']))
  2069. {
  2070. foreach($menu['links'] as $key => $link) $menu['links'][$key] = sprintf($link, $objectID);
  2071. }
  2072. }
  2073. else
  2074. {
  2075. $menu = sprintf($menu, $objectID);
  2076. $menu = vsprintf($menu, $params);
  2077. }
  2078. return $menu;
  2079. }
  2080. /**
  2081. * Process markdown.
  2082. *
  2083. * @param string $markdown
  2084. * @static
  2085. * @access public
  2086. * @return string
  2087. */
  2088. public static function processMarkdown($markdown)
  2089. {
  2090. if(empty($markdown)) return false;
  2091. global $app;
  2092. $parsedown = $app->loadClass('parsedown');
  2093. return $parsedown->text($markdown);
  2094. }
  2095. /**
  2096. * 排序FeatureBar。
  2097. * Sort featureBar.
  2098. *
  2099. * @param string $module
  2100. * @param string $method
  2101. * @static
  2102. * @access public
  2103. * @return bool
  2104. */
  2105. public static function sortFeatureMenu($module = '', $method = '')
  2106. {
  2107. global $lang, $config, $app;
  2108. $module = $module ? $module : $app->rawModule;
  2109. $method = $method ? $method : $app->rawMethod;
  2110. /* It will be sorted according to the workflow in the future */
  2111. if(!empty($config->featureBarSort[$module][$method]))
  2112. {
  2113. $featureBar = array();
  2114. if(empty($lang->$module->featureBar[$method])) return false;
  2115. foreach($lang->$module->featureBar[$method] as $key => $label)
  2116. {
  2117. foreach($config->featureBarSort[$module][$method] as $currentKey => $afterKey)
  2118. {
  2119. if($key == $currentKey) continue;
  2120. $featureBar[$method][$key] = $label;
  2121. if($key == $afterKey && !empty($lang->$module->featureBar[$method][$currentKey]))
  2122. {
  2123. $featureBar[$method][$currentKey] = $lang->$module->featureBar[$method][$currentKey];
  2124. }
  2125. }
  2126. }
  2127. $lang->$module->featureBar = $featureBar;
  2128. }
  2129. return true;
  2130. }
  2131. /**
  2132. * Get method of API.
  2133. *
  2134. * @param string $url
  2135. * @param array|object $data
  2136. * @param array $headers example: array("key1:value1", "key2:value2")
  2137. * @access public
  2138. * @return object
  2139. */
  2140. public static function apiGet($url, $data = array(), $headers = array())
  2141. {
  2142. $url .= (strpos($url, '?') !== false ? '&' : '?') . http_build_query($data, '', '&', PHP_QUERY_RFC3986);
  2143. $result = json_decode(commonModel::http($url, $data, array(CURLOPT_CUSTOMREQUEST => 'GET'), $headers, 'json'));
  2144. if($result && $result->code == 200) return $result;
  2145. return static::apiError($result);
  2146. }
  2147. /**
  2148. * Post method of API.
  2149. *
  2150. * @param string $url
  2151. * @param array|object $data
  2152. * @param array $headers example: array("key1:value1", "key2:value2")
  2153. * @access public
  2154. * @return object
  2155. */
  2156. public static function apiPost($url, $data, $headers = array())
  2157. {
  2158. $result = json_decode(commonModel::http($url, $data, array(CURLOPT_CUSTOMREQUEST => 'POST'), $headers, 'json'));
  2159. if($result && $result->code == 200) return $result;
  2160. return static::apiError($result);
  2161. }
  2162. /**
  2163. * Put method of API.
  2164. *
  2165. * @param string $url
  2166. * @param array|object $data
  2167. * @param array $headers example: array("key1:value1", "key2:value2")
  2168. * @access public
  2169. * @return object
  2170. */
  2171. public static function apiPut($url, $data, $headers = array())
  2172. {
  2173. $result = json_decode(commonModel::http($url, $data, array(CURLOPT_CUSTOMREQUEST => 'PUT'), $headers, 'json'));
  2174. if($result && $result->code == 200) return $result;
  2175. return static::apiError($result);
  2176. }
  2177. /**
  2178. * Delete method of API.
  2179. *
  2180. * @param string $url
  2181. * @param array|object $data
  2182. * @param array $headers example: array("key1:value1", "key2:value2")
  2183. * @access public
  2184. * @return object
  2185. */
  2186. public static function apiDelete($url, $data, $headers = array())
  2187. {
  2188. $result = json_decode(commonModel::http($url, $data, array(CURLOPT_CUSTOMREQUEST => 'DELETE'), $headers, 'json'));
  2189. if($result && $result->code == 200) return $result;
  2190. return static::apiError($result);
  2191. }
  2192. /**
  2193. * Return error object of api server.
  2194. *
  2195. * @param object|null $result
  2196. * @access protected
  2197. * @return object
  2198. */
  2199. protected static function apiError($result)
  2200. {
  2201. global $lang;
  2202. if($result && $result->code) return $result;
  2203. $error = new stdclass;
  2204. $error->code = 600;
  2205. $error->message = $lang->error->httpServerError;
  2206. return $error;
  2207. }
  2208. /**
  2209. * 构建详情操作链接配置。
  2210. * Build action item.
  2211. *
  2212. * @param string $module
  2213. * @param string $method
  2214. * @param string $params
  2215. * @param object $object
  2216. * @param array $attrs
  2217. * @static
  2218. * @access public
  2219. * @return void
  2220. */
  2221. public static function buildActionItem($module, $method, $params, $object = null, $attrs = array())
  2222. {
  2223. if(!commonModel::hasPriv($module, $method, $object)) return array();
  2224. global $app;
  2225. $clickable = true;
  2226. if(is_object($object))
  2227. {
  2228. if($app->getModuleName() != $module) $app->control->loadModel($module);
  2229. $modelClass = class_exists("ext{$module}Model") ? "ext{$module}Model" : $module . "Model";
  2230. if(class_exists($modelClass) and method_exists($modelClass, 'isClickable')) $clickable = call_user_func_array(array($modelClass, 'isClickable'), array($object, $method));
  2231. }
  2232. if(!$clickable) return array();
  2233. $item['url'] = helper::createLink($module, $method, $params);
  2234. foreach($attrs as $attr => $value) $item[$attr] = $value;
  2235. return $item;
  2236. }
  2237. /**
  2238. * 按照模块生成详情页的操作按钮。
  2239. * Build operate actions menu.
  2240. *
  2241. * @param object $data
  2242. * @param string $moduleName
  2243. * @access public
  2244. * @return array
  2245. */
  2246. public function buildOperateMenu($data, $moduleName = '')
  2247. {
  2248. global $app, $config;
  2249. /* build operate menu. */
  2250. $moduleName = $moduleName ? $moduleName : $app->moduleName;
  2251. $methodName = $app->methodName;
  2252. $actionsMenu = array();
  2253. $isInModal = helper::isAjaxRequest('modal');
  2254. $this->loadModel($moduleName);
  2255. foreach($config->{$moduleName}->actions->{$methodName} as $menu => $actionList)
  2256. {
  2257. $actions = array();
  2258. foreach($actionList as $action)
  2259. {
  2260. $actionData = !empty($config->{$moduleName}->actionList[$action]) ? $config->{$moduleName}->actionList[$action] : array();
  2261. if($isInModal && !empty($actionData['notInModal'])) continue;
  2262. $actionData['name'] = $action;
  2263. if(isset($actionData['data-app']) && $actionData['data-app'] == 'my') $actionData['data-app'] = $this->app->tab;
  2264. if($isInModal && !isset($actionData['data-target']) && isset($actionData['data-toggle']) && $actionData['data-toggle'] == 'modal')
  2265. {
  2266. $actionData['data-load'] = 'modal';
  2267. unset($actionData['data-toggle']);
  2268. }
  2269. if(isset($actionData['items']) && is_array($actionData['items']))
  2270. {
  2271. $itemList = array();
  2272. foreach($actionData['items'] as $itemAction)
  2273. {
  2274. $itemActionData = $config->{$moduleName}->actionList[$itemAction];
  2275. $itemActionData = $this->checkPrivForOperateAction($itemActionData, $itemAction, $moduleName, $data, $menu);
  2276. if(($isInModal && !empty($itemActionData['notInModal'])) || $itemActionData === false) continue;
  2277. $itemList[] = $itemActionData;
  2278. }
  2279. $actionData['items'] = $itemList;
  2280. if(!empty($actionData['items'])) $actions[] = $actionData;
  2281. }
  2282. else
  2283. {
  2284. $actionData = $this->checkPrivForOperateAction($actionData, $action, $moduleName, $data, $menu);
  2285. if($actionData !== false) $actions[] = $actionData;
  2286. }
  2287. }
  2288. $actionsMenu[$menu] = $actions;
  2289. }
  2290. return $actionsMenu;
  2291. }
  2292. /**
  2293. * 检查详情页操作按钮的权限。
  2294. * Check the privilege of the operate action.
  2295. *
  2296. * @param array $actionData
  2297. * @param string $action
  2298. * @param string $moduleName
  2299. * @param object $data
  2300. * @param string $menu
  2301. * @access protected
  2302. * @return array|bool
  2303. */
  2304. public function checkPrivForOperateAction($actionData, $action, $moduleName, $data, $menu)
  2305. {
  2306. $rawModule = $moduleName;
  2307. if(!empty($actionData['url']) && is_array($actionData['url']))
  2308. {
  2309. $moduleName = ($actionData['url']['module'] == 'story' && in_array($moduleName, array('epic', 'requirement', 'story'))) ? $data->type : $actionData['url']['module'];
  2310. $methodName = $actionData['url']['method'];
  2311. $params = $actionData['url']['params'];
  2312. $storySubdivide = in_array($moduleName, array('epic', 'requirement', 'story')) && $action == 'subdivide';
  2313. if(!$storySubdivide && !common::hasPriv($moduleName, $methodName, $data)) return false;
  2314. $actionData['url'] = helper::createLink($moduleName, $methodName, $params, '', !empty($actionData['url']['onlybody']) ? true : false);
  2315. }
  2316. else if(!empty($actionData['data-url']) && is_array($actionData['data-url']))
  2317. {
  2318. $moduleName = ($actionData['data-url']['module'] == 'story' && in_array($moduleName, array('epic', 'requirement', 'story'))) ? $data->type : $actionData['data-url']['module'];
  2319. $methodName = $actionData['data-url']['method'];
  2320. $params = $actionData['data-url']['params'];
  2321. $storySubdivide = in_array($moduleName, array('epic', 'requirement', 'story')) && $action == 'subdivide';
  2322. if(!$storySubdivide && !common::hasPriv($moduleName, $methodName, $data)) return false;
  2323. $actionData['data-url'] = helper::createLink($moduleName, $methodName, $params, '', !empty($actionData['data-url']['onlybody']) ? true : false);
  2324. }
  2325. elseif(empty($actionData['url']))
  2326. {
  2327. return $actionData;
  2328. }
  2329. else
  2330. {
  2331. if($action == 'importToLib' && in_array($moduleName, array('epic', 'requirement'))) $moduleName = 'story';
  2332. if(!common::hasPriv($moduleName, $action, $data)) return false;
  2333. }
  2334. if(!empty($actionData['notLoadModel']) && $moduleName != $rawModule) $moduleName = $rawModule;
  2335. if(!isset($this->$moduleName)) $this->loadModel($moduleName);
  2336. if(empty($actionData['isClickable']) && isset($this->$moduleName) && method_exists($this->{$moduleName}, 'isClickable') && false === $this->{$moduleName}->isClickable($data, $action)) return false;
  2337. global $config;
  2338. if($config->edition != 'open')
  2339. {
  2340. $flowAction = $this->loadModel('workflowaction')->getByModuleAndAction($moduleName, $action);
  2341. $isActionEnable = $flowAction && $flowAction->extensionType != 'none' && $flowAction->status == 'enable' && !empty($flowAction->conditions);
  2342. if($isActionEnable && !$this->loadModel('flow')->checkConditions($flowAction->conditions, $data))
  2343. {
  2344. return false;
  2345. }
  2346. }
  2347. if(!empty($actionData['hint']) && !isset($actionData['text'])) $actionData['text'] = $actionData['hint'];
  2348. if($menu == 'suffixActions' && !empty($actionData['text']) && empty($actionData['showText'])) $actionData['text'] = '';
  2349. if(isset($actionData['data-app']) && $actionData['data-app'] == 'my') $actionData['data-app'] = $this->app->tab;
  2350. return $actionData;
  2351. }
  2352. /**
  2353. * 计算应用运行时间。
  2354. * Print duration of a instance.
  2355. *
  2356. * @param int $seconds
  2357. * @param string $format y-m-d-h-i-s, case insensitive
  2358. * @static
  2359. * @access public
  2360. * @return string
  2361. */
  2362. public static function printDuration($seconds, $format = 'y-m-d-h-i-s')
  2363. {
  2364. global $lang;
  2365. $duration = '';
  2366. $format = strtolower($format);
  2367. if(strpos($format, 'y') !== false)
  2368. {
  2369. $years = intval($seconds / (3600 * 24 * 365));
  2370. $leftSeconds = intval($seconds % (3600 * 24 * 365));
  2371. if($years) $duration .= $years . $lang->year;
  2372. }
  2373. if(strpos($format, 'm') !== false)
  2374. {
  2375. $months = intval($leftSeconds / (3600 * 24 * 30));
  2376. $leftSeconds = intval($leftSeconds % (3600 * 24 * 30));
  2377. if($months) $duration .= $months . $lang->month;
  2378. }
  2379. if(strpos($format, 'd') !== false)
  2380. {
  2381. $days = intval($leftSeconds / (3600 * 24));
  2382. $leftSeconds = intval($leftSeconds % (3600 * 24));
  2383. if($days) $duration .= $days . trim($lang->day);
  2384. }
  2385. if(strpos($format, 'h') !== false)
  2386. {
  2387. $hours = intval($leftSeconds / 3600);
  2388. $leftSeconds = intval($leftSeconds % 3600);
  2389. if($hours) $duration .= $hours . $lang->hour;
  2390. }
  2391. if(strpos($format, 'i') !== false)
  2392. {
  2393. $minutes = intval($leftSeconds / 60);
  2394. $leftSeconds = intval($leftSeconds % 60);
  2395. if($minutes) $duration .= $minutes . $lang->minute;
  2396. }
  2397. if(strpos($format, 's') !== false)
  2398. {
  2399. $seconds = intval($leftSeconds % 3600);
  2400. if($seconds) $duration .= $seconds . $lang->second;
  2401. }
  2402. return $duration;
  2403. }
  2404. /**
  2405. * Check object priv.
  2406. *
  2407. * @param string $objectType program|project|product|execution
  2408. * @param int $objectID
  2409. * @access public
  2410. * @return bool
  2411. */
  2412. public function checkPrivByObject($objectType, $objectID)
  2413. {
  2414. if(common::isTutorialMode()) return true;
  2415. $objectType = strtolower($objectType);
  2416. if(in_array($objectType, array('program', 'project', 'product', 'execution'))) return $this->loadModel($objectType)->checkPriv($objectID);
  2417. return false;
  2418. }
  2419. /**
  2420. * Replace menu lang.
  2421. *
  2422. * @static
  2423. * @access public
  2424. * @return void
  2425. */
  2426. public static function replaceMenuLang()
  2427. {
  2428. global $lang;
  2429. if(empty($lang->db->custom)) return;
  2430. foreach($lang->db->custom as $moduleName => $sectionMenus)
  2431. {
  2432. if(strpos($moduleName, 'Menu') === false) continue;
  2433. $isSecondMenu = strpos($moduleName, 'SubMenu') === false;
  2434. $moduleName = str_replace($isSecondMenu ? 'Menu' : 'SubMenu', '', $moduleName);
  2435. foreach($sectionMenus as $section => $menus)
  2436. {
  2437. foreach($menus as $key => $value)
  2438. {
  2439. /* Get second menu. */
  2440. if($isSecondMenu)
  2441. {
  2442. $isDropMenu = strpos($section, 'DropMenu') !== false;
  2443. if(!$isDropMenu)
  2444. {
  2445. if(!isset($lang->{$moduleName}->{$section})) break;
  2446. if(is_object($lang->{$moduleName}->{$section}) and isset($lang->{$moduleName}->{$section}->{$key})) $settingMenu = &$lang->{$moduleName}->{$section}->{$key};
  2447. if(is_array($lang->{$moduleName}->{$section}) and isset($lang->{$moduleName}->{$section}[$key])) $settingMenu = &$lang->{$moduleName}->{$section}[$key];
  2448. }
  2449. else
  2450. {
  2451. /* Get drop menu in second menu. */
  2452. $dropMenuKey = str_replace('DropMenu', '', $section);
  2453. if(!isset($lang->{$moduleName}->menu->{$dropMenuKey}['dropMenu']->{$key})) break;
  2454. $settingMenu = &$lang->{$moduleName}->menu->{$dropMenuKey}['dropMenu']->{$key};
  2455. }
  2456. }
  2457. /* Get third menu. */
  2458. elseif(isset($lang->{$moduleName}->menu->{$section}['subMenu']))
  2459. {
  2460. $subMenu = $lang->{$moduleName}->menu->{$section}['subMenu'];
  2461. if(is_object($subMenu) and isset($subMenu->{$key})) $settingMenu = &$lang->{$moduleName}->menu->{$section}['subMenu']->{$key};
  2462. if(is_array($subMenu) and isset($subMenu[$key])) $settingMenu = &$lang->{$moduleName}->menu->{$section}['subMenu'][$key];
  2463. }
  2464. /* Set custom menu lang. */
  2465. if(!empty($settingMenu))
  2466. {
  2467. if(is_string($settingMenu)) $settingMenu = $value . substr($settingMenu, strpos($settingMenu, '|'));
  2468. if(is_array($settingMenu) and isset($settingMenu['link'])) $settingMenu['link'] = $value . substr($settingMenu['link'], strpos($settingMenu['link'], '|'));
  2469. unset($settingMenu);
  2470. }
  2471. }
  2472. }
  2473. }
  2474. }
  2475. /**
  2476. * Check operate effort.
  2477. *
  2478. * @param object $effort
  2479. * @access public
  2480. * @return bool
  2481. */
  2482. public function canOperateEffort($effort)
  2483. {
  2484. if($this->app->user->admin) return true;
  2485. if(empty((array)$effort)) return true;
  2486. $actor = $effort->account;
  2487. /* 如果是本人,可以直接修改。 */
  2488. if($actor == $this->app->user->account) return true;
  2489. /* 如果当前账户是项目负责人,则可以修改团队成员日志。*/
  2490. if(!empty($effort->project))
  2491. {
  2492. $PM = $this->dao->select('PM')->from(TABLE_PROJECT)->where('id')->eq($effort->project)->fetch('PM');
  2493. if($PM == $this->app->user->account) return true;
  2494. }
  2495. /* 如果当前账户是执行负责人,则可以修改团队成员日志。*/
  2496. if(!empty($effort->execution))
  2497. {
  2498. $PM = $this->dao->select('PM')->from(TABLE_PROJECT)->where('id')->eq($effort->execution)->fetch('PM');
  2499. if($PM == $this->app->user->account) return true;
  2500. }
  2501. /* 如果当前账户是上级部门(包括当前部门)负责人,则可以修改下属员工的日志。*/
  2502. $actorDeptPath = $this->dao->select('path')->from(TABLE_DEPT)->alias('t1')
  2503. ->leftJoin(TABLE_USER)->alias('t2')->on('t2.dept = t1.id')
  2504. ->where('t2.account')->eq($actor)
  2505. ->fetch('path');
  2506. $deptManagers = $this->dao->select('manager')->from(TABLE_DEPT)->where('id')->in(explode(',', trim($actorDeptPath, ',')))->fetchPairs();
  2507. if(in_array($this->app->user->account, $deptManagers)) return true;
  2508. return false;
  2509. }
  2510. /**
  2511. * Build icon button.
  2512. *
  2513. * @param string $module
  2514. * @param string $method
  2515. * @param string $vars
  2516. * @param object $object
  2517. * @param string $type button|list
  2518. * @param string $icon
  2519. * @param string $target
  2520. * @param string $extraClass
  2521. * @param bool $onlyBody
  2522. * @param string $misc
  2523. * @param bool $extraEnabled
  2524. * @static
  2525. * @access public
  2526. * @return void
  2527. */
  2528. public static function buildIconButton($module, $method, $vars = '', $object = '', $type = 'button', $icon = '', $target = '', $extraClass = '', $onlyBody = false, $misc = '', $title = '', $programID = 0, $extraEnabled = '')
  2529. {
  2530. if(isonlybody() and strpos($extraClass, 'showinonlybody') === false) return false;
  2531. /* Remove iframe for operation button in modal. Prevent pop up in modal. */
  2532. if(isonlybody() and strpos($extraClass, 'showinonlybody') !== false) $extraClass = str_replace('iframe', '', $extraClass);
  2533. global $app, $lang, $config;
  2534. /* Judge the $method of $module clickable or not, default is clickable. */
  2535. $clickable = true;
  2536. if(is_bool($extraEnabled))
  2537. {
  2538. $clickable = $extraEnabled;
  2539. }
  2540. else if(is_object($object))
  2541. {
  2542. if($app->getModuleName() != $module) $app->control->loadModel($module);
  2543. $modelClass = class_exists("ext{$module}Model") ? "ext{$module}Model" : $module . "Model";
  2544. if(class_exists($modelClass) and method_exists($modelClass, 'isClickable'))
  2545. {
  2546. //$clickable = call_user_func_array(array($modelClass, 'isClickable'), array('object' => $object, 'method' => $method));
  2547. // fix bug on php 8.0 link: https://www.php.net/manual/zh/function.call-user-func-array.php#125953
  2548. $clickable = call_user_func_array(array($modelClass, 'isClickable'), array($object, $method));
  2549. }
  2550. }
  2551. /* Add data-app attribute. */
  2552. if(strpos($misc, 'data-app') === false) $misc .= ' data-app="' . $app->tab . '"';
  2553. if($onlyBody && strpos($misc, 'data-toggle') === false && $clickable && !isonlybody()) $misc .= ' data-toggle="modal"';
  2554. /* Set module and method, then create link to it. */
  2555. if(strtolower($module) == 'story' and strtolower($method) == 'createcase') ($module = 'testcase') and ($method = 'create');
  2556. if(strtolower($module) == 'bug' and strtolower($method) == 'tostory') ($module = 'story') and ($method = 'create');
  2557. if(strtolower($module) == 'bug' and strtolower($method) == 'createcase') ($module = 'testcase') and ($method = 'create');
  2558. $currentModule = strtolower($module);
  2559. $currentMethod = strtolower($method);
  2560. if(!commonModel::hasPriv($module, $method, $object, $vars) && !in_array("$currentModule.$currentMethod", $config->openMethods) && !in_array("$currentModule.$currentMethod", $config->logonMethods)) return false;
  2561. $link = helper::createLink($module, $method, $vars, '', $onlyBody ? true : false);
  2562. /* Set the icon title, try search the $method defination in $module's lang or $common's lang. */
  2563. if(empty($title))
  2564. {
  2565. $title = $method;
  2566. if($method == 'create' and $icon == 'copy') $method = 'copy';
  2567. if(isset($lang->$method) and is_string($lang->$method)) $title = $lang->$method;
  2568. if((isset($lang->$module->$method) or $app->loadLang($module)) and isset($lang->$module->$method))
  2569. {
  2570. $title = $method == 'report' ? $lang->$module->$method->common : $lang->$module->$method;
  2571. }
  2572. if($icon == 'toStory') $title = $lang->bug->toStory;
  2573. if($icon == 'createBug') $title = $lang->testtask->createBug;
  2574. }
  2575. /* set the class. */
  2576. if(!$icon)
  2577. {
  2578. $icon = isset($lang->icons[$method]) ? $lang->icons[$method] : $method;
  2579. }
  2580. if(strpos(',edit,copy,report,export,delete,', ",$method,") !== false) $module = 'common';
  2581. $class = "icon-$module-$method";
  2582. if(!$clickable) $class .= ' disabled';
  2583. if($icon) $class .= ' icon-' . $icon;
  2584. /* Create the icon link. */
  2585. if($clickable)
  2586. {
  2587. if($app->getViewType() == 'mhtml')
  2588. {
  2589. return "<a data-remote='$link' class='$extraClass' $misc>$title</a>";
  2590. }
  2591. if($type == 'button')
  2592. {
  2593. if($method == 'edit' or $method == 'copy' or $method == 'delete' or ($method == 'review' and $module == 'charter'))
  2594. {
  2595. return html::a($link, "<i class='$class'></i>", $target, "class='btn btn-link $extraClass' title=\"$title\" $misc", false);
  2596. }
  2597. else
  2598. {
  2599. return html::a($link, "<i class='$class'></i> " . "<span class='text'>{$title}</span>", $target, "class='btn btn-link $extraClass' $misc", true);
  2600. }
  2601. }
  2602. else
  2603. {
  2604. return html::a($link, "<i class='$class'></i>", $target, "class='btn $extraClass' title=\"$title\" $misc", false) . "\n";
  2605. }
  2606. }
  2607. else
  2608. {
  2609. if($type == 'list')
  2610. {
  2611. return "<button type='button' class='disabled btn $extraClass'><i class='$class' title=\"$title\" $misc></i></button>\n";
  2612. }
  2613. }
  2614. }
  2615. /**
  2616. * Build more executions button.
  2617. *
  2618. * @param int $executionID
  2619. * @param bool $printHtml
  2620. * @static
  2621. * @access public
  2622. * @return bool
  2623. */
  2624. public static function buildMoreButton($executionID, $printHtml = true)
  2625. {
  2626. global $lang, $app, $dao;
  2627. if(commonModel::isTutorialMode()) return '';
  2628. $object = $dao->select('project,`type`')->from(TABLE_EXECUTION)->where('id')->eq($executionID)->fetch();
  2629. if(empty($object)) return '';
  2630. $executionPairs = array();
  2631. $executionList = $dao->select('id,name,parent,project,grade')->from(TABLE_EXECUTION)
  2632. ->where('project')->eq($object->project)
  2633. ->andWhere('deleted')->eq('0')
  2634. ->beginIF(!$app->user->admin)->andWhere('id')->in($app->user->view->sprints)->fi()
  2635. ->orderBy($object->type == 'stage' ? 'id_asc' : 'id_desc')->fi()
  2636. ->fetchAll();
  2637. $executions = array();
  2638. foreach($executionList as $execution) $executions[$execution->id] = $execution;
  2639. $executionList = $app->control->loadModel('execution')->resetExecutionSorts($executions);
  2640. foreach($executionList as $execution)
  2641. {
  2642. if(isset($executionPairs[$execution->parent])) unset($executionPairs[$execution->parent]);
  2643. if($execution->id == $executionID) continue;
  2644. $executionPairs[$execution->id] = $execution->name;
  2645. }
  2646. if(empty($executionPairs)) return '';
  2647. $html = "<li class='divider'></li><li class='dropdown dropdown-hover'><a href='javascript:;' data-toggle='dropdown'>{$lang->more}<span class='caret'></span></a>";
  2648. $html .= "<ul class='dropdown-menu'>";
  2649. $showCount = 0;
  2650. foreach($executionPairs as $executionID => $executionName)
  2651. {
  2652. $html .= "<li style='max-width: 300px;'>" . html::a(helper::createLink('execution', 'task', "executionID=$executionID"), $executionName, '', "title='{$executionName}' class='text-ellipsis' style='padding: 2px 10px'") . '</li>';
  2653. $showCount ++;
  2654. if($showCount == 10) break;
  2655. }
  2656. if(count($executionPairs) > 10) $html .= '<li>' . html::a(helper::createLink('project', 'execution', "status=all&projectID={$object->project}"), $lang->preview . $lang->more, '', "data-app='project' style='padding: 2px 10px'") . '</li>';
  2657. $html .= "</ul></li>\n";
  2658. if($printHtml) echo $html;
  2659. return $html;
  2660. }
  2661. /**
  2662. * Print about bar.
  2663. *
  2664. * @static
  2665. * @access public
  2666. * @return void
  2667. */
  2668. public static function printAboutBar()
  2669. {
  2670. global $app, $config, $lang;
  2671. echo "<li class='dropdown-submenu zentao-help'>";
  2672. echo "<a data-toggle='dropdown'>" . "<i class='icon icon-help'></i> " . $lang->help . "</a>";
  2673. echo "<ul class='dropdown-menu pull-left'>";
  2674. $manualUrl = ((!empty($config->isINT)) ? $config->manualUrl['int'] : $config->manualUrl['home']) . '&theme=' . $_COOKIE['theme'];
  2675. echo '<li>' . html::a($manualUrl, $lang->manual, '', "class='show-in-app' id='helpLink' data-app='help'") . '</li>';
  2676. echo '<li>' . html::a(helper::createLink('misc', 'changeLog'), $lang->changeLog, '', "class='iframe' data-width='800' data-headerless='true' data-backdrop='true' data-keyboard='true'") . '</li>';
  2677. echo "</ul></li>\n";
  2678. static::printClientLink();
  2679. echo '<li class="zentao-about">' . html::a(helper::createLink('misc', 'about'), "<i class='icon icon-about'></i> " . $lang->aboutZenTao, '', "class='about iframe' data-width='1050' data-headerless='true' data-backdrop='true' data-keyboard='true' data-class='modal-about'") . '</li>';
  2680. echo '<li class="AIUX">' . $lang->designedByAIUX . '</li>';
  2681. }
  2682. /**
  2683. * Print the link for zentao client.
  2684. *
  2685. * @static
  2686. * @access public
  2687. * @return void
  2688. */
  2689. public static function printClientLink()
  2690. {
  2691. global $config, $lang;
  2692. if(isset($config->xxserver->installed) and $config->xuanxuan->turnon)
  2693. {
  2694. echo "<li class='dropdown-submenu zentao-client'>";
  2695. echo "<a href='javascript:;'>" . "<i class='icon icon-download'></i> " . $lang->clientName . "</a><ul class='dropdown-menu pull-left'>";
  2696. echo '<li>' . html::a(helper::createLink('misc', 'downloadClient', '', '', true), $lang->downloadClient, '', "title='$lang->downloadClient' class='iframe text-ellipsis' data-width='600'") . '</li>';
  2697. echo "<li class='dropdown-submenu' id='downloadMobile'><a href='javascript:;'>" . $lang->downloadMobile . "</a><ul class='dropdown-menu pull-left''>";
  2698. echo "<li><div class='mobile-qrcode local-img'><img src='{$config->webRoot}static/images/app-qrcode.png' /></li>";
  2699. echo "</ul></li>";
  2700. echo '<li>' . html::a($lang->clientHelpLink, $lang->clientHelp, '', "title='$lang->clientHelp' target='_blank'") . '</li>';
  2701. echo '</ul></li>';
  2702. }
  2703. }
  2704. /**
  2705. * Print create button list.
  2706. *
  2707. * @static
  2708. * @access public
  2709. * @return void
  2710. */
  2711. public static function printCreateList()
  2712. {
  2713. global $app, $config, $lang, $dao;
  2714. $html = "<ul class='dropdown-menu pull-right create-list'>";
  2715. /* Initialize the default values. */
  2716. $showCreateList = $needPrintDivider = false;
  2717. /* Get default product id. */
  2718. $productID = isset($_SESSION['product']) ? $_SESSION['product'] : 0;
  2719. if($productID)
  2720. {
  2721. $product = $dao->select('id')->from(TABLE_PRODUCT)->where('deleted')->eq('0')->andWhere('vision')->eq($config->vision)->andWhere('id')->eq($productID)->fetch();
  2722. if(empty($product)) $productID = 0;
  2723. }
  2724. if(!$productID and $app->user->view->products)
  2725. {
  2726. $product = $dao->select('id')->from(TABLE_PRODUCT)->where('deleted')->eq('0')->andWhere('vision')->eq($config->vision)->andWhere('id')->in($app->user->view->products)->orderBy('order desc')->limit(1)->fetch();
  2727. if($product) $productID = $product->id;
  2728. }
  2729. if($config->vision == 'lite')
  2730. {
  2731. $object = $dao->select('id')->from(TABLE_PROJECT)
  2732. ->where('deleted')->eq('0')
  2733. ->andWhere('vision')->eq('lite')
  2734. ->andWhere('model')->eq('kanban')
  2735. ->beginIF(!$app->user->admin)->andWhere('id')->in($app->user->view->projects)->fi()
  2736. ->limit(1)
  2737. ->fetch();
  2738. if(empty($object)) unset($lang->createIcons['story'], $lang->createIcons['task'], $lang->createIcons['execution']);
  2739. }
  2740. if($config->edition == 'open') unset($lang->createIcons['effort']);
  2741. if($config->systemMode == 'light') unset($lang->createIcons['program']);
  2742. if(empty($config->board)) unset($lang->createIcons['board']);
  2743. /* Check whether the creation permission is available, and print create buttons. */
  2744. foreach($lang->createIcons as $objectType => $objectIcon)
  2745. {
  2746. $createMethod = 'create';
  2747. $module = $objectType == 'kanbanspace' ? 'kanban' : $objectType;
  2748. if($objectType == 'effort') $createMethod = 'batchCreate';
  2749. if($objectType == 'kanbanspace') $createMethod = 'createSpace';
  2750. if($objectType == 'board') $createMethod = 'createBoard';
  2751. if(strpos('|bug|execution|kanbanspace|', "|$objectType|") !== false) $needPrintDivider = true;
  2752. $hasPriv = common::hasPriv($module, $createMethod);
  2753. if(!$hasPriv and $objectType == 'doc' and common::hasPriv('api', 'create')) $hasPriv = true;
  2754. if(!$hasPriv) continue;
  2755. /* Determines whether to print a divider. */
  2756. if($needPrintDivider and $showCreateList)
  2757. {
  2758. $html .= '<li class="divider"></li>';
  2759. $needPrintDivider = false;
  2760. }
  2761. $showCreateList = true;
  2762. $isOnlyBody = false;
  2763. $attr = '';
  2764. $params = '';
  2765. switch($objectType)
  2766. {
  2767. case 'doc':
  2768. $params = "objectType=&objectID=0&libID=0";
  2769. $createMethod = 'selectLibType';
  2770. $isOnlyBody = true;
  2771. $attr = "class='iframe' data-width='750px'";
  2772. break;
  2773. case 'project':
  2774. $params = "model=scrum&programID=0&copyProjectID=0&extra=from=global";
  2775. if($config->vision == 'lite')
  2776. {
  2777. $params = "model=kanban";
  2778. }
  2779. elseif(!defined('TUTORIAL'))
  2780. {
  2781. $params = "programID=0&from=global";
  2782. $createMethod = 'createGuide';
  2783. $attr = 'data-toggle="modal" data-type="iframe"';
  2784. }
  2785. break;
  2786. case 'bug':
  2787. $params = "productID=$productID&branch=&extras=from=global";
  2788. break;
  2789. case 'story':
  2790. if(!$productID and $config->vision == 'lite')
  2791. {
  2792. $module = 'project';
  2793. $params = "model=kanban";
  2794. }
  2795. else
  2796. {
  2797. $params = "productID=$productID&branch=0&moduleID=0&storyID=0&objectID=0&bugID=0&planID=0&todoID=0&extra=from=global";
  2798. if($config->vision == 'lite')
  2799. {
  2800. $projectID = isset($_SESSION['project']) ? $_SESSION['project'] : 0;
  2801. $projects = $dao->select('t2.id')->from(TABLE_PROJECTPRODUCT)->alias('t1')
  2802. ->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.project = t2.id')
  2803. ->where('t1.product')->eq($productID)
  2804. ->andWhere('t2.type')->eq('project')
  2805. ->andWhere('t2.id')->in($app->user->view->projects)
  2806. ->orderBy('order desc')
  2807. ->fetchAll();
  2808. $projectIdList = array();
  2809. foreach($projects as $project) $projectIdList[$project->id] = $project->id;
  2810. if($projectID and !isset($projectIdList[$projectID])) $projectID = 0;
  2811. if(empty($projectID)) $projectID = key($projectIdList);
  2812. $params = "productID={$productID}&branch=0&moduleID=0&storyID=0&objectID={$projectID}&bugID=0&planID=0&todoID=0&extra=from=global";
  2813. }
  2814. }
  2815. break;
  2816. case 'task':
  2817. $params = "executionID=0&storyID=0&moduleID=0&taskID=0&todoID=0&extra=from=global";
  2818. break;
  2819. case 'testcase':
  2820. $params = "productID=$productID&branch=&moduleID=0&from=&param=0&storyID=0&extras=from=global";
  2821. break;
  2822. case 'execution':
  2823. $projectID = isset($_SESSION['project']) ? $_SESSION['project'] : 0;
  2824. $params = "projectID={$projectID}&executionID=0&copyExecutionID=0&planID=0&confirm=no&productID=0&extra=from=global";
  2825. break;
  2826. case 'product':
  2827. $params = "programID=&extra=from=global";
  2828. break;
  2829. case 'program':
  2830. $params = "parentProgramID=0&extra=from=global";
  2831. break;
  2832. case 'kanbanspace':
  2833. $isOnlyBody = true;
  2834. $attr = "class='iframe' data-width='75%'";
  2835. break;
  2836. case 'kanban':
  2837. $isOnlyBody = true;
  2838. $attr = "class='iframe' data-width='75%'";
  2839. break;
  2840. case 'board':
  2841. $createMethod = 'createByTemplate';
  2842. $params = 'templateID=0';
  2843. $isOnlyBody = true;
  2844. $attr = 'data-toggle="modal"';
  2845. break;
  2846. }
  2847. $html .= '<li>' . html::a(helper::createLink($module, $createMethod, $params, '', $isOnlyBody), "<i class='icon icon-$objectIcon'></i> " . $lang->createObjects[$objectType], '', "$attr data-app=''") . '</li>';
  2848. }
  2849. if(!$showCreateList) return '';
  2850. $html .= "</ul>";
  2851. $html .= "<a class='dropdown-toggle' data-toggle='dropdown'>";
  2852. $html .= "<i class='icon icon-plus'></i>";
  2853. $html .= "</a>";
  2854. echo $html;
  2855. }
  2856. /**
  2857. * Print upper left corner home button.
  2858. *
  2859. * @param string $tab
  2860. * @static
  2861. * @access public
  2862. * @return void
  2863. */
  2864. public static function printHomeButton($tab)
  2865. {
  2866. global $lang, $config, $app;
  2867. if(!$tab) return;
  2868. if($tab == 'admin' and $app->control and method_exists($app->control, 'loadModel')) $app->control->loadModel('admin')->setMenu();
  2869. if($config->edition != 'open' && $app->isServing()) $app->control->loadModel('common')->mergeFlowMenuLang();
  2870. $icon = zget($lang->navIcons, $tab, '');
  2871. if(!in_array($tab, array('program', 'product', 'project')))
  2872. {
  2873. if(!isset($lang->mainNav->$tab)) return;
  2874. $nav = $lang->mainNav->$tab;
  2875. list($title, $currentModule, $currentMethod, $vars) = explode('|', $nav);
  2876. if($tab == 'execution') $currentMethod = 'all';
  2877. }
  2878. else
  2879. {
  2880. $currentModule = $tab;
  2881. if($tab == 'program' or $tab == 'project') $currentMethod = 'browse';
  2882. if($tab == 'product') $currentMethod = 'all';
  2883. }
  2884. $btnTitle = isset($lang->db->custom['common']['mainNav'][$tab]) ? $lang->db->custom['common']['mainNav'][$tab] : $lang->$tab->common;
  2885. $commonKey = $tab . 'Common';
  2886. if(isset($lang->$commonKey) and $tab != 'execution') $btnTitle = $lang->$commonKey;
  2887. if($btnTitle == $lang->project->template) $currentMethod = 'template'; //项目模板appName点击后跳转到项目模板列表页面
  2888. $link = helper::createLink($currentModule, $currentMethod);
  2889. $className = $tab == 'devops' ? 'btn num' : 'btn';
  2890. $html = $link ? html::a($link, "$icon $btnTitle", '', "class='$className' style='padding-top: 2px'") : "$icon $btnTitle";
  2891. echo "<div class='btn-group header-btn'>" . $html . '</div>';
  2892. }
  2893. /**
  2894. * Print link icon.
  2895. *
  2896. * @param string $module
  2897. * @param string $method
  2898. * @param string $vars
  2899. * @param object $object
  2900. * @param string $type button|list
  2901. * @param string $icon
  2902. * @param string $target
  2903. * @param string $extraClass
  2904. * @param bool $onlyBody
  2905. * @param string $misc
  2906. * @param string $extraEnabled
  2907. * @static
  2908. * @access public
  2909. * @return void
  2910. */
  2911. public static function printIcon($module, $method, $vars = '', $object = '', $type = 'button', $icon = '', $target = '', $extraClass = '', $onlyBody = false, $misc = '', $title = '', $programID = 0, $extraEnabled = '')
  2912. {
  2913. echo common::buildIconButton($module, $method, $vars, $object, $type, $icon, $target, $extraClass, $onlyBody, $misc, $title, $programID, $extraEnabled);
  2914. }
  2915. /**
  2916. * Print the main menu.
  2917. *
  2918. * @param bool $printHtml
  2919. * @static
  2920. * @access public
  2921. * @return string
  2922. */
  2923. public static function printMainMenu($printHtml = true)
  2924. {
  2925. global $app, $lang, $config;
  2926. /* Set main menu by app tab and module. */
  2927. static::replaceMenuLang();
  2928. static::setMainMenu();
  2929. static::checkMenuVarsReplaced();
  2930. $activeMenu = '';
  2931. $tab = $app->tab;
  2932. $isTutorialMode = commonModel::isTutorialMode();
  2933. $currentModule = $app->rawModule;
  2934. $currentMethod = $app->rawMethod;
  2935. if($isTutorialMode && isset($_SESSION['wizardModule'])) $currentModule = $_SESSION['wizardModule'];
  2936. if($isTutorialMode && isset($_SESSION['wizardMethod'])) $currentMethod = $_SESSION['wizardMethod'];
  2937. /* Print all main menus. */
  2938. $menu = customModel::getMainMenu();
  2939. $menuHtml = "<ul class='nav nav-default'>\n";
  2940. foreach($menu as $menuItem)
  2941. {
  2942. if(isset($menuItem->hidden) and $menuItem->hidden and (!isset($menuItem->tutorial) or !$menuItem->tutorial)) continue;
  2943. if(empty($menuItem->link)) continue;
  2944. if($menuItem->divider && $menuItem->link['method'] != 'more') $menuHtml .= "<li class='divider'></li>";
  2945. /* Init the these vars. */
  2946. $alias = isset($menuItem->alias) ? $menuItem->alias : '';
  2947. $subModule = isset($menuItem->subModule) ? explode(',', $menuItem->subModule) : array();
  2948. $class = isset($menuItem->class) ? $menuItem->class : '';
  2949. $exclude = isset($menuItem->exclude) ? $menuItem->exclude : '';
  2950. $active = '';
  2951. if($menuItem->name == $currentModule and strpos(",$exclude,", ",$currentModule-$currentMethod,") === false)
  2952. {
  2953. $activeMenu = $menuItem->name;
  2954. $active = 'active';
  2955. }
  2956. if($subModule and in_array($currentModule, $subModule) and strpos(",$exclude,", ",$currentModule-$currentMethod,") === false)
  2957. {
  2958. $activeMenu = $menuItem->name;
  2959. $active = 'active';
  2960. }
  2961. if($menuItem->link['module'] == 'execution' and $menuItem->link['method'] == 'more')
  2962. {
  2963. $executionID = $menuItem->link['vars'];
  2964. $menuHtml .= commonModel::buildMoreButton((int)$executionID, false);
  2965. }
  2966. elseif($menuItem->link['module'] == 'app' and $menuItem->link['method'] == 'serverlink')
  2967. {
  2968. $menuHtml .= commonModel::buildAppButton(false);
  2969. }
  2970. else
  2971. {
  2972. if($menuItem->link)
  2973. {
  2974. $target = '';
  2975. $module = '';
  2976. $method = '';
  2977. $link = commonModel::createMenuLink($menuItem);
  2978. if($menuItem->link['module'] == 'project' and $menuItem->link['method'] == 'other') $link = 'javascript:void(0);';
  2979. if(is_array($menuItem->link))
  2980. {
  2981. if(isset($menuItem->link['target'])) $target = $menuItem->link['target'];
  2982. if(isset($menuItem->link['module'])) $module = $menuItem->link['module'];
  2983. if(isset($menuItem->link['method'])) $method = $menuItem->link['method'];
  2984. }
  2985. if($module == $currentModule and ($method == $currentMethod or strpos(",$alias,", ",$currentMethod,") !== false) and strpos(",$exclude,", ",$currentMethod,") === false)
  2986. {
  2987. $activeMenu = $menuItem->name;
  2988. $active = 'active';
  2989. }
  2990. $label = $menuItem->text;
  2991. $dropMenu = '';
  2992. $misc = (isset($lang->navGroup->$module) and $tab != $lang->navGroup->$module) ? "data-app='$tab'" : '';
  2993. /* Print drop menus. */
  2994. if(isset($menuItem->dropMenu))
  2995. {
  2996. foreach($menuItem->dropMenu as $dropMenuName => $dropMenuItem)
  2997. {
  2998. if(empty($dropMenuItem)) continue;
  2999. if(isset($dropMenuItem->hidden) and $dropMenuItem->hidden) continue;
  3000. /* Parse drop menu link. */
  3001. $dropMenuLink = zget($dropMenuItem, 'link', $dropMenuItem);
  3002. list($subLabel, $subModule, $subMethod, $subParams) = explode('|', $dropMenuLink);
  3003. if(!common::hasPriv($subModule, $subMethod)) continue;
  3004. $subLink = helper::createLink($subModule, $subMethod, $subParams);
  3005. $subActive = '';
  3006. $activeMainMenu = false;
  3007. if($currentModule == strtolower($subModule) and $currentMethod == strtolower($subMethod))
  3008. {
  3009. $activeMainMenu = true;
  3010. }
  3011. else
  3012. {
  3013. $subModule = isset($dropMenuItem['subModule']) ? explode(',', $dropMenuItem['subModule']) : array();
  3014. $subExclude = isset($dropMenuItem['exclude']) ? $dropMenuItem['exclude'] : $exclude;
  3015. if($subModule and in_array($currentModule, $subModule) and strpos(",$subExclude,", ",$currentModule-$currentMethod,") === false) $activeMainMenu = true;
  3016. }
  3017. if($activeMainMenu)
  3018. {
  3019. $activeMenu = $dropMenuName;
  3020. $active = 'active';
  3021. $subActive = 'active';
  3022. $label = $subLabel;
  3023. }
  3024. $dropMenu .= "<li class='$subActive' data-id='$dropMenuName'>" . html::a($subLink, $subLabel, '', "data-app='$tab'") . '</li>';
  3025. }
  3026. if(empty($dropMenu)) continue;
  3027. $label .= "<span class='caret'></span>";
  3028. $dropMenu = "<ul class='dropdown-menu'>{$dropMenu}</ul>";
  3029. $menuHtml .= "<li class='$class $active' data-id='$menuItem->name'>" . html::a($link, $label, $target, $misc) . $dropMenu . "</li>\n";
  3030. }
  3031. else
  3032. {
  3033. $menuHtml .= "<li class='$class $active' data-id='$menuItem->name'>" . html::a($link, $label, $target, $misc) . "</li>\n";
  3034. }
  3035. }
  3036. else
  3037. {
  3038. $menuHtml .= "<li class='$class $active' data-id='$menuItem->name'>$menuItem->text</li>\n";
  3039. }
  3040. }
  3041. }
  3042. $menuHtml .= "</ul>\n";
  3043. if($printHtml) echo $menuHtml;
  3044. return $activeMenu;
  3045. }
  3046. /**
  3047. * Print the module menu.
  3048. *
  3049. * @param string $activeMenu
  3050. * @static
  3051. * @access public
  3052. * @return void
  3053. */
  3054. public static function printModuleMenu($activeMenu)
  3055. {
  3056. global $app, $lang;
  3057. $moduleName = $app->rawModule;
  3058. $methodName = $app->rawMethod;
  3059. $tab = $app->tab;
  3060. if(!isset($lang->$tab->menu))
  3061. {
  3062. echo "<ul></ul>";
  3063. return;
  3064. }
  3065. /* get current module and method. */
  3066. $isTutorialMode = commonModel::isTutorialMode();
  3067. $currentModule = $app->getModuleName();
  3068. $currentMethod = $app->getMethodName();
  3069. $isMobile = $app->viewType === 'mhtml';
  3070. /* When use workflow then set rawModule to moduleName. */
  3071. if($moduleName == 'flow') $activeMenu = $app->rawModule;
  3072. $menu = customModel::getModuleMenu($activeMenu);
  3073. /* If this is not workflow then use rawModule and rawMethod to judge highlight. */
  3074. if($app->isFlow)
  3075. {
  3076. $currentModule = $app->rawModule;
  3077. $currentMethod = $app->rawMethod;
  3078. }
  3079. if($isTutorialMode and defined('WIZARD_MODULE')) $currentModule = WIZARD_MODULE;
  3080. if($isTutorialMode and defined('WIZARD_METHOD')) $currentMethod = WIZARD_METHOD;
  3081. /* The beginning of the menu. */
  3082. echo $isMobile ? '' : "<ul class='nav nav-default'>\n";
  3083. /* Cycling to print every sub menu. */
  3084. foreach($menu as $menuItem)
  3085. {
  3086. if(isset($menuItem->hidden) and $menuItem->hidden) continue;
  3087. if($isMobile and empty($menuItem->link)) continue;
  3088. if($menuItem->divider) echo "<li class='divider'></li>";
  3089. /* Init the these vars. */
  3090. $alias = isset($menuItem->alias) ? $menuItem->alias : '';
  3091. $subModule = isset($menuItem->subModule) ? explode(',', $menuItem->subModule) : array();
  3092. $class = isset($menuItem->class) ? $menuItem->class : '';
  3093. $exclude = isset($menuItem->exclude) ? $menuItem->exclude : '';
  3094. $active = '';
  3095. if($subModule and in_array($currentModule, $subModule)) $active = 'active';
  3096. if($menuItem->link)
  3097. {
  3098. $target = '';
  3099. $module = '';
  3100. $method = '';
  3101. $link = commonModel::createMenuLink($menuItem, $tab);
  3102. if(is_array($menuItem->link))
  3103. {
  3104. if(isset($menuItem->link['target'])) $target = $menuItem->link['target'];
  3105. if(isset($menuItem->link['module'])) $module = $menuItem->link['module'];
  3106. if(isset($menuItem->link['method'])) $method = $menuItem->link['method'];
  3107. }
  3108. if($module == $currentModule and $method == $currentMethod) $active = 'active';
  3109. if($module == $currentModule and strpos(",$alias,", ",$currentMethod,") !== false) $active = 'active';
  3110. if(strpos(",$exclude,", ",$currentModule-$currentMethod,") !== false or strpos(",$exclude,", ",$currentModule,") !== false) $active = '';
  3111. $label = $menuItem->text;
  3112. $dropMenu = '';
  3113. /* Print sub menus. */
  3114. if(isset($menuItem->dropMenu))
  3115. {
  3116. foreach($menuItem->dropMenu as $dropMenuKey => $dropMenuItem)
  3117. {
  3118. if(isset($dropMenuItem->hidden) and $dropMenuItem->hidden) continue;
  3119. $subActive = '';
  3120. $subModule = '';
  3121. $subMethod = '';
  3122. $subParams = '';
  3123. $subLabel = '';
  3124. list($dropMenuName, $dropMenuModule, $dropMenuMethod, $dropMenuParams) = explode('|', $dropMenuItem['link']);
  3125. if(isset($dropMenuModule)) $subModule = $dropMenuModule;
  3126. if(isset($dropMenuMethod)) $subMethod = $dropMenuMethod;
  3127. if(isset($dropMenuParams)) $subParams = $dropMenuParams;
  3128. if(isset($dropMenuName)) $subLabel = $dropMenuName;
  3129. $subLink = helper::createLink($subModule, $subMethod, $subParams);
  3130. if($currentModule == strtolower($subModule) and $currentMethod == strtolower($subMethod)) $subActive = 'active';
  3131. $misc = (isset($lang->navGroup->$subModule) and $tab != $lang->navGroup->$subModule) ? "data-app='$tab'" : '';
  3132. $dropMenu .= "<li class='$subActive' data-id='$dropMenuKey'>" . html::a($subLink, $subLabel, '', $misc) . '</li>';
  3133. }
  3134. if(empty($dropMenu)) continue;
  3135. $label .= "<span class='caret'></span>";
  3136. $dropMenu = "<ul class='dropdown-menu'>{$dropMenu}</ul>";
  3137. }
  3138. $misc = "data-app='$tab'";
  3139. $menuItemHtml = "<li class='$class $active' data-id='$menuItem->name'>" . html::a($link, $label, $target, $misc) . $dropMenu . "</li>\n";
  3140. if($isMobile) $menuItemHtml = html::a($link, $menuItem->text, $target, $misc . " class='$class $active'") . "\n";
  3141. echo $menuItemHtml;
  3142. }
  3143. else
  3144. {
  3145. echo $isMobile ? $menuItem->text : "<li class='$class $active' data-id='$menuItem->name'>$menuItem->text</li>\n";
  3146. }
  3147. }
  3148. echo $isMobile ? '' : "</ul>\n";
  3149. }
  3150. /**
  3151. * Print the link contains orderBy field.
  3152. *
  3153. * This method will auto set the orderby param according the params. Fox example, if the order by is desc,
  3154. * will be changed to asc.
  3155. *
  3156. * @param string $fieldName the field name to sort by
  3157. * @param string $orderBy the order by string
  3158. * @param string $vars the vars to be passed
  3159. * @param string $label the label of the link
  3160. * @param string $module the module name
  3161. * @param string $method the method name
  3162. *
  3163. * @access public
  3164. * @return void
  3165. */
  3166. public static function printOrderLink($fieldName, $orderBy, $vars, $label, $module = '', $method = '')
  3167. {
  3168. global $lang, $app;
  3169. if(empty($module)) $module = isset($app->rawModule) ? $app->rawModule : $app->getModuleName();
  3170. if(empty($method)) $method = isset($app->rawMethod) ? $app->rawMethod : $app->getMethodName();
  3171. $className = 'header';
  3172. $isMobile = $app->viewType === 'mhtml';
  3173. $order = explode('_', $orderBy);
  3174. $order[0] = trim($order[0], '`');
  3175. if($order[0] == $fieldName)
  3176. {
  3177. if(isset($order[1]) and $order[1] == 'asc')
  3178. {
  3179. $orderBy = "{$order[0]}_desc";
  3180. $className = $isMobile ? 'SortUp' : 'sort-up';
  3181. }
  3182. else
  3183. {
  3184. $orderBy = "{$order[0]}_asc";
  3185. $className = $isMobile ? 'SortDown' : 'sort-down';
  3186. }
  3187. }
  3188. else
  3189. {
  3190. $orderBy = trim($fieldName, '`') . '_' . 'asc';
  3191. $className = 'header';
  3192. }
  3193. $params = sprintf($vars, $orderBy);
  3194. if($app->getModuleName() == 'my' and $app->rawMethod == 'work') $params = "mode={$app->getMethodName()}&" . $params;
  3195. $link = helper::createLink($module, $method, $params);
  3196. echo $isMobile ? html::a($link, $label, '', "class='$className' data-app={$app->tab}") : html::a($link, $label, '', "class='$className' data-app={$app->tab}");
  3197. }
  3198. /**
  3199. * Print messageBar.
  3200. *
  3201. * @static
  3202. * @access public
  3203. * @return void
  3204. */
  3205. public static function printMessageBar()
  3206. {
  3207. global $app, $config;
  3208. $app->loadConfig('message');
  3209. if(!$config->message->browser->turnon) return;
  3210. $showCount = $config->message->browser->count;
  3211. $unreadCount = $app->dbh->query("SELECT COUNT(1) AS `count` FROM " . TABLE_NOTIFY . " WHERE `objectType` = 'message' AND status != 'read' AND `toList` = ',{$app->user->account},'")->fetch()->count;
  3212. $dotStyle = static::getDotStyle($showCount != '0', $unreadCount);
  3213. if($unreadCount > 99) $unreadCount = '99+';
  3214. $fetcher = helper::createLink('message', 'ajaxGetDropMenuForOld');
  3215. foreach($dotStyle as $cssKey => $cssValue) $dotStyle[$cssKey] = $cssKey . ':' . $cssValue;
  3216. $html = "<li id='messageDropdown' class='relative'>\n";
  3217. $html .= "<a class='dropdown-toggle' id='messageBar' data-fetcher='{$fetcher}' onclick='fetchMessage()'>";
  3218. $html .= "<i class='icon icon-bell'></i>";
  3219. if($unreadCount)
  3220. {
  3221. $html .= "<span class='label label-dot danger absolute";
  3222. if($showCount) $html .= ' rounded-sm';
  3223. $html .= "' style='" . implode('; ', $dotStyle) . "'>";
  3224. $html .= $showCount ? $unreadCount : '';
  3225. $html .= '</span>';
  3226. }
  3227. $html .= "</a>";
  3228. $html .= "<div class='dropdown-menu messageDropdownBox absolute' style='padding:0;left:-320px;'><div id='dropdownMessageMenu' class='not-clear-menu'></div></div>";
  3229. $html .= "</li>";
  3230. echo $html;
  3231. }
  3232. /**
  3233. * Print top bar.
  3234. *
  3235. * @static
  3236. * @access public
  3237. * @return void
  3238. */
  3239. public static function printUserBar()
  3240. {
  3241. global $lang, $app;
  3242. if(isset($app->user))
  3243. {
  3244. $isGuest = $app->user->account == 'guest';
  3245. echo "<ul class='dropdown-menu pull-right'>";
  3246. if(!$isGuest)
  3247. {
  3248. $noRole = (!empty($app->user->role) and isset($lang->user->roleList[$app->user->role])) ? '' : ' no-role';
  3249. echo '<li class="user-profile-item">';
  3250. echo "<a href='" . helper::createLink('my', 'profile', '', '', true) . "' data-width='700' class='iframe $noRole'" . '>';
  3251. echo html::avatar($app->user, '', 'avatar-circle', 'id="menu-avatar"');
  3252. echo '<div class="user-profile-name">' . (empty($app->user->realname) ? $app->user->account : $app->user->realname) . '</div>';
  3253. if(isset($lang->user->roleList[$app->user->role])) echo '<div class="user-profile-role">' . $lang->user->roleList[$app->user->role] . '</div>';
  3254. echo '</a></li><li class="divider"></li>';
  3255. $vision = $app->config->vision == 'lite' ? 'rnd' : 'lite';
  3256. echo '<li>' . html::a(helper::createLink('my', 'profile', '', '', true), "<i class='icon icon-account'></i> " . $lang->profile, '', "class='iframe' data-width='700'") . '</li>';
  3257. if($app->config->vision === 'rnd')
  3258. {
  3259. if(!commonModel::isTutorialMode())
  3260. {
  3261. echo '<li class="user-tutorial">' . html::a(helper::createLink('tutorial', 'start', '', '', true), "<i class='icon icon-guide'></i> " . $lang->tutorialAB, '', "class='iframe' data-class-name='modal-inverse' data-width='800' data-headerless='true' data-backdrop='true' data-keyboard='true'") . '</li>';
  3262. }
  3263. echo '<li class="preference-setting">' . html::a(helper::createLink('my', 'preference', 'showTip=false', '', true), "<i class='icon icon-controls'></i> " . $lang->preference, '', "class='iframe' data-width='700'") . '</li>';
  3264. }
  3265. if(common::hasPriv('my', 'changePassword')) echo '<li class="change-password">' . html::a(helper::createLink('my', 'changepassword', '', '', true), "<i class='icon icon-cog-outline'></i> " . $lang->changePassword, '', "class='iframe' data-width='600'") . '</li>';
  3266. echo "<li class='divider'></li>";
  3267. }
  3268. echo "<li class='dropdown-submenu top'>";
  3269. echo "<a href='javascript:;'>" . "<i class='icon icon-theme'></i> " . $lang->theme . "</a><ul class='dropdown-menu pull-left'>";
  3270. foreach($app->lang->themes as $key => $value)
  3271. {
  3272. echo "<li " . ($app->cookie->theme == $key ? "class='selected'" : '') . "><a href='javascript:selectTheme(\"$key\");' data-value='" . $key . "'>" . $value . "</a></li>";
  3273. }
  3274. echo '</ul></li>';
  3275. echo "<li class='dropdown-submenu top switch-language'>";
  3276. echo "<a href='javascript:;'>" . "<i class='icon icon-lang'></i> " . $lang->lang . "</a><ul class='dropdown-menu pull-left'>";
  3277. foreach ($app->config->langs as $key => $value)
  3278. {
  3279. echo "<li " . ($app->cookie->lang == $key ? "class='selected'" : '') . "><a href='javascript:selectLang(\"$key\");'>" . $value . "</a></li>";
  3280. }
  3281. echo '</ul></li>';
  3282. //if(!$isGuest and !commonModel::isTutorialMode() and $app->viewType != 'mhtml')
  3283. //{
  3284. // $customLink = helper::createLink('custom', 'ajaxMenu', "module={$app->getModuleName()}&method={$app->getMethodName()}", '', true);
  3285. // echo "<li class='custom-item'><a href='$customLink' data-toggle='modal' data-type='iframe' data-icon='cog' data-width='80%'>$lang->customMenu</a></li>";
  3286. //}
  3287. commonModel::printAboutBar();
  3288. echo '<li class="divider"></li>';
  3289. echo '<li>';
  3290. if($isGuest)
  3291. {
  3292. echo html::a(helper::createLink('user', 'login'), $lang->login, '_top');
  3293. }
  3294. else
  3295. {
  3296. echo html::a('javascript:$.apps.logout()', "<i class='icon icon-exit'></i> " . $lang->logout, '_top');
  3297. }
  3298. echo '</li></ul>';
  3299. echo "<a class='dropdown-toggle' data-toggle='dropdown'>";
  3300. echo html::avatar($app->user);
  3301. echo '</a>';
  3302. echo '<script>$("#userDropDownMenu").on("click", function(){$(this).removeClass("dropdown-hover");});$("#userDropDownMenu").on("hover", function(){$(this).next().removeClass("open");$(this).addClass("dropdown-hover");});</script>';
  3303. }
  3304. }
  3305. /**
  3306. * 打印返回链接。
  3307. * Print back link.
  3308. *
  3309. * @param string $backLink
  3310. * @param string $class
  3311. * @param string $misc
  3312. * @static
  3313. * @access public
  3314. * @return void
  3315. */
  3316. static public function printBack($backLink, $class = '', $misc = '')
  3317. {
  3318. global $lang;
  3319. if(isonlybody()) return false;
  3320. if(empty($class)) $class = 'btn';
  3321. $title = $lang->goback . $lang->backShortcutKey;
  3322. echo html::a($backLink, '<i class="icon-goback icon-back"></i> ' . $lang->goback, '', "id='back' class='{$class}' title={$title} $misc");
  3323. }
  3324. /**
  3325. * Print link to a module's method.
  3326. *
  3327. * Before printing, check the privilege first. If no privilege, return false. Else, print the link, return true.
  3328. *
  3329. * @param string $module the module name
  3330. * @param string $method the method
  3331. * @param string $vars vars to be passed
  3332. * @param string $label the label of the link
  3333. * @param string $target the target of the link
  3334. * @param string $misc others
  3335. * @param bool $newline
  3336. * @param bool $onlyBody
  3337. * @param $object
  3338. *
  3339. * @static
  3340. * @access public
  3341. * @return bool
  3342. */
  3343. public static function printLink($module, $method, $vars = '', $label = '', $target = '', $misc = '', $newline = true, $onlyBody = false, $object = null)
  3344. {
  3345. /* Add data-app attribute. */
  3346. global $app, $config;
  3347. $currentModule = strtolower($module);
  3348. $currentMethod = strtolower($method);
  3349. if(strpos($misc, 'data-app') === false) $misc .= ' data-app="' . $app->tab . '"';
  3350. if(!commonModel::hasPriv($module, $method, $object, $vars) && !in_array("$currentModule.$currentMethod", $config->openMethods) && !in_array("$currentModule.$currentMethod", $config->logonMethods)) return false;
  3351. echo html::a(helper::createLink($module, $method, $vars, '', $onlyBody), $label, $target, $misc, $newline);
  3352. return true;
  3353. }
  3354. /**
  3355. * Print pre and next link
  3356. *
  3357. * @param string $preAndNext
  3358. * @param string $linkTemplate
  3359. * @static
  3360. * @access public
  3361. * @return void
  3362. */
  3363. public static function printPreAndNext($preAndNext = '', $linkTemplate = '')
  3364. {
  3365. global $lang, $app;
  3366. if(isonlybody()) return false;
  3367. $moduleName = ($app->getModuleName() == 'story' and $app->tab == 'project') ? 'projectstory' : $app->getModuleName();
  3368. $methodName = $app->getMethodName();
  3369. echo "<nav class='container'>";
  3370. if(isset($preAndNext->pre) and $preAndNext->pre)
  3371. {
  3372. $id = (isset($_SESSION['testcaseOnlyCondition']) and !$_SESSION['testcaseOnlyCondition'] and $app->getModuleName() == 'testcase' and isset($preAndNext->pre->case)) ? 'case' : 'id';
  3373. $title = isset($preAndNext->pre->title) ? $preAndNext->pre->title : $preAndNext->pre->name;
  3374. $title = '#' . $preAndNext->pre->$id . ' ' . $title . ' ' . $lang->preShortcutKey;
  3375. $params = $moduleName == 'story' ? "&version=0&param=0&storyType={$preAndNext->pre->type}" : '';
  3376. $link = $linkTemplate ? sprintf($linkTemplate, $preAndNext->pre->$id) : helper::createLink($moduleName, $methodName, "ID={$preAndNext->pre->$id}" . $params);
  3377. $link .= '#app=' . $app->tab;
  3378. if(isset($preAndNext->pre->objectType) and $preAndNext->pre->objectType == 'doc')
  3379. {
  3380. echo html::a('javascript:void(0)', '<i class="icon-pre icon-chevron-left"></i>', '', "id='prevPage' class='btn' title='{$title}' data-url='{$link}'");
  3381. }
  3382. else
  3383. {
  3384. echo html::a($link, '<i class="icon-pre icon-chevron-left"></i>', '', "id='prevPage' class='btn' title='{$title}'");
  3385. }
  3386. }
  3387. if(isset($preAndNext->next) and $preAndNext->next)
  3388. {
  3389. $id = (isset($_SESSION['testcaseOnlyCondition']) and !$_SESSION['testcaseOnlyCondition'] and $app->getModuleName() == 'testcase' and isset($preAndNext->next->case)) ? 'case' : 'id';
  3390. $title = isset($preAndNext->next->title) ? $preAndNext->next->title : $preAndNext->next->name;
  3391. $title = '#' . $preAndNext->next->$id . ' ' . $title . ' ' . $lang->nextShortcutKey;
  3392. $params = $moduleName == 'story' ? "&version=0&param=0&storyType={$preAndNext->next->type}" : '';
  3393. $link = $linkTemplate ? sprintf($linkTemplate, $preAndNext->next->$id) : helper::createLink($moduleName, $methodName, "ID={$preAndNext->next->$id}" . $params);
  3394. $link .= '#app=' . $app->tab;
  3395. if(isset($preAndNext->next->objectType) and $preAndNext->next->objectType == 'doc')
  3396. {
  3397. echo html::a('javascript:void(0)', '<i class="icon-pre icon-chevron-right"></i>', '', "id='nextPage' class='btn' title='$title' data-url='{$link}'");
  3398. }
  3399. else
  3400. {
  3401. echo html::a($link, '<i class="icon-pre icon-chevron-right"></i>', '', "id='nextPage' class='btn' title='$title'");
  3402. }
  3403. }
  3404. echo '</nav>';
  3405. }
  3406. /**
  3407. * Print icon of comment.
  3408. *
  3409. * @param string $commentFormLink
  3410. * @param object $object
  3411. *
  3412. * @static
  3413. * @access public
  3414. * @return mixed
  3415. */
  3416. public static function printCommentIcon($commentFormLink, $object = null)
  3417. {
  3418. global $lang;
  3419. if(!commonModel::hasPriv('action', 'comment', $object)) return false;
  3420. echo html::commonButton('<i class="icon icon-chat-line"></i> ' . $lang->action->create, '', 'btn btn-link pull-right btn-comment');
  3421. echo <<<EOF
  3422. <div class="modal fade modal-comment">
  3423. <div class="modal-dialog">
  3424. <div class="modal-content">
  3425. <div class="modal-header">
  3426. <button type="button" class="close" data-dismiss="modal"><i class="icon icon-close"></i></button>
  3427. <h4 class="modal-title">{$lang->action->create}</h4>
  3428. </div>
  3429. <div class="modal-body">
  3430. <form class="load-indicator not-watch" action="{$commentFormLink}" target='hiddenwin' method='post'>
  3431. <div class="form-group">
  3432. <textarea id='comment' name='comment' class="form-control" rows="8" autofocus="autofocus"></textarea>
  3433. </div>
  3434. <div class="form-group form-actions text-center">
  3435. <button type="submit" class="btn btn-primary btn-wide">{$lang->save}</button>
  3436. <button type="button" class="btn btn-wide" data-dismiss="modal">{$lang->close}</button>
  3437. </div>
  3438. </form>
  3439. </div>
  3440. </div>
  3441. </div>
  3442. </div>
  3443. <script>
  3444. $(function()
  3445. {
  3446. \$body = $('body', window.parent.document);
  3447. if(\$body.hasClass('hide-modal-close')) \$body.removeClass('hide-modal-close');
  3448. });
  3449. </script>
  3450. EOF;
  3451. }
  3452. /**
  3453. * Get messageBar dot style.
  3454. *
  3455. * @param bool $showCount
  3456. * @param int $unreadCount
  3457. *
  3458. * @static
  3459. * @access public
  3460. * @return mixed
  3461. */
  3462. public static function getDotStyle($showCount, $unreadCount)
  3463. {
  3464. $dotStyle = array('top' => '-3px', 'right' => '-10px', 'aspect-ratio' => '0', 'padding' => '2px');
  3465. if($unreadCount < 10) $dotStyle['right'] = '-5px';
  3466. if(!$showCount)
  3467. {
  3468. $dotStyle['aspect-ratio'] = '1 / 1';
  3469. $dotStyle['width'] = '5px';
  3470. $dotStyle['height'] = '5px';
  3471. $dotStyle['right'] = '-2px';
  3472. $dotStyle['top'] = '-2px';
  3473. }
  3474. return $dotStyle;
  3475. }
  3476. /**
  3477. * Get workspace info.
  3478. *
  3479. * @return array ['enabled' => bool, 'type' => string, 'opened' => bool]
  3480. */
  3481. public static function getWorkspaceInfo()
  3482. {
  3483. global $app, $config, $lang;
  3484. if(isset($app->workspaceInfo)) return $app->workspaceInfo;
  3485. if($config->vision != 'rnd' || !empty($config->noWorkspace) || (!empty($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], 'xuanxuan') !== false))
  3486. {
  3487. $app->workspaceInfo = array('enabled' => false, 'type' => '', 'opened' => false);
  3488. return $app->workspaceInfo;
  3489. }
  3490. if($app->tab === 'project' && $lang->project->common == $lang->project->template)
  3491. {
  3492. $app->workspaceInfo = array('enabled' => false, 'type' => '', 'opened' => false);
  3493. return $app->workspaceInfo;
  3494. }
  3495. if(empty($lang->workspaceList[$app->tab]) || commonModel::setMainMenu())
  3496. {
  3497. $app->workspaceInfo = array('enabled' => true, 'type' => '', 'opened' => false);
  3498. return $app->workspaceInfo;
  3499. }
  3500. $cookieWorkspace = $app->cookie->workspace;
  3501. if($cookieWorkspace !== $app->tab)
  3502. {
  3503. $app->workspaceInfo = array('enabled' => true, 'type' => $app->tab, 'opened' => false);
  3504. return $app->workspaceInfo;
  3505. }
  3506. $app->workspaceInfo = array('enabled' => true, 'type' => $cookieWorkspace, 'opened' => true);
  3507. return $app->workspaceInfo;
  3508. }
  3509. }
  3510. class common extends commonModel
  3511. {
  3512. }